fp: Add a simplified swarming algorithm
This commit is contained in:
parent
a35cb8d2a6
commit
e467abf922
2
External/tely
vendored
2
External/tely
vendored
@ -1 +1 @@
|
||||
Subproject commit f28426b027a5d63974ff8901459b8fb8454f39fe
|
||||
Subproject commit 949f5e16338c7f981b38f03fc406dca3a499ab9a
|
170
feely_pona.cpp
170
feely_pona.cpp
@ -91,83 +91,6 @@ TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_A
|
||||
return result;
|
||||
}
|
||||
|
||||
struct FP_GameSwarmSlot
|
||||
{
|
||||
Dqn_usize count;
|
||||
Dqn_V2 world_pos;
|
||||
};
|
||||
|
||||
Dqn_FArray<FP_GameSwarmSlot, 32> FP_Entity_GetSwarmingPositions(FP_Game *game, FP_GameEntityHandle entity_handle)
|
||||
{
|
||||
Dqn_FArray<FP_GameSwarmSlot, 32> result = {};
|
||||
FP_GameEntity *entity = FP_Game_GetEntity(game, entity_handle);
|
||||
if (FP_Game_IsNilEntity(entity))
|
||||
return result;
|
||||
|
||||
DQN_FOR_UINDEX (dir_index, FP_GameDirection_Count) {
|
||||
FP_GameSwarm *swarm = entity->swarm + dir_index;
|
||||
Dqn_f32 const min_dist_between_slots = FP_Game_MetersToPixelsNx1(game, 1.f);
|
||||
|
||||
// NOTE: Calculate the number of swarming slots we need for this entity
|
||||
if (swarm->slots_active == 0) {
|
||||
switch (DQN_CAST(FP_GameDirection)dir_index) {
|
||||
case FP_GameDirection_Up: /*FALLTHRU*/
|
||||
case FP_GameDirection_Down: {
|
||||
swarm->slots_active = DQN_CAST(Dqn_usize)(entity->local_hit_box_size.w / min_dist_between_slots);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Left: /*FALLTHRU*/
|
||||
case FP_GameDirection_Right: {
|
||||
swarm->slots_active = DQN_CAST(Dqn_usize)(entity->local_hit_box_size.h / min_dist_between_slots);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break;
|
||||
}
|
||||
|
||||
DQN_ASSERT(swarm->slots_active < Dqn_FArray_Max(&swarm->slots));
|
||||
}
|
||||
|
||||
// NOTE: Calculate potential swarming positions ========================
|
||||
Dqn_Rect entity_world_hit_box = FP_Game_CalcEntityWorldHitBox(game, entity->handle);
|
||||
DQN_FOR_UINDEX (slot_index, swarm->slots_active) {
|
||||
FP_SentinelList<FP_GameEntityHandle> *list = swarm->slots.data + slot_index;
|
||||
|
||||
FP_GameSwarmSlot *slot = Dqn_FArray_Make(&result, Dqn_ZeroMem_No);
|
||||
slot->count = list->size;
|
||||
|
||||
switch (DQN_CAST(FP_GameDirection)dir_index) {
|
||||
case FP_GameDirection_Up: {
|
||||
Dqn_V2 start = Dqn_V2_InitNx2(entity_world_hit_box.pos.x,
|
||||
entity_world_hit_box.pos.y - entity_world_hit_box.size.h * .1f);
|
||||
slot->world_pos = Dqn_V2_InitNx2(start.x + slot_index * min_dist_between_slots, start.y);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Down: {
|
||||
Dqn_V2 start = Dqn_V2_InitNx2(entity_world_hit_box.pos.x,
|
||||
entity_world_hit_box.pos.y + entity_world_hit_box.size.h + entity_world_hit_box.size.h * .1f);
|
||||
slot->world_pos = Dqn_V2_InitNx2(start.x + slot_index * min_dist_between_slots, start.y);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Left: {
|
||||
Dqn_V2 start = Dqn_V2_InitNx2(entity_world_hit_box.pos.x - entity_world_hit_box.size.w * .1f,
|
||||
entity_world_hit_box.pos.y);
|
||||
slot->world_pos = Dqn_V2_InitNx2(start.x, start.y + slot_index * min_dist_between_slots);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Right: {
|
||||
Dqn_V2 start = Dqn_V2_InitNx2(entity_world_hit_box.pos.x + entity_world_hit_box.size.w + entity_world_hit_box.size.w * .1f,
|
||||
entity_world_hit_box.pos.y);
|
||||
slot->world_pos = Dqn_V2_InitNx2(start.x, start.y + slot_index * min_dist_between_slots);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle, Dqn_V2 acceleration_meters_per_s)
|
||||
{
|
||||
// f"(t) = a
|
||||
@ -223,6 +146,7 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle,
|
||||
case FP_EntityType_Terry: /*FALLTRHU*/
|
||||
case FP_EntityType_Merchant: /*FALLTRHU*/
|
||||
case FP_EntityType_Count: break;
|
||||
case FP_EntityType_ClubTerry: break;
|
||||
}
|
||||
|
||||
if (!entity_collides_with_collider)
|
||||
@ -940,6 +864,17 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_PlatformInput *input, FP_Ga
|
||||
}
|
||||
}
|
||||
|
||||
bool FindTurnSide(Dqn_f32 current, Dqn_f32 target)
|
||||
{
|
||||
Dqn_f32 diff = target - current;
|
||||
if(diff < 0)
|
||||
diff += (DQN_PI * 2.f);
|
||||
if(diff > DQN_PI)
|
||||
return false; // left turn
|
||||
else
|
||||
return true; // right turn
|
||||
}
|
||||
|
||||
void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input)
|
||||
{
|
||||
Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate);
|
||||
@ -1037,8 +972,14 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
if (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) {
|
||||
FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_Terry);
|
||||
if (closest_result.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) {
|
||||
FP_SentinelListLink<FP_GameWaypoint> *first_waypoint = FP_SentinelList_Front(&entity->waypoints);
|
||||
if (first_waypoint->data.entity != closest_result.entity) {
|
||||
|
||||
bool has_waypoint_to_terry = false;
|
||||
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
|
||||
!has_waypoint_to_terry && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
|
||||
has_waypoint_to_terry = link->data.entity == closest_result.entity;
|
||||
}
|
||||
|
||||
if (!has_waypoint_to_terry) {
|
||||
FP_GameEntity *terry = FP_Game_GetEntity(game, closest_result.entity);
|
||||
FP_GameDirection aggro_direction = FP_GameDirection_Count;
|
||||
DQN_FOR_UINDEX(dir_index, FP_GameDirection_Count) {
|
||||
@ -1050,9 +991,11 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
}
|
||||
}
|
||||
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, first_waypoint, game->chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = terry->handle;
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints,
|
||||
FP_SentinelList_Front(&entity->waypoints),
|
||||
game->chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = terry->handle;
|
||||
|
||||
if (aggro_direction != FP_GameDirection_Count) {
|
||||
waypoint->type = FP_GameWaypointType_Side;
|
||||
@ -1063,38 +1006,35 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
}
|
||||
}
|
||||
|
||||
if (entity->type == FP_EntityType_ClubTerry) {
|
||||
Dqn_FArray<FP_GameSwarmSlot, 32> candidate_swarm_slots = FP_Entity_GetSwarmingPositions(game, entity->handle);
|
||||
DQN_ASSERT(candidate_swarm_slots.size);
|
||||
for (FP_GameSwarmSlot& slot : candidate_swarm_slots)
|
||||
TELY_Render_CircleColourV4(&platform->renderer, slot.world_pos, 8.f, TELY_RenderShapeMode_Line, TELY_COLOUR_RED_TOMATO_V4);
|
||||
}
|
||||
|
||||
if (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) {
|
||||
FP_GameFindClosestEntityResult closest_result = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_ClubTerry);
|
||||
if (closest_result.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) {
|
||||
FP_SentinelListLink<FP_GameWaypoint> *first_waypoint = FP_SentinelList_Front(&entity->waypoints);
|
||||
if (first_waypoint->data.entity != closest_result.entity) {
|
||||
FP_GameEntity *club_terry = FP_Game_GetEntity(game, closest_result.entity);
|
||||
FP_GameFindClosestEntityResult closest_club = FP_Game_FindClosestEntityWithType(game, entity->handle, FP_EntityType_ClubTerry);
|
||||
if (closest_club.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 4.f))) {
|
||||
|
||||
Dqn_FArray<FP_GameSwarmSlot, 32> candidate_swarm_slots = FP_Entity_GetSwarmingPositions(game, closest_result.entity);
|
||||
DQN_ASSERT(candidate_swarm_slots.size);
|
||||
bool has_waypoint_to_club = false;
|
||||
for (FP_SentinelListLink<FP_GameWaypoint> *link = nullptr;
|
||||
!has_waypoint_to_club && FP_SentinelList_Iterate<FP_GameWaypoint>(&entity->waypoints, &link); ) {
|
||||
has_waypoint_to_club = link->data.entity == closest_club.entity;
|
||||
}
|
||||
|
||||
Dqn_f32 closest_swarm_dist = DQN_F32_MAX;
|
||||
Dqn_V2 closest_swarm_pos = {};
|
||||
for (FP_GameSwarmSlot& slot : candidate_swarm_slots) {
|
||||
Dqn_f32 dist = Dqn_V2_Length_V2x2(slot.world_pos, entity_pos);
|
||||
if (dist < closest_swarm_dist) {
|
||||
closest_swarm_dist = dist;
|
||||
closest_swarm_pos = slot.world_pos;
|
||||
}
|
||||
if (!has_waypoint_to_club) {
|
||||
Dqn_Rect club_hit_box = FP_Game_CalcEntityWorldHitBox(game, closest_club.entity);
|
||||
Dqn_V2 club_top_left = Dqn_Rect_TopLeft(club_hit_box);
|
||||
Dqn_V2 club_top_right = Dqn_Rect_TopRight(club_hit_box);
|
||||
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, FP_SentinelList_Front(&entity->waypoints), game->chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = closest_club.entity;
|
||||
waypoint->type = FP_GameWaypointType_Side;
|
||||
|
||||
if (entity_pos.x <= club_top_left.x) {
|
||||
waypoint->type_direction = FP_GameDirection_Left;
|
||||
} else if (entity_pos.x >= club_top_right.x) {
|
||||
waypoint->type_direction = FP_GameDirection_Right;
|
||||
} else if (entity_pos.y <= club_top_left.y) {
|
||||
waypoint->type_direction = FP_GameDirection_Up;
|
||||
} else {
|
||||
waypoint->type_direction = FP_GameDirection_Down;
|
||||
}
|
||||
|
||||
FP_SentinelListLink<FP_GameWaypoint> *link = FP_SentinelList_MakeBefore(&entity->waypoints, first_waypoint, game->chunk_pool);
|
||||
FP_GameWaypoint *waypoint = &link->data;
|
||||
waypoint->entity = club_terry->handle;
|
||||
waypoint->type = FP_GameWaypointType_Offset;
|
||||
waypoint->offset = closest_swarm_pos - entity_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1136,7 +1076,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
|
||||
// NOTE: We have arrived at the waypoint
|
||||
bool aggro_on_terry = (entity->flags & FP_GameEntityFlag_AggrosWhenNearTerry) && waypoint_entity->type == FP_EntityType_Terry;
|
||||
bool club_terry_response = (entity->flags & FP_GameEntityFlag_RespondsToClubTerry) && waypoint_entity->type == FP_EntityType_ClubTerry;
|
||||
if (aggro_on_terry || club_terry_response) {
|
||||
if (((waypoint->flags & FP_GameWaypointFlag_NonInterruptible) == 0) && (aggro_on_terry || club_terry_response)) {
|
||||
bool can_attack = !entity->is_dying; // TODO(doyle): State transition needs to check if it's valid to move to making this not necessary
|
||||
if (can_attack) {
|
||||
Dqn_V2 attack_dir_vectors[FP_GameDirection_Count] = {};
|
||||
@ -1497,6 +1437,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
|
||||
}
|
||||
|
||||
// NOTE: Render attack box =================================================================
|
||||
Dqn_Rect world_hit_box = FP_Game_CalcEntityWorldHitBox(game, entity->handle);
|
||||
{
|
||||
Dqn_Rect attack_box = FP_Game_CalcEntityAttackWorldHitBox(game, entity->handle);
|
||||
TELY_Render_RectColourV4(renderer, attack_box, TELY_RenderShapeMode_Line, TELY_COLOUR_RED_TOMATO_V4);
|
||||
@ -1506,7 +1447,6 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
|
||||
TELY_Render_CircleColourV4(renderer, world_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_RED_TOMATO_V4);
|
||||
|
||||
// NOTE: Render hot/active entity ==========================================================
|
||||
Dqn_Rect world_hit_box = FP_Game_CalcEntityWorldHitBox(game, entity->handle);
|
||||
if (game->clicked_entity == entity->handle) {
|
||||
TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4);
|
||||
|
||||
@ -1525,13 +1465,15 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
|
||||
if (game->hot_entity == entity->handle) {
|
||||
if (entity->name.size) {
|
||||
Dqn_V2I player_tile = Dqn_V2I_InitNx2(world_pos.x / game->tile_size, world_pos.y / game->tile_size);
|
||||
Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
|
||||
Dqn_V2 entity_world_pos = Dqn_Rect_Center(world_hit_box);
|
||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
||||
Dqn_String8 label = Dqn_String8_InitF(scratch.allocator,
|
||||
"%.*s (%.1f, %.1f) (%I32d, %I32d)",
|
||||
"%.*s|Pos: (%.1f, %.1f)|Size: %.1fx%.1f|Tile: (%I32d, %I32d)",
|
||||
DQN_STRING_FMT(entity->name),
|
||||
entity_world_pos.x,
|
||||
entity_world_pos.y,
|
||||
world_hit_box.size.w,
|
||||
world_hit_box.size.h,
|
||||
player_tile.x,
|
||||
player_tile.y);
|
||||
TELY_Render_Text(renderer, world_mouse_p, Dqn_V2_InitNx2(0.f, 1), label);
|
||||
|
@ -258,6 +258,16 @@ static bool FP_Game_IsNilEntity(FP_GameEntity *entity)
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool FP_Game_IsNilEntityHandle(FP_Game *game, FP_GameEntityHandle handle)
|
||||
{
|
||||
bool result = true;
|
||||
if (handle.id != 0) {
|
||||
FP_GameEntity *entity = FP_Game_GetEntity(game, handle);
|
||||
result = FP_Game_IsNilEntity(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void FP_Game_DetachEntityIntoFreeList(FP_Game *game, FP_GameEntityHandle handle)
|
||||
{
|
||||
FP_GameEntity *entity = FP_Game_GetEntity(game, handle);
|
||||
@ -353,7 +363,7 @@ static Dqn_V2 FP_Game_CalcEntityWorldPos(FP_Game const *game, FP_GameEntityHandl
|
||||
|
||||
static Dqn_Rect FP_Game_CalcEntityLocalHitBox(FP_Game const *game, FP_GameEntityHandle handle)
|
||||
{
|
||||
FP_GameEntity *entity = FP_Game_GetEntity(DQN_CAST(FP_Game *)game, handle);
|
||||
FP_GameEntity *entity = FP_Game_GetEntity(DQN_CAST(FP_Game *)game, handle);
|
||||
Dqn_V2 half_hit_box_size = entity->local_hit_box_size * .5f;
|
||||
Dqn_Rect result = Dqn_Rect_InitV2x2(entity->local_hit_box_offset - half_hit_box_size, entity->local_hit_box_size);
|
||||
return result;
|
||||
@ -651,7 +661,7 @@ static Dqn_V2 FP_Game_CalcWaypointWorldPos(FP_Game *game, FP_GameWaypoint const
|
||||
Dqn_Rect entity_rect = FP_Game_CalcEntityWorldHitBox(game, waypoint_entity->handle);
|
||||
switch (waypoint->type_direction) {
|
||||
case FP_GameDirection_Up: {
|
||||
result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y + entity_rect.size.h * .1f);
|
||||
result = Dqn_V2_InitNx2(entity_rect.pos.x + entity_rect.size.w * .5f, entity_rect.pos.y - entity_rect.size.h * .1f);
|
||||
} break;
|
||||
|
||||
case FP_GameDirection_Down: {
|
||||
|
@ -80,8 +80,14 @@ enum FP_GameDirection
|
||||
FP_GameDirection_Count,
|
||||
};
|
||||
|
||||
enum FP_GameWaypointFlag
|
||||
{
|
||||
FP_GameWaypointFlag_NonInterruptible = 1 << 0,
|
||||
};
|
||||
|
||||
struct FP_GameWaypoint
|
||||
{
|
||||
uint32_t flags;
|
||||
FP_GameWaypointType type;
|
||||
FP_GameDirection type_direction; // Used if type is `FP_GameWaypointType_Side`
|
||||
FP_GameEntityHandle entity; // The entity to move to
|
||||
@ -122,12 +128,6 @@ struct FP_GameRenderSprite
|
||||
uint64_t started_at_clock_ms;
|
||||
};
|
||||
|
||||
struct FP_GameSwarm
|
||||
{
|
||||
Dqn_FArray<FP_SentinelList<FP_GameEntityHandle>, 8> slots;
|
||||
Dqn_usize slots_active;
|
||||
};
|
||||
|
||||
struct FP_GameEntity
|
||||
{
|
||||
FP_GameEntity *next;
|
||||
@ -153,7 +153,6 @@ struct FP_GameEntity
|
||||
|
||||
FP_SentinelList<FP_GameWaypoint> waypoints;
|
||||
FP_GameEntityHandle aggro_slot[FP_GameDirection_Count];
|
||||
FP_GameSwarm swarm[FP_GameDirection_Count];
|
||||
|
||||
// NOTE: The entity hit box is positioned at the center of the entity.
|
||||
Dqn_V2 local_hit_box_size;
|
||||
|
@ -61,6 +61,14 @@ FP_SentinelListLink<T> *FP_SentinelList_Make(FP_SentinelList<T> *list, TELY_Chun
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
FP_SentinelListLink<T> *FP_SentinelList_Add(FP_SentinelList<T> *list, TELY_ChunkPool *pool, const T& data)
|
||||
{
|
||||
FP_SentinelListLink<T> *result = FP_SentinelList_Make(list, pool);
|
||||
result->data = data;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
FP_SentinelListLink<T> *FP_SentinelList_MakeBefore(FP_SentinelList<T> *list, FP_SentinelListLink<T> *link, TELY_ChunkPool *pool)
|
||||
{
|
||||
@ -119,3 +127,14 @@ void FP_SentinelList_Deinit(FP_SentinelList<T> *list, TELY_ChunkPool *pool)
|
||||
*list = {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
FP_SentinelListLink<T> *FP_SentinelList_Find(FP_SentinelList<T> const *list, T const &find)
|
||||
{
|
||||
FP_SentinelListLink<T> *result = nullptr;
|
||||
for (FP_SentinelListLink<FP_GameEntityHandle> *link = nullptr;
|
||||
!result && FP_SentinelList_Iterate<FP_GameEntityHandle>(DQN_CAST(FP_SentinelList<T> *)list, &link); ) {
|
||||
if (link->data == find)
|
||||
result = link;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
BIN
project.rdbg
BIN
project.rdbg
Binary file not shown.
Loading…
Reference in New Issue
Block a user