fp: Remove action to anim mapping

This commit is contained in:
doyle 2023-09-24 21:54:08 +10:00
parent 30a69e40e9
commit 468c9995bd
4 changed files with 78 additions and 117 deletions

2
External/tely vendored

@ -1 +1 @@
Subproject commit b135bb46769657e730b49c173db7476160855984
Subproject commit 92ae772b6fa8f79bb0d5a93b643305fe482a6ed3

View File

@ -106,6 +106,7 @@ TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_A
DQN_ASSERT(result.rects.size == sprite_rect_index);
DQN_ASSERT(result.anims.size == sprite_anim_index);
return result;
}
@ -167,35 +168,9 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Load sprite sheets ====================================================================
{
game->terry_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_resized_25%"));
game->terry_action_mappings = Dqn_Slice_CopyArray<FP_ActionToAnimationMapping>(&platform->arena, {
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_walk_idle)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_walk_up)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_walk_down)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_walk_left)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_walk_right)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_attack_up)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_attack_side)},
{&game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, g_anim_names.terry_attack_down)},
});
game->terry_merchant_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_merchant_resized_25%"));
game->terry_merchant_action_mappings = Dqn_Slice_CopyArray<FP_ActionToAnimationMapping>(&platform->arena, {
{&game->terry_merchant_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_merchant_sprite_sheet, g_anim_names.terry_merchant)},
});
game->smoochie_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("smoochie_resized_25%"));
game->smoochie_action_mappings = Dqn_Slice_CopyArray<FP_ActionToAnimationMapping>(&platform->arena, {
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_walk_down)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_walk_up)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_walk_down)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_walk_left)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_walk_right)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_attack_down)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_attack_side)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_attack_heart)},
{&game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, g_anim_names.smoochie_death)},
});
game->terry_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_resized_25%"));
game->terry_merchant_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_merchant_resized_25%"));
game->smoochie_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("smoochie_resized_25%"));
}
game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8);
@ -207,7 +182,6 @@ void TELY_DLL_Init(void *user_data)
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry");
entity->type = FP_EntityType_Terry;
entity->local_pos = Dqn_V2_InitNx2(1334, 396);
entity->action_to_anim_mapping = game->terry_action_mappings;
entity->size_scale = Dqn_V2_InitNx1(0.25f);
entity->local_hit_box_size = Dqn_V2_InitNx2(428, 471) * entity->size_scale;
entity->flags |= FP_GameEntityFlag_Clickable;
@ -222,17 +196,16 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Merchant
{
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Merchant");
entity->type = FP_EntityType_Merchant;
entity->local_pos = Dqn_V2_InitNx2(1000, 124);
entity->local_hit_box_size = Dqn_V2_InitNx2(50, 50);
entity->size_scale = Dqn_V2_InitNx1(0.25f);
entity->action_to_anim_mapping = game->terry_merchant_action_mappings;
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
entity->flags |= FP_GameEntityFlag_MoveByMouse;
entity->flags |= FP_GameEntityFlag_MoveByGamepad;
entity->flags |= FP_GameEntityFlag_NonTraversable;
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Merchant");
entity->type = FP_EntityType_Merchant;
entity->local_pos = Dqn_V2_InitNx2(1000, 124);
entity->local_hit_box_size = Dqn_V2_InitNx2(50, 50);
entity->size_scale = Dqn_V2_InitNx1(0.25f);
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
entity->flags |= FP_GameEntityFlag_MoveByMouse;
entity->flags |= FP_GameEntityFlag_MoveByGamepad;
entity->flags |= FP_GameEntityFlag_NonTraversable;
}
game->tile_size = 37;
@ -318,35 +291,25 @@ void TELY_DLL_Init(void *user_data)
game->test_audio = platform->func_load_audio(assets, DQN_STRING8("Test Audio"), DQN_STRING8("Data/Audio/Purrple Cat - Moonwinds.qoa"));
}
FP_ActionToAnimationMapping FP_Game_GetActionAnimMappingWithName(FP_Game *game, FP_GameEntityHandle entity_handle, Dqn_String8 name)
{
FP_GameEntity *entity = FP_Game_GetEntity(game, entity_handle);
FP_ActionToAnimationMapping result = {};
for (FP_ActionToAnimationMapping const &mapping : entity->action_to_anim_mapping) {
if (mapping.anim.label == name) {
result = mapping;
break;
}
}
return result;
}
FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 dir_vector)
FP_GameEntityActionSprite FP_EntityActionStateMachine(FP_Game *game, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 dir_vector)
{
FP_GameEntityAction *action = &entity->action;
bool we_are_clicked_entity = entity->handle == game->clicked_entity;
bool action_has_finished = action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER && action->timer_s >= action->end_at_s;
bool entity_has_velocity = entity->velocity.x || entity->velocity.y;
FP_ActionToAnimationMapping result = {};
FP_GameEntityActionSprite result = {};
switch (entity->type) {
case FP_EntityType_Terry: {
FP_EntityTerryState *state = DQN_CAST(FP_EntityTerryState *)&action->state;
FP_EntityTerryState *state = DQN_CAST(FP_EntityTerryState *) & action->state;
TELY_AssetSpriteSheet *sheet = &game->terry_sprite_sheet;
result.sheet = sheet;
if (*state == FP_EntityTerryState_Nil)
FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle);
if (*state == FP_EntityTerryState_Idle) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_walk_idle);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_idle);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result);
@ -377,11 +340,11 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
if (*state == FP_EntityTerryState_AttackSide) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_attack_side);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_attack_side);
action->flip_on_x = entity->direction == FP_GameDirection_Right;
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle);
action->flip_on_x = false;
@ -402,18 +365,18 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
if (*state == FP_EntityTerryState_AttackUp) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_attack_up);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_attack_up);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle);
}
}
if (*state == FP_EntityTerryState_AttackDown) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_attack_down);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_down);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_EntityTerryState_Idle);
}
@ -427,7 +390,7 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
case FP_GameDirection_Left: desired_action_name = g_anim_names.terry_walk_left; break;
case FP_GameDirection_Right: desired_action_name = g_anim_names.terry_walk_right; break;
}
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, desired_action_name);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, desired_action_name);
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
@ -456,7 +419,7 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
// NOTE: Also handles state transition
if (action->mapping.anim.label != result.anim.label) {
if (action->sprite.anim->label != result.anim->label) {
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result);
}
@ -466,10 +429,10 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
if (*state == FP_EntityTerryState_Dash) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_walk_right);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_walk_right);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
Dqn_V2 dash_dir = {};
switch (entity->direction) {
@ -512,12 +475,15 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
} break;
case FP_EntityType_Smoochie: {
FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *)&action->state;
FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->state;
TELY_AssetSpriteSheet *sheet = &game->smoochie_sprite_sheet;
result.sheet = sheet;
if (*state == FP_EntitySmoochieState_Nil)
FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle);
if (*state == FP_EntitySmoochieState_Idle) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.smoochie_walk_down);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_walk_down);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result);
@ -543,19 +509,19 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
if (*state == FP_EntitySmoochieState_AttackDown) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.smoochie_attack_down);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_attack_down);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle);
}
}
if (*state == FP_EntitySmoochieState_AttackSide) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.smoochie_attack_side);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.smoochie_attack_side);
action->flip_on_x = entity->direction == FP_GameDirection_Right;
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, result.anim.count * result.anim.seconds_per_frame, result);
FP_Game_EntityActionReset(action, result.anim->count * result.anim->seconds_per_frame, result);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_EntitySmoochieState_Idle);
action->flip_on_x = false;
@ -570,7 +536,7 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
case FP_GameDirection_Left: desired_action_name = g_anim_names.smoochie_walk_left; break;
case FP_GameDirection_Right: desired_action_name = g_anim_names.smoochie_walk_right; break;
}
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, desired_action_name);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, desired_action_name);
if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
@ -591,7 +557,7 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
}
// NOTE: Also handles state transition
if (action->mapping.anim.label != result.anim.label) {
if (action->sprite.anim->label != result.anim->label) {
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result);
}
@ -618,11 +584,14 @@ FP_ActionToAnimationMapping FP_EntityActionStateMachine(FP_Game *game, TELY_Plat
case FP_EntityType_Merchant: {
FP_EntityTerryMerchantState *state = DQN_CAST(FP_EntityTerryMerchantState *)&action->state;
TELY_AssetSpriteSheet *sheet = &game->terry_merchant_sprite_sheet;
result.sheet = sheet;
if (*state == FP_EntityTerryMerchantState_Nil)
FP_Game_EntityActionSetState(action, FP_EntityTerryMerchantState_Idle);
if (*state == FP_EntityTerryMerchantState_Idle) {
result = FP_Game_GetActionAnimMappingWithName(game, entity->handle, g_anim_names.terry_merchant);
result.anim = TELY_Asset_GetSpriteAnimation(sheet, g_anim_names.terry_merchant);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, result);
}
@ -887,7 +856,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
// NOTE: Handle input on entity ============================================================
FP_ActionToAnimationMapping action_to_anim_mapping = FP_EntityActionStateMachine(game, input, entity, dir_vector);
FP_EntityActionStateMachine(game, input, entity, dir_vector);
// NOTE: Mob spawner =======================================================================
if (entity->flags & FP_GameEntityFlag_MobSpawner) {
@ -1036,10 +1005,10 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
}
// NOTE: Render entity sprites =============================================================
if (entity->action.mapping.anim.label.size) {
if (entity->action.sprite.anim) {
FP_GameEntityAction const *action = &entity->action;
TELY_AssetSpriteSheet const *sprite_sheet = action->mapping.sheet;
TELY_AssetSpriteAnimation const *sprite_anim = &action->mapping.anim;
TELY_AssetSpriteSheet const *sprite_sheet = action->sprite.sheet;
TELY_AssetSpriteAnimation const *sprite_anim = action->sprite.anim;
uint16_t anim_frame = DQN_CAST(uint16_t)(action->timer_s / sprite_anim->seconds_per_frame) % sprite_anim->count;
Dqn_usize sprite_index = sprite_anim->index + anim_frame;

View File

@ -437,14 +437,14 @@ static void FP_Game_EntityActionSetState(FP_GameEntityAction *action, uint32_t s
// Reset the timers and animation for the current action and set the duration
// for the new action.
static void FP_Game_EntityActionReset(FP_GameEntityAction *action, Dqn_f32 new_action_duration, FP_ActionToAnimationMapping mapping)
static void FP_Game_EntityActionReset(FP_GameEntityAction *action, Dqn_f32 new_action_duration, FP_GameEntityActionSprite sprite)
{
if (!action)
return;
action->mapping = mapping;
action->timer_s = 0.f;
action->end_at_s = new_action_duration;
action->flags = 0;
action->sprite = sprite;
action->timer_s = 0.f;
action->end_at_s = new_action_duration;
action->flags = 0;
}
static bool FP_Game_EntityActionHasFailed(FP_GameEntityAction const *action)
@ -674,7 +674,6 @@ static FP_GameEntityHandle FP_Game_EntityAddMob(FP_Game *game, Dqn_V2 pos)
entity->type = FP_EntityType_Smoochie;
entity->local_pos = pos;
entity->size_scale = Dqn_V2_InitNx1(.25f);
entity->action_to_anim_mapping = game->smoochie_action_mappings;
entity->local_hit_box_size = Dqn_V2_InitNx2(428, 471) * entity->size_scale;
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;

View File

@ -66,21 +66,21 @@ enum FP_GameEntityActionFlag
FP_GameEntityActionFlag_Failed = 1 << 1,
};
struct FP_ActionToAnimationMapping
struct FP_GameEntityActionSprite
{
TELY_AssetSpriteSheet *sheet;
TELY_AssetSpriteAnimation anim;
TELY_AssetSpriteAnimation *anim;
};
Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f;
struct FP_GameEntityAction
{
bool flip_on_x;
uint32_t state;
uint32_t flags; // Bit flags corresponding with `FP_GameEntityActionFlag`
FP_ActionToAnimationMapping mapping;
Dqn_f32 timer_s;
Dqn_f32 end_at_s;
bool flip_on_x;
uint32_t state;
uint32_t flags; // Bit flags corresponding with `FP_GameEntityActionFlag`
FP_GameEntityActionSprite sprite;
Dqn_f32 timer_s;
Dqn_f32 end_at_s;
};
enum FP_GameDirection
@ -103,8 +103,6 @@ struct FP_GameEntity
Dqn_String8 name;
FP_GameEntityHandle handle;
// TODO(doyle): Deprecate this, it is over engineered and doesn't work
Dqn_Slice<FP_ActionToAnimationMapping> action_to_anim_mapping;
Dqn_V2 size_scale;
FP_GameEntityAction action;
Dqn_V2 velocity;
@ -167,29 +165,24 @@ struct FP_Game
Dqn_FArray<FP_GameEntityHandle, 8> parent_entity_stack;
Dqn_VArray<FP_GameEntity> entities;
TELY_AssetSpriteSheet terry_sprite_sheet;
Dqn_Slice<FP_ActionToAnimationMapping> terry_action_mappings;
TELY_AssetSpriteSheet terry_sprite_sheet;
TELY_AssetSpriteSheet smoochie_sprite_sheet;
TELY_AssetSpriteSheet terry_merchant_sprite_sheet;
TELY_AssetSpriteSheet smoochie_sprite_sheet;
Dqn_Slice<FP_ActionToAnimationMapping> smoochie_action_mappings;
FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list;
TELY_AssetSpriteSheet terry_merchant_sprite_sheet;
Dqn_Slice<FP_ActionToAnimationMapping> terry_merchant_action_mappings;
FP_GameEntityHandle player;
FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list;
FP_GameEntityHandle clicked_entity;
FP_GameEntityHandle hot_entity;
FP_GameEntityHandle active_entity;
FP_GameEntityHandle prev_clicked_entity;
FP_GameEntityHandle prev_hot_entity;
FP_GameEntityHandle prev_active_entity;
FP_GameEntityHandle player;
FP_GameEntityHandle clicked_entity;
FP_GameEntityHandle hot_entity;
FP_GameEntityHandle active_entity;
FP_GameEntityHandle prev_clicked_entity;
FP_GameEntityHandle prev_hot_entity;
FP_GameEntityHandle prev_active_entity;
FP_GameCamera camera;
TELY_RFui rfui;
FP_GameCamera camera;
TELY_RFui rfui;
};
struct FP_GameAStarNode