summaryrefslogtreecommitdiff
path: root/src/game.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game.cpp')
-rw-r--r--src/game.cpp1508
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();
+ }
+}