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