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