From 0453b8f50dc4d40083e02cc3d09b4bcaa33f1700 Mon Sep 17 00:00:00 2001 From: talha <-> Date: Thu, 15 Aug 2024 10:46:43 +0500 Subject: Added functionality, updated c to cpp: - Added entity arrays - Added memory arenas - moved c files to cpp - refactored files to work with lsp (still unity build) but more painful --- .clang-format | 1 + .clangd | 2 + build_browser.sh | 2 +- build_desktop.sh | 4 +- src/array.cpp | 113 ++++ src/array.h | 34 ++ src/characters.h | 100 ++++ src/entity.h | 15 + src/game.c | 1566 --------------------------------------------------- src/game.cpp | 1508 +++++++++++++++++++++++++++++++++++++++++++++++++ src/game_v0.c | 512 ----------------- src/memory/memory.c | 246 ++++++++ src/memory/memory.h | 98 ++++ src/memory/test.cpp | 566 +++++++++++++++++++ 14 files changed, 2686 insertions(+), 2081 deletions(-) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 src/array.cpp create mode 100644 src/array.h create mode 100644 src/characters.h create mode 100644 src/entity.h delete mode 100644 src/game.c create mode 100644 src/game.cpp delete mode 100644 src/game_v0.c create mode 100644 src/memory/memory.c create mode 100644 src/memory/memory.h create mode 100644 src/memory/test.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4b75550 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +SortIncludes: Never diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..9263e0c --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: -I/home/talha/Documents/programming/blaidville_prototype/thirdparty/raylib_desktop/include -I/home/talha/Documents/programming/blaidville_prototype/thirdparty/raylib_browser/include -I/home/talha/Documents/programming/blaidville_prototype/src diff --git a/build_browser.sh b/build_browser.sh index 8e046cb..bf65c76 100755 --- a/build_browser.sh +++ b/build_browser.sh @@ -8,7 +8,7 @@ minishell="--shell-file $HOME/Documents/Personal/programming/raylib/src/minshell lib_opts="$lraylib -s USE_GLFW=3 $minishell -DPLATFORM_WEB --preload-file assets" src_dir="src" -src_files="$src_dir/game.c" +src_files="$src_dir/game.cpp" build_dir="build/web" build_path="$build_dir/game.html" emcc -Os -Wall ./thirdparty/raylib_browser/lib/libraylib.a $include_opts $src_files $lib_opts -o $build_path diff --git a/build_desktop.sh b/build_desktop.sh index d74c086..683a084 100755 --- a/build_desktop.sh +++ b/build_desktop.sh @@ -7,7 +7,7 @@ lraylib="-L thirdparty/raylib_desktop/lib -lraylib" lib_opts="$lraylib -lGL -lm -lpthread -ldl -lrt -lX11" src_dir="src" -src_files="$src_dir/game.c" +src_files="$src_dir/game.cpp" build_dir="build/desktop" build_path="$build_dir/game.out" -cc -g -O0 $include_opts $src_files $lib_opts -o $build_path +clang++ -g -O0 $include_opts $src_files $lib_opts -o $build_path diff --git a/src/array.cpp b/src/array.cpp new file mode 100644 index 0000000..88eb492 --- /dev/null +++ b/src/array.cpp @@ -0,0 +1,113 @@ +#include "array.h" + +struct EntityArr entity_arr_init(struct Arena *arena, unsigned int capacity) { + struct EntityArr arr = {0}; + void *allocation = arena_alloc(arena, sizeof(struct EntityArr) * capacity); + + // @todo: check if allocation was not possible, log in case of that + assert(allocation != NULL); + arr.buffer = (struct Entity *)allocation; + arr.capacity = capacity; + arr.size = 0; + + return arr; +} + +void entity_arr_insert(struct EntityArr *arr, struct Entity to_insert, + unsigned int index) { + if (index >= arr->capacity) { + return; + } + + arr->buffer[index] = to_insert; +} + +struct Entity entity_arr_remove(struct EntityArr *arr, struct Entity to_remove, + unsigned int index) { + if (index >= arr->capacity) { + return EntityNone; + } + + struct Entity removed = arr->buffer[index]; + arr->buffer[index] = EntityNone; + + return removed; +} + +void entity_arr_push_head(struct EntityArr *arr, struct Entity value) { + if (arr->size >= arr->capacity) { + return; + } + + for (int i = arr->size - 1; i >= 0; i--) { + arr->buffer[i + 1] = arr->buffer[i]; + } + + arr->buffer[0] = value; + arr->size++; +} + +void entity_arr_push_tail(struct EntityArr *arr, struct Entity value) { + if (arr->size >= arr->capacity) { + return; + } + + arr->buffer[arr->size] = value; + arr->size++; +} + +struct Entity entity_arr_pop_head(struct EntityArr *arr) { + if (arr->size == 0) + return EntityNone; + + struct Entity popped = arr->buffer[0]; + for (int i = 1; i < arr->size; i++) { + arr->buffer[i - 1] = arr->buffer[i]; + } + arr->size--; + + return popped; +} + +struct Entity entity_arr_pop_tail(struct EntityArr *arr) { + if (arr->size == 0) + return EntityNone; + + struct Entity popped = arr->buffer[arr->size - 1]; + arr->size--; + + return popped; +} + +void entity_qpush(EntityQueue *queue, struct Entity value) { + entity_arr_push_tail(queue, value); +} + +struct Entity entity_qpop(EntityQueue *queue) { + return entity_arr_pop_head(queue); +} + +void entity_spush(EntityStack *stack, struct Entity value) { + entity_arr_push_tail(stack, value); +} + +struct Entity entity_spop(EntityStack *stack) { + return entity_arr_pop_tail(stack); +} + +int entity_arr_find(struct EntityArr *arr, struct Entity value) { + /* + * finds value in arr + * returns index of value + */ + int find_index = -1; + for (int i = 0; i < arr->capacity; i++) { + struct Entity entry = arr->buffer[i]; + if (entry.type == value.type && entry.id == value.id) { + find_index = i; + break; + } + } + + return find_index; +} diff --git a/src/array.h b/src/array.h new file mode 100644 index 0000000..035e1ee --- /dev/null +++ b/src/array.h @@ -0,0 +1,34 @@ +#pragma once +#include "memory/memory.h" +#include "entity.h" + +struct EntityArr { + struct Entity *buffer; + unsigned int capacity; + unsigned int size; // used for mantaining a stack and queue +}; + +typedef EntityArr EntityArrayList; + +struct EntityArr entity_arr_init(struct Arena *arena, unsigned int size); + +// -- basic insert remove operations -- +void entity_arr_insert(struct EntityArr *arr, struct Entity to_insert, unsigned int index); +struct Entity entity_arr_remove(struct EntityArr *arr, struct Entity to_remove, unsigned int index); + +void entity_arr_push_head(struct EntityArr *arr, struct Entity value); +void entity_arr_push_tail(struct EntityArr *arr, struct Entity value); + +struct Entity entity_arr_pop_head(struct EntityArr *arr); +struct Entity entity_arr_pop_tail(struct EntityArr *arr); + +typedef EntityArr EntityQueue; +void entity_qpush(EntityQueue *queue, struct Entity value); +struct Entity entity_qpop(EntityQueue *queue); + +typedef EntityArr EntityStack; +void entity_spush(EntityStack *stack, struct Entity value); +struct Entity entity_spop(EntityStack *stack, struct Entity value); + +// -- operations on values -- +int entity_arr_find(struct EntityArr *arr, struct Entity value); diff --git a/src/characters.h b/src/characters.h new file mode 100644 index 0000000..2f1e10b --- /dev/null +++ b/src/characters.h @@ -0,0 +1,100 @@ +#pragma once + +#include "raylib.h" +#include "entity.h" +#include "array.h" + +typedef enum { + AA_IDLE = 0, + AA_CHARGE = 1, + AA_ATTACK = 2, +} AttackAnimState; + +typedef enum { + C_IDLE = 0, + C_OBSERVE = 1, + C_PATROL = 2, + C_FOLLOW = 3, + C_AGGRO = 4, + C_HIT = 5, + C_DEAD = 6 +} CharState; + +typedef enum { + AI_NONE = 0, + AI_IDLE = 1, + AI_PATROL = 2, + AI_DETECT = 3, + AI_ATTACK = 4, +} AIState; + +typedef enum { + HA_IDLE = 0, + HA_PLAY = 1, +} HitAnimState; + +typedef enum { + PATROL_TOP = 0, + PATROL_LEFT = 1, + PATROL_DOWN = 2, + PATROL_RIGHT = 3, +} PatrolDir; + +typedef struct Entity AttackedEntity; +typedef struct Entity AggroEntity; +typedef struct Entity FriendEntity; + +struct Character { + bool isburning; + EntityType type; // TROLL / BANDIT + int entity_id; + CharState state; // CharState + int tile_id; // MapSymbol + int home_tile_id; // MapSymbol + AIState ai_state; + int health; + float speed_multiplier; + float move_speed; + float detection_threshold; + float attack_threshold; + // improved idle behavior + PatrolDir patrol_dir; + bool to_core_pos; + Vector2 core_position; // this is the main position characters will stick to + double t_idle_start; // time spent at core position, useful to rotate them out + // of positions + float t_idle_duration; // max allowed duration they should be idle + + Vector2 position; + Vector2 last_enemy_position; + Vector2 move_dir; + Vector2 target_position; + Rectangle target_rect; + Rectangle render_rect; + Color tint_color_base; + Color tint_color_active; + Color tint_color; + Texture2D *sprite; + // AggroEntity attackers[2]; + EntityArrayList attackers; + // FriendEntity friends[2]; + EntityArrayList friends; + // + // Animation + // + // getting hit + HitAnimState anim_hit_state; + float anim_hit_speed; + double t_anim_hit_start; + float t_anim_hit_duration; + // getting burned + double t_anim_burn_start; + + // attacking + int anim_attack_state; + float anim_attack_speed; + float t_anim_charge_duration; + float t_anim_attack_duration; + double t_anim_charge_start; + double t_anim_attack_start; +}; diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..0db15f1 --- /dev/null +++ b/src/entity.h @@ -0,0 +1,15 @@ +#pragma once + +typedef enum { + NONE = 0, + PLAYER = 1, + TROLL = 2, + BANDIT = 3, +} EntityType; + +struct Entity { + EntityType type; // EntityType + int id; // Entity ID +}; + +const struct Entity EntityNone = {.type = NONE, .id = 0}; diff --git a/src/game.c b/src/game.c deleted file mode 100644 index 2e85043..0000000 --- a/src/game.c +++ /dev/null @@ -1,1566 +0,0 @@ -#include "raylib.h" -#include "raymath.h" - -#if defined(PLATFORM_WEB) - #include -#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; yposstate == 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;iwidth; - 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;iwidth*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; istate = 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;istate == 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;istate == 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;istate == 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;istate == 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 + +#if defined(PLATFORM_WEB) +#include +#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(xpos), static_cast(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 -#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; yposwidth; - 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 +#include +#include +#include + +#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 +#include +#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; +} -- cgit v1.2.3