From 949c829dca2ec798009b42bfb1d7d95dd6f1859f Mon Sep 17 00:00:00 2001 From: doyle Date: Sun, 17 Sep 2023 00:37:26 +1000 Subject: [PATCH] fp: Add stalking of the hero using A* --- External/tely | 2 +- feely_pona.cpp | 234 ++++++++++++++++++++++++++++++++++--------------- 2 files changed, 165 insertions(+), 71 deletions(-) diff --git a/External/tely b/External/tely index 39af5c5..a807996 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 39af5c5627404982ec9200b2155f0b5c8874bf8f +Subproject commit a807996225655d43d8c7265f82913dcb4c0cf9cf diff --git a/feely_pona.cpp b/feely_pona.cpp index 172821c..bd7997a 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -259,7 +259,113 @@ void TELY_Game_EntityChangeState(TELY_GameEntity *entity, TELY_GameEntityState s } } +struct AStarNode +{ + Dqn_usize cost; + Dqn_usize heuristic; + Dqn_V2I came_from; +}; + Dqn_f32 const FP_TILE_SIZE = 37.f; +Dqn_Slice AStarPathFind(Dqn_Arena *arena, TELY_Platform *platform, Dqn_V2I src_tile, Dqn_V2I dest_tile) +{ + Dqn_DSMap astar_info = Dqn_DSMap_Init(128); + DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); }; + + Dqn_FArray frontier = {}; + Dqn_FArray_Add(&frontier, src_tile); + + Dqn_usize tile_count_x = DQN_CAST(Dqn_usize)(platform->core.window_size.w / FP_TILE_SIZE); + Dqn_usize tile_count_y = DQN_CAST(Dqn_usize)(platform->core.window_size.h / FP_TILE_SIZE); + + // NOTE: Initialise the starting cost + uint64_t src_tile_u64 = (DQN_CAST(uint64_t)src_tile.y << 32) | (DQN_CAST(uint64_t)src_tile.x << 0); + Dqn_DSMap_MakeKeyU64(&astar_info, src_tile_u64); + + while (frontier.size) { + Dqn_V2I curr_tile = Dqn_FArray_PopFront(&frontier, 1); + if (curr_tile == dest_tile) + break; + + Dqn_FArray neighbours = {}; + { + Dqn_V2I left = Dqn_V2I_InitNx2(curr_tile.x - 1, curr_tile.y); + Dqn_V2I right = Dqn_V2I_InitNx2(curr_tile.x + 1, curr_tile.y); + Dqn_V2I top = Dqn_V2I_InitNx2(curr_tile.x, curr_tile.y - 1); + Dqn_V2I bottom = Dqn_V2I_InitNx2(curr_tile.x, curr_tile.y + 1); + + if (left.x >= 0) + Dqn_FArray_Add(&neighbours, left); + if (right.x <= tile_count_x) + Dqn_FArray_Add(&neighbours, right); + if (top.y >= 0) + Dqn_FArray_Add(&neighbours, top); + if (bottom.y <= tile_count_y) + Dqn_FArray_Add(&neighbours, bottom); + } + + uint64_t const curr_tile_u64 = (DQN_CAST(uint64_t)curr_tile.y << 32) | (DQN_CAST(uint64_t)curr_tile.x << 0); + Dqn_usize const curr_cost = Dqn_DSMap_FindKeyU64(&astar_info, curr_tile_u64).value->cost; + for (Dqn_V2I next_tile : neighbours) { + + Dqn_usize new_cost = curr_cost + 1; + uint64_t next_tile_u64 = (DQN_CAST(uint64_t)next_tile.y << 32) | (DQN_CAST(uint64_t)next_tile.x << 0); + Dqn_DSMapResult next_cost_result = Dqn_DSMap_MakeKeyU64(&astar_info, next_tile_u64); + + if (next_cost_result.found && new_cost >= next_cost_result.value->cost) + continue; + + Dqn_usize manhattan_dist = DQN_ABS(dest_tile.x - next_tile.x) + DQN_ABS(dest_tile.y - next_tile.y); + next_cost_result.value->cost = new_cost; + next_cost_result.value->came_from = curr_tile; + next_cost_result.value->heuristic = new_cost + manhattan_dist; + + // TODO(doyle): Find the insert location into the frontier + bool inserted = false; + DQN_FOR_UINDEX(index, frontier.size) { + Dqn_V2I frontier_tile = frontier.data[index]; + uint64_t frontier_tile_u64 = DQN_CAST(uint64_t)frontier_tile.y << 32 | DQN_CAST(uint64_t)frontier_tile.x << 0; + Dqn_usize frontier_heuristic = Dqn_DSMap_FindKeyU64(&astar_info, frontier_tile_u64).value->heuristic; + if (next_cost_result.value->heuristic >= frontier_heuristic) + continue; + + char const *src = DQN_CAST(char *)(frontier.data + index); + char const *dest = DQN_CAST(char *)(frontier.data + (index + 1)); + char const *end = DQN_CAST(char *)(frontier.data + frontier.size); + Dqn_usize bytes_to_move = end - src; + DQN_MEMMOVE(DQN_CAST(void *)dest, src, bytes_to_move); + + frontier.data[index] = next_tile; + frontier.size++; + inserted = true; + break; + } + + if (inserted) + continue; + + Dqn_FArray_Add(&frontier, next_tile); + } + } + + Dqn_usize slice_size = 0; + for (Dqn_V2I it = dest_tile; it != src_tile; slice_size++) { + uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0); + it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from; + } + + Dqn_Slice result = Dqn_Slice_Alloc(arena, slice_size, Dqn_ZeroMem_No); + slice_size = 0; + for (Dqn_V2I it = dest_tile; it != src_tile; ) { + result.data[slice_size++] = it; + uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0); + it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from; + } + + DQN_ASSERT(result.size == slice_size); + return result; +} + void FP_GameUpdate(TELY_Platform *platform, TELY_Game *game, TELY_Renderer *renderer, TELY_PlatformInput *input) { if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) @@ -293,76 +399,6 @@ void FP_GameUpdate(TELY_Platform *platform, TELY_Game *game, TELY_Renderer *rend } } } - - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_F1)) { - // NOTE: Do A* algorithm - Dqn_V2 world_pos = TELY_Game_CalcEntityWorldPos(game, player->handle); - Dqn_V2I player_tile = Dqn_V2I_InitNx2(world_pos.x / FP_TILE_SIZE, world_pos.y / FP_TILE_SIZE); - Dqn_V2I target_tile = Dqn_V2I_InitNx2(30, 10); - - Dqn_FArray frontier = {}; - Dqn_FArray_Add(&frontier, player_tile); - - Dqn_usize tile_count_x = DQN_CAST(Dqn_usize)(platform->core.window_size.w / FP_TILE_SIZE); - Dqn_usize tile_count_y = DQN_CAST(Dqn_usize)(platform->core.window_size.h / FP_TILE_SIZE); - - Dqn_DSMap cost_so_far = Dqn_DSMap_Init(128); - Dqn_DSMap came_from = Dqn_DSMap_Init(128); - DQN_DEFER { - Dqn_DSMap_Deinit(&came_from); - Dqn_DSMap_Deinit(&cost_so_far); - }; - - - // NOTE: Initialise the starting cost - { - uint64_t tile_as_u64 = (DQN_CAST(uint64_t)player_tile.y << 32) | (DQN_CAST(uint64_t)player_tile.x << 0); - Dqn_DSMapKey key = Dqn_DSMap_KeyU64(&cost_so_far, tile_as_u64); - Dqn_DSMap_Set(&cost_so_far, key, 0 /*value*/, nullptr); - } - - while (frontier.size) { - Dqn_V2I current = frontier.data[0]; - if (current == target_tile) - break; - - Dqn_FArray neighbours = {}; - Dqn_V2I left = Dqn_V2I_InitNx2(current.x - 1, current.y); - Dqn_V2I right = Dqn_V2I_InitNx2(current.x + 1, current.y); - Dqn_V2I top = Dqn_V2I_InitNx2(current.x, current.y - 1); - Dqn_V2I bottom = Dqn_V2I_InitNx2(current.x, current.y + 1); - - if (left.x >= 0) - Dqn_FArray_Add(&neighbours, left); - if (right.x <= tile_count_x) - Dqn_FArray_Add(&neighbours, right); - if (top.y >= 0) - Dqn_FArray_Add(&neighbours, top); - if (bottom.y >= 0) - Dqn_FArray_Add(&neighbours, bottom); - - uint64_t curr_tile_as_u64 = (DQN_CAST(uint64_t)current.y << 32) | (DQN_CAST(uint64_t)current.x << 0); - Dqn_DSMapKey curr_tile_key = Dqn_DSMap_KeyU64(&cost_so_far, curr_tile_as_u64); - - #if 0 - for (Dqn_V2I next : neighbours) { - Dqn_usize new_cost = curr_cost + 1; - Dqn_usize *prev_cost = Dqn_DSMap_Find(&cost_so_far, curr_tile_key); - if (!prev_curr_cost || new_cost < *prev_curr_cost) { - *prev_cost = new_cost; - Dqn_usize manhattan_dist = DQN_ABS(target_tile.x - current.x) + DQN_ABS(target_tile.y - current.y); - Dqn_usize estimated_finish_cost = new_cost + manhattan_dist; - - // TODO(doyle): Find the insert location - - uint64_t next_tile_as_u64 = (DQN_CAST(uint64_t)next.y << 32) | (DQN_CAST(uint64_t)next.x << 0); - Dqn_DSMapKey next_tile_key = Dqn_DSMap_KeyU64(&came_from, next_tile_as_u64); - Dqn_DSMap_Set(&came_from, next_tile_key, curr_tile_as_u64); - } - } - #endif - } - } } else { game->camera.world_pos += dir_vector * 5.f; } @@ -381,6 +417,64 @@ void FP_GameUpdate(TELY_Platform *platform, TELY_Game *game, TELY_Renderer *rend } } + Dqn_V2 entity_world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + if (entity->name == DQN_STRING8("Enemy")) { + if (entity->handle != game->clicked_entity && entity->stalk_entity != game->clicked_entity) { + entity->stalk_entity = game->clicked_entity; + } + + TELY_GameEntity *stalk_entity = TELY_Game_GetEntity(game, entity->stalk_entity); + if (stalk_entity) { + Dqn_V2 stalk_world_pos = TELY_Game_CalcEntityWorldPos(game, stalk_entity->handle); + Dqn_V2I stalk_tile = Dqn_V2I_InitNx2(stalk_world_pos.x / FP_TILE_SIZE, stalk_world_pos.y / FP_TILE_SIZE); + if (entity->stalk_entity_last_known_tile != stalk_tile) { + entity->stalk_entity_last_known_tile = stalk_tile; + + // NOTE: Dealloc all waypoints + for (TELY_GameWaypoint *waypoint = entity->waypoints->next; waypoint != entity->waypoints; ) { + TELY_GameWaypoint *next = waypoint->next; + TELY_ChunkPool_Dealloc(&game->chunk_pool, waypoint); + waypoint = next; + } + entity->waypoints->next = entity->waypoints; + entity->waypoints->prev = entity->waypoints; + + Dqn_V2I entity_tile = Dqn_V2I_InitNx2(entity_world_pos.x / FP_TILE_SIZE, entity_world_pos.y / FP_TILE_SIZE); + Dqn_Slice path_find = AStarPathFind(&platform->arena, platform, entity_tile, stalk_tile); + + for (Dqn_usize index = path_find.size - 1; index < path_find.size; index--) { + TELY_GameWaypoint *waypoint = TELY_ChunkPool_New(&game->chunk_pool, TELY_GameWaypoint); + waypoint->pos = path_find.data[index]; + waypoint->next = entity->waypoints; + waypoint->prev = entity->waypoints->prev; + waypoint->next->prev = waypoint; + waypoint->prev->next = waypoint; + } + } + } + } + + for (TELY_GameWaypoint *waypoint = entity->waypoints->next; waypoint != entity->waypoints; waypoint = waypoint->next) { + Dqn_V2 circle_pos = Dqn_V2_InitNx2(waypoint->pos.x * FP_TILE_SIZE + FP_TILE_SIZE * .5f, waypoint->pos.y * FP_TILE_SIZE + FP_TILE_SIZE * .5f); + TELY_Render_CircleColourV4(renderer, circle_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_MAGENTA_V4); + } + + if (entity->waypoints->next != entity->waypoints) { + TELY_GameWaypoint *waypoint = entity->waypoints->next; + Dqn_V2I target_tile = entity->waypoints->next->pos; + Dqn_V2 target_pos = Dqn_V2_InitNx2(target_tile.x * FP_TILE_SIZE + FP_TILE_SIZE *.5f, target_tile.y * FP_TILE_SIZE + FP_TILE_SIZE * .5f); + Dqn_V2 entity_to_target_pos = target_pos - entity_world_pos; + + if (Dqn_V2_LengthSq(entity_to_target_pos) < DQN_SQUARED(entity->local_hit_box_size.x * .5f)) { + waypoint->next->prev = waypoint->prev; + waypoint->prev->next = waypoint->next; + TELY_ChunkPool_Dealloc(&game->chunk_pool, waypoint); + } else { + Dqn_V2 entity_to_target_pos_norm = Dqn_V2_Normalise(entity_to_target_pos); + entity->local_pos += entity_to_target_pos_norm * (entity->local_hit_box_size.x * .05f); + } + } + // NOTE: Core equations of motion ========================================================== { // f"(t) = a