diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/game.c | 1566 | ||||
| -rw-r--r-- | src/game_v0.c | 512 | 
2 files changed, 2078 insertions, 0 deletions
| diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..2e85043 --- /dev/null +++ b/src/game.c @@ -0,0 +1,1566 @@ +#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_v0.c b/src/game_v0.c new file mode 100644 index 0000000..62141f6 --- /dev/null +++ b/src/game_v0.c @@ -0,0 +1,512 @@ +#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(); +} + + | 
