summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/game.c1566
-rw-r--r--src/game_v0.c512
2 files changed, 2078 insertions, 0 deletions
diff --git a/src/game.c b/src/game.c
new file mode 100644
index 0000000..2e85043
--- /dev/null
+++ b/src/game.c
@@ -0,0 +1,1566 @@
+#include "raylib.h"
+#include "raymath.h"
+
+#if defined(PLATFORM_WEB)
+ #include <emscripten/emscripten.h>
+#endif
+
+typedef enum {
+ O = 0, // nothing
+ //P = 1, // player
+ t = 2, // tree
+ T = 3, // troll area
+ B = 4, // bandit area
+ T_B = 5, // troll base
+ B_B = 6, // bandit base
+ //G = 7, // gate (exit)
+} MapSymbol;
+
+typedef enum {
+ AA_IDLE = 0,
+ AA_CHARGE = 1,
+ AA_ATTACK = 2,
+} AttackAnimState;
+
+typedef enum {
+ P_IDLE = 0,
+ P_MOVE = 1,
+ P_HIT = 2,
+} PlayerState;
+
+typedef enum {
+ C_IDLE = 0,
+ C_OBSERVE = 1,
+ C_PATROL = 2,
+ C_FOLLOW = 3,
+ C_AGGRO = 4,
+ C_HIT = 5,
+ C_DEAD = 6
+} CharState;
+
+typedef enum {
+ AI_NONE = 0,
+ AI_IDLE = 1,
+ AI_PATROL = 2,
+ AI_DETECT = 3,
+ AI_ATTACK = 4,
+} AIState;
+
+typedef enum {
+ HA_IDLE = 0,
+ HA_PLAY = 1,
+} HitAnimState;
+
+typedef enum {
+ NONE = 0,
+ PLAYER = 1,
+ TROLL = 2,
+ BANDIT = 3,
+} CharacterType;
+
+typedef enum {
+ PT_NONE = 0,
+ PT_ALLY = 1,
+ PT_ATTACK = 2,
+} PotionType;
+
+typedef enum {
+ PS_NONE = 0,
+ PS_HELD = 1,
+ PS_THROWN = 2,
+ PS_SPREAD = 3,
+} PotionState;
+
+typedef enum {
+ GS_GAME = 0,
+ GS_UI = 1,
+ GS_SUCCESS = 2,
+ GS_OVER = 3,
+} GameplayState;
+
+typedef enum {
+ PATROL_TOP = 0,
+ PATROL_LEFT = 1,
+ PATROL_DOWN = 2,
+ PATROL_RIGHT = 3,
+ PATROL_END = 4,
+} PatrolDir;
+
+typedef struct ScuffString {
+ char *buffer;
+ int len;
+ int capacity;
+} String;
+
+struct Entity {
+ int type; // EntityType
+ int id; // Entity ID
+};
+
+typedef struct Entity AttackedEntity;
+typedef struct Entity AggroEntity;
+typedef struct Entity FriendEntity;
+
+struct Potion {
+ int state;
+ int type;
+ float radius; // radius during which potion effects last
+ int damage; // how much damage potion does
+ int healing; // how much healing potion does
+ float speed_multiplier; // how much enemy speed is effected inside potion radius
+ float t_potion_throw_start;
+ float t_potion_spread_start;
+ float t_potion_spread_duration; // how long does the potion spread last
+ Vector2 throw_dir;
+ Vector2 position_start;
+ Vector2 position_curr;
+ Vector2 position_target;
+ Color tint_color_spread;
+ Color tint_color_effect;
+ Texture2D *sprite;
+} Potion;
+
+typedef struct Potion FirePotion;
+typedef struct Potion FriendPotion;
+
+typedef struct Player {
+ int entity_id;
+ int state; // PlayerState
+ int tile_id; // MapSymbol
+ int health;
+ float move_speed;
+ Vector2 position;
+ Vector2 target_position;
+ Vector2 move_dir;
+ Color tint_color;
+ Rectangle render_rect;
+ Texture2D *sprite;
+ // attack state
+ bool is_attack;
+ float throw_dist;
+ Vector2 attack_dir;
+ float potion_throw_speed;
+ float potion_throw_dist;
+ struct Potion *active_potion;
+ FirePotion fire_potion;
+ FriendPotion friend_potion;
+ //
+ // Animation
+ //
+ int anim_hit_state;
+ float anim_hit_speed;
+ double t_anim_hit_start;
+ float t_anim_hit_duration;
+} Player;
+
+struct Character {
+ bool isburning;
+ int entity_type; // TROLL / BANDIT
+ int entity_id;
+ int state; // CharState
+ int tile_id; // MapSymbol
+ int home_tile_id; // MapSymbol
+ int ai_state;
+ int health;
+ float speed_multiplier;
+ float move_speed;
+ float detection_threshold;
+ float attack_threshold;
+ // improved idle behavior
+ PatrolDir patrol_dir;
+ bool to_core_pos;
+ Vector2 core_position; // this is the main position characters will stick to
+ double t_idle_start; // time spent at core position, useful to rotate them out of positions
+ float t_idle_duration; // max allowed duration they should be idle
+
+ Vector2 position;
+ Vector2 last_enemy_position;
+ Vector2 move_dir;
+ Vector2 target_position;
+ Rectangle target_rect;
+ Rectangle render_rect;
+ Color tint_color_base;
+ Color tint_color_active;
+ Color tint_color;
+ Texture2D* sprite;
+ AggroEntity attackers[2];
+ FriendEntity friends[2];
+ //
+ // Animation
+ //
+ // getting hit
+ int anim_hit_state;
+ float anim_hit_speed;
+ double t_anim_hit_start;
+ float t_anim_hit_duration;
+ // getting burned
+ double t_anim_burn_start;
+
+ // attacking
+ int anim_attack_state;
+ float anim_attack_speed;
+ float t_anim_charge_duration;
+ float t_anim_attack_duration;
+ double t_anim_charge_start;
+ double t_anim_attack_start;
+};
+
+typedef struct Character Troll;
+typedef struct Character Bandit;
+
+typedef struct State {
+ int gameplay_state;
+ // how much scaling to apply from original asset resolution
+ float render_scale;
+ Vector2 pixels_per_gridbox;
+ Vector2 mouse_position;
+ // @note: normally when we place a texture, the coordinates
+ // are the top left coordinates. This will cause a disconnect
+ // when moving the player with a point and click motion.
+ // To fix that we calculate a new position so the player_coordinates
+ // are in the center. This will make it so when the player clicks on the
+ // ground somewhere, the character sprite will be properly centered on that point.
+
+ Player player;
+ Troll _troll;
+ Troll trolls[5];
+ int troll_arr_sz;
+ Bandit _bandit;
+ Bandit bandits[5];
+ int bandit_arr_sz;
+ Vector2 bandit_position;
+ // player movement
+ // @todo
+ // direction player is being hit from, incorporate that into the gameplay code
+ // For now, just update the player_move_dir to the player_hit_dir
+ // Vector2 player_hit_dir;
+ // float player_move_delta; // amount player will move
+ // target position player will move to
+ // 1. the player can move intentionally, in which case carry forward from player position target
+ // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET
+
+ // game sprites
+ Texture2D *grass_sprite;
+
+ Texture2D *troll_weapon_sprite;
+ Texture2D *bandit_sprite;
+ Texture2D *bandit_weapon_sprite;
+
+ Texture2D *troll_area_floor_sprite;
+ Texture2D *bandit_area_floor_sprite;
+
+ Texture2D *troll_base_sprite;
+ Texture2D *bandit_base_sprite;
+
+ // key sprites and stuff
+ int key_picked_count;
+ bool key_should_render[2];
+ Texture2D *key_sprite;
+ Vector2 key_positions[2];
+ Rectangle key_render_rects[2];
+ // gate stuff
+ bool is_gate_open;
+ Vector2 gate_position;
+ Rectangle gate_render_rect;
+ Texture2D *active_gate_sprite;
+ Texture2D *gate_closed_sprite;
+ Texture2D *gate_open_sprite;
+
+ String *action_log;
+} State;
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+const int screenWidth = 1280;
+const int screenHeight = 1024;
+State mega_state = {};
+// layer ground
+int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O},
+ {O, O, O, O, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T}};
+// vegetation grid
+int floor_cover_grid_map[8][8] = {{O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, T_B},
+ {O, B_B, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O}};
+Vector2 grid_dims = {8, 8};
+//----------------------------------------------------------------------------------
+// Module functions declaration
+//----------------------------------------------------------------------------------
+void CenterRectAroundTL(Rectangle *to_center, Vector2 position);
+void UpdateDrawFrame(); // Update and Draw one frame
+void CenterRectAroundTL(Rectangle *to_center, Vector2 position)
+{
+ to_center->x = position.x - ((float)to_center->width)/2.0f;
+ to_center->y = position.y - ((float)to_center->height)/2.0f;
+}
+
+
+int main(void)
+{
+ InitWindow(screenWidth, screenHeight, "blaidville prototype");
+ SetTargetFPS(60);
+ SetExitKey(0);
+ Texture2D grass_sprite = LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png");
+ Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png");
+ Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png");
+ Texture2D bandit_sprite = LoadTexture("./assets/monster/unique/maurice_new.png");
+ Texture2D troll_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png");
+ Texture2D bandit_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_east_new.png");
+ Texture2D bandit_base_sprite = LoadTexture("./assets/dungeon/floor/hive_3.png");
+ Texture2D troll_base_sprite = LoadTexture("./assets/dungeon/floor/dirt_2_old.png");
+ Texture2D troll_weapon_sprite = LoadTexture("./assets/item/weapon/club_old.png");
+ Texture2D fire_potion_sprite = LoadTexture("./assets/item/potion/ruby_old.png");
+ Texture2D friend_potion_sprite = LoadTexture("./assets/item/potion/emerald.png");
+ Texture2D key_sprite = LoadTexture("./assets/item/misc/key.png");
+ Texture2D gate_closed_sprite = LoadTexture("./assets/dungeon/gateways/bazaar_gone.png");
+ Texture2D gate_open_sprite = LoadTexture("./assets/dungeon/gateways/bazaar_portal.png");
+
+ mega_state.render_scale = 2.0f;
+
+ mega_state.key_sprite = &key_sprite;
+ mega_state.key_positions[0] = (Vector2){200, 700};
+ mega_state.key_positions[1] = (Vector2){1200, 600};
+ mega_state.key_render_rects[0] = (Rectangle){.width=key_sprite.width, .height=key_sprite.height};
+ mega_state.key_render_rects[1] = (Rectangle){.width=key_sprite.width, .height=key_sprite.height};
+ CenterRectAroundTL(&mega_state.key_render_rects[0], mega_state.key_positions[0]);
+ CenterRectAroundTL(&mega_state.key_render_rects[1], mega_state.key_positions[1]);
+ mega_state.key_should_render[0] = 1;
+ mega_state.key_should_render[1] = 1;
+
+ mega_state.grass_sprite = &grass_sprite;
+ mega_state.bandit_sprite = &bandit_sprite;
+ mega_state.gate_open_sprite = &gate_open_sprite;
+ mega_state.gate_closed_sprite = &gate_closed_sprite;
+ mega_state.active_gate_sprite = &gate_closed_sprite;
+ mega_state.troll_area_floor_sprite = &troll_area_floor_sprite;
+ mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite;
+ mega_state.bandit_base_sprite = &bandit_base_sprite;
+ mega_state.troll_base_sprite = &troll_base_sprite;
+ mega_state.troll_weapon_sprite = &troll_weapon_sprite;
+ mega_state.pixels_per_gridbox = (Vector2){screenWidth/grid_dims.x, screenHeight/grid_dims.y};
+
+ // characters
+ // player
+ mega_state.player = (Player){
+ .health = 45.0f,
+ .position = (Vector2){50.0f, 50.0f},
+ .move_speed = 2.0f,
+ .sprite = &player_sprite,
+ .tint_color = WHITE,
+ .render_rect = (Rectangle){
+ .x=0,.y=0,
+ .width=0.5*player_sprite.width*mega_state.render_scale,
+ .height=player_sprite.height*mega_state.render_scale
+ },
+ .throw_dist = 100.0f,
+ .fire_potion = (FirePotion){
+ .state=PS_NONE,.type=PT_ATTACK,.radius=40.0f,.damage=5,
+ .speed_multiplier=0.5f, .sprite=&fire_potion_sprite,
+ .t_potion_spread_duration = 5.0f,
+ .tint_color_spread=(Color){200, 50, 50, 100}, .tint_color_effect=(Color){200, 50, 50, 255}
+ },
+ .friend_potion = (FriendPotion){
+ .state=PS_HELD,.type=PT_ALLY,.radius=40.0f,
+ .speed_multiplier=1.0f, .sprite=&friend_potion_sprite,
+ .t_potion_spread_duration = 5.0f,
+ .tint_color_spread=(Color){50, 200, 50, 100}, .tint_color_effect=(Color){50, 200, 50, 255}
+ },
+ .potion_throw_speed = 250.0f,
+ .potion_throw_dist = 200.0f,
+ .anim_hit_speed = 50.0f,
+ .t_anim_hit_duration = 0.2f,
+ };
+ // troll
+ Troll troll = {
+ .state = C_IDLE,
+ .entity_type = TROLL,
+ .home_tile_id = T,
+ .health = 50,
+ .move_speed = 2.0f,
+ .speed_multiplier = 1.0f,
+ .tint_color_base = WHITE,
+ .tint_color_active = WHITE,
+ .tint_color = WHITE,
+ .render_rect = (Rectangle){.x=0,.y=0,.width=troll_sprite.width*mega_state.render_scale,.height=troll_sprite.height*mega_state.render_scale},
+ .sprite = &troll_sprite,
+ .t_anim_charge_duration = 1.0f,
+ .t_anim_attack_duration = 0.4,
+ .anim_attack_speed = 100.0f,
+ .anim_hit_speed = 20.0f,
+ .t_anim_hit_duration = 0.2f,
+ .t_idle_duration = 2.0f,
+ .to_core_pos = true,
+ };
+ mega_state.troll_arr_sz = 5;
+
+ mega_state.trolls[0] = troll;
+ mega_state.trolls[0].entity_id = 0;
+ mega_state.trolls[0].t_idle_duration = 4.0f;
+ mega_state.trolls[0].position = (Vector2){750, 340};
+ mega_state.trolls[0].core_position = mega_state.trolls[0].position;
+
+ mega_state.trolls[1] = troll;
+ mega_state.trolls[1].entity_id = 1;
+ mega_state.trolls[1].position = (Vector2){1200, 300};
+ mega_state.trolls[1].core_position = mega_state.trolls[1].position;
+
+ mega_state.trolls[2] = troll;
+ mega_state.trolls[2].entity_id = 2;
+ mega_state.trolls[2].t_idle_duration = 3.5f;
+ mega_state.trolls[2].position = (Vector2){800, 800};
+ mega_state.trolls[2].core_position = mega_state.trolls[2].position;
+
+ mega_state.trolls[3] = troll;
+ mega_state.trolls[3].entity_id = 3;
+ mega_state.trolls[3].t_idle_duration = 3.0f;
+ mega_state.trolls[3].position = (Vector2){1130, 640};
+ mega_state.trolls[3].core_position = mega_state.trolls[0].position;
+
+ mega_state.trolls[4] = troll;
+ mega_state.trolls[4].entity_id = 4;
+ mega_state.trolls[4].t_idle_duration = 1.5f;
+ mega_state.trolls[4].position = (Vector2){1000, 900};
+ mega_state.trolls[4].core_position = mega_state.trolls[4].position;
+
+ // bandit
+ Bandit bandit = (Bandit){
+ .state = C_IDLE,
+ .ai_state = AI_NONE,
+ .entity_type = BANDIT,
+ .home_tile_id = B,
+ .health = 40,
+ .move_speed = 4.0f,
+ .speed_multiplier = 1.0f,
+ .tint_color_base = WHITE,
+ .tint_color_active = WHITE,
+ .tint_color = WHITE,
+ .render_rect = (Rectangle){.x=0,.y=0,.width=bandit_sprite.width*mega_state.render_scale,.height=bandit_sprite.height*mega_state.render_scale},
+ .sprite = &bandit_sprite,
+ .t_anim_charge_duration = 0.5f,
+ .t_anim_attack_duration = 0.2f,
+ .anim_attack_speed = 50.0f,
+ .anim_hit_speed = 50.0f,
+ .t_anim_hit_duration = 0.2f,
+ .t_idle_duration = 2.0f,
+ .to_core_pos = true
+ };
+ mega_state.bandit_arr_sz = 5;
+
+ mega_state.bandits[0] = bandit;
+ mega_state.bandits[0].entity_id = 0;
+ mega_state.bandits[0].position = (Vector2){264.0f, 900.0f};
+ mega_state.bandits[0].core_position = mega_state.bandits[0].position;
+
+ mega_state.bandits[1] = bandit;
+ mega_state.bandits[1].entity_id = 1;
+ mega_state.bandits[1].t_idle_duration = 4.0f;
+ mega_state.bandits[1].position = (Vector2){164.0f, 600.0f};
+ mega_state.bandits[1].core_position = mega_state.bandits[1].position;
+
+ mega_state.bandits[2] = bandit;
+ mega_state.bandits[2].entity_id = 2;
+ mega_state.bandits[2].t_idle_duration = 3.0f;
+ mega_state.bandits[2].position = (Vector2){264.0f, 400.0f};
+ mega_state.bandits[2].core_position = mega_state.bandits[2].position;
+
+ mega_state.bandits[3] = bandit;
+ mega_state.bandits[3].entity_id = 3;
+ mega_state.bandits[3].t_idle_duration = 2.5f;
+ mega_state.bandits[3].position = (Vector2){400.0f, 800.0f};
+ mega_state.bandits[3].core_position = mega_state.bandits[3].position;
+
+ mega_state.bandits[4] = bandit;
+ mega_state.bandits[4].entity_id = 4;
+ mega_state.bandits[4].t_idle_duration = 1.0f;
+ mega_state.bandits[4].position = (Vector2){400.0f, 600.0f};
+ mega_state.bandits[4].core_position = mega_state.bandits[4].position;
+
+ // @todo: action log to describe and dictate all the major events happen
+ char *str_buffer = (char*)MemAlloc(512*sizeof(char));
+ mega_state.player.active_potion = &mega_state.player.friend_potion;
+ CenterRectAroundTL(&mega_state.trolls[0].render_rect, mega_state.trolls[0].position);
+ CenterRectAroundTL(&mega_state.trolls[1].render_rect, mega_state.trolls[1].position);
+
+ CenterRectAroundTL(&mega_state.bandits[0].render_rect, mega_state.bandits[0].position);
+ CenterRectAroundTL(&mega_state.bandits[0].render_rect, mega_state.bandits[0].position);
+ // no action log right now
+ // mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, .len=0};
+ mega_state.gameplay_state = GS_UI;
+
+ mega_state.gate_position = (Vector2){.x=500.0f, .y=16.0f};
+ mega_state.gate_render_rect = (Rectangle){.width=mega_state.gate_closed_sprite->width*1.5, .height=mega_state.gate_closed_sprite->height*1.5};
+ CenterRectAroundTL(&mega_state.gate_render_rect, mega_state.gate_position);
+
+ #if defined(PLATFORM_WEB)
+ emscripten_set_main_loop(UpdateDrawFrame, 0, 1);
+ #else
+ SetTargetFPS(60);
+
+ while(!WindowShouldClose())
+ {
+ UpdateDrawFrame();
+ }
+ #endif
+
+ CloseWindow();
+
+ return 0;
+}
+
+void DrawMapFloorTiles()
+{
+ Color BlackTint = {128,128,128,255};
+ Texture2D grass = *mega_state.grass_sprite;
+ float winc = (float)grass.width * mega_state.render_scale;
+ float hinc = (float)grass.height * mega_state.render_scale;
+ Vector2 grid_pos = {0, 0};
+ for (int ypos=0; ypos<screenHeight; ypos+=hinc)
+ {
+ grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y);
+ for (int xpos=0; xpos<screenWidth; xpos+=winc)
+ {
+ grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x);
+ Vector2 pixel_pos = {xpos, ypos};
+ int grid_ele = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ Texture2D render_tex;
+ switch (grid_ele) {
+ case (O):
+ {
+ render_tex = *mega_state.grass_sprite;
+ } break;
+ case (B):
+ {
+ render_tex = *mega_state.bandit_area_floor_sprite;
+ } break;
+ case (T):
+ {
+ render_tex = *mega_state.troll_area_floor_sprite;
+ } break;
+ default:
+ {
+ } break;
+ }
+ DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, BlackTint);
+ }
+ }
+}
+
+void DrawFloorCoverTiles()
+{
+ Color BlackTint = {128,128,128,255};
+ Texture2D null_tex = *mega_state.player.sprite;
+ float winc = (float)null_tex.width * mega_state.render_scale;
+ float hinc = (float)null_tex.height * mega_state.render_scale;
+
+ Vector2 grid_pos = {0, 0};
+ for (int ypos=0; ypos<screenHeight; ypos+=hinc)
+ {
+ grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y);
+ for (int xpos=0; xpos<screenWidth; xpos+=winc)
+ {
+ grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x);
+ Vector2 pixel_pos = {xpos, ypos};
+ int grid_ele = floor_cover_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ Texture2D render_tex;
+ switch (grid_ele) {
+ case (B_B):
+ {
+ render_tex = *mega_state.bandit_base_sprite;
+ } break;
+ case (T_B):
+ {
+ render_tex = *mega_state.troll_base_sprite;
+ } break;
+ default:
+ {
+ render_tex = null_tex;
+ } continue;
+ }
+ DrawTextureEx(render_tex, pixel_pos, 0, mega_state.render_scale, BlackTint);
+ }
+ }
+}
+
+Vector2 GetSpriteCenterPosition(Texture2D sprite, Vector2 position, float render_scale)
+{
+ float centered_x = position.x - ((float)sprite.width)*render_scale/2.0f;
+ float centered_y = position.y - ((float)sprite.height)*render_scale/2.0f;
+
+ return (Vector2){.x = centered_x, .y = centered_y};
+}
+
+void DrawCharacters()
+{
+ DrawTextureEx(*mega_state.active_gate_sprite,
+ (Vector2){mega_state.gate_render_rect.x, mega_state.gate_render_rect.y},
+ 0, mega_state.render_scale*1.5, WHITE);
+ // draw potion
+ if (mega_state.player.active_potion->state == PS_THROWN)
+ {
+ DrawTextureEx(*mega_state.player.active_potion->sprite,
+ mega_state.player.active_potion->position_curr,
+ 0, 0.4*mega_state.render_scale, WHITE);
+ }
+ else if (mega_state.player.active_potion->state == PS_SPREAD)
+ {
+ Vector2 pos = mega_state.player.active_potion->position_curr;
+ DrawCircle(pos.x, pos.y, mega_state.player.active_potion->radius, mega_state.player.active_potion->tint_color_spread);
+ }
+ if (mega_state.key_should_render[0] == 1)
+ {
+ DrawTextureEx(*mega_state.key_sprite,
+ (Vector2){mega_state.key_positions[0].x, mega_state.key_positions[0].y}, 0,
+ mega_state.render_scale/2.0f, WHITE);
+ }
+ if (mega_state.key_should_render[1] == 1)
+ {
+ DrawTextureEx(*mega_state.key_sprite,
+ (Vector2){mega_state.key_positions[1].x, mega_state.key_positions[1].y}, 0,
+ mega_state.render_scale/2.0f, WHITE);
+ }
+
+ Rectangle src_rect, dest_rect;
+ // draw Troll
+ // - troll weapon
+ for (int i=0;i<mega_state.troll_arr_sz;i++)
+ {
+ Vector2 troll_weapon_centered = GetSpriteCenterPosition(*mega_state.troll_weapon_sprite, mega_state.trolls[i].position, 1.0f);
+ // move to left hand
+ troll_weapon_centered.x -= mega_state.trolls[i].sprite->width;
+ src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.troll_weapon_sprite->width, .height=mega_state.troll_weapon_sprite->height};
+ dest_rect = (Rectangle){.x=troll_weapon_centered.x, .y=troll_weapon_centered.y,
+ .width=0.5f*mega_state.render_scale*mega_state.troll_weapon_sprite->width,
+ .height=0.5f*mega_state.render_scale*mega_state.troll_weapon_sprite->height};
+ DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect,
+ (Vector2){0,0}, 0, RAYWHITE);
+ // - troll sprite
+ DrawTextureEx(*mega_state.trolls[i].sprite,
+ (Vector2){mega_state.trolls[i].render_rect.x, mega_state.trolls[i].render_rect.y},
+ 0, mega_state.render_scale, mega_state.trolls[i].tint_color);
+ // draw collision box
+ //DrawRectangleLines(mega_state.troll.render_rect.x, mega_state.troll.render_rect.y,
+ // mega_state.troll.render_rect.width, mega_state.troll.render_rect.height, RED);
+ }
+
+ // draw Bandit
+ for (int i=0;i<mega_state.bandit_arr_sz;i++)
+ {
+ DrawTextureEx(*mega_state.bandits[i].sprite,
+ (Vector2){mega_state.bandits[i].render_rect.x, mega_state.bandits[i].render_rect.y},
+ 0, mega_state.render_scale, mega_state.bandits[i].tint_color);
+ }
+ // draw player
+ float p_render_scale = 1.0f;
+ Vector2 player_weapon_centered = GetSpriteCenterPosition(*mega_state.player.sprite, mega_state.player.position, p_render_scale);
+ // move to left hand
+ player_weapon_centered.x -= (mega_state.player.sprite->width*p_render_scale/2.0f) - 2.0f;
+ player_weapon_centered.y += (12.0f);
+ src_rect = (Rectangle){
+ .x=0, .y=0,
+ .width = mega_state.player.active_potion->sprite->width,
+ .height = mega_state.player.active_potion->sprite->height
+ };
+
+ dest_rect = (Rectangle){.x=player_weapon_centered.x, .y=player_weapon_centered.y,
+ .width=0.4f*mega_state.render_scale*mega_state.player.active_potion->sprite->width,
+ .height=0.4f*mega_state.render_scale*mega_state.player.active_potion->sprite->height};
+
+ DrawTexturePro(*mega_state.player.active_potion->sprite, src_rect, dest_rect,
+ (Vector2){0,0}, 0, RAYWHITE);
+ Vector2 player_center = GetSpriteCenterPosition(*mega_state.player.sprite, mega_state.player.position, mega_state.render_scale);
+ DrawTextureEx(*mega_state.player.sprite, player_center, 0, mega_state.render_scale, mega_state.player.tint_color);
+
+}
+
+int GetTileIDFromWorld(Vector2 world_position)
+{
+ Vector2 grid_pos = Vector2Divide(Vector2Multiply(world_position, grid_dims), (Vector2){screenWidth, screenHeight});
+ int tile = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ return tile;
+}
+
+bool MoveCharacter(struct Character *to_move, Vector2 position)
+{
+ Vector2 new_pos = Vector2Add(to_move->position, position);
+ int tile_at_new_pos = GetTileIDFromWorld(new_pos);
+ int tile_at_curr_pos = GetTileIDFromWorld(to_move->position);
+ bool char_at_home = to_move->home_tile_id == tile_at_curr_pos ? true: false;
+ // move only if next position in home tile
+ if (tile_at_new_pos == to_move->home_tile_id || to_move->state == C_FOLLOW || char_at_home == false)
+ {
+ to_move->position = Vector2Add(to_move->position, position);
+ return true;
+ }
+ else
+ {
+ to_move->target_position = to_move->position;
+ return false;
+ }
+}
+
+AttackedEntity GetEntityToAttack(struct Character attacker)
+{
+ AttackedEntity attackee = {};
+ // check if player can be attacked
+ bool attacker_at_home_tile = GetTileIDFromWorld(attacker.position) == attacker.home_tile_id ? true: false;
+ Vector2 char_position = mega_state.player.position;
+ int char_tile_id = GetTileIDFromWorld(char_position);
+ Rectangle char_rect = mega_state.player.render_rect;
+ float min_dist = screenWidth;
+
+ if ((attacker.attackers[0].type == PLAYER || char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false)
+ && attacker.friends[0].type != PLAYER)
+ {
+ float char_dist = Vector2Distance(char_position, attacker.position);
+ if (char_dist < 200)
+ {
+ attackee.type = PLAYER;
+ if (attacker.attackers[0].type == PLAYER) return attackee;
+ }
+ }
+
+ // check if bandit can instead be attacked
+ for (int i=0; i<mega_state.bandit_arr_sz;i++)
+ {
+ Bandit bandit = mega_state.bandits[i];
+ char_tile_id = GetTileIDFromWorld(char_position);
+ char_position = bandit.position;
+ char_rect = bandit.render_rect;
+ if (bandit.state == C_DEAD) continue;
+ if ((char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false) &&
+ ((attacker.attackers[0].type == BANDIT && attacker.attackers[0].id == i) ||
+ attacker.entity_type != BANDIT))
+ {
+ float char_dist = Vector2Distance(char_position, attacker.position);
+ if (char_dist < 200 && (char_dist < min_dist || attacker.attackers[0].type == BANDIT))
+ {
+ min_dist = char_dist;
+ attackee.type = BANDIT;
+ attackee.id = bandit.entity_id;
+ if (attacker.attackers[0].type == BANDIT) return attackee;
+ }
+ }
+ }
+
+ // check if troll can instead be attacked
+ for (int i=0; i<mega_state.troll_arr_sz;i++) {
+ char_position = mega_state.trolls[i].position;
+ char_tile_id = GetTileIDFromWorld(char_position);
+ char_rect = mega_state.trolls[i].render_rect;
+ if (mega_state.trolls[i].state == C_DEAD) continue;
+ if ((char_tile_id == attacker.home_tile_id || attacker_at_home_tile == false) &&
+ ((attacker.attackers[0].type == TROLL && attacker.attackers[0].id == i) ||
+ attacker.entity_type != TROLL))
+ {
+ float char_dist = Vector2Distance(char_position, attacker.position);
+ if (char_dist < 200 && (char_dist < min_dist || attacker.attackers[0].type == TROLL))
+ {
+ min_dist = char_dist;
+ attackee.type = TROLL;
+ attackee.id = mega_state.trolls[i].entity_id;
+ if (attacker.attackers[0].type == TROLL) return attackee;
+ }
+ }
+ }
+
+ return attackee;
+}
+
+bool TrollAttackTarget(Troll *troll, AttackedEntity to_attack)
+{
+ float dist_to_target = 0;
+
+ troll->state = C_IDLE;
+ troll->ai_state = AI_NONE;
+ switch (to_attack.type)
+ {
+ case (PLAYER):
+ {
+ troll->last_enemy_position = mega_state.player.position;
+ troll->target_position = mega_state.player.position;
+ troll->target_rect = mega_state.player.render_rect;
+
+ dist_to_target = Vector2Distance(troll->target_position, troll->position);
+ troll->ai_state = AI_DETECT;
+ troll->state = C_FOLLOW;
+ } break;
+ case (BANDIT):
+ {
+ troll->last_enemy_position = mega_state.bandits[to_attack.id].position;
+ troll->target_position = mega_state.bandits[to_attack.id].position;
+ troll->target_rect = mega_state.bandits[to_attack.id].render_rect;
+
+ dist_to_target = Vector2Distance(troll->target_position, troll->position);
+ troll->ai_state = AI_DETECT;
+ troll->state = C_FOLLOW;
+ } break;
+ default:
+ break;
+ }
+ if (troll->ai_state == AI_DETECT && dist_to_target < 80)
+ {
+ troll->ai_state = AI_ATTACK;
+ }
+ if (troll->ai_state >= AI_DETECT && troll->anim_attack_state != AA_ATTACK)
+ {
+ troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position));
+ if (dist_to_target > 10.0f)
+ {
+ // @note: this actually might not even be needed
+ // move only if both are more than 10pixels apart.
+ // it becomes jarring other wise
+ MoveCharacter(troll, Vector2Scale(troll->move_dir, troll->speed_multiplier*troll->move_speed));
+ }
+ }
+ CenterRectAroundTL(&troll->render_rect, troll->position);
+ if (troll->anim_attack_state == AA_IDLE && troll->ai_state == AI_ATTACK)
+ {
+ // start attack animation
+ troll->t_anim_charge_start = GetTime();
+ troll->anim_attack_state = AA_CHARGE;
+ }
+ float charge_time = 0.0f;
+ float charge_color_delta = 0.0f;
+ if (troll->anim_attack_state == AA_CHARGE)
+ {
+ // get color based on time
+ float charge_progress = (GetTime() - troll->t_anim_charge_start) / troll->t_anim_charge_duration;
+ troll->tint_color.r = Clamp(troll->tint_color_active.r - (charge_progress*troll->tint_color_active.r), 0, 255);
+ if (charge_progress >= 1.0f)
+ {
+ troll->anim_attack_state = AA_ATTACK;
+ troll->t_anim_attack_start = GetTime();
+ }
+ }
+ else if (troll->anim_attack_state == AA_ATTACK)
+ {
+ // maximum distance to attack jump in
+ float anim_length = GetTime() - troll->t_anim_attack_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*troll->anim_attack_speed/troll->t_anim_attack_duration;
+ troll->position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, pixel_per_second_per_ft));
+ troll->tint_color = (Color){255, 128, 255, 255};
+ // get troll center position and check for collision with player
+ // now check if colliding with player
+ bool is_colliding = CheckCollisionRecs(troll->target_rect, troll->render_rect);
+ if (anim_length >= troll->t_anim_attack_duration || is_colliding)
+ {
+ troll->state = C_IDLE;
+ troll->ai_state = AI_NONE;
+ troll->anim_attack_state = AA_IDLE;
+ troll->tint_color = troll->tint_color_active;
+ if (is_colliding == true)
+ {
+ return true; // is colliding with target
+ }
+ }
+ }
+ // once troll is done moving, clear state.
+ troll->state = C_IDLE;
+ troll->ai_state = AI_NONE;
+ return false;
+}
+
+bool BanditAttackTarget(Bandit *bandit, AttackedEntity to_attack)
+{
+ float dist_to_target = 0;
+
+ bandit->state = C_IDLE;
+ bandit->ai_state = AI_NONE;
+ switch (to_attack.type)
+ {
+ case (PLAYER):
+ {
+ bandit->last_enemy_position = mega_state.player.position;
+ bandit->target_position = mega_state.player.position;
+ bandit->target_rect = mega_state.player.render_rect;
+
+ dist_to_target = Vector2Distance(bandit->target_position, bandit->position);
+ bandit->ai_state = AI_DETECT;
+ bandit->state = C_FOLLOW;
+ } break;
+ case (TROLL):
+ {
+ bandit->last_enemy_position = mega_state.trolls[to_attack.id].position;
+ bandit->target_position = mega_state.trolls[to_attack.id].position;
+ bandit->target_rect = mega_state.trolls[to_attack.id].render_rect;
+
+ dist_to_target = Vector2Distance(bandit->target_position, bandit->position);
+ bandit->ai_state = AI_DETECT;
+ bandit->state = C_FOLLOW;
+ } break;
+ default:
+ break;
+ }
+ if (bandit->ai_state == AI_DETECT && dist_to_target < 80)
+ {
+ bandit->ai_state = AI_ATTACK;
+ }
+ if (bandit->ai_state >= AI_DETECT && bandit->anim_attack_state != AA_ATTACK)
+ {
+ bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position));
+ if (dist_to_target > 10.0f)
+ {
+ // @note: this actually might not even be needed
+ // move only if both are more than 10pixels apart.
+ // it becomes jarring other wise
+ bool can_move = MoveCharacter(bandit, Vector2Scale(bandit->move_dir, bandit->speed_multiplier*bandit->move_speed));
+ }
+ }
+ CenterRectAroundTL(&bandit->render_rect, bandit->position);
+ if (bandit->anim_attack_state == AA_IDLE && bandit->ai_state == AI_ATTACK)
+ {
+ // start attack animation
+ bandit->t_anim_charge_start = GetTime();
+ bandit->anim_attack_state = AA_CHARGE;
+ }
+ float charge_time = 0.0f;
+ float charge_color_delta = 0.0f;
+ if (bandit->anim_attack_state == AA_CHARGE)
+ {
+ // get color based on time
+ float charge_progress = (GetTime() - bandit->t_anim_charge_start) / bandit->t_anim_charge_duration;
+ bandit->tint_color.r = Clamp(bandit->tint_color_active.r - (charge_progress*bandit->tint_color_active.r), 0, 255);
+ if (charge_progress >= 1.0f)
+ {
+ bandit->anim_attack_state = AA_ATTACK;
+ bandit->t_anim_attack_start = GetTime();
+ }
+ }
+ else if (bandit->anim_attack_state == AA_ATTACK)
+ {
+ // maximum distance to attack jump in
+ float anim_length = GetTime() - bandit->t_anim_attack_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*bandit->anim_attack_speed/bandit->t_anim_attack_duration;
+ bandit->position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, pixel_per_second_per_ft));
+ bandit->tint_color = (Color){255, 128, 255, 255};
+ // get bandit center position and check for collision with player
+ // now check if colliding with player
+ bool is_colliding = CheckCollisionRecs(bandit->target_rect, bandit->render_rect);
+ if (anim_length >= bandit->t_anim_attack_duration || is_colliding)
+ {
+ bandit->state = C_IDLE;
+ bandit->ai_state = AI_NONE;
+ bandit->anim_attack_state = AA_IDLE;
+ bandit->tint_color = bandit->tint_color_active;
+ if (is_colliding == true)
+ {
+ return true; // is colliding with target
+ }
+ }
+ }
+ bandit->state = C_IDLE;
+ bandit->ai_state = AI_NONE;
+ return false;
+}
+void UpdateDrawFrame(void)
+{
+ // INPUT
+ if (IsKeyPressed(KEY_ESCAPE))
+ {
+ mega_state.gameplay_state = mega_state.gameplay_state == GS_UI ? GS_GAME : GS_UI;
+ }
+ if (mega_state.gameplay_state == GS_GAME)
+ {
+ mega_state.mouse_position = GetMousePosition();
+ if (IsKeyPressed(KEY_ONE))
+ {
+ mega_state.player.active_potion = &mega_state.player.fire_potion;
+ mega_state.player.active_potion->t_potion_throw_start = 0;
+ mega_state.player.active_potion->t_potion_spread_start = 0;
+ mega_state.player.active_potion->state = PS_HELD;
+ }
+ else if (IsKeyPressed(KEY_TWO))
+ {
+ mega_state.player.active_potion = &mega_state.player.friend_potion;
+ mega_state.player.active_potion->t_potion_throw_start = 0;
+ mega_state.player.active_potion->t_potion_spread_start = 0;
+ mega_state.player.active_potion->state = PS_HELD;
+ }
+ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mega_state.player.state <= P_MOVE)
+ {
+ mega_state.player.state = P_MOVE;
+ mega_state.player.target_position = mega_state.mouse_position;
+ mega_state.player.move_dir = Vector2Normalize(Vector2Subtract(mega_state.player.target_position, mega_state.player.position));
+ }
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT))
+ {
+ // @todo: abilities
+ mega_state.player.active_potion->throw_dir = Vector2Normalize(Vector2Subtract(mega_state.mouse_position, mega_state.player.position));
+ mega_state.player.active_potion->position_start = mega_state.player.position;
+ mega_state.player.active_potion->position_curr = mega_state.player.position;
+ mega_state.player.active_potion->position_target = mega_state.mouse_position;
+ mega_state.player.active_potion->state = PS_THROWN;
+ mega_state.player.active_potion->t_potion_throw_start = GetTime();
+ }
+
+ // PROCESSING
+ // initialize values
+ for (int i = 0; i < mega_state.troll_arr_sz; i++)
+ {
+ if (mega_state.trolls[i].health <= 0)
+ {
+ mega_state.trolls[i].state = C_DEAD;
+ mega_state.trolls[i].tint_color = (Color){128, 128, 128, 128};
+ }
+ mega_state.trolls[i].speed_multiplier = 1.0f;
+ }
+ for (int i = 0; i < mega_state.bandit_arr_sz; i++)
+ {
+ if (mega_state.bandits[i].health <= 0)
+ {
+ mega_state.bandits[i].state = C_DEAD;
+ mega_state.bandits[i].tint_color = (Color){128, 128, 128, 128};
+ }
+ mega_state.bandits[i].speed_multiplier = 1.0f;
+ }
+ if (mega_state.player.health <= 0)
+ {
+ mega_state.gameplay_state = GS_OVER;
+ }
+
+ switch (mega_state.player.state)
+ {
+ case (P_MOVE):
+ {
+ // MOVE PLAYER
+ Vector2 next_position = Vector2Add(mega_state.player.position,
+ Vector2Scale(mega_state.player.move_dir, mega_state.player.move_speed));
+ // checke player collisions
+ bool is_colliding_gate = CheckCollisionRecs(mega_state.player.render_rect, mega_state.gate_render_rect);
+ if (is_colliding_gate)
+ {
+ if (mega_state.key_picked_count == 2)
+ {
+ mega_state.is_gate_open = true;
+ mega_state.active_gate_sprite = mega_state.gate_open_sprite;
+ mega_state.gameplay_state = GS_SUCCESS;
+ }
+ }
+ mega_state.player.position = next_position;
+
+ if (Vector2Distance(mega_state.player.position, mega_state.player.target_position) <= 5.0f)
+ {
+ mega_state.player.state = P_IDLE;
+ }
+ // calculate center positions
+ CenterRectAroundTL(&mega_state.player.render_rect, mega_state.player.position);
+ mega_state.player.tile_id = GetTileIDFromWorld(mega_state.player.position);
+ // check if a key can be picked up
+ for (int i=0;i<2;i++)
+ {
+ bool is_colliding = CheckCollisionRecs(mega_state.player.render_rect, mega_state.key_render_rects[i]);
+ if (is_colliding && mega_state.key_should_render[i] == 1)
+ {
+ mega_state.key_picked_count += 1;
+ mega_state.key_should_render[i] = 0;
+ }
+ }
+ } break;
+ case (P_HIT):
+ {
+ float anim_length = GetTime() - mega_state.player.t_anim_hit_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*mega_state.player.anim_hit_speed/mega_state.player.t_anim_hit_duration;
+ mega_state.player.position = Vector2Add(mega_state.player.position, Vector2Scale(mega_state.player.move_dir, pixel_per_second_per_ft));
+ mega_state.player.tint_color = (Color){255, 128, 128, 255};
+ if (anim_length >= mega_state.player.t_anim_hit_duration)
+ {
+ mega_state.player.state = P_IDLE;
+ mega_state.player.anim_hit_state = HA_IDLE;
+ mega_state.player.tint_color = (Color){255, 255, 255, 255};
+ }
+ // calculate center positions
+ CenterRectAroundTL(&mega_state.player.render_rect, mega_state.player.position);
+ mega_state.player.tile_id = GetTileIDFromWorld(mega_state.player.position);
+ } break;
+ default:
+ {}break;
+ }
+ switch (mega_state.player.active_potion->state)
+ {
+ case (PS_THROWN):
+ {
+ // play potion throw animation
+ float throw_speed = GetFrameTime() * mega_state.player.potion_throw_speed;
+ float dist_thrown = Vector2Distance(mega_state.player.active_potion->position_curr, mega_state.player.active_potion->position_start);
+ mega_state.player.active_potion->position_curr = Vector2Add(mega_state.player.active_potion->position_curr,
+ Vector2Scale(mega_state.player.active_potion->throw_dir, throw_speed));
+ float dist_to_target = Vector2Distance(mega_state.player.active_potion->position_curr, mega_state.player.active_potion->position_target);
+ if (dist_thrown > mega_state.player.potion_throw_dist || dist_to_target <= 5.0f)
+ {
+ // change potion state to potion explode
+ mega_state.player.active_potion->state = PS_SPREAD;
+ mega_state.player.active_potion->t_potion_spread_start = GetTime();
+ }
+ } break;
+ case (PS_SPREAD):
+ {
+ float spread_duration = GetTime() - mega_state.player.active_potion->t_potion_spread_start;
+ if (spread_duration >= mega_state.player.active_potion->t_potion_spread_duration)
+ {
+ mega_state.player.active_potion->state = PS_HELD;
+ }
+ // loop through enemies to check if any are being burned
+ // check troll
+ for (int i=0;i<mega_state.troll_arr_sz;i++)
+ {
+ Troll *troll = &mega_state.trolls[i];
+ if (troll->state == C_DEAD) continue;
+ float dist_to_enemy = Vector2Distance(troll->position, mega_state.player.active_potion->position_curr);
+ if (dist_to_enemy <= mega_state.player.active_potion->radius)
+ {
+ if (mega_state.player.active_potion->type == PT_ATTACK)
+ {
+ // mark troll state as being burned
+ if (troll->isburning == false)
+ {
+ troll->isburning = true;
+ troll->t_anim_burn_start = GetTime();
+ troll->attackers[0].type = PLAYER;
+ troll->attackers[0].id = 0;
+ }
+ }
+ else if (mega_state.player.active_potion->type == PT_ALLY)
+ {
+ troll->friends[0] = (FriendEntity){.type=PLAYER, .id=0};
+ }
+ troll->tint_color_active = mega_state.player.active_potion->tint_color_effect;
+ troll->tint_color = troll->tint_color_active;
+ troll->speed_multiplier = mega_state.player.active_potion->speed_multiplier;
+ }
+ else
+ {
+ troll->speed_multiplier = 1.0f;
+ }
+ if (mega_state.player.active_potion->state != PS_SPREAD)
+ {
+ troll->tint_color_active = troll->tint_color_base;
+ troll->tint_color = troll->tint_color_active;
+ troll->isburning = false;
+ // remove player/entity as friend
+ troll->friends[0].type = 0;
+ troll->friends[0].id = 0;
+ }
+ }
+ // check bandit
+ for (int i=0;i<mega_state.bandit_arr_sz;i++)
+ {
+ Bandit *bandit = &mega_state.bandits[i];
+ if (bandit->state == C_DEAD) continue;
+ float dist_to_enemy = Vector2Distance(bandit->position, mega_state.player.active_potion->position_curr);
+ if (dist_to_enemy <= mega_state.player.active_potion->radius)
+ {
+ if (mega_state.player.active_potion->type == PT_ATTACK)
+ {
+ // mark bandit state as being burned
+ if (bandit->isburning == false)
+ {
+ bandit->isburning = true;
+ bandit->t_anim_burn_start = GetTime();
+ bandit->attackers[0].type = PLAYER;
+ bandit->attackers[0].id = 0;
+ }
+ }
+ else if (mega_state.player.active_potion->type == PT_ALLY)
+ {
+ bandit->friends[0] = (FriendEntity){.type=PLAYER, .id=0};
+ }
+ bandit->tint_color_active = mega_state.player.active_potion->tint_color_effect;
+ bandit->tint_color = bandit->tint_color_active;
+ bandit->speed_multiplier = mega_state.player.active_potion->speed_multiplier;
+ }
+ else
+ {
+ bandit->speed_multiplier = 1.0f;
+ }
+ if (mega_state.player.active_potion->state != PS_SPREAD)
+ {
+ bandit->tint_color_active = bandit->tint_color_base;
+ bandit->tint_color = bandit->tint_color_active;
+ bandit->isburning = false;
+ // remove player/entity as friend
+ bandit->friends[0].type = 0;
+ bandit->friends[0].id = 0;
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+
+ // ================= TROLL DETECTION LOOP AND STUFF =======================
+ for (int i=0;i<mega_state.troll_arr_sz;i++)
+ {
+ Troll *troll = &mega_state.trolls[i];
+ if (troll->state == C_HIT)
+ {
+ float anim_length = GetTime() - troll->t_anim_hit_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*troll->anim_hit_speed/troll->t_anim_hit_duration;
+ troll->position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, pixel_per_second_per_ft));
+ troll->tint_color = (Color){255, 128, 128, 255};
+ if (anim_length >= troll->t_anim_hit_duration)
+ {
+ troll->state = C_IDLE;
+ troll->anim_hit_state = HA_IDLE;
+ troll->tint_color = (Color){255, 255, 255, 255};
+ }
+ // calculate center positions
+ CenterRectAroundTL(&troll->render_rect, troll->position);
+ troll->tile_id = GetTileIDFromWorld(troll->position);
+ }
+ else if (troll->state != C_DEAD)
+ {
+ if (troll->isburning == true)
+ {
+ int anim_length_ms = 1000*(int)(GetTime() - troll->t_anim_burn_start);
+ if (anim_length_ms%500 == 0 && anim_length_ms > 0)
+ {
+ // decrement health every 500ms
+ troll->health -= 8;
+ troll->t_anim_burn_start = GetTime();
+ }
+ }
+ troll->speed_multiplier *= troll->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f;
+ AttackedEntity tentity = GetEntityToAttack(*troll);
+ if (tentity.type > NONE || troll->anim_attack_state >= AA_CHARGE)
+ {
+ bool entity_hit = TrollAttackTarget(troll, tentity);
+ if (entity_hit)
+ {
+ switch (tentity.type)
+ {
+ case (PLAYER):
+ {
+ mega_state.player.anim_hit_state = HA_PLAY;
+ mega_state.player.t_anim_hit_start = GetTime();
+ mega_state.player.move_dir = troll->move_dir;
+ mega_state.player.state = P_HIT; // disable walk check, stuff been thrown off
+ mega_state.player.health -= 8;
+ } break;
+ case (TROLL):
+ {
+ // trigger troll get hit animation
+ } break;
+ case (BANDIT):
+ {
+ // trigger bandit get hit animation
+ AggroEntity attacker = {.type=TROLL, .id=0};
+ mega_state.bandits[tentity.id].attackers[0] = attacker;
+ mega_state.bandits[tentity.id].anim_hit_state = HA_PLAY;
+ mega_state.bandits[tentity.id].t_anim_hit_start = GetTime();
+ mega_state.bandits[tentity.id].move_dir = troll->move_dir;
+ mega_state.bandits[tentity.id].state = C_HIT;
+ mega_state.bandits[tentity.id].health -= 10;
+ } break;
+ default:
+ // do nothing
+ }
+ }
+ }
+ else
+ {
+ // get entity to follow
+ FriendEntity follow = {};
+ if (troll->friends[0].type == PLAYER)
+ {
+ float char_dist = Vector2Distance(mega_state.player.position, troll->position);
+ if (char_dist < 200)
+ {
+ follow.type = PLAYER;
+ }
+ }
+ if (troll->state == C_IDLE && troll->ai_state == AI_NONE)
+ {
+ troll->t_idle_start = GetTime();
+ troll->move_dir = Vector2Zero();
+ troll->ai_state = AI_IDLE;
+ troll->to_core_pos = troll->to_core_pos == true ? false: true;
+ }
+ double idle_time = GetTime() - troll->t_idle_start;
+ if (idle_time >= troll->t_idle_duration && troll->ai_state == AI_IDLE)
+ {
+ if (troll->to_core_pos == false)
+ {
+ // calculate target position from patrol direction
+ float max_dist = 200.0f;
+ Vector2 move_dir;
+ switch(troll->patrol_dir)
+ {
+ case (PATROL_TOP):
+ {
+ move_dir = (Vector2){.x=0.0f, .y=-1.0f};
+ } break;
+ case (PATROL_LEFT):
+ {
+ move_dir = (Vector2){.x=-1.0f, .y=0.0f};
+ } break;
+ case (PATROL_DOWN):
+ {
+ move_dir = (Vector2){.x=0.0f, .y=1.0f};
+ } break;
+ case (PATROL_RIGHT):
+ {
+ move_dir = (Vector2){.x=1.0f, .y=0.0f};
+ } break;
+ };
+ troll->move_dir = move_dir;
+ troll->target_position = Vector2Add(troll->position, Vector2Scale(troll->move_dir, max_dist));
+ }
+ else
+ {
+ troll->target_position = troll->core_position;
+ troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position));
+ }
+ troll->state = C_PATROL;
+ troll->ai_state = AI_PATROL;
+ }
+ // follow player / friend
+ if (follow.type == PLAYER)
+ {
+ troll->state = C_FOLLOW;
+ troll->ai_state = AI_NONE;
+ troll->target_position = mega_state.player.position;
+ troll->move_dir = Vector2Normalize(Vector2Subtract(troll->target_position, troll->position));
+ }
+ float dist_to_target = Vector2Distance(troll->target_position, troll->position);
+ if (dist_to_target > 50.0f)
+ {
+ MoveCharacter(troll, Vector2Scale(troll->move_dir, troll->move_speed));
+ }
+ else if (troll->state == C_PATROL && troll->ai_state == AI_PATROL)
+ {
+ // we have reached target position
+ // in case we were patrolling, increment the direction to move to
+ // @note: maybe do this when first changing state from idle after character has been idle for hours
+ if (troll->to_core_pos == false)
+ {
+ troll->patrol_dir = (troll->patrol_dir + 1)%PATROL_END;
+ }
+ troll->state = C_IDLE;
+ troll->ai_state = AI_NONE;
+ }
+ CenterRectAroundTL(&troll->render_rect, troll->position);
+ }
+ }
+ }
+ // ========== BANDIT DETECTION AND STUFF
+ for (int i=0;i<mega_state.bandit_arr_sz;i++)
+ {
+ Bandit *bandit = &mega_state.bandits[i];
+ if (bandit->state == C_HIT)
+ {
+ float anim_length = GetTime() - bandit->t_anim_hit_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*bandit->anim_hit_speed/bandit->t_anim_hit_duration;
+ bandit->position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, pixel_per_second_per_ft));
+ bandit->tint_color = (Color){255, 128, 128, 255};
+ if (anim_length >= bandit->t_anim_hit_duration)
+ {
+ bandit->state = P_IDLE;
+ bandit->anim_hit_state = HA_IDLE;
+ bandit->tint_color = (Color){255, 255, 255, 255};
+ }
+ // calculate center positions
+ CenterRectAroundTL(&bandit->render_rect, bandit->position);
+ bandit->tile_id = GetTileIDFromWorld(bandit->position);
+ }
+ else if (bandit->state != C_DEAD)
+ {
+ if (bandit->isburning == true)
+ {
+ int anim_length_ms = 1000*(int)(GetTime() - bandit->t_anim_burn_start);
+ if (anim_length_ms%500 == 0 && anim_length_ms > 0)
+ {
+ // decrement health every 500ms
+ bandit->health -= 8;
+ bandit->t_anim_burn_start = GetTime();
+ }
+ }
+ bandit->speed_multiplier *= bandit->anim_attack_state == AA_CHARGE ? 0.2f : 1.0f;
+ AttackedEntity tentity = GetEntityToAttack(*bandit);
+ if (tentity.type > NONE || bandit->anim_attack_state >= AA_CHARGE)
+ {
+ bool entity_hit = BanditAttackTarget(bandit, tentity);
+ if (tentity.type > NONE)
+ {
+ if (entity_hit)
+ {
+ switch (tentity.type)
+ {
+ case (PLAYER):
+ {
+ mega_state.player.anim_hit_state = HA_PLAY;
+ mega_state.player.t_anim_hit_start = GetTime();
+ mega_state.player.move_dir = bandit->move_dir;
+ mega_state.player.state = P_HIT; // disable walk check, stuff been thrown off
+ mega_state.player.health -= 5;
+ } break;
+ case (TROLL):
+ {
+ // trigger troll get hit animation
+ AggroEntity attacker = {.type=BANDIT, .id=0};
+ mega_state.trolls[tentity.id].attackers[0] = attacker;
+ mega_state.trolls[tentity.id].anim_hit_state = HA_PLAY;
+ mega_state.trolls[tentity.id].t_anim_hit_start = GetTime();
+ mega_state.trolls[tentity.id].move_dir = bandit->move_dir;
+ mega_state.trolls[tentity.id].state = C_HIT;
+ mega_state.trolls[tentity.id].health -= 5;
+ } break;
+ case (BANDIT):
+ {
+ // trigger bandit get hit animation
+ } break;
+ default:
+ // do nothing
+ }
+ }
+ }
+ }
+ else
+ {
+ if (bandit->state == C_IDLE && bandit->ai_state == AI_NONE)
+ {
+ bandit->t_idle_start = GetTime();
+ bandit->move_dir = Vector2Zero();
+ bandit->ai_state = AI_IDLE;
+ bandit->to_core_pos = bandit->to_core_pos == true ? false: true;
+ }
+ double idle_time = GetTime() - bandit->t_idle_start;
+ if (idle_time >= bandit->t_idle_duration && bandit->ai_state == AI_IDLE)
+ {
+ if (bandit->to_core_pos == false)
+ {
+ // calculate target position from patrol direction
+ float max_dist = 200.0f;
+ Vector2 move_dir;
+ switch(bandit->patrol_dir)
+ {
+ case (PATROL_TOP):
+ {
+ move_dir = (Vector2){.x=0.0f, .y=-1.0f};
+ } break;
+ case (PATROL_LEFT):
+ {
+ move_dir = (Vector2){.x=-1.0f, .y=0.0f};
+ } break;
+ case (PATROL_DOWN):
+ {
+ move_dir = (Vector2){.x=0.0f, .y=1.0f};
+ } break;
+ case (PATROL_RIGHT):
+ {
+ move_dir = (Vector2){.x=1.0f, .y=0.0f};
+ } break;
+ };
+ bandit->move_dir = move_dir;
+ bandit->target_position = Vector2Add(bandit->position, Vector2Scale(bandit->move_dir, max_dist));
+ }
+ else
+ {
+ bandit->target_position = bandit->core_position;
+ bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position));
+ }
+ bandit->state = C_PATROL;
+ bandit->ai_state = AI_PATROL;
+ }
+ // get entity to follow
+ FriendEntity follow = {};
+ if (bandit->friends[0].type == PLAYER)
+ {
+ float char_dist = Vector2Distance(mega_state.player.position, bandit->position);
+ if (char_dist < 200)
+ {
+ follow.type = PLAYER;
+ }
+ }
+ // follow player / friend
+ if (follow.type == PLAYER)
+ {
+ bandit->target_position = mega_state.player.position;
+ bandit->state = C_FOLLOW;
+ bandit->ai_state = AI_NONE;
+ bandit->move_dir = Vector2Normalize(Vector2Subtract(bandit->target_position, bandit->position));
+ }
+ float dist_to_target = Vector2Distance(bandit->target_position, bandit->position);
+ if (dist_to_target > 50.0f)
+ {
+ MoveCharacter(bandit, Vector2Scale(bandit->move_dir, bandit->move_speed));
+ }
+ else if (bandit->state == C_PATROL && bandit->ai_state == AI_PATROL)
+ {
+ if (bandit->to_core_pos == false)
+ {
+ bandit->patrol_dir = (bandit->patrol_dir + 1)%PATROL_END;
+ }
+ bandit->state = C_IDLE;
+ bandit->ai_state = AI_NONE;
+ }
+ CenterRectAroundTL(&bandit->render_rect, bandit->position);
+
+ }
+ }
+ }
+
+ // RENDERING
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ DrawMapFloorTiles();
+ DrawFloorCoverTiles();
+ DrawCharacters();
+
+ // do text rendering at the end
+ // draw mouse position for debugging
+ if (mega_state.player.state == P_HIT)
+ {
+ DrawText("player hit", 500, 20, 20, GREEN);
+ }
+ DrawText(TextFormat("Keys: %d/2", mega_state.key_picked_count), 1100, 20, 20, GREEN);
+ EndDrawing();
+ }
+ else if (mega_state.gameplay_state == GS_UI)
+ {
+ BeginDrawing();
+ ClearBackground(BLACK);
+ DrawText("Blaidville", screenWidth/2 - 100, 40, 28, WHITE);
+ DrawText("Instructions", screenWidth/2 - 140, 100, 22, WHITE);
+ DrawText("Find keys to unlock the gate", screenWidth/2 - 140, 140, 18, WHITE);
+ DrawText("Unlock the gate to escape", screenWidth/2 - 140, 160, 18, WHITE);
+ DrawText("Controls", screenWidth/2 - 140, 220, 22, WHITE);
+ DrawText("1 -> Potion 1", screenWidth/2 - 140, 260, 18, WHITE);
+ DrawText("2 -> Potion 2", screenWidth/2 - 140, 280, 18, WHITE);
+ DrawText("right mouse click -> throw potion", screenWidth/2 - 140, 300, 18, WHITE);
+ DrawText("left mouse click -> move", screenWidth/2 - 140, 320, 18, WHITE);
+ DrawText("Escape to play or pause the game", screenWidth/2 - 140, 340, 18, WHITE);
+ DrawText("Restart the game to reset state", screenWidth/2 - 140, 360, 18, WHITE);
+ EndDrawing();
+ }
+ else if (mega_state.gameplay_state == GS_SUCCESS)
+ {
+ BeginDrawing();
+ ClearBackground(BLACK);
+ DrawText("Objective Complete", screenWidth/2 - 100, 40, 28, GREEN);
+ EndDrawing();
+ }
+ else if (mega_state.gameplay_state == GS_OVER)
+ {
+ BeginDrawing();
+ ClearBackground(BLACK);
+ DrawText("You Died", screenWidth/2 - 100, 40, 28, RED);
+ EndDrawing();
+ }
+}
+
diff --git a/src/game_v0.c b/src/game_v0.c
new file mode 100644
index 0000000..62141f6
--- /dev/null
+++ b/src/game_v0.c
@@ -0,0 +1,512 @@
+#include "raylib.h"
+#include "raymath.h"
+
+#if defined(PLATFORM_WEB)
+ #include <emscripten/emscripten.h>
+#endif
+
+typedef enum {
+ O = 0, // nothing
+ //P = 1, // player
+ t = 2, // tree
+ T = 3, // troll area
+ B = 4, // bandit area
+ T_B = 5, // troll base
+ B_B = 6, // bandit base
+ //G = 7, // gate (exit)
+} MapSymbol;
+
+typedef enum {
+ AA_IDLE = 0,
+ AA_CHARGE = 1,
+ AA_ATTACK = 2,
+} AttackAnimState;
+
+typedef enum {
+ T_IDLE = 0,
+ T_FOLLOW = 1,
+ T_AGGRO = 2,
+} TrollState;
+
+typedef enum {
+ HA_IDLE = 0,
+ HA_PLAY = 1,
+} HitAnimState;
+
+typedef struct ScuffString {
+ char *buffer;
+ int len;
+ int capacity;
+} String;
+
+typedef struct State {
+ // how much scaling to apply from original asset resolution
+ float render_scale;
+ // game positions
+ bool is_player_hit;
+ Vector2 pixels_per_gridbox;
+ Vector2 mouse_position;
+ Vector2 player_position;
+ Color player_tint;
+ // @note: normally when we place a texture, the coordinates
+ // are the top left coordinates. This will cause a disconnect
+ // when moving the player with a point and click motion.
+ // To fix that we calculate a new position so the player_coordinates
+ // are in the center. This will make it so when the player clicks on the
+ // ground somewhere, the character sprite will be properly centered on that point.
+ Rectangle player_render_rect;
+ Vector2 player_position_target;
+
+ Vector2 bandit_position;
+ // troll state
+ int troll_state;
+ float troll_move_delta;
+ Vector2 troll_position;
+ Vector2 troll_last_player_position;
+ Vector2 troll_move_dir;
+ Vector2 troll_position_target;
+ Rectangle troll_render_rect;
+ Color troll_tint_base;
+ Color troll_tint;
+ // - troll attack
+ bool troll_anim_attack_play;
+ int troll_attack_anime_state;
+ double t_troll_charge_anim_start;
+ float t_troll_charge_anim_duration;
+ float t_troll_anim_freq;
+ float troll_attack_offset_max;
+ float t_troll_attack_anim_start;
+ float t_troll_attack_anim_duration;
+
+ // player movement
+ bool is_player_move;
+ Vector2 player_move_dir;
+ // @todo
+ // direction player is being hit from, incorporate that into the gameplay code
+ // For now, just update the player_move_dir to the player_hit_dir
+ // Vector2 player_hit_dir;
+ float player_move_delta; // amount player will move
+ // target position player will move to
+ // 1. the player can move intentionally, in which case carry forward from player position target
+ // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET
+ int player_hit_anim_state;
+ float player_hit_anim_speed;
+ float t_player_hit_anim_duration;
+ float t_player_hit_anim_start;
+ Vector2 player_move_target;
+
+ // game sprites
+ Texture2D *grass_sprite;
+ Texture2D *player_sprite;
+
+ Texture2D *troll_sprite;
+ Texture2D *troll_weapon_sprite;
+ Texture2D *bandit_sprite;
+ Texture2D *bandit_weapon_sprite;
+
+ Texture2D *troll_area_floor_sprite;
+ Texture2D *bandit_area_floor_sprite;
+
+ Texture2D *troll_base_sprite;
+ Texture2D *bandit_base_sprite;
+
+ String *action_log;
+} State;
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+const int screenWidth = 1280;
+const int screenHeight = 1024;
+State mega_state = {};
+// layer ground
+int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O},
+ {O, O, O, O, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T},
+ {B, B, B, B, T, T, T, T}};
+// vegetation grid
+int floor_cover_grid_map[8][8] = {{O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, T_B},
+ {O, B_B, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O},
+ {O, O, O, O, O, O, O, O}};
+Vector2 grid_dims = {8, 8};
+//----------------------------------------------------------------------------------
+// Module functions declaration
+//----------------------------------------------------------------------------------
+void UpdateDrawFrame(); // Update and Draw one frame
+
+int main(void)
+{
+ InitWindow(screenWidth, screenHeight, "raylib browser example");
+ SetTargetFPS(60);
+ Texture2D grass_sprite = LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png");
+ Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png");
+ Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png");
+ Texture2D bandit_sprite = LoadTexture("./assets/monster/unique/maurice_new.png");
+ Texture2D troll_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png");
+ Texture2D bandit_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_east_new.png");
+ Texture2D bandit_base_sprite = LoadTexture("./assets/dungeon/floor/hive_3.png");
+ Texture2D troll_base_sprite = LoadTexture("./assets/dungeon/floor/dirt_2_old.png");
+ Texture2D troll_weapon_sprite = LoadTexture("./assets/item/weapon/club_old.png");
+
+ mega_state.render_scale = 2.0f;
+ mega_state.grass_sprite = &grass_sprite;
+ mega_state.player_sprite = &player_sprite;
+ mega_state.troll_sprite = &troll_sprite;
+ mega_state.bandit_sprite = &bandit_sprite;
+ mega_state.troll_area_floor_sprite = &troll_area_floor_sprite;
+ mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite;
+ mega_state.bandit_base_sprite = &bandit_base_sprite;
+ mega_state.troll_base_sprite = &troll_base_sprite;
+ mega_state.troll_weapon_sprite = &troll_weapon_sprite;
+ mega_state.player_position = (Vector2){50.0f, 50.0f};
+ mega_state.player_hit_anim_speed = 50.0f; // pixels per second;
+ mega_state.t_player_hit_anim_duration = 0.2f;
+ mega_state.player_render_rect = (Rectangle){.x=0,.y=0,.width=player_sprite.width,.height=player_sprite.height*mega_state.render_scale};
+ mega_state.player_move_delta = 5.0f;
+ mega_state.pixels_per_gridbox = (Vector2){screenWidth/grid_dims.x, screenHeight/grid_dims.y};
+ mega_state.player_tint = (Color){255, 255, 255, 255};
+ mega_state.troll_state = T_IDLE;
+
+ mega_state.bandit_position = (Vector2){64.0f, 800.0f};
+ mega_state.troll_position = (Vector2){1024.0f, 600.0f};
+ mega_state.troll_move_delta = 2.0f;
+ mega_state.t_troll_charge_anim_duration = 1.0f;
+ mega_state.troll_tint_base = (Color){255, 255, 255, 255};
+ mega_state.troll_tint = mega_state.troll_tint_base;
+ mega_state.t_troll_attack_anim_duration = 0.4f;
+ mega_state.troll_attack_offset_max = 100.0f;
+ mega_state.troll_render_rect = (Rectangle){.x=0,.y=0,.width=troll_sprite.width*mega_state.render_scale,.height=troll_sprite.height*mega_state.render_scale};
+
+ char *str_buffer = (char*)MemAlloc(512*sizeof(char));
+ mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, .len=0};
+
+ #if defined(PLATFORM_WEB)
+ emscripten_set_main_loop(UpdateDrawFrame, 0, 1);
+ #else
+ SetTargetFPS(60);
+
+ while(!WindowShouldClose())
+ {
+ UpdateDrawFrame();
+ }
+ #endif
+
+ CloseWindow();
+
+ return 0;
+}
+
+void DrawMapFloorTiles(float scale)
+{
+ // @note: testing this, might need lighting
+ Color BlackTint = {128,128,128,255};
+ Texture2D grass = *mega_state.grass_sprite;
+ float winc = (float)grass.width * scale;
+ float hinc = (float)grass.height * scale;
+ Vector2 grid_pos = {0, 0};
+ for (int ypos=0; ypos<screenHeight; ypos+=hinc)
+ {
+ grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y);
+ for (int xpos=0; xpos<screenWidth; xpos+=winc)
+ {
+ grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x);
+ Vector2 pixel_pos = {xpos, ypos};
+ int grid_ele = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ Texture2D render_tex;
+ switch (grid_ele) {
+ case (O):
+ {
+ render_tex = *mega_state.grass_sprite;
+ } break;
+ case (B):
+ {
+ render_tex = *mega_state.bandit_area_floor_sprite;
+ } break;
+ case (T):
+ {
+ render_tex = *mega_state.troll_area_floor_sprite;
+ } break;
+ default:
+ {
+ } break;
+ }
+ DrawTextureEx(render_tex, pixel_pos, 0, scale, BlackTint);
+ }
+ }
+}
+
+void DrawFloorCoverTiles(float scale)
+{
+ // @note: testing this, might need lighting
+ Color BlackTint = {128,128,128,255};
+ Texture2D null_tex = *mega_state.player_sprite;
+ float winc = (float)null_tex.width * scale;
+ float hinc = (float)null_tex.height * scale;
+
+ Vector2 grid_pos = {0, 0};
+ for (int ypos=0; ypos<screenHeight; ypos+=hinc)
+ {
+ grid_pos.y = ((float)ypos) / ((float)mega_state.pixels_per_gridbox.y);
+ for (int xpos=0; xpos<screenWidth; xpos+=winc)
+ {
+ grid_pos.x = ((float)xpos) / ((float)mega_state.pixels_per_gridbox.x);
+ Vector2 pixel_pos = {xpos, ypos};
+ int grid_ele = floor_cover_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ Texture2D render_tex;
+ switch (grid_ele) {
+ case (B_B):
+ {
+ render_tex = *mega_state.bandit_base_sprite;
+ } break;
+ case (T_B):
+ {
+ render_tex = *mega_state.troll_base_sprite;
+ } break;
+ default:
+ {
+ render_tex = null_tex;
+ } continue;
+ }
+ DrawTextureEx(render_tex, pixel_pos, 0, scale, BlackTint);
+ }
+ }
+}
+
+Vector2 GetSpriteCenterPosition(Texture2D sprite, Vector2 position, float render_scale)
+{
+ float centered_x = position.x - ((float)sprite.width)*render_scale/2.0f;
+ float centered_y = position.y - ((float)sprite.height)*render_scale/2.0f;
+
+ return (Vector2){.x = centered_x, .y = centered_y};
+}
+
+Vector2 GetRenderCenterFromTL(Rectangle render_rect, Vector2 position)
+{
+ float centered_x = position.x - ((float)render_rect.width)/2.0f;
+ float centered_y = position.y - ((float)render_rect.height)/2.0f;
+
+ return (Vector2){.x = centered_x, .y = centered_y};
+}
+
+void DrawCharacters(float scale)
+{
+ Rectangle src_rect, dest_rect;
+ // draw Troll
+ // - troll weapon
+ Vector2 troll_weapon_centered = GetSpriteCenterPosition(*mega_state.troll_weapon_sprite, mega_state.troll_position, 1.0f);
+ // move to left hand
+ troll_weapon_centered.x -= mega_state.troll_sprite->width;
+ src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.troll_weapon_sprite->width, .height=mega_state.troll_weapon_sprite->height};
+ dest_rect = (Rectangle){.x=troll_weapon_centered.x, .y=troll_weapon_centered.y,
+ .width=0.5f*scale*mega_state.troll_weapon_sprite->width,
+ .height=0.5f*scale*mega_state.troll_weapon_sprite->height};
+ DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect,
+ (Vector2){0,0}, 0, RAYWHITE);
+ // - troll sprite
+ DrawTextureEx(*mega_state.troll_sprite, (Vector2){mega_state.troll_render_rect.x, mega_state.troll_render_rect.y}, 0, scale, mega_state.troll_tint);
+ // draw collision box
+ DrawRectangleLines(mega_state.troll_render_rect.x, mega_state.troll_render_rect.y, mega_state.troll_render_rect.width, mega_state.troll_render_rect.height, RED);
+
+ // draw Bandit
+ Vector2 bandit_center = GetSpriteCenterPosition(*mega_state.bandit_sprite, mega_state.bandit_position, scale);
+ src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.bandit_sprite->width, .height=mega_state.bandit_sprite->height};
+ dest_rect = (Rectangle){.x=bandit_center.x, .y=bandit_center.y,
+ .width=scale*mega_state.bandit_sprite->width,
+ .height=scale*mega_state.bandit_sprite->height};
+ DrawTexturePro(*mega_state.bandit_sprite, src_rect, dest_rect,
+ (Vector2){0,0}, 0, (Color){255, 128, 128, 255});
+ // draw player
+ Vector2 player_center = GetSpriteCenterPosition(*mega_state.player_sprite, mega_state.player_position, scale);
+ DrawTextureEx(*mega_state.player_sprite, player_center, 0, scale, mega_state.player_tint);
+ //DrawTextureEx(*mega_state.player_sprite, (Vector2){mega_state.player_render_rect.x, mega_state.player_render_rect.y}, 0, scale, RAYWHITE);
+ DrawRectangleLines(mega_state.player_render_rect.x, mega_state.player_render_rect.y, mega_state.player_render_rect.width, mega_state.player_render_rect.height, RED);
+}
+
+void UpdateDrawFrame(void)
+{
+ BeginDrawing();
+ {
+ // INPUT
+ mega_state.mouse_position = GetMousePosition();
+ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mega_state.player_hit_anim_state != HA_PLAY)
+ {
+ mega_state.is_player_move = true;
+ mega_state.player_position_target = mega_state.mouse_position;
+ mega_state.player_move_dir = Vector2Normalize(Vector2Subtract(mega_state.player_position_target, mega_state.player_position));
+ }
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT))
+ {
+ // @debug: cleaning up troll collision state
+ mega_state.troll_attack_anime_state = AA_IDLE;
+ mega_state.troll_tint = WHITE;
+ mega_state.is_player_hit = false;
+ }
+
+ // PROCESSING
+ if (mega_state.player_hit_anim_state == HA_PLAY)
+ {
+ float anim_length = GetTime() - mega_state.t_player_hit_anim_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*mega_state.player_hit_anim_speed/mega_state.t_player_hit_anim_duration;
+ mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, pixel_per_second_per_ft));
+ mega_state.player_tint = (Color){255, 128, 128, 255};
+ if (anim_length >= mega_state.t_player_hit_anim_duration)
+ {
+ mega_state.player_hit_anim_state = HA_IDLE;
+ mega_state.player_tint = (Color){255, 255, 255, 255};
+ }
+ }
+ if (mega_state.is_player_move)
+ {
+ mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, mega_state.player_move_delta));
+ if (Vector2Distance(mega_state.player_position, mega_state.player_position_target) <= 5.0f)
+ {
+ mega_state.is_player_move = false;
+ }
+ }
+ // calculate center positions
+ Vector2 player_centered = GetRenderCenterFromTL(mega_state.player_render_rect, mega_state.player_position);
+ mega_state.player_render_rect.x = player_centered.x;
+ mega_state.player_render_rect.y = player_centered.y;
+
+ // process enemies detecting the player
+ // troll
+ // get player location on map
+ // map_world_to_grid
+ Vector2 grid_pos = Vector2Divide(Vector2Multiply(mega_state.player_position, grid_dims), (Vector2){screenWidth, screenHeight});
+ int tile_at_player = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x];
+ bool in_troll_area = true;
+ bool troll_persue_player = true;
+ if (tile_at_player != T)
+ {
+ in_troll_area = false;
+ if (mega_state.troll_state != T_AGGRO)
+ {
+ mega_state.troll_attack_anime_state = AA_IDLE;
+ troll_persue_player = false;
+ }
+ }
+
+ float player_troll_dist = Vector2Distance(mega_state.player_position, mega_state.troll_position);
+ bool troll_detect = false;
+ bool troll_attack_start = false;
+ float slow_multiplier = mega_state.troll_attack_anime_state == AA_CHARGE ? 0.2f : 1.0f;
+ if (player_troll_dist < 200 && troll_persue_player)
+ {
+ troll_detect = true;
+ mega_state.troll_state = T_FOLLOW;
+ if (player_troll_dist < 80 && mega_state.troll_attack_anime_state == AA_IDLE)
+ {
+ troll_attack_start = true;
+ }
+ }
+ if (troll_detect && mega_state.troll_attack_anime_state != AA_ATTACK)
+ {
+ mega_state.troll_last_player_position = mega_state.player_position;
+ mega_state.troll_position_target = mega_state.troll_last_player_position;
+ mega_state.troll_move_dir = Vector2Normalize(Vector2Subtract(mega_state.troll_position_target, mega_state.troll_position));
+ if (player_troll_dist > 10.0f)
+ {
+ // @note: this actually might not even be needed
+ // move only if both are more than 10pixels apart.
+ // it becomes jarring other wise
+ mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, slow_multiplier*mega_state.troll_move_delta));
+ }
+ }
+ Vector2 troll_centered = GetSpriteCenterPosition(*mega_state.troll_sprite, mega_state.troll_position, mega_state.render_scale);
+ mega_state.troll_render_rect.x = troll_centered.x;
+ mega_state.troll_render_rect.y = troll_centered.y;
+ if (troll_attack_start)
+ {
+ // start attack animation
+ mega_state.troll_anim_attack_play = true;
+ mega_state.t_troll_charge_anim_start = GetTime();
+ mega_state.troll_attack_anime_state = AA_CHARGE;
+ }
+ float charge_time = 0.0f;
+ float charge_color_delta = 0.0f;
+ if (mega_state.troll_attack_anime_state == AA_CHARGE)
+ {
+ // get color based on time
+ float charge_progress = (GetTime() - mega_state.t_troll_charge_anim_start) / mega_state.t_troll_charge_anim_duration;
+ mega_state.troll_tint.r = Clamp(mega_state.troll_tint_base.r - (charge_progress*mega_state.troll_tint_base.r), 0, 255);
+ if (charge_progress >= 1.0f)
+ {
+ mega_state.troll_attack_anime_state = AA_ATTACK;
+ mega_state.t_troll_attack_anim_start = GetTime();
+ }
+ }
+ else if (mega_state.troll_attack_anime_state == AA_ATTACK)
+ {
+ // maximum distance to attack jump in
+ // mega_state.troll_attack_offset_max = 10.0f;
+ // mega_state.troll_attack_anim_duration = 1.0f;
+ float anim_length = GetTime() - mega_state.t_troll_attack_anim_start;
+ float ft = GetFrameTime();
+ float pixel_per_second_per_ft = ft*mega_state.troll_attack_offset_max/mega_state.t_troll_attack_anim_duration;
+ mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, pixel_per_second_per_ft));
+ mega_state.troll_tint = (Color){255, 128, 255, 255};
+ // get troll center position and check for collision with player
+ // now check if colliding with player
+ bool is_colliding = CheckCollisionRecs(mega_state.player_render_rect, mega_state.troll_render_rect);
+ if (is_colliding)
+ {
+ mega_state.is_player_hit = true;
+ }
+ if (anim_length >= mega_state.t_troll_attack_anim_duration || is_colliding)
+ {
+ mega_state.troll_attack_anime_state = AA_IDLE;
+ mega_state.troll_tint = mega_state.troll_tint_base;
+ if (is_colliding)
+ {
+ // make player get hit
+ mega_state.player_hit_anim_state = HA_PLAY;
+ mega_state.t_player_hit_anim_start = GetTime();
+ mega_state.player_move_dir = mega_state.troll_move_dir;
+ mega_state.is_player_move = false; // disable walk check, stuff been thrown off
+ }
+ }
+ }
+
+ float player_bandit_delta = Vector2Distance(mega_state.player_position, mega_state.bandit_position);
+ bool bandit_detect = false;
+ if (player_bandit_delta < 200)
+ {
+ bandit_detect = true;
+ }
+ // RENDERING
+ ClearBackground(RAYWHITE);
+ DrawMapFloorTiles(2.0f);
+ DrawFloorCoverTiles(2.0f);
+ DrawCharacters(2.0f);
+
+ // do text rendering at the end
+ // draw mouse position for debugging
+ if (mega_state.is_player_hit)
+ {
+ DrawText("player hit", 500, 20, 20, GREEN);
+ }
+ if (troll_detect)
+ {
+ DrawText("Detecting", mega_state.troll_position.x, mega_state.troll_position.y + mega_state.troll_sprite->height + 20, 20, GREEN);
+ }
+ if (bandit_detect)
+ {
+ DrawText("Detecting", mega_state.bandit_position.x, mega_state.bandit_position.y + mega_state.bandit_sprite->height + 20, 20, GREEN);
+ }
+ DrawText(TextFormat("FPS: %d", GetFPS()), 1200, 20, 20, GREEN);
+ DrawText(TextFormat("Player Tile: %d", tile_at_player), 20, 20, 20, GREEN);
+ }
+ EndDrawing();
+}
+
+