#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(); } }