diff options
| author | talha <-> | 2024-08-15 10:46:43 +0500 | 
|---|---|---|
| committer | talha <-> | 2024-08-15 10:46:43 +0500 | 
| commit | 0453b8f50dc4d40083e02cc3d09b4bcaa33f1700 (patch) | |
| tree | dc9956d8b3be69d76cd03dd4d9b1e0d16dae0a14 | |
| parent | 0cb9fa1c023033c250fd0bf33c2cee49fc85f178 (diff) | |
- Added entity arrays
- Added memory arenas
- moved c files to cpp
- refactored files to work with lsp (still unity build) but more painful
| -rw-r--r-- | .clang-format | 1 | ||||
| -rw-r--r-- | .clangd | 2 | ||||
| -rwxr-xr-x | build_browser.sh | 2 | ||||
| -rwxr-xr-x | build_desktop.sh | 4 | ||||
| -rw-r--r-- | src/array.cpp | 113 | ||||
| -rw-r--r-- | src/array.h | 34 | ||||
| -rw-r--r-- | src/characters.h | 100 | ||||
| -rw-r--r-- | src/entity.h | 15 | ||||
| -rw-r--r-- | src/game.c | 1566 | ||||
| -rw-r--r-- | src/game.cpp | 1508 | ||||
| -rw-r--r-- | src/game_v0.c | 512 | ||||
| -rw-r--r-- | src/memory/memory.c | 246 | ||||
| -rw-r--r-- | src/memory/memory.h | 98 | ||||
| -rw-r--r-- | src/memory/test.cpp | 566 | 
14 files changed, 2686 insertions, 2081 deletions
| diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4b75550 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +SortIncludes: Never @@ -0,0 +1,2 @@ +CompileFlags: +  Add: -I/home/talha/Documents/programming/blaidville_prototype/thirdparty/raylib_desktop/include -I/home/talha/Documents/programming/blaidville_prototype/thirdparty/raylib_browser/include -I/home/talha/Documents/programming/blaidville_prototype/src diff --git a/build_browser.sh b/build_browser.sh index 8e046cb..bf65c76 100755 --- a/build_browser.sh +++ b/build_browser.sh @@ -8,7 +8,7 @@ minishell="--shell-file $HOME/Documents/Personal/programming/raylib/src/minshell  lib_opts="$lraylib -s USE_GLFW=3 $minishell -DPLATFORM_WEB --preload-file assets"  src_dir="src" -src_files="$src_dir/game.c" +src_files="$src_dir/game.cpp"  build_dir="build/web"  build_path="$build_dir/game.html"  emcc -Os -Wall ./thirdparty/raylib_browser/lib/libraylib.a $include_opts $src_files $lib_opts -o $build_path diff --git a/build_desktop.sh b/build_desktop.sh index d74c086..683a084 100755 --- a/build_desktop.sh +++ b/build_desktop.sh @@ -7,7 +7,7 @@ lraylib="-L thirdparty/raylib_desktop/lib -lraylib"  lib_opts="$lraylib -lGL -lm -lpthread -ldl -lrt -lX11"  src_dir="src" -src_files="$src_dir/game.c" +src_files="$src_dir/game.cpp"  build_dir="build/desktop"  build_path="$build_dir/game.out" -cc -g -O0 $include_opts $src_files $lib_opts -o $build_path +clang++ -g -O0 $include_opts $src_files $lib_opts -o $build_path diff --git a/src/array.cpp b/src/array.cpp new file mode 100644 index 0000000..88eb492 --- /dev/null +++ b/src/array.cpp @@ -0,0 +1,113 @@ +#include "array.h" + +struct EntityArr entity_arr_init(struct Arena *arena, unsigned int capacity) { +  struct EntityArr arr = {0}; +  void *allocation = arena_alloc(arena, sizeof(struct EntityArr) * capacity); + +  // @todo: check if allocation was not possible, log in case of that +  assert(allocation != NULL); +  arr.buffer = (struct Entity *)allocation; +  arr.capacity = capacity; +  arr.size = 0; + +  return arr; +} + +void entity_arr_insert(struct EntityArr *arr, struct Entity to_insert, +                       unsigned int index) { +  if (index >= arr->capacity) { +    return; +  } + +  arr->buffer[index] = to_insert; +} + +struct Entity entity_arr_remove(struct EntityArr *arr, struct Entity to_remove, +                                unsigned int index) { +  if (index >= arr->capacity) { +    return EntityNone; +  } + +  struct Entity removed = arr->buffer[index]; +  arr->buffer[index] = EntityNone; + +  return removed; +} + +void entity_arr_push_head(struct EntityArr *arr, struct Entity value) { +  if (arr->size >= arr->capacity) { +    return; +  } + +  for (int i = arr->size - 1; i >= 0; i--) { +    arr->buffer[i + 1] = arr->buffer[i]; +  } + +  arr->buffer[0] = value; +  arr->size++; +} + +void entity_arr_push_tail(struct EntityArr *arr, struct Entity value) { +  if (arr->size >= arr->capacity) { +    return; +  } + +  arr->buffer[arr->size] = value; +  arr->size++; +} + +struct Entity entity_arr_pop_head(struct EntityArr *arr) { +  if (arr->size == 0) +    return EntityNone; + +  struct Entity popped = arr->buffer[0]; +  for (int i = 1; i < arr->size; i++) { +    arr->buffer[i - 1] = arr->buffer[i]; +  } +  arr->size--; + +  return popped; +} + +struct Entity entity_arr_pop_tail(struct EntityArr *arr) { +  if (arr->size == 0) +    return EntityNone; + +  struct Entity popped = arr->buffer[arr->size - 1]; +  arr->size--; + +  return popped; +} + +void entity_qpush(EntityQueue *queue, struct Entity value) { +  entity_arr_push_tail(queue, value); +} + +struct Entity entity_qpop(EntityQueue *queue) { +  return entity_arr_pop_head(queue); +} + +void entity_spush(EntityStack *stack, struct Entity value) { +  entity_arr_push_tail(stack, value); +} + +struct Entity entity_spop(EntityStack *stack) { +  return entity_arr_pop_tail(stack); +} + +int entity_arr_find(struct EntityArr *arr, struct Entity value) { +  /* +   * finds value in arr +   * returns index of value +   */ +  int find_index = -1; +  for (int i = 0; i < arr->capacity; i++) { +    struct Entity entry = arr->buffer[i]; +    if (entry.type == value.type && entry.id == value.id) { +      find_index = i; +      break; +    } +  } + +  return find_index; +} diff --git a/src/array.h b/src/array.h new file mode 100644 index 0000000..035e1ee --- /dev/null +++ b/src/array.h @@ -0,0 +1,34 @@ +#pragma once +#include "memory/memory.h" +#include "entity.h" + +struct EntityArr { +  struct Entity *buffer; +  unsigned int capacity; +  unsigned int size; // used for mantaining a stack and queue +}; + +typedef EntityArr EntityArrayList; + +struct EntityArr entity_arr_init(struct Arena *arena, unsigned int size); + +// -- basic insert remove operations -- +void entity_arr_insert(struct EntityArr *arr, struct Entity to_insert, unsigned int index); +struct Entity entity_arr_remove(struct EntityArr *arr, struct Entity to_remove, unsigned int index); + +void entity_arr_push_head(struct EntityArr *arr, struct Entity value); +void entity_arr_push_tail(struct EntityArr *arr, struct Entity value); + +struct Entity entity_arr_pop_head(struct EntityArr *arr); +struct Entity entity_arr_pop_tail(struct EntityArr *arr); + +typedef EntityArr EntityQueue; +void entity_qpush(EntityQueue *queue, struct Entity value); +struct Entity entity_qpop(EntityQueue *queue); + +typedef EntityArr EntityStack; +void entity_spush(EntityStack *stack, struct Entity value); +struct Entity entity_spop(EntityStack *stack, struct Entity value); + +// -- operations on values -- +int entity_arr_find(struct EntityArr *arr, struct Entity value); diff --git a/src/characters.h b/src/characters.h new file mode 100644 index 0000000..2f1e10b --- /dev/null +++ b/src/characters.h @@ -0,0 +1,100 @@ +#pragma once + +#include "raylib.h" +#include "entity.h" +#include "array.h" + +typedef enum { +  AA_IDLE = 0, +  AA_CHARGE = 1, +  AA_ATTACK = 2, +} AttackAnimState; + +typedef enum { +  C_IDLE = 0, +  C_OBSERVE = 1, +  C_PATROL = 2, +  C_FOLLOW = 3, +  C_AGGRO = 4, +  C_HIT = 5, +  C_DEAD = 6 +} CharState; + +typedef enum { +  AI_NONE = 0, +  AI_IDLE = 1, +  AI_PATROL = 2, +  AI_DETECT = 3, +  AI_ATTACK = 4, +} AIState; + +typedef enum { +  HA_IDLE = 0, +  HA_PLAY = 1, +} HitAnimState; + +typedef enum { +  PATROL_TOP = 0, +  PATROL_LEFT = 1, +  PATROL_DOWN = 2, +  PATROL_RIGHT = 3, +} PatrolDir; + +typedef struct Entity AttackedEntity; +typedef struct Entity AggroEntity; +typedef struct Entity FriendEntity; + +struct Character { +  bool isburning; +  EntityType type; // TROLL / BANDIT +  int entity_id; +  CharState state;  // CharState +  int tile_id;      // MapSymbol +  int home_tile_id; // MapSymbol +  AIState ai_state; +  int health; +  float speed_multiplier; +  float move_speed; +  float detection_threshold; +  float attack_threshold; +  // improved idle behavior +  PatrolDir patrol_dir; +  bool to_core_pos; +  Vector2 core_position; // this is the main position characters will stick to +  double t_idle_start; // time spent at core position, useful to rotate them out +  // of positions +  float t_idle_duration; // max allowed duration they should be idle + +  Vector2 position; +  Vector2 last_enemy_position; +  Vector2 move_dir; +  Vector2 target_position; +  Rectangle target_rect; +  Rectangle render_rect; +  Color tint_color_base; +  Color tint_color_active; +  Color tint_color; +  Texture2D *sprite; +  // AggroEntity attackers[2]; +  EntityArrayList attackers; +  // FriendEntity friends[2]; +  EntityArrayList friends; +  // +  // Animation +  // +  // getting hit +  HitAnimState anim_hit_state; +  float anim_hit_speed; +  double t_anim_hit_start; +  float t_anim_hit_duration; +  // getting burned +  double t_anim_burn_start; + +  // attacking +  int anim_attack_state; +  float anim_attack_speed; +  float t_anim_charge_duration; +  float t_anim_attack_duration; +  double t_anim_charge_start; +  double t_anim_attack_start; +}; diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..0db15f1 --- /dev/null +++ b/src/entity.h @@ -0,0 +1,15 @@ +#pragma once + +typedef enum { +    NONE = 0, +    PLAYER = 1, +    TROLL = 2, +    BANDIT = 3, +} EntityType; + +struct Entity { +    EntityType type; // EntityType +    int id;             // Entity ID +}; + +const struct Entity EntityNone = {.type = NONE, .id = 0}; diff --git a/src/game.c b/src/game.c deleted file mode 100644 index 2e85043..0000000 --- a/src/game.c +++ /dev/null @@ -1,1566 +0,0 @@ -#include "raylib.h" -#include "raymath.h" - -#if defined(PLATFORM_WEB) -    #include <emscripten/emscripten.h> -#endif - -typedef enum { -  O = 0, // nothing -  //P = 1, // player -  t = 2, // tree -  T = 3, // troll area -  B = 4, // bandit area -  T_B = 5, // troll base -  B_B = 6, // bandit base -  //G = 7, // gate (exit) -} MapSymbol; - -typedef enum { -  AA_IDLE = 0, -  AA_CHARGE = 1, -  AA_ATTACK = 2, -} AttackAnimState; - -typedef enum { -  P_IDLE = 0, -  P_MOVE = 1, -  P_HIT = 2, -} PlayerState; - -typedef enum { -  C_IDLE = 0, -  C_OBSERVE = 1, -  C_PATROL = 2, -  C_FOLLOW = 3, -  C_AGGRO = 4, -  C_HIT = 5, -  C_DEAD = 6 -} CharState; - -typedef enum { -  AI_NONE = 0, -  AI_IDLE = 1, -  AI_PATROL = 2, -  AI_DETECT = 3, -  AI_ATTACK = 4, -} AIState; - -typedef enum { -  HA_IDLE = 0, -  HA_PLAY = 1, -} HitAnimState; - -typedef enum { -  NONE = 0, -  PLAYER = 1, -  TROLL = 2, -  BANDIT = 3, -} CharacterType; - -typedef enum { -  PT_NONE = 0, -  PT_ALLY = 1, -  PT_ATTACK = 2, -} PotionType; - -typedef enum { -  PS_NONE = 0, -  PS_HELD = 1, -  PS_THROWN = 2, -  PS_SPREAD = 3, -} PotionState; - -typedef enum { -  GS_GAME = 0, -  GS_UI = 1, -  GS_SUCCESS = 2, -  GS_OVER = 3, -} GameplayState; - -typedef enum { -  PATROL_TOP    = 0, -  PATROL_LEFT   = 1, -  PATROL_DOWN   = 2, -  PATROL_RIGHT  = 3, -  PATROL_END    = 4, -} PatrolDir; - -typedef struct ScuffString { -  char  *buffer; -  int len; -  int capacity; -} String; - -struct Entity { -  int type;      // EntityType -  int id; // Entity ID -}; - -typedef struct Entity AttackedEntity; -typedef struct Entity AggroEntity; -typedef struct Entity FriendEntity; - -struct Potion { -  int state; -  int type; -  float radius; // radius during which potion effects last -  int damage; // how much damage potion does -  int healing;  // how much healing potion does -  float speed_multiplier; // how much enemy speed is effected inside potion radius -  float t_potion_throw_start; -  float t_potion_spread_start; -  float t_potion_spread_duration; // how long does the potion spread last -  Vector2 throw_dir; -  Vector2 position_start; -  Vector2 position_curr; -  Vector2 position_target; -  Color tint_color_spread; -  Color tint_color_effect; -  Texture2D *sprite; -} Potion; - -typedef struct Potion FirePotion; -typedef struct Potion FriendPotion; - -typedef struct Player { -  int entity_id; -  int state; // PlayerState -  int tile_id; // MapSymbol -  int health; -  float move_speed; -  Vector2 position; -  Vector2 target_position; -  Vector2 move_dir; -  Color tint_color; -  Rectangle render_rect; -  Texture2D *sprite; -  // attack state -  bool is_attack; -  float throw_dist; -  Vector2 attack_dir; -  float potion_throw_speed; -  float potion_throw_dist; -  struct Potion *active_potion; -  FirePotion fire_potion; -  FriendPotion friend_potion; -  // -  // Animation -  // -  int anim_hit_state; -  float anim_hit_speed; -  double t_anim_hit_start; -  float t_anim_hit_duration; -} Player; - -struct Character { -  bool isburning; -  int entity_type; // TROLL / BANDIT -  int entity_id; -  int state; // CharState -  int tile_id; // MapSymbol -  int home_tile_id; // MapSymbol -  int ai_state; -  int health; -  float speed_multiplier; -  float move_speed; -  float detection_threshold; -  float attack_threshold; -  // improved idle behavior -  PatrolDir patrol_dir; -  bool to_core_pos; -  Vector2 core_position; // this is the main position characters will stick to -  double t_idle_start; // time spent at core position, useful to rotate them out of positions -  float t_idle_duration; // max allowed duration they should be idle - -  Vector2 position; -  Vector2 last_enemy_position; -  Vector2 move_dir; -  Vector2 target_position; -  Rectangle target_rect; -  Rectangle render_rect; -  Color tint_color_base; -  Color tint_color_active; -  Color tint_color; -  Texture2D* sprite; -  AggroEntity attackers[2]; -  FriendEntity friends[2]; -  // -  // Animation -  // -  // getting hit -  int anim_hit_state; -  float anim_hit_speed; -  double t_anim_hit_start; -  float t_anim_hit_duration; -  // getting burned -  double t_anim_burn_start; - -  // attacking -  int anim_attack_state; -  float anim_attack_speed; -  float t_anim_charge_duration; -  float t_anim_attack_duration; -  double t_anim_charge_start; -  double t_anim_attack_start; -}; - -typedef struct Character Troll; -typedef struct Character Bandit; - -typedef struct State { -  int gameplay_state; -  // how much scaling to apply from original asset resolution -  float render_scale; -  Vector2 pixels_per_gridbox; -  Vector2 mouse_position; -  // @note: normally when we place a texture, the coordinates -  // are the top left coordinates. This will cause a disconnect -  // when moving the player with a point and click motion. -  // To fix that we calculate a new position so the player_coordinates -  // are in the center. This will make it so when the player clicks on the  -  // ground somewhere, the character sprite will be properly centered on that point. - -  Player player; -  Troll _troll; -  Troll trolls[5]; -  int troll_arr_sz; -  Bandit _bandit; -  Bandit bandits[5]; -  int bandit_arr_sz; -  Vector2 bandit_position; -  // player movement -  // @todo -  // direction player is being hit from, incorporate that into the gameplay code -  // For now, just update the player_move_dir to the player_hit_dir -  // Vector2 player_hit_dir;  -  // float player_move_delta; // amount player will move -  // target position player will move to -  // 1. the player can move intentionally, in which case carry forward from player position target -  // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET - -  // game sprites -  Texture2D *grass_sprite; - -  Texture2D *troll_weapon_sprite; -  Texture2D *bandit_sprite; -  Texture2D *bandit_weapon_sprite; - -  Texture2D *troll_area_floor_sprite; -  Texture2D *bandit_area_floor_sprite; - -  Texture2D *troll_base_sprite; -  Texture2D *bandit_base_sprite; - -  // key sprites and stuff -  int key_picked_count; -  bool key_should_render[2]; -  Texture2D *key_sprite; -  Vector2 key_positions[2]; -  Rectangle key_render_rects[2]; -  // gate stuff -  bool is_gate_open; -  Vector2 gate_position; -  Rectangle gate_render_rect; -  Texture2D *active_gate_sprite; -  Texture2D *gate_closed_sprite; -  Texture2D *gate_open_sprite; - -  String *action_log; -} State; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -const int screenWidth = 1280; -const int screenHeight = 1024; -State mega_state = {}; -// layer ground  -int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O}, -                            {O, O, O, O, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}}; -// vegetation grid  -int floor_cover_grid_map[8][8] = {{O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, T_B}, -                                  {O, B_B, O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}}; -Vector2 grid_dims = {8, 8}; -//---------------------------------------------------------------------------------- -// Module functions declaration -//---------------------------------------------------------------------------------- -void CenterRectAroundTL(Rectangle *to_center, Vector2 position); -void UpdateDrawFrame();     // Update and Draw one frame -void CenterRectAroundTL(Rectangle *to_center, Vector2 position) -{ -  to_center->x = position.x - ((float)to_center->width)/2.0f; -  to_center->y = position.y - ((float)to_center->height)/2.0f; -} - - -int main(void) -{ -  InitWindow(screenWidth, screenHeight, "blaidville prototype"); -  SetTargetFPS(60); -  SetExitKey(0); -  Texture2D grass_sprite = LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png"); -  Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png"); -  Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png"); -  Texture2D bandit_sprite = LoadTexture("./assets/monster/unique/maurice_new.png"); -  Texture2D troll_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png"); -  Texture2D bandit_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_east_new.png"); -  Texture2D bandit_base_sprite = LoadTexture("./assets/dungeon/floor/hive_3.png"); -  Texture2D troll_base_sprite = LoadTexture("./assets/dungeon/floor/dirt_2_old.png"); -  Texture2D troll_weapon_sprite = LoadTexture("./assets/item/weapon/club_old.png"); -  Texture2D fire_potion_sprite = LoadTexture("./assets/item/potion/ruby_old.png"); -  Texture2D friend_potion_sprite = LoadTexture("./assets/item/potion/emerald.png"); -  Texture2D key_sprite = LoadTexture("./assets/item/misc/key.png"); -  Texture2D gate_closed_sprite = LoadTexture("./assets/dungeon/gateways/bazaar_gone.png"); -  Texture2D gate_open_sprite = LoadTexture("./assets/dungeon/gateways/bazaar_portal.png"); - -  mega_state.render_scale = 2.0f; - -  mega_state.key_sprite = &key_sprite; -  mega_state.key_positions[0] = (Vector2){200, 700}; -  mega_state.key_positions[1] = (Vector2){1200, 600}; -  mega_state.key_render_rects[0] = (Rectangle){.width=key_sprite.width, .height=key_sprite.height}; -  mega_state.key_render_rects[1] = (Rectangle){.width=key_sprite.width, .height=key_sprite.height}; -  CenterRectAroundTL(&mega_state.key_render_rects[0], mega_state.key_positions[0]); -  CenterRectAroundTL(&mega_state.key_render_rects[1], mega_state.key_positions[1]); -  mega_state.key_should_render[0] = 1; -  mega_state.key_should_render[1] = 1; - -  mega_state.grass_sprite = &grass_sprite; -  mega_state.bandit_sprite = &bandit_sprite; -  mega_state.gate_open_sprite = &gate_open_sprite; -  mega_state.gate_closed_sprite = &gate_closed_sprite; -  mega_state.active_gate_sprite = &gate_closed_sprite; -  mega_state.troll_area_floor_sprite = &troll_area_floor_sprite; -  mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite; -  mega_state.bandit_base_sprite = &bandit_base_sprite; -  mega_state.troll_base_sprite = &troll_base_sprite; -  mega_state.troll_weapon_sprite = &troll_weapon_sprite; -  mega_state.pixels_per_gridbox = (Vector2){screenWidth/grid_dims.x, screenHeight/grid_dims.y}; - -  // characters -  // player -  mega_state.player = (Player){ -    .health         = 45.0f, -    .position       = (Vector2){50.0f, 50.0f}, -    .move_speed     = 2.0f, -    .sprite         = &player_sprite, -    .tint_color     = WHITE, -    .render_rect    = (Rectangle){ -                        .x=0,.y=0, -                        .width=0.5*player_sprite.width*mega_state.render_scale, -                        .height=player_sprite.height*mega_state.render_scale -                      }, -    .throw_dist     = 100.0f, -    .fire_potion    = (FirePotion){ -      .state=PS_NONE,.type=PT_ATTACK,.radius=40.0f,.damage=5, -      .speed_multiplier=0.5f, .sprite=&fire_potion_sprite, -      .t_potion_spread_duration = 5.0f,  -      .tint_color_spread=(Color){200, 50, 50, 100}, .tint_color_effect=(Color){200, 50, 50, 255} -    }, -    .friend_potion    = (FriendPotion){ -      .state=PS_HELD,.type=PT_ALLY,.radius=40.0f, -      .speed_multiplier=1.0f, .sprite=&friend_potion_sprite, -      .t_potion_spread_duration = 5.0f,  -      .tint_color_spread=(Color){50, 200, 50, 100}, .tint_color_effect=(Color){50, 200, 50, 255} -    }, -    .potion_throw_speed   = 250.0f, -    .potion_throw_dist    = 200.0f, -    .anim_hit_speed       = 50.0f, -    .t_anim_hit_duration  = 0.2f, -  }; -  // troll -  Troll troll = { -    .state            = C_IDLE, -    .entity_type      = TROLL, -    .home_tile_id     = T, -    .health           = 50, -    .move_speed       = 2.0f, -    .speed_multiplier = 1.0f, -    .tint_color_base  = WHITE, -    .tint_color_active  = WHITE, -    .tint_color       = WHITE, -    .render_rect      = (Rectangle){.x=0,.y=0,.width=troll_sprite.width*mega_state.render_scale,.height=troll_sprite.height*mega_state.render_scale}, -    .sprite           = &troll_sprite, -    .t_anim_charge_duration = 1.0f, -    .t_anim_attack_duration = 0.4, -    .anim_attack_speed = 100.0f, -    .anim_hit_speed = 20.0f, -    .t_anim_hit_duration = 0.2f, -    .t_idle_duration = 2.0f, -    .to_core_pos      = true, -  }; -  mega_state.troll_arr_sz = 5; - -  mega_state.trolls[0] = troll; -  mega_state.trolls[0].entity_id = 0; -  mega_state.trolls[0].t_idle_duration = 4.0f; -  mega_state.trolls[0].position = (Vector2){750, 340}; -  mega_state.trolls[0].core_position = mega_state.trolls[0].position; - -  mega_state.trolls[1] = troll; -  mega_state.trolls[1].entity_id = 1; -  mega_state.trolls[1].position = (Vector2){1200, 300}; -  mega_state.trolls[1].core_position = mega_state.trolls[1].position; - -  mega_state.trolls[2] = troll; -  mega_state.trolls[2].entity_id = 2; -  mega_state.trolls[2].t_idle_duration = 3.5f; -  mega_state.trolls[2].position = (Vector2){800, 800}; -  mega_state.trolls[2].core_position = mega_state.trolls[2].position; - -  mega_state.trolls[3] = troll; -  mega_state.trolls[3].entity_id = 3; -  mega_state.trolls[3].t_idle_duration = 3.0f; -  mega_state.trolls[3].position = (Vector2){1130, 640}; -  mega_state.trolls[3].core_position = mega_state.trolls[0].position; - -  mega_state.trolls[4] = troll; -  mega_state.trolls[4].entity_id = 4; -  mega_state.trolls[4].t_idle_duration = 1.5f; -  mega_state.trolls[4].position = (Vector2){1000, 900}; -  mega_state.trolls[4].core_position = mega_state.trolls[4].position; - -  // bandit -  Bandit bandit = (Bandit){ -    .state = C_IDLE, -    .ai_state = AI_NONE, -    .entity_type = BANDIT, -    .home_tile_id = B, -    .health = 40, -    .move_speed = 4.0f, -    .speed_multiplier = 1.0f, -    .tint_color_base = WHITE, -    .tint_color_active  = WHITE, -    .tint_color = WHITE, -    .render_rect = (Rectangle){.x=0,.y=0,.width=bandit_sprite.width*mega_state.render_scale,.height=bandit_sprite.height*mega_state.render_scale}, -    .sprite = &bandit_sprite, -    .t_anim_charge_duration = 0.5f, -    .t_anim_attack_duration = 0.2f, -    .anim_attack_speed = 50.0f, -    .anim_hit_speed = 50.0f, -    .t_anim_hit_duration = 0.2f, -    .t_idle_duration = 2.0f, -    .to_core_pos = true -  }; -  mega_state.bandit_arr_sz = 5; - -  mega_state.bandits[0] = bandit; -  mega_state.bandits[0].entity_id = 0; -  mega_state.bandits[0].position = (Vector2){264.0f, 900.0f}; -  mega_state.bandits[0].core_position = mega_state.bandits[0].position; - -  mega_state.bandits[1] = bandit; -  mega_state.bandits[1].entity_id = 1; -  mega_state.bandits[1].t_idle_duration = 4.0f; -  mega_state.bandits[1].position = (Vector2){164.0f, 600.0f}; -  mega_state.bandits[1].core_position = mega_state.bandits[1].position; - -  mega_state.bandits[2] = bandit; -  mega_state.bandits[2].entity_id = 2; -  mega_state.bandits[2].t_idle_duration = 3.0f; -  mega_state.bandits[2].position = (Vector2){264.0f, 400.0f}; -  mega_state.bandits[2].core_position = mega_state.bandits[2].position; - -  mega_state.bandits[3] = bandit; -  mega_state.bandits[3].entity_id = 3; -  mega_state.bandits[3].t_idle_duration = 2.5f; -  mega_state.bandits[3].position = (Vector2){400.0f, 800.0f}; -  mega_state.bandits[3].core_position = mega_state.bandits[3].position; - -  mega_state.bandits[4] = bandit; -  mega_state.bandits[4].entity_id = 4; -  mega_state.bandits[4].t_idle_duration = 1.0f; -  mega_state.bandits[4].position = (Vector2){400.0f, 600.0f}; -  mega_state.bandits[4].core_position = mega_state.bandits[4].position; -   -  // @todo: action log to describe and dictate all the major events happen -  char *str_buffer = (char*)MemAlloc(512*sizeof(char)); -  mega_state.player.active_potion = &mega_state.player.friend_potion; -  CenterRectAroundTL(&mega_state.trolls[0].render_rect, mega_state.trolls[0].position); -  CenterRectAroundTL(&mega_state.trolls[1].render_rect, mega_state.trolls[1].position); - -  CenterRectAroundTL(&mega_state.bandits[0].render_rect, mega_state.bandits[0].position); -  CenterRectAroundTL(&mega_state.bandits[0].render_rect, mega_state.bandits[0].position); -  // no action log right now -  // mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, .len=0}; -  mega_state.gameplay_state = GS_UI; - -  mega_state.gate_position = (Vector2){.x=500.0f, .y=16.0f}; -  mega_state.gate_render_rect = (Rectangle){.width=mega_state.gate_closed_sprite->width*1.5, .height=mega_state.gate_closed_sprite->height*1.5}; -  CenterRectAroundTL(&mega_state.gate_render_rect, mega_state.gate_position); - -  #if defined(PLATFORM_WEB) -    emscripten_set_main_loop(UpdateDrawFrame, 0, 1); -  #else -    SetTargetFPS(60); -     -    while(!WindowShouldClose()) -    { -      UpdateDrawFrame(); -    } -  #endif - -  CloseWindow(); - -  return 0; -} - -void DrawMapFloorTiles() -{ -  Color BlackTint = {128,128,128,255}; -  Texture2D grass = *mega_state.grass_sprite; -  float winc = (float)grass.width * mega_state.render_scale; -  float hinc = (float)grass.height * mega_state.render_scale; -  Vector2 grid_pos = {0, 0}; -  for (int ypos=0; ypos<screenHeight; ypos+=hinc) -  { -    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); -    for (int xpos=0; xpos<screenWidth; xpos+=winc) -    { -      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); -      Vector2 pixel_pos = {xpos, ypos}; -      int grid_ele = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -      Texture2D render_tex; -      switch (grid_ele) { -        case (O): -          { -            render_tex = *mega_state.grass_sprite; -          } break; -        case (B): -          { -            render_tex = *mega_state.bandit_area_floor_sprite; -          } break; -        case (T): -          { -            render_tex = *mega_state.troll_area_floor_sprite; -          } break; -        default: -          { -          } break; -      } -      DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, BlackTint); -    } -  } -} - -void DrawFloorCoverTiles() -{ -  Color BlackTint = {128,128,128,255}; -  Texture2D null_tex = *mega_state.player.sprite; -  float winc = (float)null_tex.width * mega_state.render_scale; -  float hinc = (float)null_tex.height * mega_state.render_scale; - -  Vector2 grid_pos = {0, 0}; -  for (int ypos=0; ypos<screenHeight; ypos+=hinc) -  { -    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); -    for (int xpos=0; xpos<screenWidth; xpos+=winc) -    { -      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); -      Vector2 pixel_pos = {xpos, ypos}; -      int grid_ele = floor_cover_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -      Texture2D render_tex; -      switch (grid_ele) { -        case (B_B): -          { -            render_tex = *mega_state.bandit_base_sprite; -          } break; -        case (T_B): -          { -            render_tex = *mega_state.troll_base_sprite; -          } break; -        default: -          { -            render_tex = null_tex; -          } continue; -      } -      DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, BlackTint); -    } -  } -} - -Vector2 GetSpriteCenterPosition(Texture2D sprite, Vector2 position, float render_scale) -{ -  float centered_x = position.x - ((float)sprite.width)*render_scale/2.0f; -  float centered_y = position.y - ((float)sprite.height)*render_scale/2.0f; - -  return (Vector2){.x = centered_x, .y = centered_y}; -} - -void DrawCharacters() -{ -  DrawTextureEx(*mega_state.active_gate_sprite, -                (Vector2){mega_state.gate_render_rect.x, mega_state.gate_render_rect.y}, -                0, mega_state.render_scale*1.5, WHITE); -  // draw potion -  if (mega_state.player.active_potion->state == PS_THROWN) -  { -    DrawTextureEx(*mega_state.player.active_potion->sprite,  -                  mega_state.player.active_potion->position_curr, -                  0, 0.4*mega_state.render_scale, WHITE); -  } -  else if (mega_state.player.active_potion->state == PS_SPREAD) -  { -    Vector2 pos = mega_state.player.active_potion->position_curr; -    DrawCircle(pos.x, pos.y, mega_state.player.active_potion->radius, mega_state.player.active_potion->tint_color_spread); -  } -  if (mega_state.key_should_render[0] == 1) -  { -    DrawTextureEx(*mega_state.key_sprite, -                  (Vector2){mega_state.key_positions[0].x, mega_state.key_positions[0].y}, 0, -                  mega_state.render_scale/2.0f, WHITE); -  } -  if (mega_state.key_should_render[1] == 1) -  { -    DrawTextureEx(*mega_state.key_sprite, -                  (Vector2){mega_state.key_positions[1].x, mega_state.key_positions[1].y}, 0, -                  mega_state.render_scale/2.0f, WHITE); -  } - -  Rectangle src_rect, dest_rect; -  // draw Troll -  // - troll weapon -  for (int i=0;i<mega_state.troll_arr_sz;i++) -  { -    Vector2 troll_weapon_centered = GetSpriteCenterPosition(*mega_state.troll_weapon_sprite, mega_state.trolls[i].position, 1.0f); -    // move to left hand -    troll_weapon_centered.x -= mega_state.trolls[i].sprite->width; -    src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.troll_weapon_sprite->width, .height=mega_state.troll_weapon_sprite->height}; -    dest_rect = (Rectangle){.x=troll_weapon_centered.x, .y=troll_weapon_centered.y,  -      .width=0.5f*mega_state.render_scale*mega_state.troll_weapon_sprite->width,  -      .height=0.5f*mega_state.render_scale*mega_state.troll_weapon_sprite->height}; -    DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect, -                   (Vector2){0,0}, 0, RAYWHITE);  -    // - troll sprite -    DrawTextureEx(*mega_state.trolls[i].sprite,  -                  (Vector2){mega_state.trolls[i].render_rect.x, mega_state.trolls[i].render_rect.y},  -                  0, mega_state.render_scale, mega_state.trolls[i].tint_color);  -    // draw collision box -    //DrawRectangleLines(mega_state.troll.render_rect.x, mega_state.troll.render_rect.y,  -    //                   mega_state.troll.render_rect.width, mega_state.troll.render_rect.height, RED);  -  } - -  // draw Bandit -  for (int i=0;i<mega_state.bandit_arr_sz;i++) -  { -    DrawTextureEx(*mega_state.bandits[i].sprite,  -                  (Vector2){mega_state.bandits[i].render_rect.x, mega_state.bandits[i].render_rect.y},  -                  0, mega_state.render_scale, mega_state.bandits[i].tint_color); -  } -  // draw player -  float p_render_scale = 1.0f; -  Vector2 player_weapon_centered = GetSpriteCenterPosition(*mega_state.player.sprite, mega_state.player.position, p_render_scale); -  // move to left hand -  player_weapon_centered.x -= (mega_state.player.sprite->width*p_render_scale/2.0f) - 2.0f; -  player_weapon_centered.y += (12.0f); -  src_rect = (Rectangle){ -    .x=0, .y=0,  -    .width  = mega_state.player.active_potion->sprite->width,  -    .height = mega_state.player.active_potion->sprite->height -  }; - -  dest_rect = (Rectangle){.x=player_weapon_centered.x, .y=player_weapon_centered.y,  -    .width=0.4f*mega_state.render_scale*mega_state.player.active_potion->sprite->width,  -    .height=0.4f*mega_state.render_scale*mega_state.player.active_potion->sprite->height}; - -  DrawTexturePro(*mega_state.player.active_potion->sprite, src_rect, dest_rect, -                 (Vector2){0,0}, 0, RAYWHITE);  -  Vector2 player_center = GetSpriteCenterPosition(*mega_state.player.sprite, mega_state.player.position, mega_state.render_scale); -  DrawTextureEx(*mega_state.player.sprite, player_center, 0, mega_state.render_scale, mega_state.player.tint_color);  -   -} - -int GetTileIDFromWorld(Vector2 world_position) -{ -  Vector2 grid_pos = Vector2Divide(Vector2Multiply(world_position, grid_dims), (Vector2){screenWidth, screenHeight}); -  int tile = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -  return tile; -} - -bool MoveCharacter(struct Character *to_move, Vector2 position) -{ -  Vector2 new_pos = Vector2Add(to_move->position, position); -  int tile_at_new_pos = GetTileIDFromWorld(new_pos); -  int tile_at_curr_pos = GetTileIDFromWorld(to_move->position); -  bool char_at_home = to_move->home_tile_id == tile_at_curr_pos ? true: false; -  // move only if next position in home tile -  if (tile_at_new_pos == to_move->home_tile_id || to_move->state == C_FOLLOW || char_at_home == false) -  { -    to_move->position = Vector2Add(to_move->position, position); -    return true; -  } -  else -  { -    to_move->target_position = to_move->position; -    return false; -  } -} - -AttackedEntity GetEntityToAttack(struct Character attacker) -{ -  AttackedEntity attackee = {}; -  // check if player can be attacked -  bool attacker_at_home_tile = GetTileIDFromWorld(attacker.position) == attacker.home_tile_id ? true: false; -  Vector2 char_position = mega_state.player.position; -  int char_tile_id = GetTileIDFromWorld(char_position); -  Rectangle char_rect = mega_state.player.render_rect; -  float min_dist = screenWidth; - -  if ((attacker.attackers[0].type == PLAYER || char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false) -      && attacker.friends[0].type != PLAYER) -  { -    float char_dist = Vector2Distance(char_position, attacker.position); -    if (char_dist < 200) -    { -      attackee.type = PLAYER; -      if (attacker.attackers[0].type == PLAYER) return attackee; -    }  -  } - -  // check if bandit can instead be attacked -  for (int i=0; i<mega_state.bandit_arr_sz;i++) -  { -    Bandit bandit = mega_state.bandits[i]; -    char_tile_id = GetTileIDFromWorld(char_position); -    char_position = bandit.position; -    char_rect = bandit.render_rect; -    if (bandit.state == C_DEAD) continue; -    if ((char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false) &&  -      ((attacker.attackers[0].type == BANDIT && attacker.attackers[0].id == i) ||  -      attacker.entity_type != BANDIT)) -    { -      float char_dist = Vector2Distance(char_position, attacker.position); -      if (char_dist < 200 && (char_dist < min_dist || attacker.attackers[0].type == BANDIT)) -      { -        min_dist = char_dist; -        attackee.type = BANDIT; -        attackee.id = bandit.entity_id; -        if (attacker.attackers[0].type == BANDIT) return attackee; -      } -    } -  } -   -  // check if troll can instead be attacked -  for (int i=0; i<mega_state.troll_arr_sz;i++) { -    char_position = mega_state.trolls[i].position; -    char_tile_id = GetTileIDFromWorld(char_position); -    char_rect = mega_state.trolls[i].render_rect; -    if (mega_state.trolls[i].state == C_DEAD) continue; -    if ((char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false) &&  -      ((attacker.attackers[0].type == TROLL && attacker.attackers[0].id == i) || -      attacker.entity_type != TROLL)) -    { -      float char_dist = Vector2Distance(char_position, attacker.position); -      if (char_dist < 200 && (char_dist < min_dist || attacker.attackers[0].type == TROLL)) -      { -        min_dist = char_dist; -        attackee.type = TROLL; -        attackee.id = mega_state.trolls[i].entity_id; -        if (attacker.attackers[0].type == TROLL) return attackee; -      } -    } -  } - -  return attackee; -} - -bool TrollAttackTarget(Troll *troll, AttackedEntity to_attack) -{ -  float dist_to_target = 0; -   -  troll->state = C_IDLE; -  troll->ai_state = AI_NONE; -  switch (to_attack.type) -  { -    case (PLAYER): -    { -      troll->last_enemy_position = mega_state.player.position; -      troll->target_position = mega_state.player.position; -      troll->target_rect = mega_state.player.render_rect; - -      dist_to_target = Vector2Distance(troll->target_position, troll->position); -      troll->ai_state = AI_DETECT; -      troll->state = C_FOLLOW; -    } break; -    case (BANDIT): -    { -      troll->last_enemy_position = mega_state.bandits[to_attack.id].position; -      troll->target_position = mega_state.bandits[to_attack.id].position; -      troll->target_rect = mega_state.bandits[to_attack.id].render_rect; - -      dist_to_target = Vector2Distance(troll->target_position, troll->position); -      troll->ai_state = AI_DETECT; -      troll->state = C_FOLLOW; -    } break; -    default: -    break; -  } -  if (troll->ai_state == AI_DETECT && dist_to_target < 80) -  { -    troll->ai_state = AI_ATTACK; -  } -  if (troll->ai_state >= AI_DETECT && troll->anim_attack_state != AA_ATTACK) -  { -    troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position)); -    if (dist_to_target > 10.0f) -    { -      // @note: this actually might not even be needed -      // move only if both are more than 10pixels apart. -      // it becomes jarring other wise -      MoveCharacter(troll, Vector2Scale(troll->move_dir, troll->speed_multiplier*troll->move_speed));  -    } -  } -  CenterRectAroundTL(&troll->render_rect, troll->position); -  if (troll->anim_attack_state == AA_IDLE && troll->ai_state == AI_ATTACK) -  { -    // start attack animation -    troll->t_anim_charge_start = GetTime(); -    troll->anim_attack_state = AA_CHARGE; -  } -  float charge_time = 0.0f; -  float charge_color_delta = 0.0f; -  if (troll->anim_attack_state == AA_CHARGE) -  { -    // get color based on time -    float charge_progress = (GetTime() - troll->t_anim_charge_start) / troll->t_anim_charge_duration; -    troll->tint_color.r = Clamp(troll->tint_color_active.r - (charge_progress*troll->tint_color_active.r), 0, 255); -    if (charge_progress >= 1.0f) -    { -      troll->anim_attack_state = AA_ATTACK; -      troll->t_anim_attack_start = GetTime(); -    } -  }  -  else if (troll->anim_attack_state == AA_ATTACK) -  { -    // maximum distance to attack jump in -    float anim_length = GetTime() - troll->t_anim_attack_start; -    float ft = GetFrameTime(); -    float pixel_per_second_per_ft = ft*troll->anim_attack_speed/troll->t_anim_attack_duration; -    troll->position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, pixel_per_second_per_ft)); -    troll->tint_color = (Color){255, 128, 255, 255}; -    // get troll center position and check for collision with player -    // now check if colliding with player -    bool is_colliding = CheckCollisionRecs(troll->target_rect, troll->render_rect); -    if (anim_length >= troll->t_anim_attack_duration || is_colliding) -    { -      troll->state = C_IDLE; -      troll->ai_state = AI_NONE; -      troll->anim_attack_state = AA_IDLE; -      troll->tint_color = troll->tint_color_active; -      if (is_colliding == true) -      { -        return true; // is colliding with target -      } -    } -  } -  // once troll is done moving, clear state. -  troll->state = C_IDLE; -  troll->ai_state = AI_NONE; -  return false; -} - -bool BanditAttackTarget(Bandit *bandit, AttackedEntity to_attack) -{ -  float dist_to_target = 0; -   -  bandit->state = C_IDLE; -  bandit->ai_state = AI_NONE; -  switch (to_attack.type) -  { -    case (PLAYER): -    { -      bandit->last_enemy_position = mega_state.player.position; -      bandit->target_position = mega_state.player.position; -      bandit->target_rect = mega_state.player.render_rect; - -      dist_to_target = Vector2Distance(bandit->target_position, bandit->position); -      bandit->ai_state = AI_DETECT; -      bandit->state = C_FOLLOW; -    } break; -    case (TROLL): -    { -      bandit->last_enemy_position = mega_state.trolls[to_attack.id].position; -      bandit->target_position = mega_state.trolls[to_attack.id].position; -      bandit->target_rect = mega_state.trolls[to_attack.id].render_rect; - -      dist_to_target = Vector2Distance(bandit->target_position, bandit->position); -      bandit->ai_state = AI_DETECT; -      bandit->state = C_FOLLOW; -    } break; -    default: -    break; -  } -  if (bandit->ai_state == AI_DETECT && dist_to_target < 80) -  { -    bandit->ai_state = AI_ATTACK; -  } -  if (bandit->ai_state >= AI_DETECT && bandit->anim_attack_state != AA_ATTACK) -  { -    bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position)); -    if (dist_to_target > 10.0f) -    { -      // @note: this actually might not even be needed -      // move only if both are more than 10pixels apart. -      // it becomes jarring other wise -      bool can_move = MoveCharacter(bandit, Vector2Scale(bandit->move_dir, bandit->speed_multiplier*bandit->move_speed)); -    } -  } -  CenterRectAroundTL(&bandit->render_rect, bandit->position); -  if (bandit->anim_attack_state == AA_IDLE && bandit->ai_state == AI_ATTACK) -  { -    // start attack animation -    bandit->t_anim_charge_start = GetTime(); -    bandit->anim_attack_state = AA_CHARGE; -  } -  float charge_time = 0.0f; -  float charge_color_delta = 0.0f; -  if (bandit->anim_attack_state == AA_CHARGE) -  { -    // get color based on time -    float charge_progress = (GetTime() - bandit->t_anim_charge_start) / bandit->t_anim_charge_duration; -    bandit->tint_color.r = Clamp(bandit->tint_color_active.r - (charge_progress*bandit->tint_color_active.r), 0, 255); -    if (charge_progress >= 1.0f) -    { -      bandit->anim_attack_state = AA_ATTACK; -      bandit->t_anim_attack_start = GetTime(); -    } -  }  -  else if (bandit->anim_attack_state == AA_ATTACK) -  { -    // maximum distance to attack jump in -    float anim_length = GetTime() - bandit->t_anim_attack_start; -    float ft = GetFrameTime(); -    float pixel_per_second_per_ft = ft*bandit->anim_attack_speed/bandit->t_anim_attack_duration; -    bandit->position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, pixel_per_second_per_ft)); -    bandit->tint_color = (Color){255, 128, 255, 255}; -    // get bandit center position and check for collision with player -    // now check if colliding with player -    bool is_colliding = CheckCollisionRecs(bandit->target_rect, bandit->render_rect); -    if (anim_length >= bandit->t_anim_attack_duration || is_colliding) -    { -      bandit->state = C_IDLE; -      bandit->ai_state = AI_NONE; -      bandit->anim_attack_state = AA_IDLE; -      bandit->tint_color = bandit->tint_color_active; -      if (is_colliding == true) -      { -        return true; // is colliding with target -      } -    } -  } -  bandit->state = C_IDLE; -  bandit->ai_state = AI_NONE; -  return false; -} -void UpdateDrawFrame(void) -{ -  // INPUT -  if (IsKeyPressed(KEY_ESCAPE)) -  { -    mega_state.gameplay_state = mega_state.gameplay_state == GS_UI ? GS_GAME : GS_UI; -  } -  if (mega_state.gameplay_state == GS_GAME) -  { -    mega_state.mouse_position = GetMousePosition(); -    if (IsKeyPressed(KEY_ONE)) -    { -      mega_state.player.active_potion = &mega_state.player.fire_potion; -      mega_state.player.active_potion->t_potion_throw_start = 0; -      mega_state.player.active_potion->t_potion_spread_start = 0; -      mega_state.player.active_potion->state = PS_HELD; -    } -    else if (IsKeyPressed(KEY_TWO)) -    { -      mega_state.player.active_potion = &mega_state.player.friend_potion; -      mega_state.player.active_potion->t_potion_throw_start = 0; -      mega_state.player.active_potion->t_potion_spread_start = 0; -      mega_state.player.active_potion->state = PS_HELD; -    } -    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mega_state.player.state <= P_MOVE) -    { -      mega_state.player.state = P_MOVE; -      mega_state.player.target_position = mega_state.mouse_position; -      mega_state.player.move_dir = Vector2Normalize(Vector2Subtract(mega_state.player.target_position, mega_state.player.position)); -    } -    if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) -    { -      // @todo: abilities -      mega_state.player.active_potion->throw_dir = Vector2Normalize(Vector2Subtract(mega_state.mouse_position, mega_state.player.position)); -      mega_state.player.active_potion->position_start = mega_state.player.position; -      mega_state.player.active_potion->position_curr = mega_state.player.position; -      mega_state.player.active_potion->position_target = mega_state.mouse_position; -      mega_state.player.active_potion->state = PS_THROWN; -      mega_state.player.active_potion->t_potion_throw_start = GetTime(); -    } - -    // PROCESSING -    // initialize values -    for (int i = 0; i < mega_state.troll_arr_sz; i++) -    { -      if (mega_state.trolls[i].health <= 0) -      { -        mega_state.trolls[i].state = C_DEAD; -        mega_state.trolls[i].tint_color = (Color){128, 128, 128, 128}; -      } -      mega_state.trolls[i].speed_multiplier = 1.0f; -    } -    for (int i = 0; i < mega_state.bandit_arr_sz; i++) -    { -      if (mega_state.bandits[i].health <= 0) -      { -        mega_state.bandits[i].state = C_DEAD; -        mega_state.bandits[i].tint_color = (Color){128, 128, 128, 128}; -      } -      mega_state.bandits[i].speed_multiplier = 1.0f; -    } -    if (mega_state.player.health <= 0) -    { -      mega_state.gameplay_state = GS_OVER; -    } - -    switch (mega_state.player.state) -    { -      case (P_MOVE): -        { -          // MOVE PLAYER -          Vector2 next_position = Vector2Add(mega_state.player.position,  -                                                  Vector2Scale(mega_state.player.move_dir, mega_state.player.move_speed)); -          // checke player collisions -          bool is_colliding_gate = CheckCollisionRecs(mega_state.player.render_rect, mega_state.gate_render_rect); -          if (is_colliding_gate) -          { -            if (mega_state.key_picked_count == 2) -            { -              mega_state.is_gate_open = true; -              mega_state.active_gate_sprite = mega_state.gate_open_sprite; -              mega_state.gameplay_state = GS_SUCCESS; -            } -          } -          mega_state.player.position = next_position; - -          if (Vector2Distance(mega_state.player.position, mega_state.player.target_position) <= 5.0f) -          { -            mega_state.player.state = P_IDLE; -          } -          // calculate center positions -          CenterRectAroundTL(&mega_state.player.render_rect, mega_state.player.position); -          mega_state.player.tile_id = GetTileIDFromWorld(mega_state.player.position); -          // check if a key can be picked up -          for (int i=0;i<2;i++) -          { -            bool is_colliding = CheckCollisionRecs(mega_state.player.render_rect, mega_state.key_render_rects[i]); -            if (is_colliding && mega_state.key_should_render[i] == 1) -            { -              mega_state.key_picked_count += 1; -              mega_state.key_should_render[i] = 0; -            } -          } -        } break; -      case (P_HIT): -        { -          float anim_length = GetTime() - mega_state.player.t_anim_hit_start; -          float ft = GetFrameTime(); -          float pixel_per_second_per_ft = ft*mega_state.player.anim_hit_speed/mega_state.player.t_anim_hit_duration; -          mega_state.player.position = Vector2Add(mega_state.player.position, Vector2Scale(mega_state.player.move_dir, pixel_per_second_per_ft)); -          mega_state.player.tint_color = (Color){255, 128, 128, 255}; -          if (anim_length >= mega_state.player.t_anim_hit_duration) -          { -            mega_state.player.state = P_IDLE; -            mega_state.player.anim_hit_state = HA_IDLE; -            mega_state.player.tint_color = (Color){255, 255, 255, 255}; -          } -          // calculate center positions -          CenterRectAroundTL(&mega_state.player.render_rect, mega_state.player.position); -          mega_state.player.tile_id = GetTileIDFromWorld(mega_state.player.position); -        } break; -      default: -        {}break; -    } -    switch (mega_state.player.active_potion->state) -    { -      case (PS_THROWN): -        { -          // play potion throw animation -          float throw_speed = GetFrameTime() * mega_state.player.potion_throw_speed; -          float dist_thrown = Vector2Distance(mega_state.player.active_potion->position_curr, mega_state.player.active_potion->position_start); -          mega_state.player.active_potion->position_curr = Vector2Add(mega_state.player.active_potion->position_curr,  -                                                                      Vector2Scale(mega_state.player.active_potion->throw_dir, throw_speed)); -          float dist_to_target = Vector2Distance(mega_state.player.active_potion->position_curr, mega_state.player.active_potion->position_target); -          if (dist_thrown > mega_state.player.potion_throw_dist || dist_to_target <= 5.0f) -          { -            // change potion state to potion explode -            mega_state.player.active_potion->state = PS_SPREAD; -            mega_state.player.active_potion->t_potion_spread_start = GetTime(); -          } -        } break; -      case (PS_SPREAD): -        { -          float spread_duration = GetTime() - mega_state.player.active_potion->t_potion_spread_start; -          if (spread_duration >= mega_state.player.active_potion->t_potion_spread_duration)  -          { -            mega_state.player.active_potion->state = PS_HELD; -          } -          // loop through enemies to check if any are being burned -          // check troll -          for (int i=0;i<mega_state.troll_arr_sz;i++) -          { -            Troll *troll = &mega_state.trolls[i]; -            if (troll->state == C_DEAD) continue; -            float dist_to_enemy = Vector2Distance(troll->position, mega_state.player.active_potion->position_curr); -            if (dist_to_enemy <= mega_state.player.active_potion->radius) -            { -              if (mega_state.player.active_potion->type == PT_ATTACK) -              { -                // mark troll state as being burned -                if (troll->isburning == false) -                { -                  troll->isburning = true; -                  troll->t_anim_burn_start = GetTime(); -                  troll->attackers[0].type = PLAYER; -                  troll->attackers[0].id = 0; -                } -              } -              else if (mega_state.player.active_potion->type == PT_ALLY) -              { -                troll->friends[0] = (FriendEntity){.type=PLAYER, .id=0}; -              } -              troll->tint_color_active = mega_state.player.active_potion->tint_color_effect; -              troll->tint_color = troll->tint_color_active; -              troll->speed_multiplier = mega_state.player.active_potion->speed_multiplier; -            } -            else  -            { -              troll->speed_multiplier = 1.0f; -            } -            if (mega_state.player.active_potion->state != PS_SPREAD)  -            { -              troll->tint_color_active = troll->tint_color_base; -              troll->tint_color = troll->tint_color_active; -              troll->isburning = false; -              // remove player/entity as friend -              troll->friends[0].type = 0; -              troll->friends[0].id = 0; -            } -          } -          // check bandit -          for (int i=0;i<mega_state.bandit_arr_sz;i++) -          { -            Bandit *bandit = &mega_state.bandits[i]; -            if (bandit->state == C_DEAD) continue; -            float dist_to_enemy = Vector2Distance(bandit->position, mega_state.player.active_potion->position_curr); -            if (dist_to_enemy <= mega_state.player.active_potion->radius) -            { -              if (mega_state.player.active_potion->type == PT_ATTACK) -              { -                // mark bandit state as being burned -                if (bandit->isburning == false) -                { -                  bandit->isburning = true; -                  bandit->t_anim_burn_start = GetTime(); -                  bandit->attackers[0].type = PLAYER; -                  bandit->attackers[0].id = 0; -                } -              } -              else if (mega_state.player.active_potion->type == PT_ALLY) -              { -                bandit->friends[0] = (FriendEntity){.type=PLAYER, .id=0}; -              } -              bandit->tint_color_active = mega_state.player.active_potion->tint_color_effect; -              bandit->tint_color = bandit->tint_color_active; -              bandit->speed_multiplier = mega_state.player.active_potion->speed_multiplier; -            } -            else -            { -              bandit->speed_multiplier = 1.0f; -            } -            if (mega_state.player.active_potion->state != PS_SPREAD)  -            { -              bandit->tint_color_active = bandit->tint_color_base; -              bandit->tint_color = bandit->tint_color_active; -              bandit->isburning = false; -              // remove player/entity as friend -              bandit->friends[0].type = 0; -              bandit->friends[0].id = 0; -            } -          } -        } break; -      default: -        break; -    } - -    // ================= TROLL DETECTION LOOP AND STUFF ======================= -    for (int i=0;i<mega_state.troll_arr_sz;i++) -    { -      Troll *troll = &mega_state.trolls[i]; -      if (troll->state == C_HIT) -      { -        float anim_length = GetTime() - troll->t_anim_hit_start; -        float ft = GetFrameTime(); -        float pixel_per_second_per_ft = ft*troll->anim_hit_speed/troll->t_anim_hit_duration; -        troll->position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, pixel_per_second_per_ft)); -        troll->tint_color = (Color){255, 128, 128, 255}; -        if (anim_length >= troll->t_anim_hit_duration) -        { -          troll->state = C_IDLE; -          troll->anim_hit_state = HA_IDLE; -          troll->tint_color = (Color){255, 255, 255, 255}; -        } -        // calculate center positions -        CenterRectAroundTL(&troll->render_rect, troll->position); -        troll->tile_id = GetTileIDFromWorld(troll->position); -      } -      else if (troll->state != C_DEAD) -      { -        if (troll->isburning == true) -        { -          int anim_length_ms = 1000*(int)(GetTime() - troll->t_anim_burn_start); -          if (anim_length_ms%500 == 0 && anim_length_ms > 0) -          { -            // decrement health every 500ms -            troll->health -= 8; -            troll->t_anim_burn_start = GetTime(); -          } -        } -        troll->speed_multiplier *= troll->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f; -        AttackedEntity tentity = GetEntityToAttack(*troll); -        if (tentity.type > NONE || troll->anim_attack_state >= AA_CHARGE) -        { -          bool entity_hit = TrollAttackTarget(troll, tentity); -          if (entity_hit) -          { -            switch (tentity.type) -            { -              case (PLAYER): -                { -                  mega_state.player.anim_hit_state = HA_PLAY; -                  mega_state.player.t_anim_hit_start = GetTime(); -                  mega_state.player.move_dir = troll->move_dir; -                  mega_state.player.state = P_HIT; // disable walk check, stuff been thrown off -                  mega_state.player.health -= 8; -                } break; -              case (TROLL): -                { -                  // trigger troll get hit animation -                } break; -              case (BANDIT): -                { -                  // trigger bandit get hit animation -                  AggroEntity attacker = {.type=TROLL, .id=0}; -                  mega_state.bandits[tentity.id].attackers[0] = attacker; -                  mega_state.bandits[tentity.id].anim_hit_state = HA_PLAY; -                  mega_state.bandits[tentity.id].t_anim_hit_start = GetTime(); -                  mega_state.bandits[tentity.id].move_dir = troll->move_dir; -                  mega_state.bandits[tentity.id].state = C_HIT; -                  mega_state.bandits[tentity.id].health -= 10; -                } break; -              default: -              // do nothing -            } -          } -        } -        else -        { -          // get entity to follow -          FriendEntity follow = {}; -          if (troll->friends[0].type == PLAYER) -          { -            float char_dist = Vector2Distance(mega_state.player.position, troll->position); -            if (char_dist < 200) -            { -              follow.type = PLAYER; -            } -          } -          if (troll->state == C_IDLE && troll->ai_state == AI_NONE) -          { -            troll->t_idle_start = GetTime(); -            troll->move_dir = Vector2Zero(); -            troll->ai_state = AI_IDLE; -            troll->to_core_pos = troll->to_core_pos == true ? false: true; -          } -          double idle_time = GetTime() - troll->t_idle_start; -          if (idle_time >= troll->t_idle_duration && troll->ai_state == AI_IDLE) -          { -            if (troll->to_core_pos == false) -            { -              // calculate target position from patrol direction -              float max_dist = 200.0f; -              Vector2 move_dir; -              switch(troll->patrol_dir) -              { -                case (PATROL_TOP): -                { -                  move_dir = (Vector2){.x=0.0f, .y=-1.0f}; -                } break; -                case (PATROL_LEFT): -                { -                  move_dir = (Vector2){.x=-1.0f, .y=0.0f}; -                } break; -                case (PATROL_DOWN): -                { -                  move_dir = (Vector2){.x=0.0f, .y=1.0f}; -                } break; -                case (PATROL_RIGHT): -                { -                  move_dir = (Vector2){.x=1.0f, .y=0.0f}; -                } break; -              }; -              troll->move_dir = move_dir; -              troll->target_position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, max_dist)); -            } -            else -            { -              troll->target_position = troll->core_position; -              troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position)); -            } -            troll->state = C_PATROL; -            troll->ai_state = AI_PATROL; -          } -          // follow player / friend -          if (follow.type == PLAYER) -          { -            troll->state = C_FOLLOW; -            troll->ai_state = AI_NONE; -            troll->target_position = mega_state.player.position; -            troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position)); -          } -          float dist_to_target = Vector2Distance(troll->target_position, troll->position); -          if (dist_to_target > 50.0f) -          { -            MoveCharacter(troll, Vector2Scale(troll->move_dir, troll->move_speed)); -          } -          else if (troll->state == C_PATROL && troll->ai_state == AI_PATROL) -          { -            // we have reached target position -            // in case we were patrolling, increment the direction to move to -            // @note: maybe do this when first changing state from idle after character has been idle for hours -            if (troll->to_core_pos == false) -            { -              troll->patrol_dir = (troll->patrol_dir + 1)%PATROL_END; -            } -            troll->state = C_IDLE; -            troll->ai_state = AI_NONE; -          } -          CenterRectAroundTL(&troll->render_rect, troll->position); -        } -      } -    } -    // ========== BANDIT DETECTION AND STUFF -    for (int i=0;i<mega_state.bandit_arr_sz;i++) -    { -      Bandit *bandit = &mega_state.bandits[i]; -      if (bandit->state == C_HIT) -      { -        float anim_length = GetTime() - bandit->t_anim_hit_start; -        float ft = GetFrameTime(); -        float pixel_per_second_per_ft = ft*bandit->anim_hit_speed/bandit->t_anim_hit_duration; -        bandit->position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, pixel_per_second_per_ft)); -        bandit->tint_color = (Color){255, 128, 128, 255}; -        if (anim_length >= bandit->t_anim_hit_duration) -        { -          bandit->state = P_IDLE; -          bandit->anim_hit_state = HA_IDLE; -          bandit->tint_color = (Color){255, 255, 255, 255}; -        } -        // calculate center positions -        CenterRectAroundTL(&bandit->render_rect, bandit->position); -        bandit->tile_id = GetTileIDFromWorld(bandit->position); -      } -      else if (bandit->state != C_DEAD) -      { -        if (bandit->isburning == true) -        { -          int anim_length_ms = 1000*(int)(GetTime() - bandit->t_anim_burn_start); -          if (anim_length_ms%500 == 0 && anim_length_ms > 0) -          { -            // decrement health every 500ms -            bandit->health -= 8; -            bandit->t_anim_burn_start = GetTime(); -          } -        } -        bandit->speed_multiplier *= bandit->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f; -        AttackedEntity tentity = GetEntityToAttack(*bandit); -        if (tentity.type > NONE || bandit->anim_attack_state >= AA_CHARGE) -        { -          bool entity_hit = BanditAttackTarget(bandit, tentity); -          if (tentity.type > NONE) -          { -            if (entity_hit) -            { -              switch (tentity.type) -              { -                case (PLAYER): -                  { -                    mega_state.player.anim_hit_state = HA_PLAY; -                    mega_state.player.t_anim_hit_start = GetTime(); -                    mega_state.player.move_dir = bandit->move_dir; -                    mega_state.player.state = P_HIT; // disable walk check, stuff been thrown off -                    mega_state.player.health -= 5; -                  } break; -                case (TROLL): -                  { -                    // trigger troll get hit animation -                    AggroEntity attacker = {.type=BANDIT, .id=0}; -                    mega_state.trolls[tentity.id].attackers[0] = attacker; -                    mega_state.trolls[tentity.id].anim_hit_state = HA_PLAY; -                    mega_state.trolls[tentity.id].t_anim_hit_start = GetTime(); -                    mega_state.trolls[tentity.id].move_dir = bandit->move_dir; -                    mega_state.trolls[tentity.id].state = C_HIT; -                    mega_state.trolls[tentity.id].health -= 5; -                  } break; -                case (BANDIT): -                  { -                    // trigger bandit get hit animation -                  } break; -                default: -                // do nothing -              } -            } -          } -        } -        else -        { -          if (bandit->state == C_IDLE && bandit->ai_state == AI_NONE) -          { -            bandit->t_idle_start = GetTime(); -            bandit->move_dir = Vector2Zero(); -            bandit->ai_state = AI_IDLE; -            bandit->to_core_pos = bandit->to_core_pos == true ? false: true; -          } -          double idle_time = GetTime() - bandit->t_idle_start; -          if (idle_time >= bandit->t_idle_duration && bandit->ai_state == AI_IDLE) -          { -            if (bandit->to_core_pos == false) -            { -              // calculate target position from patrol direction -              float max_dist = 200.0f; -              Vector2 move_dir; -              switch(bandit->patrol_dir) -              { -                case (PATROL_TOP): -                { -                  move_dir = (Vector2){.x=0.0f, .y=-1.0f}; -                } break; -                case (PATROL_LEFT): -                { -                  move_dir = (Vector2){.x=-1.0f, .y=0.0f}; -                } break; -                case (PATROL_DOWN): -                { -                  move_dir = (Vector2){.x=0.0f, .y=1.0f}; -                } break; -                case (PATROL_RIGHT): -                { -                  move_dir = (Vector2){.x=1.0f, .y=0.0f}; -                } break; -              }; -              bandit->move_dir = move_dir; -              bandit->target_position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, max_dist)); -            } -            else -            { -              bandit->target_position = bandit->core_position; -              bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position)); -            } -            bandit->state = C_PATROL; -            bandit->ai_state = AI_PATROL; -          } -          // get entity to follow -          FriendEntity follow = {}; -          if (bandit->friends[0].type == PLAYER) -          { -            float char_dist = Vector2Distance(mega_state.player.position, bandit->position); -            if (char_dist < 200) -            { -              follow.type = PLAYER; -            } -          } -          // follow player / friend -          if (follow.type == PLAYER) -          { -            bandit->target_position = mega_state.player.position; -            bandit->state = C_FOLLOW; -            bandit->ai_state = AI_NONE; -            bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position)); -          } -          float dist_to_target = Vector2Distance(bandit->target_position, bandit->position); -          if (dist_to_target > 50.0f) -          { -            MoveCharacter(bandit, Vector2Scale(bandit->move_dir, bandit->move_speed)); -          } -          else if (bandit->state == C_PATROL && bandit->ai_state == AI_PATROL) -          { -            if (bandit->to_core_pos == false) -            { -              bandit->patrol_dir = (bandit->patrol_dir + 1)%PATROL_END; -            } -            bandit->state = C_IDLE; -            bandit->ai_state = AI_NONE; -          } -          CenterRectAroundTL(&bandit->render_rect, bandit->position); - -        } -      } -    } - -    // RENDERING -    BeginDrawing(); -    ClearBackground(RAYWHITE); -    DrawMapFloorTiles(); -    DrawFloorCoverTiles(); -    DrawCharacters(); - -    // do text rendering at the end -    // draw mouse position for debugging -    if (mega_state.player.state == P_HIT) -    { -      DrawText("player hit", 500, 20, 20, GREEN); -    } -    DrawText(TextFormat("Keys: %d/2", mega_state.key_picked_count), 1100, 20, 20, GREEN); -    EndDrawing(); -  } -  else if (mega_state.gameplay_state == GS_UI) -  { -    BeginDrawing(); -    ClearBackground(BLACK); -    DrawText("Blaidville", screenWidth/2 - 100, 40, 28, WHITE); -    DrawText("Instructions", screenWidth/2 - 140, 100, 22, WHITE); -    DrawText("Find keys to unlock the gate", screenWidth/2 - 140, 140, 18, WHITE); -    DrawText("Unlock the gate to escape", screenWidth/2 - 140, 160, 18, WHITE); -    DrawText("Controls", screenWidth/2 - 140, 220, 22, WHITE); -    DrawText("1 -> Potion 1", screenWidth/2 - 140, 260, 18, WHITE); -    DrawText("2 -> Potion 2", screenWidth/2 - 140, 280, 18, WHITE); -    DrawText("right mouse click -> throw potion", screenWidth/2 - 140, 300, 18, WHITE); -    DrawText("left mouse click -> move", screenWidth/2 - 140, 320, 18, WHITE); -    DrawText("Escape to play or pause the game", screenWidth/2 - 140, 340, 18, WHITE); -    DrawText("Restart the game to reset state", screenWidth/2 - 140, 360, 18, WHITE); -    EndDrawing(); -  } -  else if (mega_state.gameplay_state == GS_SUCCESS) -  { -    BeginDrawing(); -    ClearBackground(BLACK); -    DrawText("Objective Complete", screenWidth/2 - 100, 40, 28, GREEN); -    EndDrawing(); -  } -  else if (mega_state.gameplay_state == GS_OVER) -  { -    BeginDrawing(); -    ClearBackground(BLACK); -    DrawText("You Died", screenWidth/2 - 100, 40, 28, RED); -    EndDrawing(); -  } -} - diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..2bb0ba6 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,1508 @@ +#include "array.h" +#include "raylib.h" +#include "raymath.h" +#include <stdlib.h> + +#if defined(PLATFORM_WEB) +#include <emscripten/emscripten.h> +#endif + +#define KB(x) (1024 * (x)) +#define MB(x) (1024 * KB((x))) + +typedef enum { +  O = 0, // nothing +  // P = 1, // player +  t = 2,   // tree +  T = 3,   // troll area +  B = 4,   // bandit area +  T_B = 5, // troll base +  B_B = 6, // bandit base +  // G = 7, // gate (exit) +} MapSymbol; + +#include "entity.h" +#include "memory/memory.c" +#include "array.cpp" +#include "characters.h" + +typedef enum { +  PT_NONE = 0, +  PT_ALLY = 1, +  PT_ATTACK = 2, +} PotionType; + +typedef enum { +  PS_NONE = 0, +  PS_HELD = 1, +  PS_THROWN = 2, +  PS_SPREAD = 3, +} PotionState; + +typedef enum { +  P_IDLE = 0, +  P_MOVE = 1, +  P_HIT = 2, +} PlayerState; + +typedef enum { +  GS_GAME = 0, +  GS_UI = 1, +  GS_SUCCESS = 2, +  GS_OVER = 3, +} GameplayState; + +struct Potion { +  PotionState state; +  PotionType type; +  float radius; // radius during which potion effects last +  int damage;   // how much damage potion does +  int healing;  // how much healing potion does +  float +      speed_multiplier; // how much enemy speed is effected inside potion radius +  float t_potion_throw_start; +  float t_potion_spread_start; +  float t_potion_spread_duration; // how long does the potion spread last +  Vector2 throw_dir; +  Vector2 position_start; +  Vector2 position_curr; +  Vector2 position_target; +  Color tint_color_spread; +  Color tint_color_effect; +  Texture2D *sprite; +} Potion; + +typedef struct Potion FirePotion; +typedef struct Potion FriendPotion; + +typedef struct Player { +  int entity_id; +  PlayerState state; // PlayerState +  int tile_id;       // MapSymbol +  int health; +  float move_speed; +  Vector2 position; +  Vector2 target_position; +  Vector2 move_dir; +  Color tint_color; +  Rectangle render_rect; +  Texture2D *sprite; +  // attack state +  bool is_attack; +  float throw_dist; +  Vector2 attack_dir; +  float potion_throw_speed; +  float potion_throw_dist; +  struct Potion *active_potion; +  FirePotion fire_potion; +  FriendPotion friend_potion; +  // +  // Animation +  // +  HitAnimState anim_hit_state; +  float anim_hit_speed; +  double t_anim_hit_start; +  float t_anim_hit_duration; +} Player; + +typedef struct Character Troll; +typedef struct Character Bandit; + +typedef struct State { +  GameplayState gameplay_state; +  // how much scaling to apply from original asset resolution +  float render_scale; +  Vector2 pixels_per_gridbox; +  Vector2 mouse_position; +  // @note: normally when we place a texture, the coordinates +  // are the top left coordinates. This will cause a disconnect +  // when moving the player with a point and click motion. +  // To fix that we calculate a new position so the player_coordinates +  // are in the center. This will make it so when the player clicks on the +  // ground somewhere, the character sprite will be properly centered on that +  // point. + +  Player player; +  Troll _troll; +  Troll trolls[5]; +  int troll_arr_sz; +  Bandit _bandit; +  Bandit bandits[5]; +  int bandit_arr_sz; +  Vector2 bandit_position; +  // player movement +  // @todo +  // direction player is being hit from, incorporate that into the gameplay code +  // For now, just update the player_move_dir to the player_hit_dir +  // Vector2 player_hit_dir; +  // float player_move_delta; // amount player will move +  // target position player will move to +  // 1. the player can move intentionally, in which case carry forward from +  // player position target +  // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET + +  // game sprites +  Texture2D *grass_sprite; + +  Texture2D *troll_weapon_sprite; +  Texture2D *bandit_sprite; +  Texture2D *bandit_weapon_sprite; + +  Texture2D *troll_area_floor_sprite; +  Texture2D *bandit_area_floor_sprite; + +  Texture2D *troll_base_sprite; +  Texture2D *bandit_base_sprite; + +  // key sprites and stuff +  int key_picked_count; +  bool key_should_render[2]; +  Texture2D *key_sprite; +  Vector2 key_positions[2]; +  Rectangle key_render_rects[2]; +  // gate stuff +  bool is_gate_open; +  Vector2 gate_position; +  Rectangle gate_render_rect; +  Texture2D *active_gate_sprite; +  Texture2D *gate_closed_sprite; +  Texture2D *gate_open_sprite; + +  // memory +  unsigned char *permanent_storage; +  struct Arena arena_main; +} State; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +const int screenWidth = 1280; +const int screenHeight = 1024; +State mega_state = {}; +// layer ground +int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O}, {O, O, O, O, T, T, T, T}, +                            {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, +                            {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, +                            {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}}; +// vegetation grid +int floor_cover_grid_map[8][8] = { +    {O, O, O, O, O, O, O, O},   {O, O, O, O, O, O, O, O}, +    {O, O, O, O, O, O, O, O},   {O, O, O, O, O, O, O, O}, +    {O, O, O, O, O, O, O, T_B}, {O, B_B, O, O, O, O, O, O}, +    {O, O, O, O, O, O, O, O},   {O, O, O, O, O, O, O, O}}; +Vector2 grid_dims = {8, 8}; +//---------------------------------------------------------------------------------- +// Module functions declaration +//---------------------------------------------------------------------------------- +void CenterRectAroundTL(Rectangle *to_center, Vector2 position); +void UpdateDrawFrame(); // Update and Draw one frame +void CenterRectAroundTL(Rectangle *to_center, Vector2 position) { +  to_center->x = position.x - ((float)to_center->width) / 2.0f; +  to_center->y = position.y - ((float)to_center->height) / 2.0f; +} + +int main(void) { +  InitWindow(screenWidth, screenHeight, "blaidville prototype"); +  SetTargetFPS(60); +  SetExitKey(0); +  Texture2D grass_sprite = +      LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png"); +  Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png"); +  Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png"); +  Texture2D bandit_sprite = +      LoadTexture("./assets/monster/unique/maurice_new.png"); +  Texture2D troll_area_floor_sprite = +      LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png"); +  Texture2D bandit_area_floor_sprite = +      LoadTexture("./assets/dungeon/floor/dirt_east_new.png"); +  Texture2D bandit_base_sprite = +      LoadTexture("./assets/dungeon/floor/hive_3.png"); +  Texture2D troll_base_sprite = +      LoadTexture("./assets/dungeon/floor/dirt_2_old.png"); +  Texture2D troll_weapon_sprite = +      LoadTexture("./assets/item/weapon/club_old.png"); +  Texture2D fire_potion_sprite = +      LoadTexture("./assets/item/potion/ruby_old.png"); +  Texture2D friend_potion_sprite = +      LoadTexture("./assets/item/potion/emerald.png"); +  Texture2D key_sprite = LoadTexture("./assets/item/misc/key.png"); +  Texture2D gate_closed_sprite = +      LoadTexture("./assets/dungeon/gateways/bazaar_gone.png"); +  Texture2D gate_open_sprite = +      LoadTexture("./assets/dungeon/gateways/bazaar_portal.png"); + +  mega_state.render_scale = 2.0f; + +  mega_state.key_sprite = &key_sprite; +  mega_state.key_positions[0] = (Vector2){200, 700}; +  mega_state.key_positions[1] = (Vector2){1200, 600}; +  mega_state.key_render_rects[0] = (Rectangle){ +      .width = (float)key_sprite.width, .height = (float)key_sprite.height}; +  mega_state.key_render_rects[1] = (Rectangle){ +      .width = (float)key_sprite.width, .height = (float)key_sprite.height}; +  CenterRectAroundTL(&mega_state.key_render_rects[0], +                     mega_state.key_positions[0]); +  CenterRectAroundTL(&mega_state.key_render_rects[1], +                     mega_state.key_positions[1]); +  mega_state.key_should_render[0] = 1; +  mega_state.key_should_render[1] = 1; + +  mega_state.grass_sprite = &grass_sprite; +  mega_state.bandit_sprite = &bandit_sprite; +  mega_state.gate_open_sprite = &gate_open_sprite; +  mega_state.gate_closed_sprite = &gate_closed_sprite; +  mega_state.active_gate_sprite = &gate_closed_sprite; +  mega_state.troll_area_floor_sprite = &troll_area_floor_sprite; +  mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite; +  mega_state.bandit_base_sprite = &bandit_base_sprite; +  mega_state.troll_base_sprite = &troll_base_sprite; +  mega_state.troll_weapon_sprite = &troll_weapon_sprite; +  mega_state.pixels_per_gridbox = +      (Vector2){screenWidth / grid_dims.x, screenHeight / grid_dims.y}; + +  // characters +  // player +  mega_state.player = (Player){ +      .health = (int)45.0f, +      .move_speed = 5.0f, +      .position = (Vector2){50.0f, 50.0f}, +      .tint_color = WHITE, +      .render_rect = +          (Rectangle){.x = 0, +                      .y = 0, +                      .width = +                          0.5f * player_sprite.width * mega_state.render_scale, +                      .height = player_sprite.height * mega_state.render_scale}, +      .sprite = &player_sprite, +      .throw_dist = 100.0f, +      .potion_throw_speed = 250.0f, +      .potion_throw_dist = 200.0f, +      .fire_potion = +          (FirePotion){ +              .state = PS_NONE, +              .type = PT_ATTACK, +              .radius = 40.0f, +              .damage = 5, +              .speed_multiplier = 0.5f, +              .t_potion_spread_duration = 5.0f, +              .tint_color_spread = (Color){200, 50, 50, 100}, +              .tint_color_effect = (Color){200, 50, 50, 255}, +              .sprite = &fire_potion_sprite, +          }, +      .friend_potion = +          (FriendPotion){ +              .state = PS_HELD, +              .type = PT_ALLY, +              .radius = 40.0f, +              .speed_multiplier = 1.0f, +              .t_potion_spread_duration = 5.0f, +              .tint_color_spread = (Color){50, 200, 50, 100}, +              .tint_color_effect = (Color){50, 200, 50, 255}, +              .sprite = &friend_potion_sprite, +          }, + +      .anim_hit_speed = 50.0f, +      .t_anim_hit_duration = 0.2f, +  }; +  // troll +  Troll troll = { +      .type = TROLL, +      .state = C_IDLE, +      .home_tile_id = T, +      .health = 50, +      .speed_multiplier = 1.0f, +      .move_speed = 2.0f, +      .to_core_pos = true, +      .t_idle_duration = 2.0f, +      .render_rect = +          (Rectangle){.x = 0, +                      .y = 0, +                      .width = troll_sprite.width * mega_state.render_scale, +                      .height = troll_sprite.height * mega_state.render_scale}, +      .tint_color_base = WHITE, +      .tint_color_active = WHITE, +      .tint_color = WHITE, +      .sprite = &troll_sprite, +      .anim_hit_speed = 20.0f, +      .t_anim_hit_duration = 0.2f, +      .anim_attack_speed = 100.0f, +      .t_anim_charge_duration = 1.0f, +      .t_anim_attack_duration = 0.4, +  }; +  mega_state.troll_arr_sz = 1; + +  mega_state.trolls[0] = troll; +  mega_state.trolls[0].entity_id = 0; +  mega_state.trolls[0].t_idle_duration = 4.0f; +  mega_state.trolls[0].position = (Vector2){750, 340}; +  mega_state.trolls[0].core_position = mega_state.trolls[0].position; + +  mega_state.trolls[1] = troll; +  mega_state.trolls[1].entity_id = 1; +  mega_state.trolls[1].position = (Vector2){1200, 300}; +  mega_state.trolls[1].core_position = mega_state.trolls[1].position; + +  mega_state.trolls[2] = troll; +  mega_state.trolls[2].entity_id = 2; +  mega_state.trolls[2].t_idle_duration = 3.5f; +  mega_state.trolls[2].position = (Vector2){800, 800}; +  mega_state.trolls[2].core_position = mega_state.trolls[2].position; + +  mega_state.trolls[3] = troll; +  mega_state.trolls[3].entity_id = 3; +  mega_state.trolls[3].t_idle_duration = 3.0f; +  mega_state.trolls[3].position = (Vector2){1130, 640}; +  mega_state.trolls[3].core_position = mega_state.trolls[0].position; + +  mega_state.trolls[4] = troll; +  mega_state.trolls[4].entity_id = 4; +  mega_state.trolls[4].t_idle_duration = 1.5f; +  mega_state.trolls[4].position = (Vector2){1000, 900}; +  mega_state.trolls[4].core_position = mega_state.trolls[4].position; + +  // bandit +  Bandit bandit = (Bandit){ +      .type = BANDIT, +      .state = C_IDLE, +      .home_tile_id = B, +      .ai_state = AI_NONE, +      .health = 40, +      .speed_multiplier = 1.0f, +      .move_speed = 3.0f, +      .to_core_pos = true, +      .t_idle_duration = 2.0f, +      .render_rect = +          (Rectangle){.x = 0, +                      .y = 0, +                      .width = bandit_sprite.width * mega_state.render_scale, +                      .height = bandit_sprite.height * mega_state.render_scale}, +      .tint_color_base = WHITE, +      .tint_color_active = WHITE, +      .tint_color = WHITE, +      .sprite = &bandit_sprite, +      .anim_hit_speed = 50.0f, +      .t_anim_hit_duration = 0.2f, +      .anim_attack_speed = 50.0f, +      .t_anim_charge_duration = 0.5f, +      .t_anim_attack_duration = 0.2f, +  }; +  mega_state.bandit_arr_sz = 1; + +  mega_state.bandits[0] = bandit; +  mega_state.bandits[0].entity_id = 0; +  mega_state.bandits[0].position = (Vector2){264.0f, 900.0f}; +  mega_state.bandits[0].core_position = mega_state.bandits[0].position; + +  mega_state.bandits[1] = bandit; +  mega_state.bandits[1].entity_id = 1; +  mega_state.bandits[1].t_idle_duration = 4.0f; +  mega_state.bandits[1].position = (Vector2){164.0f, 600.0f}; +  mega_state.bandits[1].core_position = mega_state.bandits[1].position; + +  mega_state.bandits[2] = bandit; +  mega_state.bandits[2].entity_id = 2; +  mega_state.bandits[2].t_idle_duration = 3.0f; +  mega_state.bandits[2].position = (Vector2){264.0f, 400.0f}; +  mega_state.bandits[2].core_position = mega_state.bandits[2].position; + +  mega_state.bandits[3] = bandit; +  mega_state.bandits[3].entity_id = 3; +  mega_state.bandits[3].t_idle_duration = 2.5f; +  mega_state.bandits[3].position = (Vector2){400.0f, 800.0f}; +  mega_state.bandits[3].core_position = mega_state.bandits[3].position; + +  mega_state.bandits[4] = bandit; +  mega_state.bandits[4].entity_id = 4; +  mega_state.bandits[4].t_idle_duration = 1.0f; +  mega_state.bandits[4].position = (Vector2){400.0f, 600.0f}; +  mega_state.bandits[4].core_position = mega_state.bandits[4].position; + +  // @todo: action log to describe and dictate all the major events happen +  // char *str_buffer = (char*)MemAlloc(512*sizeof(char)); +  mega_state.player.active_potion = &mega_state.player.friend_potion; +  CenterRectAroundTL(&mega_state.trolls[0].render_rect, +                     mega_state.trolls[0].position); +  CenterRectAroundTL(&mega_state.trolls[1].render_rect, +                     mega_state.trolls[1].position); + +  CenterRectAroundTL(&mega_state.bandits[0].render_rect, +                     mega_state.bandits[0].position); +  CenterRectAroundTL(&mega_state.bandits[0].render_rect, +                     mega_state.bandits[0].position); +  // no action log right now +  // mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, +  // .len=0}; +  mega_state.gameplay_state = GS_UI; + +  mega_state.gate_position = (Vector2){.x = 500.0f, .y = 16.0f}; +  mega_state.gate_render_rect = +      (Rectangle){.width = mega_state.gate_closed_sprite->width * 1.5f, +                  .height = mega_state.gate_closed_sprite->height * 1.5f}; +  CenterRectAroundTL(&mega_state.gate_render_rect, mega_state.gate_position); + +  // allocate large memory +  mega_state.permanent_storage = (unsigned char *)malloc(MB(10)); +  arena_init(&mega_state.arena_main, mega_state.permanent_storage, MB(10)); +  for (int i = 0; i < mega_state.troll_arr_sz; i++) { +    mega_state.trolls[i].attackers = entity_arr_init(&mega_state.arena_main, 5); +    mega_state.trolls[i].friends = entity_arr_init(&mega_state.arena_main, 5); +  } +  for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +    mega_state.bandits[i].attackers = +        entity_arr_init(&mega_state.arena_main, 5); +    mega_state.bandits[i].friends = entity_arr_init(&mega_state.arena_main, 5); +  } + +#if defined(PLATFORM_WEB) +  emscripten_set_main_loop(UpdateDrawFrame, 0, 1); +#else +  SetTargetFPS(60); + +  while (!WindowShouldClose()) { +    UpdateDrawFrame(); +  } +#endif + +  CloseWindow(); + +  return 0; +} + +void DrawMapFloorTiles() { +  Color BlackTint = {128, 128, 128, 255}; +  Texture2D grass = *mega_state.grass_sprite; +  float winc = (float)grass.width * mega_state.render_scale; +  float hinc = (float)grass.height * mega_state.render_scale; +  Vector2 grid_pos = {0, 0}; +  for (int ypos = 0; ypos < screenHeight; ypos += hinc) { +    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); +    for (int xpos = 0; xpos < screenWidth; xpos += winc) { +      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); +      Vector2 pixel_pos = {static_cast<float>(xpos), static_cast<float>(ypos)}; +      int grid_ele = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; +      Texture2D render_tex; +      switch (grid_ele) { +      case (O): { +        render_tex = *mega_state.grass_sprite; +      } break; +      case (B): { +        render_tex = *mega_state.bandit_area_floor_sprite; +      } break; +      case (T): { +        render_tex = *mega_state.troll_area_floor_sprite; +      } break; +      default: { +      } break; +      } +      DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, +                    BlackTint); +    } +  } +} + +void DrawFloorCoverTiles() { +  Color BlackTint = {128, 128, 128, 255}; +  Texture2D null_tex = *mega_state.player.sprite; +  float winc = (float)null_tex.width * mega_state.render_scale; +  float hinc = (float)null_tex.height * mega_state.render_scale; + +  Vector2 grid_pos = {0, 0}; +  for (int ypos = 0; ypos < screenHeight; ypos += hinc) { +    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); +    for (int xpos = 0; xpos < screenWidth; xpos += winc) { +      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); +      Vector2 pixel_pos = {(float)xpos, (float)ypos}; +      int grid_ele = floor_cover_grid_map[(int)grid_pos.y][(int)grid_pos.x]; +      Texture2D render_tex; +      switch (grid_ele) { +      case (B_B): { +        render_tex = *mega_state.bandit_base_sprite; +      } break; +      case (T_B): { +        render_tex = *mega_state.troll_base_sprite; +      } break; +      default: { +        render_tex = null_tex; +      } +        continue; +      } +      DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, +                    BlackTint); +    } +  } +} + +Vector2 GetSpriteCenterPosition(Texture2D sprite, Vector2 position, +                                float render_scale) { +  float centered_x = position.x - ((float)sprite.width) * render_scale / 2.0f; +  float centered_y = position.y - ((float)sprite.height) * render_scale / 2.0f; + +  return (Vector2){.x = centered_x, .y = centered_y}; +} + +void DrawCharacters() { +  DrawTextureEx( +      *mega_state.active_gate_sprite, +      (Vector2){mega_state.gate_render_rect.x, mega_state.gate_render_rect.y}, +      0, mega_state.render_scale * 1.5, WHITE); +  // draw potion +  if (mega_state.player.active_potion->state == PS_THROWN) { +    DrawTextureEx(*mega_state.player.active_potion->sprite, +                  mega_state.player.active_potion->position_curr, 0, +                  0.4 * mega_state.render_scale, WHITE); +  } else if (mega_state.player.active_potion->state == PS_SPREAD) { +    Vector2 pos = mega_state.player.active_potion->position_curr; +    DrawCircle(pos.x, pos.y, mega_state.player.active_potion->radius, +               mega_state.player.active_potion->tint_color_spread); +  } +  if (mega_state.key_should_render[0] == 1) { +    DrawTextureEx( +        *mega_state.key_sprite, +        (Vector2){mega_state.key_positions[0].x, mega_state.key_positions[0].y}, +        0, mega_state.render_scale / 2.0f, WHITE); +  } +  if (mega_state.key_should_render[1] == 1) { +    DrawTextureEx( +        *mega_state.key_sprite, +        (Vector2){mega_state.key_positions[1].x, mega_state.key_positions[1].y}, +        0, mega_state.render_scale / 2.0f, WHITE); +  } + +  Rectangle src_rect, dest_rect; +  // draw Troll +  // - troll weapon +  for (int i = 0; i < mega_state.troll_arr_sz; i++) { +    Vector2 troll_weapon_centered = GetSpriteCenterPosition( +        *mega_state.troll_weapon_sprite, mega_state.trolls[i].position, 1.0f); +    // move to left hand +    troll_weapon_centered.x -= mega_state.trolls[i].sprite->width; +    src_rect = +        (Rectangle){.x = 0, +                    .y = 0, +                    .width = -(float)mega_state.troll_weapon_sprite->width, +                    .height = (float)mega_state.troll_weapon_sprite->height}; +    dest_rect = (Rectangle){.x = troll_weapon_centered.x, +                            .y = troll_weapon_centered.y, +                            .width = 0.5f * mega_state.render_scale * +                                     mega_state.troll_weapon_sprite->width, +                            .height = 0.5f * mega_state.render_scale * +                                      mega_state.troll_weapon_sprite->height}; +    DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect, +                   (Vector2){0, 0}, 0, RAYWHITE); +    // - troll sprite +    DrawTextureEx(*mega_state.trolls[i].sprite, +                  (Vector2){mega_state.trolls[i].render_rect.x, +                            mega_state.trolls[i].render_rect.y}, +                  0, mega_state.render_scale, mega_state.trolls[i].tint_color); +    // draw collision box +    // DrawRectangleLines(mega_state.troll.render_rect.x, +    // mega_state.troll.render_rect.y, +    //                   mega_state.troll.render_rect.width, +    //                   mega_state.troll.render_rect.height, RED); +  } + +  // draw Bandit +  for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +    DrawTextureEx(*mega_state.bandits[i].sprite, +                  (Vector2){mega_state.bandits[i].render_rect.x, +                            mega_state.bandits[i].render_rect.y}, +                  0, mega_state.render_scale, mega_state.bandits[i].tint_color); +  } +  // draw player +  float p_render_scale = 1.0f; +  Vector2 player_weapon_centered = GetSpriteCenterPosition( +      *mega_state.player.sprite, mega_state.player.position, p_render_scale); +  // move to left hand +  player_weapon_centered.x -= +      (mega_state.player.sprite->width * p_render_scale / 2.0f) - 2.0f; +  player_weapon_centered.y += (12.0f); +  src_rect = (Rectangle){ +      .x = 0, +      .y = 0, +      .width = (float)mega_state.player.active_potion->sprite->width, +      .height = (float)mega_state.player.active_potion->sprite->height}; + +  dest_rect = +      (Rectangle){.x = player_weapon_centered.x, +                  .y = player_weapon_centered.y, +                  .width = 0.4f * mega_state.render_scale * +                           mega_state.player.active_potion->sprite->width, +                  .height = 0.4f * mega_state.render_scale * +                            mega_state.player.active_potion->sprite->height}; + +  DrawTexturePro(*mega_state.player.active_potion->sprite, src_rect, dest_rect, +                 (Vector2){0, 0}, 0, RAYWHITE); +  Vector2 player_center = GetSpriteCenterPosition(*mega_state.player.sprite, +                                                  mega_state.player.position, +                                                  mega_state.render_scale); +  DrawTextureEx(*mega_state.player.sprite, player_center, 0, +                mega_state.render_scale, mega_state.player.tint_color); +} + +int GetTileIDFromWorld(Vector2 world_position) { +  Vector2 grid_pos = Vector2Divide(Vector2Multiply(world_position, grid_dims), +                                   (Vector2){screenWidth, screenHeight}); +  int tile = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; +  return tile; +} + +bool MoveCharacter(struct Character *to_move, Vector2 position) { +  Vector2 new_pos = Vector2Add(to_move->position, position); +  int tile_at_new_pos = GetTileIDFromWorld(new_pos); +  int tile_at_curr_pos = GetTileIDFromWorld(to_move->position); +  bool char_at_home = to_move->home_tile_id == tile_at_curr_pos ? true : false; +  // move only if next position in home tile +  if (tile_at_new_pos == to_move->home_tile_id || to_move->state == C_FOLLOW || +      char_at_home == false) { +    to_move->position = Vector2Add(to_move->position, position); +    return true; +  } else { +    to_move->target_position = to_move->position; +    return false; +  } +} + +AttackedEntity GetEntityToAttack(struct Character attacker) { +  AttackedEntity attackee = {}; +  // check if player can be attacked +  bool attacker_at_home_tile = +      GetTileIDFromWorld(attacker.position) == attacker.home_tile_id ? true +                                                                     : false; +  Vector2 char_position = mega_state.player.position; +  int char_tile_id = GetTileIDFromWorld(char_position); +  Rectangle char_rect = mega_state.player.render_rect; +  float min_dist = screenWidth; + +  struct Entity entity_player = {.type = PLAYER, .id = 0}; +  int attacker_attack_player_index = +      entity_arr_find(&attacker.attackers, entity_player); +  if ((attacker_attack_player_index >= 0 || +       char_tile_id == attacker.home_tile_id || +       attacker_at_home_tile == false) && +      entity_arr_find(&attacker.friends, entity_player) == -1) { +    float char_dist = Vector2Distance(char_position, attacker.position); +    if (char_dist < 200) { +      attackee.type = PLAYER; +    } +  } + +  // check if bandit can instead be attacked +  int attacker_attack_entity_index = -1; +  for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +    Bandit bandit = mega_state.bandits[i]; +    char_tile_id = GetTileIDFromWorld(char_position); +    char_position = bandit.position; +    char_rect = bandit.render_rect; +    int _attack_bandit_index = entity_arr_find( +        &attacker.attackers, +        (struct Entity){.type = bandit.type, .id = bandit.entity_id}); +    if (bandit.state == C_DEAD) +      continue; + +    bool is_closest_attacker = +        _attack_bandit_index >= 0 && +        (_attack_bandit_index < attacker_attack_entity_index || +         attacker_attack_entity_index == -1); +    if ((char_tile_id == attacker.home_tile_id || +         attacker_at_home_tile == false) && +        (is_closest_attacker || attacker.type != BANDIT)) { +      float char_dist = Vector2Distance(char_position, attacker.position); +      if (char_dist < 200 && char_dist < min_dist) { +        min_dist = char_dist; +        attackee.type = BANDIT; +        attackee.id = bandit.entity_id; +        attacker_attack_entity_index = _attack_bandit_index; +      } +    } +  } + +  // check if troll can instead be attacked +  for (int i = 0; i < mega_state.troll_arr_sz; i++) { +    Troll troll = mega_state.trolls[i]; +    char_position = troll.position; +    char_tile_id = GetTileIDFromWorld(char_position); +    char_rect = troll.render_rect; +    if (troll.state == C_DEAD) +      continue; +    int _attack_troll_index = entity_arr_find( +        &attacker.attackers, +        (struct Entity){.type = troll.type, .id = troll.entity_id}); +    int is_closest_attacker = +        _attack_troll_index >= 0 && +        (_attack_troll_index < attacker_attack_entity_index || +         attacker_attack_entity_index == -1); +    if ((char_tile_id == attacker.home_tile_id || +         attacker_at_home_tile == false) && +        (is_closest_attacker || attacker.type != TROLL)) { +      float char_dist = Vector2Distance(char_position, attacker.position); +      if (char_dist < 200 && char_dist < min_dist) { +        min_dist = char_dist; +        attackee.type = TROLL; +        attackee.id = troll.entity_id; +        attacker_attack_entity_index = _attack_troll_index; +      } +    } +  } + +  return attackee; +} + +bool TrollAttackTarget(Troll *troll, AttackedEntity to_attack) { +  float dist_to_target = 0; + +  troll->state = C_IDLE; +  troll->ai_state = AI_NONE; +  switch (to_attack.type) { +  case (PLAYER): { +    troll->last_enemy_position = mega_state.player.position; +    troll->target_position = mega_state.player.position; +    troll->target_rect = mega_state.player.render_rect; + +    dist_to_target = Vector2Distance(troll->target_position, troll->position); +    troll->ai_state = AI_DETECT; +    troll->state = C_FOLLOW; +  } break; +  case (BANDIT): { +    troll->last_enemy_position = mega_state.bandits[to_attack.id].position; +    troll->target_position = mega_state.bandits[to_attack.id].position; +    troll->target_rect = mega_state.bandits[to_attack.id].render_rect; + +    dist_to_target = Vector2Distance(troll->target_position, troll->position); +    troll->ai_state = AI_DETECT; +    troll->state = C_FOLLOW; +  } break; +  default: +    break; +  } +  if (troll->ai_state == AI_DETECT && dist_to_target < 80) { +    troll->ai_state = AI_ATTACK; +  } +  if (troll->ai_state >= AI_DETECT && troll->anim_attack_state != AA_ATTACK) { +    troll->move_dir = Vector2Normalize( +        Vector2Subtract(troll->target_position, troll->position)); +    if (dist_to_target > 10.0f) { +      // @note: this actually might not even be needed +      // move only if both are more than 10pixels apart. +      // it becomes jarring other wise +      MoveCharacter(troll, +                    Vector2Scale(troll->move_dir, +                                 troll->speed_multiplier * troll->move_speed)); +    } +  } +  CenterRectAroundTL(&troll->render_rect, troll->position); +  if (troll->anim_attack_state == AA_IDLE && troll->ai_state == AI_ATTACK) { +    // start attack animation +    troll->t_anim_charge_start = GetTime(); +    troll->anim_attack_state = AA_CHARGE; +  } +  float charge_time = 0.0f; +  float charge_color_delta = 0.0f; +  if (troll->anim_attack_state == AA_CHARGE) { +    // get color based on time +    float charge_progress = (GetTime() - troll->t_anim_charge_start) / +                            troll->t_anim_charge_duration; +    troll->tint_color.r = +        Clamp(troll->tint_color_active.r - +                  (charge_progress * troll->tint_color_active.r), +              0, 255); +    if (charge_progress >= 1.0f) { +      troll->anim_attack_state = AA_ATTACK; +      troll->t_anim_attack_start = GetTime(); +    } +  } else if (troll->anim_attack_state == AA_ATTACK) { +    // maximum distance to attack jump in +    float anim_length = GetTime() - troll->t_anim_attack_start; +    float ft = GetFrameTime(); +    float pixel_per_second_per_ft = +        ft * troll->anim_attack_speed / troll->t_anim_attack_duration; +    troll->position = +        Vector2Add(troll->position, +                   Vector2Scale(troll->move_dir, pixel_per_second_per_ft)); +    troll->tint_color = (Color){255, 128, 255, 255}; +    // get troll center position and check for collision with player +    // now check if colliding with player +    bool is_colliding = +        CheckCollisionRecs(troll->target_rect, troll->render_rect); +    if (anim_length >= troll->t_anim_attack_duration || is_colliding) { +      troll->state = C_IDLE; +      troll->ai_state = AI_NONE; +      troll->anim_attack_state = AA_IDLE; +      troll->tint_color = troll->tint_color_active; +      if (is_colliding == true) { +        return true; // is colliding with target +      } +    } +  } +  // once troll is done moving, clear state. +  troll->state = C_IDLE; +  troll->ai_state = AI_NONE; +  return false; +} + +bool BanditAttackTarget(Bandit *bandit, AttackedEntity to_attack) { +  float dist_to_target = 0; + +  bandit->state = C_IDLE; +  bandit->ai_state = AI_NONE; +  switch (to_attack.type) { +  case (PLAYER): { +    bandit->last_enemy_position = mega_state.player.position; +    bandit->target_position = mega_state.player.position; +    bandit->target_rect = mega_state.player.render_rect; + +    dist_to_target = Vector2Distance(bandit->target_position, bandit->position); +    bandit->ai_state = AI_DETECT; +    bandit->state = C_FOLLOW; +  } break; +  case (TROLL): { +    bandit->last_enemy_position = mega_state.trolls[to_attack.id].position; +    bandit->target_position = mega_state.trolls[to_attack.id].position; +    bandit->target_rect = mega_state.trolls[to_attack.id].render_rect; + +    dist_to_target = Vector2Distance(bandit->target_position, bandit->position); +    bandit->ai_state = AI_DETECT; +    bandit->state = C_FOLLOW; +  } break; +  default: +    break; +  } +  if (bandit->ai_state == AI_DETECT && dist_to_target < 80) { +    bandit->ai_state = AI_ATTACK; +  } +  if (bandit->ai_state >= AI_DETECT && bandit->anim_attack_state != AA_ATTACK) { +    bandit->move_dir = Vector2Normalize( +        Vector2Subtract(bandit->target_position, bandit->position)); +    if (dist_to_target > 10.0f) { +      // @note: this actually might not even be needed +      // move only if both are more than 10pixels apart. +      // it becomes jarring other wise +      bool can_move = MoveCharacter( +          bandit, Vector2Scale(bandit->move_dir, +                               bandit->speed_multiplier * bandit->move_speed)); +    } +  } +  CenterRectAroundTL(&bandit->render_rect, bandit->position); +  if (bandit->anim_attack_state == AA_IDLE && bandit->ai_state == AI_ATTACK) { +    // start attack animation +    bandit->t_anim_charge_start = GetTime(); +    bandit->anim_attack_state = AA_CHARGE; +  } +  float charge_time = 0.0f; +  float charge_color_delta = 0.0f; +  if (bandit->anim_attack_state == AA_CHARGE) { +    // get color based on time +    float charge_progress = (GetTime() - bandit->t_anim_charge_start) / +                            bandit->t_anim_charge_duration; +    bandit->tint_color.r = +        Clamp(bandit->tint_color_active.r - +                  (charge_progress * bandit->tint_color_active.r), +              0, 255); +    if (charge_progress >= 1.0f) { +      bandit->anim_attack_state = AA_ATTACK; +      bandit->t_anim_attack_start = GetTime(); +    } +  } else if (bandit->anim_attack_state == AA_ATTACK) { +    // maximum distance to attack jump in +    float anim_length = GetTime() - bandit->t_anim_attack_start; +    float ft = GetFrameTime(); +    float pixel_per_second_per_ft = +        ft * bandit->anim_attack_speed / bandit->t_anim_attack_duration; +    bandit->position = +        Vector2Add(bandit->position, +                   Vector2Scale(bandit->move_dir, pixel_per_second_per_ft)); +    bandit->tint_color = (Color){255, 128, 255, 255}; +    // get bandit center position and check for collision with player +    // now check if colliding with player +    bool is_colliding = +        CheckCollisionRecs(bandit->target_rect, bandit->render_rect); +    if (anim_length >= bandit->t_anim_attack_duration || is_colliding) { +      bandit->state = C_IDLE; +      bandit->ai_state = AI_NONE; +      bandit->anim_attack_state = AA_IDLE; +      bandit->tint_color = bandit->tint_color_active; +      if (is_colliding == true) { +        return true; // is colliding with target +      } +    } +  } +  bandit->state = C_IDLE; +  bandit->ai_state = AI_NONE; +  return false; +} + +PatrolDir GetNextPatrolDir(PatrolDir dir) { +  PatrolDir next = dir; +  switch (dir) { +  case PATROL_TOP: { +    next = PATROL_LEFT; +  } break; +  case PATROL_LEFT: { +    next = PATROL_DOWN; +  } break; +  case PATROL_DOWN: { +    next = PATROL_RIGHT; +  } break; +  case PATROL_RIGHT: { +    next = PATROL_TOP; +  } break; +  } + +  return next; +} + +void UpdateDrawFrame(void) { +  // INPUT +  if (IsKeyPressed(KEY_ESCAPE)) { +    mega_state.gameplay_state = +        mega_state.gameplay_state == GS_UI ? GS_GAME : GS_UI; +  } +  if (mega_state.gameplay_state == GS_GAME) { +    mega_state.mouse_position = GetMousePosition(); +    if (IsKeyPressed(KEY_ONE)) { +      mega_state.player.active_potion = &mega_state.player.fire_potion; +      mega_state.player.active_potion->t_potion_throw_start = 0; +      mega_state.player.active_potion->t_potion_spread_start = 0; +      mega_state.player.active_potion->state = PS_HELD; +    } else if (IsKeyPressed(KEY_TWO)) { +      mega_state.player.active_potion = &mega_state.player.friend_potion; +      mega_state.player.active_potion->t_potion_throw_start = 0; +      mega_state.player.active_potion->t_potion_spread_start = 0; +      mega_state.player.active_potion->state = PS_HELD; +    } +    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && +        mega_state.player.state <= P_MOVE) { +      mega_state.player.state = P_MOVE; +      mega_state.player.target_position = mega_state.mouse_position; +      mega_state.player.move_dir = Vector2Normalize(Vector2Subtract( +          mega_state.player.target_position, mega_state.player.position)); +    } +    if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { +      // @todo: abilities +      mega_state.player.active_potion->throw_dir = +          Vector2Normalize(Vector2Subtract(mega_state.mouse_position, +                                           mega_state.player.position)); +      mega_state.player.active_potion->position_start = +          mega_state.player.position; +      mega_state.player.active_potion->position_curr = +          mega_state.player.position; +      mega_state.player.active_potion->position_target = +          mega_state.mouse_position; +      mega_state.player.active_potion->state = PS_THROWN; +      mega_state.player.active_potion->t_potion_throw_start = GetTime(); +    } + +    // PROCESSING +    // initialize values +    for (int i = 0; i < mega_state.troll_arr_sz; i++) { +      if (mega_state.trolls[i].health <= 0) { +        mega_state.trolls[i].state = C_DEAD; +        mega_state.trolls[i].tint_color = (Color){128, 128, 128, 128}; +      } +      mega_state.trolls[i].speed_multiplier = 1.0f; +    } +    for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +      if (mega_state.bandits[i].health <= 0) { +        mega_state.bandits[i].state = C_DEAD; +        mega_state.bandits[i].tint_color = (Color){128, 128, 128, 128}; +      } +      mega_state.bandits[i].speed_multiplier = 1.0f; +    } +    if (mega_state.player.health <= 0) { +      mega_state.gameplay_state = GS_OVER; +    } + +    switch (mega_state.player.state) { +    case (P_MOVE): { +      // MOVE PLAYER +      Vector2 next_position = +          Vector2Add(mega_state.player.position, +                     Vector2Scale(mega_state.player.move_dir, +                                  mega_state.player.move_speed)); +      // checke player collisions +      bool is_colliding_gate = CheckCollisionRecs(mega_state.player.render_rect, +                                                  mega_state.gate_render_rect); +      if (is_colliding_gate) { +        if (mega_state.key_picked_count == 2) { +          mega_state.is_gate_open = true; +          mega_state.active_gate_sprite = mega_state.gate_open_sprite; +          mega_state.gameplay_state = GS_SUCCESS; +        } +      } +      mega_state.player.position = next_position; + +      if (Vector2Distance(mega_state.player.position, +                          mega_state.player.target_position) <= 5.0f) { +        mega_state.player.state = P_IDLE; +      } +      // calculate center positions +      CenterRectAroundTL(&mega_state.player.render_rect, +                         mega_state.player.position); +      mega_state.player.tile_id = +          GetTileIDFromWorld(mega_state.player.position); +      // check if a key can be picked up +      for (int i = 0; i < 2; i++) { +        bool is_colliding = CheckCollisionRecs(mega_state.player.render_rect, +                                               mega_state.key_render_rects[i]); +        if (is_colliding && mega_state.key_should_render[i] == 1) { +          mega_state.key_picked_count += 1; +          mega_state.key_should_render[i] = 0; +        } +      } +    } break; +    case (P_HIT): { +      float anim_length = GetTime() - mega_state.player.t_anim_hit_start; +      float ft = GetFrameTime(); +      float pixel_per_second_per_ft = ft * mega_state.player.anim_hit_speed / +                                      mega_state.player.t_anim_hit_duration; +      mega_state.player.position = Vector2Add( +          mega_state.player.position, +          Vector2Scale(mega_state.player.move_dir, pixel_per_second_per_ft)); +      mega_state.player.tint_color = (Color){255, 128, 128, 255}; +      if (anim_length >= mega_state.player.t_anim_hit_duration) { +        mega_state.player.state = P_IDLE; +        mega_state.player.anim_hit_state = HA_IDLE; +        mega_state.player.tint_color = (Color){255, 255, 255, 255}; +      } +      // calculate center positions +      CenterRectAroundTL(&mega_state.player.render_rect, +                         mega_state.player.position); +      mega_state.player.tile_id = +          GetTileIDFromWorld(mega_state.player.position); +    } break; +    default: { +    } break; +    } +    switch (mega_state.player.active_potion->state) { +    case (PS_THROWN): { +      // play potion throw animation +      float throw_speed = GetFrameTime() * mega_state.player.potion_throw_speed; +      float dist_thrown = +          Vector2Distance(mega_state.player.active_potion->position_curr, +                          mega_state.player.active_potion->position_start); +      mega_state.player.active_potion->position_curr = +          Vector2Add(mega_state.player.active_potion->position_curr, +                     Vector2Scale(mega_state.player.active_potion->throw_dir, +                                  throw_speed)); +      float dist_to_target = +          Vector2Distance(mega_state.player.active_potion->position_curr, +                          mega_state.player.active_potion->position_target); +      if (dist_thrown > mega_state.player.potion_throw_dist || +          dist_to_target <= 5.0f) { +        // change potion state to potion explode +        mega_state.player.active_potion->state = PS_SPREAD; +        mega_state.player.active_potion->t_potion_spread_start = GetTime(); +      } +    } break; +    case (PS_SPREAD): { +      float spread_duration = +          GetTime() - mega_state.player.active_potion->t_potion_spread_start; +      if (spread_duration >= +          mega_state.player.active_potion->t_potion_spread_duration) { +        mega_state.player.active_potion->state = PS_HELD; +      } +      // loop through enemies to check if any are being burned +      // check troll +      for (int i = 0; i < mega_state.troll_arr_sz; i++) { +        Troll *troll = &mega_state.trolls[i]; +        if (troll->state == C_DEAD) +          continue; +        float dist_to_enemy = Vector2Distance( +            troll->position, mega_state.player.active_potion->position_curr); +        if (dist_to_enemy <= mega_state.player.active_potion->radius) { +          if (mega_state.player.active_potion->type == PT_ATTACK) { +            // mark troll state as being burned +            if (troll->isburning == false) { +              troll->isburning = true; +              troll->t_anim_burn_start = GetTime(); +              troll->attackers.buffer[0].type = PLAYER; +              troll->attackers.buffer[0].id = 0; +            } +          } else if (mega_state.player.active_potion->type == PT_ALLY) { +            troll->friends.buffer[0] = (FriendEntity){.type = PLAYER, .id = 0}; +          } +          troll->tint_color_active = +              mega_state.player.active_potion->tint_color_effect; +          troll->tint_color = troll->tint_color_active; +          troll->speed_multiplier = +              mega_state.player.active_potion->speed_multiplier; +        } else { +          troll->speed_multiplier = 1.0f; +        } +        if (mega_state.player.active_potion->state != PS_SPREAD) { +          troll->tint_color_active = troll->tint_color_base; +          troll->tint_color = troll->tint_color_active; +          troll->isburning = false; +          // remove player/entity as friend +          troll->friends.buffer[0].type = NONE; +          troll->friends.buffer[0].id = 0; +        } +      } +      // check bandit +      for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +        Bandit *bandit = &mega_state.bandits[i]; +        if (bandit->state == C_DEAD) +          continue; +        float dist_to_enemy = Vector2Distance( +            bandit->position, mega_state.player.active_potion->position_curr); +        if (dist_to_enemy <= mega_state.player.active_potion->radius) { +          if (mega_state.player.active_potion->type == PT_ATTACK) { +            // mark bandit state as being burned +            if (bandit->isburning == false) { +              bandit->isburning = true; +              bandit->t_anim_burn_start = GetTime(); +              bandit->attackers.buffer[0].type = PLAYER; +              bandit->attackers.buffer[0].id = 0; +            } +          } else if (mega_state.player.active_potion->type == PT_ALLY) { +            bandit->friends.buffer[0] = (FriendEntity){.type = PLAYER, .id = 0}; +          } +          bandit->tint_color_active = +              mega_state.player.active_potion->tint_color_effect; +          bandit->tint_color = bandit->tint_color_active; +          bandit->speed_multiplier = +              mega_state.player.active_potion->speed_multiplier; +        } else { +          bandit->speed_multiplier = 1.0f; +        } +        if (mega_state.player.active_potion->state != PS_SPREAD) { +          bandit->tint_color_active = bandit->tint_color_base; +          bandit->tint_color = bandit->tint_color_active; +          bandit->isburning = false; +          // remove player/entity as friend +          bandit->friends.buffer[0].type = NONE; +          bandit->friends.buffer[0].id = 0; +        } +      } +    } break; +    default: +      break; +    } + +    // ================= TROLL DETECTION LOOP AND STUFF ======================= +    for (int i = 0; i < mega_state.troll_arr_sz; i++) { +      Troll *troll = &mega_state.trolls[i]; +      if (troll->state == C_HIT) { +        float anim_length = GetTime() - troll->t_anim_hit_start; +        float ft = GetFrameTime(); +        float pixel_per_second_per_ft = +            ft * troll->anim_hit_speed / troll->t_anim_hit_duration; +        troll->position = +            Vector2Add(troll->position, +                       Vector2Scale(troll->move_dir, pixel_per_second_per_ft)); +        troll->tint_color = (Color){255, 128, 128, 255}; +        if (anim_length >= troll->t_anim_hit_duration) { +          troll->state = C_IDLE; +          troll->anim_hit_state = HA_IDLE; +          troll->tint_color = (Color){255, 255, 255, 255}; +        } +        // calculate center positions +        CenterRectAroundTL(&troll->render_rect, troll->position); +        troll->tile_id = GetTileIDFromWorld(troll->position); +      } else if (troll->state != C_DEAD) { +        if (troll->isburning == true) { +          int anim_length_ms = +              1000 * (int)(GetTime() - troll->t_anim_burn_start); +          if (anim_length_ms % 500 == 0 && anim_length_ms > 0) { +            // decrement health every 500ms +            troll->health -= 8; +            troll->t_anim_burn_start = GetTime(); +          } +        } +        troll->speed_multiplier *= +            troll->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f; +        AttackedEntity tentity = GetEntityToAttack(*troll); +        if (tentity.type > NONE || troll->anim_attack_state >= AA_CHARGE) { +          bool entity_hit = TrollAttackTarget(troll, tentity); +          if (entity_hit) { +            switch (tentity.type) { +            case (PLAYER): { +              mega_state.player.anim_hit_state = HA_PLAY; +              mega_state.player.t_anim_hit_start = GetTime(); +              mega_state.player.move_dir = troll->move_dir; +              mega_state.player.state = +                  P_HIT; // disable walk check, stuff been thrown off +              // mega_state.player.health -= 8; +            } break; +            case (TROLL): { +              // trigger troll get hit animation +            } break; +            case (BANDIT): { +              // trigger bandit get hit animation +              AggroEntity attacker = {.type = TROLL, .id = 0}; +              mega_state.bandits[tentity.id].attackers.buffer[0] = attacker; +              mega_state.bandits[tentity.id].anim_hit_state = HA_PLAY; +              mega_state.bandits[tentity.id].t_anim_hit_start = GetTime(); +              mega_state.bandits[tentity.id].move_dir = troll->move_dir; +              mega_state.bandits[tentity.id].state = C_HIT; +              mega_state.bandits[tentity.id].health -= 10; +            } break; +            default: +              break; +            } +          } +        } else { +          // get entity to follow +          FriendEntity follow = {}; +          if (troll->friends.buffer[0].type == PLAYER) { +            float char_dist = +                Vector2Distance(mega_state.player.position, troll->position); +            if (char_dist < 200) { +              follow.type = PLAYER; +            } +          } +          if (troll->state == C_IDLE && troll->ai_state == AI_NONE) { +            troll->t_idle_start = GetTime(); +            troll->move_dir = Vector2Zero(); +            troll->ai_state = AI_IDLE; +            troll->to_core_pos = troll->to_core_pos == true ? false : true; +          } +          double idle_time = GetTime() - troll->t_idle_start; +          if (idle_time >= troll->t_idle_duration && +              troll->ai_state == AI_IDLE) { +            if (troll->to_core_pos == false) { +              // calculate target position from patrol direction +              float max_dist = 200.0f; +              Vector2 move_dir; +              switch (troll->patrol_dir) { +              case (PATROL_TOP): { +                move_dir = (Vector2){.x = 0.0f, .y = -1.0f}; +              } break; +              case (PATROL_LEFT): { +                move_dir = (Vector2){.x = -1.0f, .y = 0.0f}; +              } break; +              case (PATROL_DOWN): { +                move_dir = (Vector2){.x = 0.0f, .y = 1.0f}; +              } break; +              case (PATROL_RIGHT): { +                move_dir = (Vector2){.x = 1.0f, .y = 0.0f}; +              } break; +              default: +                break; +              }; +              troll->move_dir = move_dir; +              troll->target_position = Vector2Add( +                  troll->position, Vector2Scale(troll->move_dir, max_dist)); +            } else { +              troll->target_position = troll->core_position; +              troll->move_dir = Vector2Normalize( +                  Vector2Subtract(troll->target_position, troll->position)); +            } +            troll->state = C_PATROL; +            troll->ai_state = AI_PATROL; +          } +          // follow player / friend +          if (follow.type == PLAYER) { +            troll->state = C_FOLLOW; +            troll->ai_state = AI_NONE; +            troll->target_position = mega_state.player.position; +            troll->move_dir = Vector2Normalize( +                Vector2Subtract(troll->target_position, troll->position)); +          } +          float dist_to_target = +              Vector2Distance(troll->target_position, troll->position); +          if (dist_to_target > 50.0f) { +            MoveCharacter(troll, +                          Vector2Scale(troll->move_dir, troll->move_speed)); +          } else if (troll->state == C_PATROL && troll->ai_state == AI_PATROL) { +            // we have reached target position +            // in case we were patrolling, increment the direction to move to +            // @note: maybe do this when first changing state from idle after +            // character has been idle for hours +            if (troll->to_core_pos == false) { +              troll->patrol_dir = GetNextPatrolDir(troll->patrol_dir); +            } +            troll->state = C_IDLE; +            troll->ai_state = AI_NONE; +          } +          CenterRectAroundTL(&troll->render_rect, troll->position); +        } +      } +    } +    // ========== BANDIT DETECTION AND STUFF +    for (int i = 0; i < mega_state.bandit_arr_sz; i++) { +      Bandit *bandit = &mega_state.bandits[i]; +      if (bandit->state == C_HIT) { +        float anim_length = GetTime() - bandit->t_anim_hit_start; +        float ft = GetFrameTime(); +        float pixel_per_second_per_ft = +            ft * bandit->anim_hit_speed / bandit->t_anim_hit_duration; +        bandit->position = +            Vector2Add(bandit->position, +                       Vector2Scale(bandit->move_dir, pixel_per_second_per_ft)); +        bandit->tint_color = (Color){255, 128, 128, 255}; +        if (anim_length >= bandit->t_anim_hit_duration) { +          bandit->state = C_IDLE; +          bandit->anim_hit_state = HA_IDLE; +          bandit->tint_color = (Color){255, 255, 255, 255}; +        } +        // calculate center positions +        CenterRectAroundTL(&bandit->render_rect, bandit->position); +        bandit->tile_id = GetTileIDFromWorld(bandit->position); +      } else if (bandit->state != C_DEAD) { +        if (bandit->isburning == true) { +          int anim_length_ms = +              1000 * (int)(GetTime() - bandit->t_anim_burn_start); +          if (anim_length_ms % 500 == 0 && anim_length_ms > 0) { +            // decrement health every 500ms +            bandit->health -= 8; +            bandit->t_anim_burn_start = GetTime(); +          } +        } +        bandit->speed_multiplier *= +            bandit->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f; +        AttackedEntity tentity = GetEntityToAttack(*bandit); +        if (tentity.type > NONE || bandit->anim_attack_state >= AA_CHARGE) { +          bool entity_hit = BanditAttackTarget(bandit, tentity); +          if (tentity.type > NONE) { +            if (entity_hit) { +              switch (tentity.type) { +              case (PLAYER): { +                mega_state.player.anim_hit_state = HA_PLAY; +                mega_state.player.t_anim_hit_start = GetTime(); +                mega_state.player.move_dir = bandit->move_dir; +                mega_state.player.state = +                    P_HIT; // disable walk check, stuff been thrown off +                // mega_state.player.health -= 5; +              } break; +              case (TROLL): { +                // trigger troll get hit animation +                AggroEntity attacker = {.type = BANDIT, .id = 0}; +                mega_state.trolls[tentity.id].attackers.buffer[0] = attacker; +                mega_state.trolls[tentity.id].anim_hit_state = HA_PLAY; +                mega_state.trolls[tentity.id].t_anim_hit_start = GetTime(); +                mega_state.trolls[tentity.id].move_dir = bandit->move_dir; +                mega_state.trolls[tentity.id].state = C_HIT; +                mega_state.trolls[tentity.id].health -= 5; +              } break; +              case (BANDIT): { +                // trigger bandit get hit animation +              } break; +              default: +                // do nothing +                break; +              } +            } +          } +        } else { +          if (bandit->state == C_IDLE && bandit->ai_state == AI_NONE) { +            bandit->t_idle_start = GetTime(); +            bandit->move_dir = Vector2Zero(); +            bandit->ai_state = AI_IDLE; +            bandit->to_core_pos = bandit->to_core_pos == true ? false : true; +          } +          double idle_time = GetTime() - bandit->t_idle_start; +          if (idle_time >= bandit->t_idle_duration && +              bandit->ai_state == AI_IDLE) { +            if (bandit->to_core_pos == false) { +              // calculate target position from patrol direction +              float max_dist = 200.0f; +              Vector2 move_dir; +              switch (bandit->patrol_dir) { +              case (PATROL_TOP): { +                move_dir = (Vector2){.x = 0.0f, .y = -1.0f}; +              } break; +              case (PATROL_LEFT): { +                move_dir = (Vector2){.x = -1.0f, .y = 0.0f}; +              } break; +              case (PATROL_DOWN): { +                move_dir = (Vector2){.x = 0.0f, .y = 1.0f}; +              } break; +              case (PATROL_RIGHT): { +                move_dir = (Vector2){.x = 1.0f, .y = 0.0f}; +              } break; +              default: +                break; +              }; +              bandit->move_dir = move_dir; +              bandit->target_position = Vector2Add( +                  bandit->position, Vector2Scale(bandit->move_dir, max_dist)); +            } else { +              bandit->target_position = bandit->core_position; +              bandit->move_dir = Vector2Normalize( +                  Vector2Subtract(bandit->target_position, bandit->position)); +            } +            bandit->state = C_PATROL; +            bandit->ai_state = AI_PATROL; +          } +          // get entity to follow +          FriendEntity follow = {}; +          if (bandit->friends.buffer[0].type == PLAYER) { +            float char_dist = +                Vector2Distance(mega_state.player.position, bandit->position); +            if (char_dist < 200) { +              follow.type = PLAYER; +            } +          } +          // follow player / friend +          if (follow.type == PLAYER) { +            bandit->target_position = mega_state.player.position; +            bandit->state = C_FOLLOW; +            bandit->ai_state = AI_NONE; +            bandit->move_dir = Vector2Normalize( +                Vector2Subtract(bandit->target_position, bandit->position)); +          } +          float dist_to_target = +              Vector2Distance(bandit->target_position, bandit->position); +          if (dist_to_target > 50.0f) { +            MoveCharacter(bandit, +                          Vector2Scale(bandit->move_dir, bandit->move_speed)); +          } else if (bandit->state == C_PATROL && +                     bandit->ai_state == AI_PATROL) { +            if (bandit->to_core_pos == false) { +              bandit->patrol_dir = GetNextPatrolDir(bandit->patrol_dir); +            } +            bandit->state = C_IDLE; +            bandit->ai_state = AI_NONE; +          } +          CenterRectAroundTL(&bandit->render_rect, bandit->position); +        } +      } +    } + +    // RENDERING +    BeginDrawing(); +    ClearBackground(RAYWHITE); +    DrawMapFloorTiles(); +    DrawFloorCoverTiles(); +    DrawCharacters(); + +    // do text rendering at the end +    // draw mouse position for debugging +    if (mega_state.player.state == P_HIT) { +      DrawText("player hit", 500, 20, 20, GREEN); +    } +    DrawText(TextFormat("Keys: %d/2", mega_state.key_picked_count), 1100, 20, +             20, GREEN); +    EndDrawing(); +  } else if (mega_state.gameplay_state == GS_UI) { +    BeginDrawing(); +    ClearBackground(BLACK); +    DrawText("Blaidville", screenWidth / 2 - 100, 40, 28, WHITE); +    DrawText("Instructions", screenWidth / 2 - 140, 100, 22, WHITE); +    DrawText("Find keys to unlock the gate", screenWidth / 2 - 140, 140, 18, +             WHITE); +    DrawText("Unlock the gate to escape", screenWidth / 2 - 140, 160, 18, +             WHITE); +    DrawText("Controls", screenWidth / 2 - 140, 220, 22, WHITE); +    DrawText("1 -> Potion 1", screenWidth / 2 - 140, 260, 18, WHITE); +    DrawText("2 -> Potion 2", screenWidth / 2 - 140, 280, 18, WHITE); +    DrawText("right mouse click -> throw potion", screenWidth / 2 - 140, 300, +             18, WHITE); +    DrawText("left mouse click -> move", screenWidth / 2 - 140, 320, 18, WHITE); +    DrawText("Escape to play or pause the game", screenWidth / 2 - 140, 340, 18, +             WHITE); +    DrawText("Restart the game to reset state", screenWidth / 2 - 140, 360, 18, +             WHITE); +    EndDrawing(); +  } else if (mega_state.gameplay_state == GS_SUCCESS) { +    BeginDrawing(); +    ClearBackground(BLACK); +    DrawText("Objective Complete", screenWidth / 2 - 100, 40, 28, GREEN); +    EndDrawing(); +  } else if (mega_state.gameplay_state == GS_OVER) { +    BeginDrawing(); +    ClearBackground(BLACK); +    DrawText("You Died", screenWidth / 2 - 100, 40, 28, RED); +    EndDrawing(); +  } +} diff --git a/src/game_v0.c b/src/game_v0.c deleted file mode 100644 index 62141f6..0000000 --- a/src/game_v0.c +++ /dev/null @@ -1,512 +0,0 @@ -#include "raylib.h" -#include "raymath.h" - -#if defined(PLATFORM_WEB) -    #include <emscripten/emscripten.h> -#endif - -typedef enum { -  O = 0, // nothing -  //P = 1, // player -  t = 2, // tree -  T = 3, // troll area -  B = 4, // bandit area -  T_B = 5, // troll base -  B_B = 6, // bandit base -  //G = 7, // gate (exit) -} MapSymbol; - -typedef enum { -  AA_IDLE = 0, -  AA_CHARGE = 1, -  AA_ATTACK = 2, -} AttackAnimState; - -typedef enum { -  T_IDLE = 0, -  T_FOLLOW = 1, -  T_AGGRO = 2, -} TrollState; - -typedef enum { -  HA_IDLE = 0, -  HA_PLAY = 1, -} HitAnimState; - -typedef struct ScuffString { -  char  *buffer; -  int len; -  int capacity; -} String; - -typedef struct State { -  // how much scaling to apply from original asset resolution -  float render_scale; -  // game positions -  bool is_player_hit; -  Vector2 pixels_per_gridbox; -  Vector2 mouse_position; -  Vector2 player_position; -  Color player_tint; -  // @note: normally when we place a texture, the coordinates -  // are the top left coordinates. This will cause a disconnect -  // when moving the player with a point and click motion. -  // To fix that we calculate a new position so the player_coordinates -  // are in the center. This will make it so when the player clicks on the  -  // ground somewhere, the character sprite will be properly centered on that point. -  Rectangle player_render_rect; -  Vector2 player_position_target; - -  Vector2 bandit_position; -  // troll state -  int troll_state; -  float troll_move_delta; -  Vector2 troll_position; -  Vector2 troll_last_player_position; -  Vector2 troll_move_dir; -  Vector2 troll_position_target; -  Rectangle troll_render_rect; -  Color troll_tint_base; -  Color troll_tint; -  // - troll attack -  bool troll_anim_attack_play; -  int troll_attack_anime_state; -  double t_troll_charge_anim_start; -  float t_troll_charge_anim_duration; -  float t_troll_anim_freq; -  float troll_attack_offset_max; -  float t_troll_attack_anim_start; -  float t_troll_attack_anim_duration; - -  // player movement -  bool is_player_move; -  Vector2 player_move_dir; -  // @todo -  // direction player is being hit from, incorporate that into the gameplay code -  // For now, just update the player_move_dir to the player_hit_dir -  // Vector2 player_hit_dir;  -  float player_move_delta; // amount player will move -  // target position player will move to -  // 1. the player can move intentionally, in which case carry forward from player position target -  // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET -  int player_hit_anim_state; -  float player_hit_anim_speed; -  float t_player_hit_anim_duration; -  float t_player_hit_anim_start; -  Vector2 player_move_target;  - -  // game sprites -  Texture2D *grass_sprite; -  Texture2D *player_sprite; - -  Texture2D *troll_sprite; -  Texture2D *troll_weapon_sprite; -  Texture2D *bandit_sprite; -  Texture2D *bandit_weapon_sprite; - -  Texture2D *troll_area_floor_sprite; -  Texture2D *bandit_area_floor_sprite; - -  Texture2D *troll_base_sprite; -  Texture2D *bandit_base_sprite; - -  String *action_log; -} State; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -const int screenWidth = 1280; -const int screenHeight = 1024; -State mega_state = {}; -// layer ground  -int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O}, -                            {O, O, O, O, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}, -                            {B, B, B, B, T, T, T, T}}; -// vegetation grid  -int floor_cover_grid_map[8][8] = {{O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, T_B}, -                                  {O, B_B, O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}, -                                  {O, O,   O, O, O, O, O, O}}; -Vector2 grid_dims = {8, 8}; -//---------------------------------------------------------------------------------- -// Module functions declaration -//---------------------------------------------------------------------------------- -void UpdateDrawFrame();     // Update and Draw one frame - -int main(void) -{ -  InitWindow(screenWidth, screenHeight, "raylib browser example"); -  SetTargetFPS(60); -  Texture2D grass_sprite = LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png"); -  Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png"); -  Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png"); -  Texture2D bandit_sprite = LoadTexture("./assets/monster/unique/maurice_new.png"); -  Texture2D troll_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png"); -  Texture2D bandit_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_east_new.png"); -  Texture2D bandit_base_sprite = LoadTexture("./assets/dungeon/floor/hive_3.png"); -  Texture2D troll_base_sprite = LoadTexture("./assets/dungeon/floor/dirt_2_old.png"); -  Texture2D troll_weapon_sprite = LoadTexture("./assets/item/weapon/club_old.png"); - -  mega_state.render_scale = 2.0f; -  mega_state.grass_sprite = &grass_sprite; -  mega_state.player_sprite = &player_sprite; -  mega_state.troll_sprite = &troll_sprite; -  mega_state.bandit_sprite = &bandit_sprite; -  mega_state.troll_area_floor_sprite = &troll_area_floor_sprite; -  mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite; -  mega_state.bandit_base_sprite = &bandit_base_sprite; -  mega_state.troll_base_sprite = &troll_base_sprite; -  mega_state.troll_weapon_sprite = &troll_weapon_sprite; -  mega_state.player_position = (Vector2){50.0f, 50.0f}; -  mega_state.player_hit_anim_speed = 50.0f; // pixels per second; -  mega_state.t_player_hit_anim_duration = 0.2f; -  mega_state.player_render_rect = (Rectangle){.x=0,.y=0,.width=player_sprite.width,.height=player_sprite.height*mega_state.render_scale}; -  mega_state.player_move_delta = 5.0f; -  mega_state.pixels_per_gridbox = (Vector2){screenWidth/grid_dims.x, screenHeight/grid_dims.y}; -  mega_state.player_tint = (Color){255, 255, 255, 255}; -  mega_state.troll_state = T_IDLE; - -  mega_state.bandit_position = (Vector2){64.0f, 800.0f}; -  mega_state.troll_position = (Vector2){1024.0f, 600.0f}; -  mega_state.troll_move_delta = 2.0f; -  mega_state.t_troll_charge_anim_duration = 1.0f; -  mega_state.troll_tint_base = (Color){255, 255, 255, 255}; -  mega_state.troll_tint = mega_state.troll_tint_base; -  mega_state.t_troll_attack_anim_duration = 0.4f; -  mega_state.troll_attack_offset_max = 100.0f; -  mega_state.troll_render_rect = (Rectangle){.x=0,.y=0,.width=troll_sprite.width*mega_state.render_scale,.height=troll_sprite.height*mega_state.render_scale}; - -  char *str_buffer = (char*)MemAlloc(512*sizeof(char)); -  mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, .len=0}; - -  #if defined(PLATFORM_WEB) -    emscripten_set_main_loop(UpdateDrawFrame, 0, 1); -  #else -    SetTargetFPS(60); -     -    while(!WindowShouldClose()) -    { -      UpdateDrawFrame(); -    } -  #endif - -  CloseWindow(); - -  return 0; -} - -void DrawMapFloorTiles(float scale) -{ -  // @note: testing this, might need lighting -  Color BlackTint = {128,128,128,255}; -  Texture2D grass = *mega_state.grass_sprite; -  float winc = (float)grass.width * scale; -  float hinc = (float)grass.height * scale; -  Vector2 grid_pos = {0, 0}; -  for (int ypos=0; ypos<screenHeight; ypos+=hinc) -  { -    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); -    for (int xpos=0; xpos<screenWidth; xpos+=winc) -    { -      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); -      Vector2 pixel_pos = {xpos, ypos}; -      int grid_ele = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -      Texture2D render_tex; -      switch (grid_ele) { -        case (O): -          { -            render_tex = *mega_state.grass_sprite; -          } break; -        case (B): -          { -            render_tex = *mega_state.bandit_area_floor_sprite; -          } break; -        case (T): -          { -            render_tex = *mega_state.troll_area_floor_sprite; -          } break; -        default: -          { -          } break; -      } -      DrawTextureEx(render_tex, pixel_pos, 0, scale, BlackTint); -    } -  } -} - -void DrawFloorCoverTiles(float scale) -{ -  // @note: testing this, might need lighting -  Color BlackTint = {128,128,128,255}; -  Texture2D null_tex = *mega_state.player_sprite; -  float winc = (float)null_tex.width * scale; -  float hinc = (float)null_tex.height * scale; - -  Vector2 grid_pos = {0, 0}; -  for (int ypos=0; ypos<screenHeight; ypos+=hinc) -  { -    grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y); -    for (int xpos=0; xpos<screenWidth; xpos+=winc) -    { -      grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x); -      Vector2 pixel_pos = {xpos, ypos}; -      int grid_ele = floor_cover_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -      Texture2D render_tex; -      switch (grid_ele) { -        case (B_B): -          { -            render_tex = *mega_state.bandit_base_sprite; -          } break; -        case (T_B): -          { -            render_tex = *mega_state.troll_base_sprite; -          } break; -        default: -          { -            render_tex = null_tex; -          } continue; -      } -      DrawTextureEx(render_tex, pixel_pos, 0, scale, BlackTint); -    } -  } -} - -Vector2 GetSpriteCenterPosition(Texture2D sprite, Vector2 position, float render_scale) -{ -  float centered_x = position.x - ((float)sprite.width)*render_scale/2.0f; -  float centered_y = position.y - ((float)sprite.height)*render_scale/2.0f; - -  return (Vector2){.x = centered_x, .y = centered_y}; -} - -Vector2 GetRenderCenterFromTL(Rectangle render_rect, Vector2 position) -{ -  float centered_x = position.x - ((float)render_rect.width)/2.0f; -  float centered_y = position.y - ((float)render_rect.height)/2.0f; - -  return (Vector2){.x = centered_x, .y = centered_y}; -} - -void DrawCharacters(float scale) -{ -  Rectangle src_rect, dest_rect; -  // draw Troll -  // - troll weapon -  Vector2 troll_weapon_centered = GetSpriteCenterPosition(*mega_state.troll_weapon_sprite, mega_state.troll_position, 1.0f); -  // move to left hand -  troll_weapon_centered.x -= mega_state.troll_sprite->width; -  src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.troll_weapon_sprite->width, .height=mega_state.troll_weapon_sprite->height}; -  dest_rect = (Rectangle){.x=troll_weapon_centered.x, .y=troll_weapon_centered.y,  -                          .width=0.5f*scale*mega_state.troll_weapon_sprite->width,  -                          .height=0.5f*scale*mega_state.troll_weapon_sprite->height}; -  DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect, -                 (Vector2){0,0}, 0, RAYWHITE);  -  // - troll sprite -  DrawTextureEx(*mega_state.troll_sprite, (Vector2){mega_state.troll_render_rect.x, mega_state.troll_render_rect.y}, 0, scale, mega_state.troll_tint);  -  // draw collision box -  DrawRectangleLines(mega_state.troll_render_rect.x, mega_state.troll_render_rect.y, mega_state.troll_render_rect.width, mega_state.troll_render_rect.height, RED);  - -  // draw Bandit -  Vector2 bandit_center = GetSpriteCenterPosition(*mega_state.bandit_sprite, mega_state.bandit_position, scale); -  src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.bandit_sprite->width, .height=mega_state.bandit_sprite->height}; -  dest_rect = (Rectangle){.x=bandit_center.x, .y=bandit_center.y,  -                          .width=scale*mega_state.bandit_sprite->width,  -                          .height=scale*mega_state.bandit_sprite->height}; -  DrawTexturePro(*mega_state.bandit_sprite, src_rect, dest_rect, -                 (Vector2){0,0}, 0, (Color){255, 128, 128, 255});  -  // draw player -  Vector2 player_center = GetSpriteCenterPosition(*mega_state.player_sprite, mega_state.player_position, scale); -  DrawTextureEx(*mega_state.player_sprite, player_center, 0, scale, mega_state.player_tint);  -  //DrawTextureEx(*mega_state.player_sprite, (Vector2){mega_state.player_render_rect.x, mega_state.player_render_rect.y}, 0, scale, RAYWHITE);  -  DrawRectangleLines(mega_state.player_render_rect.x, mega_state.player_render_rect.y, mega_state.player_render_rect.width, mega_state.player_render_rect.height, RED);  -} - -void UpdateDrawFrame(void) -{ -  BeginDrawing(); -  { -    // INPUT -    mega_state.mouse_position = GetMousePosition(); -    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mega_state.player_hit_anim_state != HA_PLAY) -    { -      mega_state.is_player_move = true; -      mega_state.player_position_target = mega_state.mouse_position; -      mega_state.player_move_dir = Vector2Normalize(Vector2Subtract(mega_state.player_position_target, mega_state.player_position)); -    } -    if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) -    { -      // @debug: cleaning up troll collision state -      mega_state.troll_attack_anime_state = AA_IDLE; -      mega_state.troll_tint = WHITE; -      mega_state.is_player_hit = false; -    } - -    // PROCESSING -    if (mega_state.player_hit_anim_state == HA_PLAY) -    { -      float anim_length = GetTime() - mega_state.t_player_hit_anim_start; -      float ft = GetFrameTime(); -      float pixel_per_second_per_ft = ft*mega_state.player_hit_anim_speed/mega_state.t_player_hit_anim_duration; -      mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, pixel_per_second_per_ft)); -      mega_state.player_tint = (Color){255, 128, 128, 255}; -      if (anim_length >= mega_state.t_player_hit_anim_duration) -      { -        mega_state.player_hit_anim_state = HA_IDLE; -      mega_state.player_tint = (Color){255, 255, 255, 255}; -      } -    } -    if (mega_state.is_player_move) -    { -      mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, mega_state.player_move_delta)); -      if (Vector2Distance(mega_state.player_position, mega_state.player_position_target) <= 5.0f) -      { -        mega_state.is_player_move = false; -      } -    } -    // calculate center positions -    Vector2 player_centered = GetRenderCenterFromTL(mega_state.player_render_rect, mega_state.player_position); -    mega_state.player_render_rect.x = player_centered.x; -    mega_state.player_render_rect.y = player_centered.y; - -    // process enemies detecting the player -    // troll -    // get player location on map -    // map_world_to_grid -    Vector2 grid_pos = Vector2Divide(Vector2Multiply(mega_state.player_position, grid_dims), (Vector2){screenWidth, screenHeight}); -    int tile_at_player = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; -    bool in_troll_area = true; -    bool troll_persue_player = true; -    if (tile_at_player != T) -    { -      in_troll_area = false; -      if (mega_state.troll_state != T_AGGRO) -      { -        mega_state.troll_attack_anime_state = AA_IDLE; -        troll_persue_player = false; -      } -    } - -    float player_troll_dist = Vector2Distance(mega_state.player_position, mega_state.troll_position); -    bool troll_detect = false; -    bool troll_attack_start = false; -    float slow_multiplier = mega_state.troll_attack_anime_state == AA_CHARGE ? 0.2f : 1.0f; -    if (player_troll_dist < 200 && troll_persue_player) -    { -      troll_detect = true; -      mega_state.troll_state = T_FOLLOW; -      if (player_troll_dist < 80 && mega_state.troll_attack_anime_state == AA_IDLE) -      { -        troll_attack_start = true; -      } -    } -    if (troll_detect && mega_state.troll_attack_anime_state != AA_ATTACK) -    { -      mega_state.troll_last_player_position = mega_state.player_position; -      mega_state.troll_position_target = mega_state.troll_last_player_position; -      mega_state.troll_move_dir = Vector2Normalize(Vector2Subtract(mega_state.troll_position_target, mega_state.troll_position)); -      if (player_troll_dist > 10.0f) -      { -        // @note: this actually might not even be needed -        // move only if both are more than 10pixels apart. -        // it becomes jarring other wise -        mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, slow_multiplier*mega_state.troll_move_delta)); -      } -    } -    Vector2 troll_centered = GetSpriteCenterPosition(*mega_state.troll_sprite, mega_state.troll_position, mega_state.render_scale); -    mega_state.troll_render_rect.x = troll_centered.x; -    mega_state.troll_render_rect.y = troll_centered.y; -    if (troll_attack_start) -    { -      // start attack animation -      mega_state.troll_anim_attack_play = true; -      mega_state.t_troll_charge_anim_start = GetTime(); -      mega_state.troll_attack_anime_state = AA_CHARGE; -    } -    float charge_time = 0.0f; -    float charge_color_delta = 0.0f; -    if (mega_state.troll_attack_anime_state == AA_CHARGE) -    { -      // get color based on time -      float charge_progress = (GetTime() - mega_state.t_troll_charge_anim_start) / mega_state.t_troll_charge_anim_duration; -      mega_state.troll_tint.r = Clamp(mega_state.troll_tint_base.r - (charge_progress*mega_state.troll_tint_base.r), 0, 255); -      if (charge_progress >= 1.0f) -      { -        mega_state.troll_attack_anime_state = AA_ATTACK; -        mega_state.t_troll_attack_anim_start = GetTime(); -      } -    }  -    else if (mega_state.troll_attack_anime_state == AA_ATTACK) -    { -      // maximum distance to attack jump in -      // mega_state.troll_attack_offset_max = 10.0f; -      // mega_state.troll_attack_anim_duration = 1.0f; -      float anim_length = GetTime() - mega_state.t_troll_attack_anim_start; -      float ft = GetFrameTime(); -      float pixel_per_second_per_ft = ft*mega_state.troll_attack_offset_max/mega_state.t_troll_attack_anim_duration; -      mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, pixel_per_second_per_ft)); -      mega_state.troll_tint = (Color){255, 128, 255, 255}; -      // get troll center position and check for collision with player -      // now check if colliding with player -      bool is_colliding = CheckCollisionRecs(mega_state.player_render_rect, mega_state.troll_render_rect); -      if (is_colliding) -      { -        mega_state.is_player_hit = true; -      } -      if (anim_length >= mega_state.t_troll_attack_anim_duration || is_colliding) -      { -        mega_state.troll_attack_anime_state = AA_IDLE; -        mega_state.troll_tint = mega_state.troll_tint_base; -        if (is_colliding) -        { -          // make player get hit -          mega_state.player_hit_anim_state = HA_PLAY; -          mega_state.t_player_hit_anim_start = GetTime(); -          mega_state.player_move_dir = mega_state.troll_move_dir; -          mega_state.is_player_move = false; // disable walk check, stuff been thrown off -        } -      } -    } - -    float player_bandit_delta = Vector2Distance(mega_state.player_position, mega_state.bandit_position); -    bool bandit_detect = false; -    if (player_bandit_delta < 200) -    { -      bandit_detect = true; -    } -    // RENDERING -    ClearBackground(RAYWHITE); -    DrawMapFloorTiles(2.0f); -    DrawFloorCoverTiles(2.0f); -    DrawCharacters(2.0f); - -    // do text rendering at the end -    // draw mouse position for debugging -    if (mega_state.is_player_hit) -    { -      DrawText("player hit", 500, 20, 20, GREEN); -    } -    if (troll_detect) -    { -      DrawText("Detecting", mega_state.troll_position.x, mega_state.troll_position.y + mega_state.troll_sprite->height + 20, 20, GREEN); -    } -    if (bandit_detect) -    { -      DrawText("Detecting", mega_state.bandit_position.x, mega_state.bandit_position.y + mega_state.bandit_sprite->height + 20, 20, GREEN); -    } -    DrawText(TextFormat("FPS: %d", GetFPS()), 1200, 20, 20, GREEN); -    DrawText(TextFormat("Player Tile: %d", tile_at_player), 20, 20, 20, GREEN); -  } -  EndDrawing(); -} - - diff --git a/src/memory/memory.c b/src/memory/memory.c new file mode 100644 index 0000000..1d521e4 --- /dev/null +++ b/src/memory/memory.c @@ -0,0 +1,246 @@ +#include "memory.h" + +b8 is_power_of_two(uintptr_t x) { return (x & (x - 1)) == 0; } + +uintptr_t fast_modulo(uintptr_t p, uintptr_t a) { return (p & (a - 1)); } + +uintptr_t align_forward(uintptr_t ptr, size_t alignment) { +  uintptr_t p, a, modulo; + +  assert(is_power_of_two(alignment)); + +  p = ptr; +  a = (uintptr_t)alignment; +  modulo = fast_modulo(p, a); + +  if (modulo != 0) { +    p += (a - modulo); +  } + +  return p; +} + +//=========================================================================================== +// ---------------------------------- Arena +// ------------------------------------------------- +//=========================================================================================== + +/* +  A cases where arena allocation WILL fail: +  | size = size_t + ${some_number_that_comes_up_higher_than_offset} + +  This is because there is no check being made +*/ +void arena_init(struct Arena *a, unsigned char *backing_store, +                size_t capacity) { +  a->buffer = backing_store; +  a->curr_offset = 0; +  a->prev_offset = 0; +  a->capacity = capacity; +} + +void *arena_alloc_aligned(struct Arena *a, size_t size, size_t alignment) { +  void *ptr = NULL; + +  assert(is_power_of_two(alignment)); + +  uintptr_t curr_ptr = (uintptr_t)a->buffer + a->curr_offset; +  uintptr_t offset = align_forward(curr_ptr, alignment); +  offset = offset - (uintptr_t)a->buffer; + +  if (size <= a->capacity - offset) { +    ptr = &a->buffer[offset]; +    a->prev_offset = a->curr_offset; +    a->curr_offset = offset + size; +    memset(ptr, 0, size); +  } + +  return ptr; +} + +void *arena_alloc(struct Arena *a, size_t size) { +  return arena_alloc_aligned(a, size, DEFAULT_ALIGNMENT); +} + +void *arena_resize_aligned(struct Arena *a, void *old_memory, size_t old_size, +                           size_t new_size, size_t alignment) { +  unsigned char *old = (unsigned char *)old_memory; +  void *ptr = NULL; + +  assert(is_power_of_two(alignment)); + +  if (old >= a->buffer && old < a->buffer + a->capacity) { +    if (a->buffer + a->prev_offset == old) { +      // extend_last_element +      if (new_size > old_size) { +        size_t size_increase = new_size - old_size; +        if (size_increase > (a->capacity - a->curr_offset)) { +          new_size = old_size; +          size_increase = 0; +        } +        memset(&a->buffer[a->curr_offset], 0, size_increase); +      } +      a->curr_offset = a->prev_offset + new_size; +      ptr = old_memory; +    } else { +      ptr = arena_alloc_aligned(a, new_size, alignment); +      if (ptr != NULL) { +        size_t copy_size = old_size < new_size ? old_size : new_size; +        memmove(ptr, old_memory, copy_size); +      } +    } +  } + +  return ptr; +} + +void *arena_resize(struct Arena *a, void *old_mem, size_t old_size, +                   size_t new_size) { +  return arena_resize_aligned(a, old_mem, old_size, new_size, +                              DEFAULT_ALIGNMENT); +} + +void arena_clear(struct Arena *a) { +  a->curr_offset = 0; +  a->prev_offset = 0; +} + +//=========================================================================================== +// ---------------------------------- STACK +// ------------------------------------------------- +//=========================================================================================== + +void stack_init(struct stack *s, void *backing_store, size_t capacity) { +  s->buffer = (unsigned char *)backing_store; +  s->prev_offset = 0; +  s->curr_offset = 0; +  s->capacity = capacity; +} + +size_t calc_padding_with_header(uintptr_t ptr, uintptr_t alignment, +                                size_t hdr_sz) { +  uintptr_t p, a, modulo, padding, space_needed; + +  assert(is_power_of_two(alignment)); + +  padding = space_needed = 0; + +  p = ptr; +  a = alignment; +  modulo = fast_modulo(p, a); + +  if (modulo != 0) { +    padding = a - modulo; +  } + +  space_needed = (uintptr_t)hdr_sz; +  if (padding < space_needed) { +    space_needed -= padding; +    if (fast_modulo(space_needed, a) != 0) { +      padding = padding + space_needed + a; +    } else { +      padding = padding + space_needed; +    } +  } + +  return (size_t)padding; +} + +struct ResVoid stack_alloc_aligned(struct stack *s, size_t size, +                                   size_t alignment) { +  uintptr_t curr_addr, next_addr; +  size_t padding; +  struct stack_hdr *header; + +  assert(is_power_of_two(alignment)); +  if (alignment > 128) { +    alignment = 128; +  } + +  struct ResVoid result = {.status = MEM_OK, .bytes_count = 0, .memory = 0}; + +  curr_addr = (uintptr_t)s->buffer + (uintptr_t)s->curr_offset; +  padding = calc_padding_with_header(curr_addr, (uintptr_t)alignment, +                                     sizeof(struct stack_hdr)); + +  if (size > s->capacity - (s->curr_offset + padding)) { +    result.status = MEM_FULL; +    return result; +  } + +  next_addr = curr_addr + (uintptr_t)padding; +  header = (struct stack_hdr *)(next_addr - sizeof(struct stack_hdr)); +  header->prev_offset = s->prev_offset; +  header->padding = padding; + +  s->prev_offset = s->curr_offset + padding; +  s->curr_offset = s->prev_offset + size; + +  result.memory = memset((void *)next_addr, 0, size); +  result.bytes_count = size; + +  return result; +} + +struct ResVoid stack_alloc(struct stack *s, size_t size) { +  return stack_alloc_aligned(s, size, DEFAULT_ALIGNMENT); +} + +enum MemStatus stack_free(struct stack *s) { +  uintptr_t last_ele = (uintptr_t)s->buffer + (uintptr_t)s->prev_offset; +  struct stack_hdr *header = +      (struct stack_hdr *)(last_ele - sizeof(struct stack_hdr)); + +  uintptr_t prev_ele = (uintptr_t)s->buffer + (uintptr_t)header->prev_offset; +  s->curr_offset = +      (size_t)((last_ele - (uintptr_t)header->padding) - (uintptr_t)s->buffer); +  s->prev_offset = (size_t)(prev_ele - (uintptr_t)s->buffer); + +  return MEM_OK; +} + +struct ResVoid stack_resize_aligned(struct stack *s, void *old_memory, +                                    size_t old_size, size_t new_size, +                                    size_t alignment) { +  struct ResVoid result = {.status = MEM_OK, .bytes_count = 0, .memory = 0}; + +  if (old_memory < s->buffer || old_memory > s->buffer + s->capacity) { +    result.status = MEM_OUT_OF_BOUNDS; +    return result; +  } + +  // is_last_element() +  if (s->buffer + s->prev_offset == old_memory) { +    if (new_size > old_size) { +      size_t size_difference = new_size - old_size; +      if (size_difference > s->capacity - s->curr_offset) { +        result.status = MEM_FULL; +        return result; +      } + +      memset(&s->buffer[s->curr_offset], 0, size_difference); +    } +    s->curr_offset = s->prev_offset + new_size; + +    result.memory = old_memory; +    return result; +  } + +  result = stack_alloc_aligned(s, new_size, alignment); +  size_t min_size = +      old_size < result.bytes_count ? old_size : result.bytes_count; +  memmove(result.memory, old_memory, min_size); + +  return result; +} + +struct ResVoid stack_resize(struct stack *s, void *old_memory, size_t old_size, +                            size_t new_size) { +  return stack_resize_aligned(s, old_memory, old_size, new_size, +                              DEFAULT_ALIGNMENT); +} + +void stack_clear(struct stack *s) { +  s->prev_offset = 0; +  s->curr_offset = 0; +} diff --git a/src/memory/memory.h b/src/memory/memory.h new file mode 100644 index 0000000..e402c0b --- /dev/null +++ b/src/memory/memory.h @@ -0,0 +1,98 @@ +#ifndef AMR_MEMORY_H +#define AMR_MEMORY_H + +#include <stddef.h> +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#ifndef AMR_TYPES_H +#define AMR_TYPES_H + +typedef int8_t   s8; +typedef int16_t	 s16; +typedef int32_t  s32; +typedef int64_t  s64; + +typedef uint8_t  u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef u8 b8; + +#endif // AMR_TYPES_H + + +#ifndef DEFAULT_ALIGNMENT +#define DEFAULT_ALIGNMENT (2*sizeof(void *)) +#endif + +// @todo: build a logging mechanism for handling errors +// maybe read about that + +enum MemStatus { MEM_OK=0, MEM_OUT_OF_BOUNDS, MEM_FULL }; + +struct ResVoid { +  enum MemStatus status; +  size_t bytes_count; +  void* memory; +}; + +b8 is_power_of_two(uintptr_t x); +uintptr_t fast_modulo(uintptr_t p, uintptr_t a); +uintptr_t align_forward(uintptr_t ptr, size_t align); + +//=========================================================================================== +// ---------------------------------- ARENA ------------------------------------------------- +//=========================================================================================== + +struct Arena { +  unsigned char* buffer; +  size_t prev_offset; +  size_t curr_offset; +  size_t capacity; +}; + +void  arena_init(struct Arena *a, unsigned char *backing_store, size_t capacity); +void* arena_alloc_aligned(struct Arena* a, size_t size, size_t align); +void* arena_alloc(struct Arena* a, size_t size); +void* arena_resize_aligned(struct Arena* a, void* old_memory, size_t old_size, +                                    size_t new_size, size_t align);  +void* arena_resize(struct Arena* a, void* old_mem, size_t old_size, +                            size_t new_size); +void arena_clear(struct Arena *a); + +//=========================================================================================== +// ---------------------------------- STACK ------------------------------------------------- +//=========================================================================================== + +/*  +* @todo: stack needs to be updated, it's really just a work in progress right now. +* The main thing is minimizing the use of compound types, since that is pretty annoying to deal with. +* I would rather write code that makes sure to collapse all possible cases and lets me just not worry about code. +* Would rather stick to worrying about data being data +*/ + +struct stack { +  unsigned char* buffer; +  size_t prev_offset; +  size_t curr_offset; +  size_t capacity; +}; + +struct stack_hdr { +  size_t prev_offset; +  size_t padding; +}; + +void           stack_init(struct stack* s, void *backing_store, size_t capacity);  +struct ResVoid stack_alloc_aligned(struct stack* s, size_t size, size_t alignment); +struct ResVoid stack_alloc(struct stack* s, size_t size);  +enum MemStatus stack_free(struct stack* s); +struct ResVoid stack_resize_aligned(struct stack* s, void* old_memory, size_t old_size, +                                    size_t new_size, size_t alignment); +struct ResVoid stack_resize(struct stack* s, void* old_memory, size_t old_size, size_t new_size); +void           stack_clear(struct stack* s); + +#endif diff --git a/src/memory/test.cpp b/src/memory/test.cpp new file mode 100644 index 0000000..f62ce96 --- /dev/null +++ b/src/memory/test.cpp @@ -0,0 +1,566 @@ +#include <stdlib.h> +#include <stdio.h> +#include "memory.c" + +// ================ ARENA ================  // +void test_arena_init(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  assert((a.buffer == buffer) && "Arena does not start at the buffer"); +  assert((a.curr_offset == 0) && (a.prev_offset == 0) && "Arena offsets not initialised properly"); +  assert((a.capacity == buffer_size) && "Arena capacity does not match buffer size"); +} + +void test_arena_alloc(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  assert((qres != NULL) && "Arena Allocation Test Failed: " +      "Reason: Failed to allocate memory"); +} + +void test_arena_alloc_low_memory(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, buffer_size + 20); +  assert((qres == NULL) && "Low Memory Arena Allocation Test Failed: " +      "Reason: Should have allocated the entire buffer since size is larger. \n"); +  qres = arena_alloc(&a, 20); +} + +void test_arena_alloc_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 16); +  u8* n1 = (u8*)qres; + +  qres = arena_alloc(&a, -16); + +  assert((qres == NULL) && "Low Memory Arena Allocation Test Failed: " +      "Reason: Should have allocated the remaining buffer. \n"); +} + +void test_arena_resize(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 8); +  u8* n2 = (u8*)qres; +  *n2 = 20; + +  qres = arena_resize(&a, n1, 8, 16); +  assert((qres != NULL) && "Arena Resize Test Failed: " +  "Reason: Failed to resize previously allocated memory\n" +  "this should not have happened since we do not have anything causing availability issues\n"); +  u8* n1_1 = (u8*)qres; + +  assert((*n1 == *n1_1) && "Arena Resize Test Failed: " +  "Reason: Value of resized memory changed. This should not happen in any case!\n"); +} + +void test_arena_resize_tail(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 16); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 16); +  u8* n2 = (u8*)qres; +  *n2 = 20; + +  qres = arena_resize(&a, n2, 16, 8); +  /*assert((qres.bytes_count == 8) && "Arena Resize Tail Test Failed: " +  "Reason: Failed to resize previously allocated memory at tail\n");*/ +  u8* n2_1 = (u8*)qres; + +  assert((*n2 == *n2_1) && "Arena Resize Test Failed: " +  "Reason: Value of resized memory changed. This should not happen in any case!\n"); +} + +void test_arena_resize_filled_completely(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 8); +  u8* n2 = (u8*)qres; +  *n2 = 20; +   +  qres = arena_resize(&a, n1, 8, buffer_size - 32); +  assert((qres != NULL) && "Arena Resize Filled Completely Failed: \n" +  "Reason: Failed to resize an element even though it perfectly fills the memory Arena"); +} + +void test_arena_resize_out_of_bounds(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 8); +  u8* n2 = (u8*)qres; +  *n2 = 20; +   +  qres = arena_resize(&a, n1, 8, buffer_size - 8); +  assert(qres == NULL && "Arena Resize OUT OF BOUNDS test Failed: \n" +  "Reason: Allocated a larger than capacity element within Arena capacity.\n"); +} + +void test_arena_resize_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 8); +  u8* n2 = (u8*)qres; +  *n2 = 20; +   +  qres = arena_resize(&a, n1, 8, -35); +  assert(qres == NULL && "Arena Resize -ve size test Failed: \n" +  "Reason: Failed to handle allocating a -ve size (VERY LARGE IN UINT) space.\n"); +} + +void test_arena_resize_tail_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 23; + +  qres = arena_alloc(&a, 8); +  u8* n2 = (u8*)qres; +  *n2 = 20; +   +  qres = arena_resize(&a, n2, 8, -35); +  assert(qres == NULL && "Arena Resize OUT OF BOUNDS test Failed: \n" +  "Reason: Failed to fit a larger than capacity element within Arena capacity.\n"); +} + +void test_arena_clear(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  arena_clear(&a); +  assert((a.prev_offset == 0) && (a.curr_offset == 0) && "Arena Clear Test Failed: \n" +  "Reason: Failed to clear offsets on an active Arena entity after it was cleared.\n"); +} + +void test_arena_realloc(unsigned char *buffer, u32 buffer_size) +{ +  struct Arena a = {0}; +  arena_init(&a, buffer, buffer_size); +  void *qres = arena_alloc(&a, 8); +  u8* n1 = (u8*)qres; +  *n1 = 12; +  u8 n1_value = *n1; + +  // free Arena +  arena_clear(&a); + +  qres = arena_alloc(&a, 16); +  u8* n1_realloc = (u8*)qres; + +  assert((*n1_realloc != n1_value) && "Arena Realloc Test Failed: \n" +      "Reason: Failed to properly clear memory in a region where memory was already allocated.\n"); +} + +// ================ STACK ================  // + +void test_stack_init(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); +  assert((s.buffer == buffer) && "stack does not start at the buffer"); +  assert((s.curr_offset == 0) && (s.prev_offset == 0) && "stack offsets not initialised properly"); +  assert((s.capacity == buffer_size) && "stack capacity does not match buffer size"); +} + +void test_stack_alloc_n1(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres = stack_alloc(&s, ele_sz); +  assert((qres.status == MEM_OK) && "failed to allocate stack element"); + +  u8 *ele = (u8 *)qres.memory; +  struct stack_hdr *header = (struct stack_hdr*)((uintptr_t)ele - sizeof(struct stack_hdr)); +  assert((header->prev_offset == 0) && "incorrect prev_offset for first stack element"); + +  size_t pad = header->padding; + +  assert((s.curr_offset == pad + ele_sz ) && "incorrect curr offset memory"); +} + +void test_stack_alloc_n2(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  stack_alloc(&s, ele_sz); + +  size_t last_ele_head = s.prev_offset; +  size_t last_ele_tail = s.curr_offset; +  struct ResVoid qres = stack_alloc(&s, ele_sz); +  assert((qres.status == MEM_OK) && "failed to allocate stack element"); + +  u8 *ele = (u8 *)qres.memory; +  struct stack_hdr *header = (struct stack_hdr*)((uintptr_t)ele - sizeof(struct stack_hdr)); +  assert((header->prev_offset == last_ele_head) && "incorrect prev_offset for second stack element"); + +  size_t pad = header->padding; +  assert((s.curr_offset == last_ele_tail + pad + ele_sz ) && "incorrect curr offset memory"); +} + +void test_stack_free(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  stack_alloc(&s, ele_sz); + +  enum MemStatus status = stack_free(&s); +  assert((status == MEM_OK) && "failed to free stack element"); + +  assert((s.buffer == buffer) && "failed to reset buffer pointer to start of memory"); +  assert((s.prev_offset == 0) && (s.curr_offset == 0) && "failed to move offsets back correctly"); +} + +void test_stack_resize_n0(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 static_mem[8] = {0,1,2,3,4,5,6,7}; +  struct ResVoid qres = stack_resize(&s, static_mem, 8, 16); +  assert((qres.status == MEM_OUT_OF_BOUNDS) && "Stack Resize n0 Failed: \ +        Reason: should not have resized an element not belonging to the stack\n"); +} + +void test_stack_resize_n1(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres =  stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 23; + +  qres = stack_resize(&s, n1, ele_sz, ele_sz + 20); +  assert((qres.status == MEM_OK) && "Stack Resize n1 Failed: \ +      Reason: failed to resize stack element\n"); +  n1 = (u8*)qres.memory; + +  struct stack_hdr* header = (struct stack_hdr*)(n1 - sizeof(struct stack_hdr)); +  assert((s.curr_offset == 52) && "Stack Resize n1 Failed: \ +      Reason: incorrectly resized stack element\n"); +} + +void test_stack_resize_n1_low_memory(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres =  stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 23; + +  qres = stack_resize(&s, n1, ele_sz, ele_sz + 256); +  n1 = (u8*)qres.memory; + +  assert((qres.status == MEM_FULL) && "Stack Resize n1 low memory Failed: \ +      Reason: Should not be allowed to resize beyond the allocated stack capacity\n"); +} + +void test_stack_resize_n2(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres =  stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; + +  qres = stack_alloc(&s, ele_sz); +  u8* n2 = (u8*)qres.memory; +  *n2 = 2; + +  size_t last_ele_head = s.prev_offset; + +  qres = stack_resize(&s, n1, ele_sz, 8); +  u8* n3 = (u8*)qres.memory; +  assert((qres.status == MEM_OK) && "Stack Resize n2 Failed: " +      "Reason: failed to resize first element in a two element array\n"); + +  assert((*n3 == *n1) && "Stack Resize n2 Failed: " +      "Reason: failed to move over data properly after resizing elements"); + +  struct stack_hdr* header = (struct stack_hdr*)(n3 - sizeof(struct stack_hdr)); +  assert((header->prev_offset == last_ele_head) && "Stack Resize 2 Failed: " +      "Reason: failed to set the previous offset of the header element properly after resize"); +} + +void test_stack_resize_tail(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres =  stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; + +  qres = stack_resize(&s, n1, ele_sz, 8); +  assert((qres.status == MEM_OK) && "Stack Resize tail Failed: " +      "Reason: failed to resize last element to be smaller\n"); +} + +void test_stack_alloc_free(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres = stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; +  size_t head_n1 = s.prev_offset; +  size_t tail_n1 = s.curr_offset; + +  qres = stack_alloc(&s, ele_sz); +  u8* n2 = (u8*)qres.memory; +  *n2 = 2; +  size_t head_n2 = s.prev_offset; +  size_t tail_n2 = s.curr_offset; + +  /* @note: elements are allocated, now we will be testing a few things +   * +   * 1. can we free all elements up till the stack UNTIL the point where,  +   * we can not free the stack since it is empty +   * +   * 2. when we free elements, does the offset reset to the point we expect it to +   * By which I mean, +   * +   *                . . . . . ele_1 . . . . . ele_2 . . . . . +   *                              |               | +   *                              |               |-> curr_offset is here at the end of ele2 +   *                              | +   *                              |-> this is the end of ele1, once we free, we expect the curr_offset +   *                              to return to this point and we expect to start considering allocations from +   *                              this point on +   * +   * +   * */ +  stack_free(&s); + +  assert((s.prev_offset == head_n1) && "Stack alloc free error" +      "Reason: failed to move the prev_offset back to the correct position"); +  assert((s.curr_offset == tail_n1) && "Stack alloc free error" +      "Reason: failed to move the curr_offset back to the correct position"); + +  /* +   * @note: we now want to test after allocating something, whether the memory was overwritten properly +   * and there was no garbage data present +   */ + +  qres = stack_alloc(&s, ele_sz); +  u8* n2_1 = (u8*)qres.memory; +  assert((*n2_1 == 0) && "Stack Alloc Free error" +      "Reason: failed to free up memory properly on allocating memory that was cleared"); +} + +void test_stack_alloc_free_resize(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres = stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; +  size_t head_n1 = s.prev_offset; +  size_t tail_n1 = s.curr_offset; + +  qres = stack_alloc(&s, ele_sz); +  u8* n2 = (u8*)qres.memory; +  *n2 = 2; +  size_t head_n2 = s.prev_offset; +  size_t tail_n2 = s.curr_offset; + +  qres = stack_alloc(&s, ele_sz); +  u8* n3 = (u8*)qres.memory; +  *n3 = 3; +  size_t head_n3 = s.prev_offset; +  size_t tail_n3 = s.curr_offset; + +  stack_free(&s); + +  qres = stack_resize(&s, n1, ele_sz, 32); +  u8* n1_1 = (u8*)qres.memory; +  assert((*n1_1 != 3) && "Stack Alloc Free Resize Test Failed: " +      "Reason: the newly resized memory was not setup properly.\n" +      "It had the memory contents of a previously allocated statement\n"); +  assert((n1_1 == n3) && "Stack Alloc Free Resize Test Failed: " +      "Reason: the newly resized memory was not allocated to the correct next region\n" +      "It should have the same memory address as memory allocation #3\n"); +} + +void test_stack_resize_n2_low_space(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres = stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; +  size_t head_n1 = s.prev_offset; +  size_t tail_n1 = s.curr_offset; + +  qres = stack_alloc(&s, s.capacity - 64); +  u8* n2 = (u8*)qres.memory; +  *n2 = 2; +  size_t head_n2 = s.prev_offset; +  size_t tail_n2 = s.curr_offset; + +  qres = stack_resize(&s, n1, ele_sz, 30); +   +  assert((qres.status == MEM_FULL) && "Test Stack Resize Low Space with 2 elements Failed: " +      "Reason: Failed to catch resize with size larger than available space. \n"); +} + +void test_stack_alloc_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  // @note: the way this works is that size_t is unsigned, which is what the function takes +  // so we get a really large number, that actually ends up looping around and becomes less than the array. +  // so we have a bounds check for that. +  stack_alloc(&s, 16); +  struct ResVoid qres = stack_alloc(&s, -16); +  assert((qres.status == MEM_FULL) && "Test Stack Alloc Negative Size Failed\n" +      "Reason: Failed to catch allocation with -ve size. \n" +      "That will translate to be a very large size since the size variable is a size_t (unsigned)\n"); +} + +void test_stack_resize_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  struct ResVoid qres = stack_alloc(&s, 16); +  s8* n1 = (s8*)qres.memory; +  *n1 = -20; + +  qres = stack_resize(&s, n1, 16, -16); +  assert((qres.status == MEM_FULL) && "Test Stack Alloc Negative Size Failed\n" +      "Reason: Failed to catch resize with -ve size. \n" +      "That will translate to be a very large size since the size variable is a size_t (unsigned)\n"); +} + +void test_stack_resize_tail_negative_size(unsigned char *buffer, u32 buffer_size) +{ +  struct stack s = {0}; +  stack_init(&s, buffer, buffer_size); + +  u8 ele_sz = 16; +  struct ResVoid qres =  stack_alloc(&s, ele_sz); +  u8* n1 = (u8*)qres.memory; +  *n1 = 1; + +  qres = stack_resize(&s, n1, ele_sz, -8); +  u8* n2 = (u8*)qres.memory; +  assert((qres.status == MEM_FULL) && "Stack Resize tail Negative Size Failed: " +      "Reason: Failed to catch resize with -ve size. \n" +      "That will translate to be a very large size since the size variable is a size_t (unsigned)\n"); +} + +int main(int argc, char** argv) { +  u32 sz = 256*sizeof(u8); +  u8 *buffer = (u8 *)malloc(sz); +  // --------- ARENA --------- // +  printf("\n===== Testing Arena Allocator =====\n"); +  test_arena_init(buffer, sz); +  printf("- Arena initialization test passed\n"); +  test_arena_alloc(buffer, sz); +  test_arena_alloc_low_memory(buffer, sz); +  test_arena_alloc_negative_size(buffer, sz); +  printf("- Arena allocation tests passed\n"); +  test_arena_resize(buffer, sz); +  test_arena_resize_tail(buffer, sz); +  test_arena_resize_negative_size(buffer, sz); +  test_arena_resize_tail_negative_size(buffer, sz); +  test_arena_resize_filled_completely(buffer, sz); +  test_arena_resize_out_of_bounds(buffer, sz); +  printf("- Arena resize tests passed\n"); +  test_arena_clear(buffer, sz); +  printf("- Arena clear tests passed\n"); +  test_arena_realloc(buffer, sz); +  printf("- Arena reallocation tests passed\n"); + +  // --------- STACK --------- // +  printf("\n===== Testing Stack Allocator =====\n"); + +  test_stack_init(buffer, sz); +  printf("- stack initialization passed\n"); + +  test_stack_alloc_n1(buffer, sz); +  test_stack_alloc_n2(buffer, sz); +  printf("- Stack allocation tests passed\n"); + +  test_stack_free(buffer, sz); +  printf("- stack free passed\n"); + +  test_stack_resize_n0(buffer, sz); +  test_stack_resize_n1(buffer, sz); +  test_stack_resize_n1_low_memory(buffer, sz); +  test_stack_resize_n2(buffer, sz); +  test_stack_resize_tail(buffer, sz); +  printf("- stack resize passed\n"); + +  printf("- testing stack alloc free edge cases \n"); +  printf("  - edge cases passed:\n"); +  test_stack_alloc_free(buffer, sz); +  printf("    1. alloc then free\n"); +  test_stack_alloc_free_resize(buffer, sz); +  printf("    2. alloc then free then resize\n"); +  test_stack_resize_n2_low_space(buffer, sz); +  printf("    3. alloc two elements and then resize when there is low space\n"); +  test_stack_alloc_negative_size(buffer, sz); +  printf("    4. allocating negative size detection\n"); +  test_stack_resize_negative_size(buffer, sz); +  printf("    5. resizing negative size detection\n"); +  test_stack_resize_tail_negative_size(buffer, sz); +  printf("    6. resizing tail to negative size detection\n"); + +  printf("\n===== Memory tests completed successfully =====\n"); +  free(buffer); +  return 1; +} | 
