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 /src/game.cpp | |
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
Diffstat (limited to 'src/game.cpp')
-rw-r--r-- | src/game.cpp | 1508 |
1 files changed, 1508 insertions, 0 deletions
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(); + } +} |