fp: Add stalking of the hero using A*

This commit is contained in:
doyle 2023-09-17 00:37:26 +10:00
parent d2bf171d98
commit 640c4e7fbb
2 changed files with 165 additions and 71 deletions

2
External/tely vendored

@ -1 +1 @@
Subproject commit 39af5c5627404982ec9200b2155f0b5c8874bf8f
Subproject commit a807996225655d43d8c7265f82913dcb4c0cf9cf

View File

@ -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<Dqn_V2I> AStarPathFind(Dqn_Arena *arena, TELY_Platform *platform, Dqn_V2I src_tile, Dqn_V2I dest_tile)
{
Dqn_DSMap<AStarNode> astar_info = Dqn_DSMap_Init<AStarNode>(128);
DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); };
Dqn_FArray<Dqn_V2I, 128> 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<Dqn_V2I, 4> 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<AStarNode> 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<Dqn_V2I> result = Dqn_Slice_Alloc<Dqn_V2I>(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<Dqn_V2I, 128> 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<Dqn_usize> cost_so_far = Dqn_DSMap_Init<Dqn_usize>(128);
Dqn_DSMap<Dqn_usize> came_from = Dqn_DSMap_Init<Dqn_usize>(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<Dqn_usize>(&cost_so_far, key, 0 /*value*/, nullptr);
}
while (frontier.size) {
Dqn_V2I current = frontier.data[0];
if (current == target_tile)
break;
Dqn_FArray<Dqn_V2I, 4> 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<Dqn_V2I> 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