2023-10-14 06:21:23 +00:00
# if defined(_CLANGD)
# pragma once
# include "feely_pona_unity.h"
2023-09-16 02:21:24 +00:00
# endif
2023-09-24 14:43:22 +00:00
Dqn_f32 const PHYSICS_STEP = 1 / 60.f ;
2023-09-24 07:01:21 +00:00
2023-10-05 12:09:39 +00:00
Dqn_Rect FP_Game_GetBuildingPlacementRectForEntity ( FP_Game * game , FP_GamePlaceableBuilding placeable_building , FP_GameEntityHandle handle )
2023-10-02 11:38:36 +00:00
{
2023-10-14 00:21:09 +00:00
Dqn_Rect result = { } ;
2023-10-02 11:38:36 +00:00
FP_GameEntity * entity = FP_Game_GetEntity ( game , handle ) ;
if ( FP_Game_IsNilEntity ( entity ) )
return result ;
2023-10-05 12:09:39 +00:00
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , placeable_building . type , placeable_building . state , entity - > direction ) ;
2023-10-02 11:38:36 +00:00
Dqn_Rect box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
Dqn_V2 build_p = { } ;
switch ( entity - > direction ) {
case FP_GameDirection_Up : {
2023-10-05 12:09:39 +00:00
build_p = Dqn_Rect_InterpolatedPoint ( box , Dqn_V2_InitNx2 ( 0.5f , 0.f ) ) - Dqn_V2_InitNx2 ( 0.f , render_data . render_size . h * .5f + 10.f ) ;
2023-10-02 11:38:36 +00:00
} break ;
case FP_GameDirection_Down : {
2023-10-05 12:09:39 +00:00
build_p = Dqn_Rect_InterpolatedPoint ( box , Dqn_V2_InitNx2 ( 0.5f , 1.f ) ) + Dqn_V2_InitNx2 ( 0.f , render_data . render_size . h * .5f + 10.f ) ;
2023-10-02 11:38:36 +00:00
} break ;
case FP_GameDirection_Left : {
2023-10-05 12:09:39 +00:00
build_p = Dqn_Rect_InterpolatedPoint ( box , Dqn_V2_InitNx2 ( 0.0f , 0.5f ) ) - Dqn_V2_InitNx2 ( render_data . render_size . w * .5f + 10.f , 0 ) ;
2023-10-02 11:38:36 +00:00
} break ;
case FP_GameDirection_Right : {
2023-10-05 12:09:39 +00:00
build_p = Dqn_Rect_InterpolatedPoint ( box , Dqn_V2_InitNx2 ( 1.f , 0.5f ) ) + Dqn_V2_InitNx2 ( render_data . render_size . w * .5f + 10.f , 0 ) ;
2023-10-02 11:38:36 +00:00
} break ;
case FP_GameDirection_Count : DQN_INVALID_CODE_PATH ; break ;
}
2023-10-05 12:09:39 +00:00
result . size = render_data . render_size ;
result . pos = build_p - ( render_data . render_size * .5f ) ;
2023-10-02 11:38:36 +00:00
return result ;
}
2023-09-24 05:44:52 +00:00
TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec ( TELY_Platform * platform , TELY_Assets * assets , Dqn_Arena * arena , Dqn_String8 sheet_name )
2023-09-24 04:20:27 +00:00
{
2023-09-24 05:44:52 +00:00
TELY_AssetSpriteSheet result = { } ;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( arena ) ;
result . sprite_size = Dqn_V2I_InitNx2 ( 185 , 170 ) ;
result . type = TELY_AssetSpriteSheetType_Rects ;
2023-09-24 04:20:27 +00:00
// NOTE: Load the sprite meta file =========================================================
Dqn_String8 sprite_spec_path = Dqn_FsPath_ConvertF ( scratch . arena , " %.*s/%.*s.txt " , DQN_STRING_FMT ( assets - > textures_dir ) , DQN_STRING_FMT ( sheet_name ) ) ;
Dqn_String8 sprite_spec_buffer = platform - > func_load_file ( scratch . arena , sprite_spec_path ) ;
Dqn_String8SplitAllocResult lines = Dqn_String8_SplitAlloc ( scratch . allocator , sprite_spec_buffer , DQN_STRING8 ( " \n " ) ) ;
Dqn_usize sprite_rect_index = 0 ;
Dqn_usize sprite_anim_index = 0 ;
DQN_FOR_UINDEX ( line_index , lines . size ) {
Dqn_String8 line = lines . data [ line_index ] ;
Dqn_String8SplitAllocResult line_splits = Dqn_String8_SplitAlloc ( scratch . allocator , line , DQN_STRING8 ( " ; " ) ) ;
if ( line_index = = 0 ) {
DQN_ASSERTF ( line_splits . size = = 4 , " Expected 4 splits for @file lines " ) ;
DQN_ASSERT ( Dqn_String8_StartsWith ( line_splits . data [ 0 ] , DQN_STRING8 ( " @file " ) , Dqn_String8EqCase_Sensitive ) ) ;
// NOTE: Sprite sheet path
Dqn_String8 sprite_sheet_path = Dqn_FsPath_ConvertF ( scratch . arena , " %.*s/%.*s " , DQN_STRING_FMT ( assets - > textures_dir ) , DQN_STRING_FMT ( line_splits . data [ 1 ] ) ) ;
2023-09-24 05:44:52 +00:00
result . tex_handle = platform - > func_load_texture ( assets , sheet_name , sprite_sheet_path ) ;
2023-09-24 04:20:27 +00:00
DQN_ASSERTF ( Dqn_Fs_Exists ( sprite_sheet_path ) , " Required file does not exist '%.*s' " , DQN_STRING_FMT ( sprite_sheet_path ) ) ;
// NOTE: Total sprite frame count
Dqn_String8ToU64Result total_frame_count = Dqn_String8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
DQN_ASSERT ( total_frame_count . success ) ;
2023-09-24 05:44:52 +00:00
result . rects = Dqn_Slice_Alloc < Dqn_Rect > ( arena , total_frame_count . value , Dqn_ZeroMem_No ) ;
2023-09-24 04:20:27 +00:00
// NOTE: Total animation count
Dqn_String8ToU64Result total_anim_count = Dqn_String8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
DQN_ASSERT ( total_anim_count . success ) ;
result . anims = Dqn_Slice_Alloc < TELY_AssetSpriteAnimation > ( arena , total_anim_count . value , Dqn_ZeroMem_No ) ;
// TODO: Sprite size?
// TODO: Texture name?
continue ;
}
if ( Dqn_String8_StartsWith ( line , DQN_STRING8 ( " @anim " ) ) ) {
DQN_ASSERTF ( line_splits . size = = 4 , " Expected 4 splits for @anim lines " ) ;
Dqn_String8 anim_name = line_splits . data [ 1 ] ;
Dqn_String8ToU64Result frames_per_second = Dqn_String8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
Dqn_String8ToU64Result frame_count = Dqn_String8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
DQN_ASSERT ( anim_name . size ) ;
DQN_ASSERT ( frame_count . success ) ;
DQN_ASSERT ( frames_per_second . success ) ;
Dqn_Allocator allocator = Dqn_Arena_Allocator ( arena ) ;
TELY_AssetSpriteAnimation * anim = result . anims . data + sprite_anim_index + + ;
anim - > label = Dqn_String8_Copy ( allocator , anim_name ) ;
anim - > index = DQN_CAST ( uint16_t ) sprite_rect_index ;
anim - > count = DQN_CAST ( uint16_t ) frame_count . value ;
2023-09-24 13:08:30 +00:00
anim - > ms_per_frame = DQN_CAST ( uint32_t ) ( 1000.f / frames_per_second . value ) ;
DQN_ASSERT ( anim - > ms_per_frame ! = 0 ) ;
2023-09-24 04:20:27 +00:00
} else {
2023-10-08 01:26:36 +00:00
DQN_ASSERTF ( line_splits . size = = 5 , " Expected 5 splits for sprite frame lines " ) ;
2023-09-24 04:20:27 +00:00
Dqn_String8ToU64Result x = Dqn_String8_ToU64 ( line_splits . data [ 0 ] , 0 ) ;
Dqn_String8ToU64Result y = Dqn_String8_ToU64 ( line_splits . data [ 1 ] , 0 ) ;
Dqn_String8ToU64Result w = Dqn_String8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
Dqn_String8ToU64Result h = Dqn_String8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
DQN_ASSERT ( x . success ) ;
DQN_ASSERT ( y . success ) ;
DQN_ASSERT ( w . success ) ;
DQN_ASSERT ( h . success ) ;
2023-09-24 05:44:52 +00:00
result . rects . data [ sprite_rect_index + + ] =
2023-09-24 04:20:27 +00:00
Dqn_Rect_InitNx4 ( DQN_CAST ( Dqn_f32 ) x . value ,
DQN_CAST ( Dqn_f32 ) y . value ,
DQN_CAST ( Dqn_f32 ) w . value ,
DQN_CAST ( Dqn_f32 ) h . value ) ;
}
}
2023-09-24 05:44:52 +00:00
DQN_ASSERT ( result . rects . size = = sprite_rect_index ) ;
2023-09-24 04:20:27 +00:00
DQN_ASSERT ( result . anims . size = = sprite_anim_index ) ;
2023-09-24 11:54:08 +00:00
2023-09-24 04:20:27 +00:00
return result ;
}
2023-09-29 05:44:02 +00:00
static void FP_Game_MoveEntity ( FP_Game * game , FP_GameEntityHandle entity_handle , Dqn_V2 acceleration_meters_per_s )
{
// f"(t) = a
// f'(t) = at + v
// f (t) = 0.5f*a(t^2) + vt + p
FP_GameEntity * entity = FP_Game_GetEntity ( game , entity_handle ) ;
if ( FP_Game_IsNilEntity ( entity ) )
return ;
Dqn_f32 t = DQN_CAST ( Dqn_f32 ) DQN_SQUARED ( PHYSICS_STEP ) ;
Dqn_f32 t_squared = DQN_SQUARED ( t ) ;
Dqn_f32 velocity_falloff_coefficient = 0.82f ;
Dqn_f32 acceleration_feel_good_factor = 15'000 . f ;
2023-10-14 00:21:09 +00:00
Dqn_V2 acceleration = FP_Game_MetersToPixelsV2 ( game - > play , acceleration_meters_per_s ) * acceleration_feel_good_factor ;
2023-09-29 05:44:02 +00:00
entity - > velocity = ( acceleration * t ) + entity - > velocity * velocity_falloff_coefficient ;
// NOTE: Zero out velocity with epsilon
if ( DQN_ABS ( entity - > velocity . x ) < 5.f )
entity - > velocity . x = 0.f ;
if ( DQN_ABS ( entity - > velocity . y ) < 5.f )
entity - > velocity . y = 0.f ;
2023-10-14 00:21:09 +00:00
Dqn_V2 delta_pos = ( acceleration * 0.5f * t_squared ) + ( entity - > velocity * t ) ;
2023-09-29 05:44:02 +00:00
Dqn_Rect entity_world_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
2023-10-14 00:21:09 +00:00
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
Dqn_V2 entity_new_pos = entity_pos + delta_pos ;
2023-09-29 05:44:02 +00:00
2023-10-14 00:21:09 +00:00
Dqn_f32 const SENTINEL_T = 999.f ;
Dqn_f32 global_earliest_t = SENTINEL_T ;
Dqn_V2 global_earliest_pos_just_before_collide = { } ;
2023-10-01 11:16:49 +00:00
2023-10-07 09:31:01 +00:00
if ( ( entity - > flags & FP_GameEntityFlag_NoClip ) = = 0 ) {
2023-10-14 00:21:09 +00:00
for ( FP_GameEntityIterator collider_it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & collider_it , game - > play . root_entity ) ; ) {
2023-10-07 09:31:01 +00:00
FP_GameEntity * collider = collider_it . entity ;
if ( collider - > handle = = entity - > handle )
continue ;
// TODO(doyle): Calculate the list of collidables at the start of the frame
if ( ( collider - > flags & FP_GameEntityFlag_NonTraversable ) = = 0 )
continue ;
bool entity_collides_with_collider = true ;
switch ( entity - > type ) {
case FP_EntityType_Catfish : /*FALLTHRU*/
case FP_EntityType_Smoochie : /*FALLTHRU*/
case FP_EntityType_Clinger : {
if ( collider - > type = = FP_EntityType_Smoochie | | collider - > type = = FP_EntityType_Clinger | | collider - > type = = FP_EntityType_Catfish ) {
2023-10-06 10:01:54 +00:00
entity_collides_with_collider = false ;
2023-10-07 09:31:01 +00:00
} else if ( FP_Entity_IsBuildingForMobs ( collider ) ) {
#if 0
// NOTE: We disable collision on buildings we have visited to avoid some
// problems ...
if ( FP_SentinelList_Find ( & entity - > buildings_visited , collider - > handle ) ) {
entity_collides_with_collider = false ;
}
# else
2023-10-01 12:22:18 +00:00
entity_collides_with_collider = false ;
2023-10-07 09:31:01 +00:00
# endif
2023-10-08 08:04:11 +00:00
} else if ( collider - > type = = FP_EntityType_Heart | |
collider - > type = = FP_EntityType_MerchantGym | |
collider - > type = = FP_EntityType_MerchantTerry | |
collider - > type = = FP_EntityType_MerchantGraveyard | |
collider - > type = = FP_EntityType_MerchantPhoneCompany ) {
entity_collides_with_collider = false ;
2023-10-07 09:31:01 +00:00
}
} break ;
2023-10-05 10:10:50 +00:00
2023-10-07 09:31:01 +00:00
case FP_EntityType_Terry : {
// NOTE: Don't collide with mobs when dashing (e.g. phase through)
FP_EntityTerryState state = * DQN_CAST ( FP_EntityTerryState * ) & entity - > action . state ;
2023-10-08 07:02:10 +00:00
if ( state = = FP_EntityTerryState_Dash | | state = = FP_EntityTerryState_DeadGhost ) {
2023-10-07 09:31:01 +00:00
if ( collider - > type = = FP_EntityType_Smoochie | | collider - > type = = FP_EntityType_Clinger | | collider - > type = = FP_EntityType_Catfish )
entity_collides_with_collider = false ;
}
} break ;
2023-09-29 05:44:02 +00:00
2023-10-07 09:31:01 +00:00
case FP_EntityType_Nil : break ;
case FP_EntityType_MerchantTerry : break ;
case FP_EntityType_Count : break ;
case FP_EntityType_ClubTerry : break ;
case FP_EntityType_Map : break ;
case FP_EntityType_MerchantGraveyard : break ;
case FP_EntityType_MerchantGym : break ;
case FP_EntityType_MerchantPhoneCompany : break ;
case FP_EntityType_Heart : break ;
case FP_EntityType_AirportTerry : break ;
case FP_EntityType_ChurchTerry : break ;
case FP_EntityType_KennelTerry : break ;
case FP_EntityType_PhoneMessageProjectile : break ;
2023-10-14 14:31:33 +00:00
case FP_EntityType_AirportTerryPlane :
case FP_EntityType_MobSpawner :
case FP_EntityType_PortalMonkey : break ;
2023-10-07 09:31:01 +00:00
}
2023-09-29 05:44:02 +00:00
2023-10-07 09:31:01 +00:00
if ( ! entity_collides_with_collider )
continue ;
// NOTE: Sweep collider with half the radius of the source entity
Dqn_Rect collider_world_hit_box = FP_Game_CalcEntityWorldHitBox ( game , collider - > handle ) ;
if ( Dqn_V2_Area ( collider_world_hit_box . size ) < = 0 )
continue ;
Dqn_Rect swept_collider_world_hit_box = collider_world_hit_box ;
swept_collider_world_hit_box . pos - = ( entity_world_hit_box . size * .5f ) ;
swept_collider_world_hit_box . size + = entity_world_hit_box . size ;
if ( ! Dqn_Rect_ContainsPoint ( swept_collider_world_hit_box , entity_new_pos ) )
continue ;
Dqn_f32 collider_left_wall_x = swept_collider_world_hit_box . pos . x ;
Dqn_f32 collider_right_wall_x = swept_collider_world_hit_box . pos . x + swept_collider_world_hit_box . size . w ;
Dqn_f32 collider_top_wall_y = swept_collider_world_hit_box . pos . y ;
Dqn_f32 collider_bottom_wall_y = swept_collider_world_hit_box . pos . y + swept_collider_world_hit_box . size . h ;
Dqn_V2 o = entity_pos ;
Dqn_V2 d = delta_pos ;
// NOTE: Solve collision by determining the 't' value at which
// we hit one of the walls of the collider and move the entity
// at exactly that point.
// O + td = x
// td = x - O
// t = (x - O) / d
Dqn_f32 earliest_t = SENTINEL_T ;
if ( d . x ! = 0.f ) {
Dqn_f32 left_t = ( collider_left_wall_x - o . x ) / d . x ;
Dqn_f32 right_t = ( collider_right_wall_x - o . x ) / d . x ;
if ( left_t > = 0.f & & left_t < = 1.f )
earliest_t = DQN_MIN ( earliest_t , left_t ) ;
if ( right_t > = 0.f & & right_t < = 1.f )
earliest_t = DQN_MIN ( earliest_t , right_t ) ;
}
2023-09-29 05:44:02 +00:00
2023-10-07 09:31:01 +00:00
if ( d . y ! = 0.f ) {
Dqn_f32 top_t = ( collider_top_wall_y - o . y ) / d . y ;
Dqn_f32 bottom_t = ( collider_bottom_wall_y - o . y ) / d . y ;
if ( top_t > = 0.f & & top_t < = 1.f )
earliest_t = DQN_MIN ( earliest_t , top_t ) ;
if ( bottom_t > = 0.f & & bottom_t < = 1.f )
earliest_t = DQN_MIN ( earliest_t , bottom_t ) ;
}
2023-09-29 05:44:02 +00:00
2023-10-07 09:31:01 +00:00
if ( earliest_t < global_earliest_t ) {
global_earliest_t = earliest_t ;
global_earliest_pos_just_before_collide = entity_pos + ( d * earliest_t ) ;
}
2023-09-29 05:44:02 +00:00
}
}
2023-10-07 09:31:01 +00:00
2023-10-01 11:16:49 +00:00
if ( global_earliest_t = = SENTINEL_T ) {
2023-09-29 05:44:02 +00:00
entity - > local_pos + = delta_pos ;
2023-10-01 11:16:49 +00:00
} else {
Dqn_V2 new_delta_pos = global_earliest_pos_just_before_collide - entity_pos ;
entity - > local_pos + = new_delta_pos ;
2023-09-29 05:44:02 +00:00
}
}
2023-10-08 05:14:31 +00:00
static void FP_PlayReset ( FP_Game * game , TELY_Platform * platform )
2023-09-16 02:21:24 +00:00
{
2023-10-08 05:14:31 +00:00
FP_GamePlay * play = & game - > play ;
if ( game - > play . root_entity )
FP_Game_DeleteEntity ( game , game - > play . root_entity - > handle ) ;
2023-09-16 02:21:24 +00:00
2023-10-08 05:14:31 +00:00
Dqn_VArray < FP_GameEntity > shallow_entities_copy = play - > entities ;
* play = { } ;
2023-09-16 02:21:24 +00:00
2023-10-08 05:14:31 +00:00
play - > chunk_pool = & platform - > chunk_pool ;
play - > meters_to_pixels = 65.416f ;
play - > entities = shallow_entities_copy ;
2023-09-16 02:21:24 +00:00
2023-10-08 05:14:31 +00:00
if ( play - > entities . data ) {
Dqn_VArray_Clear ( & play - > entities , Dqn_ZeroMem_Yes ) ;
} else {
play - > entities = Dqn_VArray_Init < FP_GameEntity > ( & play - > arena , 1024 * 8 ) ;
2023-09-16 02:21:24 +00:00
}
2023-10-08 05:14:31 +00:00
play - > root_entity = Dqn_VArray_Make ( & play - > entities , Dqn_ZeroMem_No ) ;
Dqn_FArray_Add ( & play - > parent_entity_stack , play - > root_entity - > handle ) ;
Dqn_PCG32_Seed ( & play - > rng , 0xABCDEF ) ;
2023-09-16 02:21:24 +00:00
2023-10-01 10:15:05 +00:00
// NOTE: Map ===================================================================================
2023-09-30 09:14:35 +00:00
{
2023-10-01 05:55:48 +00:00
TELY_AssetSpriteAnimation * sprite_anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . map ) ;
Dqn_Rect sprite_rect = game - > atlas_sprite_sheet . rects . data [ sprite_anim - > index ] ;
2023-09-30 09:14:35 +00:00
2023-10-08 02:22:08 +00:00
FP_GameEntity * entity = FP_Game_MakeEntityPointerF ( game , " Map " ) ;
entity - > type = FP_EntityType_Map ;
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , entity - > type , 0 /*state*/ , FP_GameDirection_Down ) ;
entity - > sprite_height = render_data . height ;
entity - > local_pos = { } ;
2023-09-30 09:14:35 +00:00
Dqn_f32 size_scale = FP_Entity_CalcSpriteScaleForDesiredHeight ( game , entity - > sprite_height , sprite_rect ) ;
Dqn_V2 sprite_rect_scaled = sprite_rect . size * size_scale ;
entity - > local_hit_box_size = sprite_rect_scaled ;
FP_Entity_AddDebugEditorFlags ( game , entity - > handle ) ;
2023-10-08 02:22:08 +00:00
FP_Game_EntityActionReset ( game , entity - > handle , FP_GAME_ENTITY_ACTION_INFINITE_TIMER , render_data . sprite ) ;
2023-10-08 05:14:31 +00:00
play - > map = entity ;
2023-09-30 09:14:35 +00:00
}
2023-10-01 10:15:05 +00:00
// NOTE: Map walls =============================================================================
2023-10-08 05:14:31 +00:00
FP_GameEntity const * map = play - > map ;
2023-10-02 12:41:08 +00:00
Dqn_Rect const map_hit_box = FP_Game_CalcEntityWorldHitBox ( game , map - > handle ) ;
2023-10-01 10:15:05 +00:00
{
2023-10-08 05:14:31 +00:00
Dqn_f32 wall_thickness = FP_Game_MetersToPixelsNx1 ( game - > play , 1.f ) ;
2023-10-01 10:15:05 +00:00
Dqn_f32 half_wall_thickness = wall_thickness * .5f ;
FP_Entity_CreateWallAtPos ( game ,
DQN_STRING8 ( " Left Wall " ) ,
Dqn_Rect_InterpolatedPoint ( map_hit_box , Dqn_V2_InitNx2 ( 0.f , 0.5f ) ) - Dqn_V2_InitNx2 ( half_wall_thickness , 0.f ) ,
Dqn_V2_InitNx2 ( wall_thickness , map_hit_box . size . h ) ) ;
FP_Entity_CreateWallAtPos ( game ,
DQN_STRING8 ( " Right Wall " ) ,
Dqn_Rect_InterpolatedPoint ( map_hit_box , Dqn_V2_InitNx2 ( 1.f , 0.5f ) ) + Dqn_V2_InitNx2 ( half_wall_thickness , 0.f ) ,
Dqn_V2_InitNx2 ( wall_thickness , map_hit_box . size . h ) ) ;
FP_Entity_CreateWallAtPos ( game ,
DQN_STRING8 ( " Top Wall " ) ,
Dqn_Rect_InterpolatedPoint ( map_hit_box , Dqn_V2_InitNx2 ( 0.5f , 0.f ) ) - Dqn_V2_InitNx2 ( 0.f , half_wall_thickness ) ,
Dqn_V2_InitNx2 ( map_hit_box . size . w , wall_thickness ) ) ;
FP_Entity_CreateWallAtPos ( game ,
DQN_STRING8 ( " Bottom Wall " ) ,
Dqn_Rect_InterpolatedPoint ( map_hit_box , Dqn_V2_InitNx2 ( 0.5f , 1.f ) ) + Dqn_V2_InitNx2 ( 0.f , half_wall_thickness ) ,
Dqn_V2_InitNx2 ( map_hit_box . size . w , wall_thickness ) ) ;
}
2023-10-02 12:41:08 +00:00
// NOTE: Map building zones
{
{
FP_Entity_CreatePermittedBuildZone ( game ,
Dqn_V2_InitNx2 ( 0.f , - 1206 ) ,
Dqn_V2_InitNx2 ( map_hit_box . size . w , 335 ) ,
" Building Zone " ) ;
}
{
FP_Entity_CreatePermittedBuildZone ( game ,
Dqn_V2_InitNx2 ( - 839.9 , - 460 ) ,
Dqn_V2_InitNx2 ( 2991.3 , 670 ) ,
" Building Zone " ) ;
}
{
FP_Entity_CreatePermittedBuildZone ( game ,
Dqn_V2_InitNx2 ( - 839.9 , 460 ) ,
Dqn_V2_InitNx2 ( 2991.3 , 670 ) ,
" Building Zone " ) ;
}
{
FP_Entity_CreatePermittedBuildZone ( game ,
Dqn_V2_InitNx2 ( 0.f , 1200 ) ,
Dqn_V2_InitNx2 ( map_hit_box . size . w , 335 ) ,
" Building Zone " ) ;
}
}
2023-10-07 14:29:50 +00:00
Dqn_V2 base_mid_p = Dqn_V2_InitNx2 ( 1580 , 0.f ) ;
{
// NOTE: Mid lane mob spawner ==================================================================
2023-10-08 05:14:31 +00:00
Dqn_V2 mid_lane_mob_spawner_pos = Dqn_V2_InitNx2 ( play - > map - > local_hit_box_size . w * - 0.5f + 128.f , 0.f ) ;
2023-10-07 14:29:50 +00:00
Dqn_usize spawn_cap = 16 ;
{
FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner ( game , mid_lane_mob_spawner_pos , spawn_cap , " Mob spawner " ) ;
FP_Game_PushParentEntity ( game , mob_spawner ) ;
FP_Entity_CreateWaypointF ( game , Dqn_V2_InitNx2 ( - mid_lane_mob_spawner_pos . x + base_mid_p . x , base_mid_p . y ) , " Waypoint " ) ;
FP_Game_PopParentEntity ( game ) ;
2023-10-08 05:14:31 +00:00
Dqn_FArray_Add ( & play - > mob_spawners , mob_spawner ) ;
2023-10-07 14:29:50 +00:00
}
// NOTE: Bottom lane spawner ===================================================================
# if 1
Dqn_V2 bottom_lane_mob_spawner_pos = Dqn_V2_InitNx2 ( mid_lane_mob_spawner_pos . x , mid_lane_mob_spawner_pos . y + 932.f ) ;
{
FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner ( game , bottom_lane_mob_spawner_pos , spawn_cap , " Mob spawner " ) ;
FP_Game_PushParentEntity ( game , mob_spawner ) ;
FP_Entity_CreateWaypointF ( game , Dqn_V2_InitNx2 ( - bottom_lane_mob_spawner_pos . x + base_mid_p . x , 0.f ) , " Waypoint " ) ;
FP_Entity_CreateWaypointF ( game , Dqn_V2_InitNx2 ( - bottom_lane_mob_spawner_pos . x + base_mid_p . x , - 932.f ) , " Waypoint " ) ;
FP_Game_PopParentEntity ( game ) ;
2023-10-08 05:14:31 +00:00
Dqn_FArray_Add ( & play - > mob_spawners , mob_spawner ) ;
2023-10-07 14:29:50 +00:00
}
// NOTE: Top lane spawner ===================================================================
Dqn_V2 top_lane_mob_spawner_pos = Dqn_V2_InitNx2 ( mid_lane_mob_spawner_pos . x , mid_lane_mob_spawner_pos . y - 915.f ) ;
{
FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner ( game , top_lane_mob_spawner_pos , spawn_cap , " Mob spawner " ) ;
FP_Game_PushParentEntity ( game , mob_spawner ) ;
FP_Entity_CreateWaypointF ( game , Dqn_V2_InitNx2 ( - top_lane_mob_spawner_pos . x + base_mid_p . x , 0.f ) , " Waypoint " ) ;
FP_Entity_CreateWaypointF ( game , Dqn_V2_InitNx2 ( - top_lane_mob_spawner_pos . x + base_mid_p . x , + 915.f ) , " Waypoint " ) ;
FP_Game_PopParentEntity ( game ) ;
2023-10-08 05:14:31 +00:00
Dqn_FArray_Add ( & play - > mob_spawners , mob_spawner ) ;
2023-10-07 14:29:50 +00:00
}
# endif
}
// NOTE: Monkey ============================================================
{
Dqn_V2 monkey_base_p = Dqn_V2_InitNx2 ( base_mid_p . x + 400.f , base_mid_p . y + 16.f ) ;
FP_GameEntityHandle monkey_a = FP_Entity_CreatePortalMonkey ( game , Dqn_V2_InitNx2 ( monkey_base_p . x , monkey_base_p . y ) , " Monkey A " ) ;
FP_GameEntityHandle monkey_b = FP_Entity_CreatePortalMonkey ( game , Dqn_V2_InitNx2 ( monkey_base_p . x , monkey_base_p . y + 100.f ) , " Monkey B " ) ;
FP_GameEntityHandle monkey_c = FP_Entity_CreatePortalMonkey ( game , Dqn_V2_InitNx2 ( monkey_base_p . x , monkey_base_p . y - 100.f ) , " Monkey C " ) ;
2023-10-08 05:14:31 +00:00
Dqn_FArray_Add ( & play - > portal_monkeys , monkey_a ) ;
Dqn_FArray_Add ( & play - > portal_monkeys , monkey_b ) ;
Dqn_FArray_Add ( & play - > portal_monkeys , monkey_c ) ;
2023-10-07 14:29:50 +00:00
}
2023-10-01 10:15:05 +00:00
// NOTE: Hero ==================================================================================
2023-09-16 02:21:24 +00:00
{
2023-09-30 12:27:25 +00:00
FP_GameEntityHandle terry = FP_Entity_CreateTerry ( game , Dqn_V2_InitNx2 ( 1434 , 11 ) , " Terry " ) ;
2023-10-08 05:14:31 +00:00
play - > clicked_entity = terry ;
play - > player = terry ;
2023-09-16 02:21:24 +00:00
}
2023-10-01 06:50:32 +00:00
{
Dqn_V2 base_top_left_pos = Dqn_V2_InitNx2 ( 1018 , - 335 ) ;
2023-10-06 12:16:55 +00:00
Dqn_V2 base_bottom_right_pos = Dqn_V2_InitNx2 ( 2050 , + 351 ) ;
2023-10-01 06:50:32 +00:00
Dqn_V2 base_top_left = base_top_left_pos ;
Dqn_V2 base_top_right = Dqn_V2_InitNx2 ( base_bottom_right_pos . x , base_top_left_pos . y ) ;
Dqn_V2 base_bottom_left = Dqn_V2_InitNx2 ( base_top_left_pos . x , base_bottom_right_pos . y ) ;
Dqn_V2 base_bottom_right = Dqn_V2_InitNx2 ( base_bottom_right_pos . x , base_bottom_right_pos . y ) ;
2023-10-08 05:14:31 +00:00
play - > merchant_terry = FP_Entity_CreateMerchantTerry ( game , base_top_left , " Merchant " ) ;
play - > merchant_graveyard = FP_Entity_CreateMerchantGraveyard ( game , base_bottom_left , " Graveyard " ) ;
play - > merchant_gym = FP_Entity_CreateMerchantGym ( game , base_bottom_right , " Gym " ) ;
play - > merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany ( game , base_top_right , " PhoneCompany " ) ;
2023-10-01 06:50:32 +00:00
}
2023-10-08 08:00:13 +00:00
#if 0
2023-10-07 09:31:01 +00:00
FP_Entity_CreateClubTerry ( game , Dqn_V2_InitNx2 ( + 500 , - 191 ) , " Club Terry " ) ;
FP_Entity_CreateKennelTerry ( game , Dqn_V2_InitNx2 ( - 300 , - 191 ) , " Kennel Terry " ) ;
FP_Entity_CreateChurchTerry ( game , Dqn_V2_InitNx2 ( - 800 , - 191 ) , " Church Terry " ) ;
FP_Entity_CreateAirportTerry ( game , Dqn_V2_InitNx2 ( - 1200 , - 191 ) , " Airport Terry " ) ;
2023-10-08 08:00:13 +00:00
# endif
2023-09-24 08:05:28 +00:00
2023-10-07 14:29:50 +00:00
// NOTE: Heart
2023-10-08 08:04:11 +00:00
game - > play . heart = FP_Entity_CreateHeart ( game , base_mid_p , " Heart " ) ;
2023-10-14 14:31:33 +00:00
play - > tile_size = 37 ;
2023-10-15 10:02:28 +00:00
// NOTE: Camera ================================================================================
2023-10-15 11:48:53 +00:00
play - > camera . world_pos = base_mid_p - Dqn_V2_InitV2I ( platform - > core . window_size * .5f ) ;
play - > camera . scale = Dqn_V2_InitNx1 ( 1 ) ;
play - > camera . size = Dqn_V2_InitNx2 ( 1826 , 1046 ) ;
2023-10-08 05:14:31 +00:00
}
2023-10-14 06:21:23 +00:00
# if defined(DQN_OS_WIN32)
# define FP_DLL_FUNCTION extern "C" __declspec(dllexport)
# else
# define FP_DLL_FUNCTION
# endif
FP_DLL_FUNCTION
2023-10-08 05:14:31 +00:00
void TELY_DLL_Reload ( void * user_data )
{
TELY_Platform * platform = DQN_CAST ( TELY_Platform * ) user_data ;
Dqn_Library_SetPointer ( platform - > core . dqn_lib ) ;
}
2023-10-14 06:21:23 +00:00
FP_DLL_FUNCTION
2023-10-08 05:14:31 +00:00
void TELY_DLL_Init ( void * user_data )
{
TELY_Platform * platform = DQN_CAST ( TELY_Platform * ) user_data ;
TELY_DLL_Reload ( user_data ) ;
FP_UnitTests ( platform ) ;
// NOTE: TELY Game =============================================================================
TELY_Assets * assets = & platform - > assets ;
FP_Game * game = Dqn_Arena_New ( & platform - > arena , FP_Game , Dqn_ZeroMem_Yes ) ;
2023-10-08 09:10:04 +00:00
uint16_t font_size = 18 ;
game - > inter_regular_font_large = platform - > func_load_font ( assets , DQN_STRING8 ( " Inter (Regular) " ) , DQN_STRING8 ( " Data/Inter-Regular.otf " ) , DQN_CAST ( uint16_t ) ( font_size * 5.f ) ) ;
game - > inter_regular_font = platform - > func_load_font ( assets , DQN_STRING8 ( " Inter (Regular) " ) , DQN_STRING8 ( " Data/Inter-Regular.otf " ) , DQN_CAST ( uint16_t ) ( font_size ) ) ;
game - > inter_italic_font = platform - > func_load_font ( assets , DQN_STRING8 ( " Inter (Italic) " ) , DQN_STRING8 ( " Data/Inter-Italic.otf " ) , font_size ) ;
game - > jetbrains_mono_font = platform - > func_load_font ( assets , DQN_STRING8 ( " JetBrains Mono NL (Regular) " ) , DQN_STRING8 ( " Data/JetBrainsMonoNL-Regular.ttf " ) , font_size ) ;
game - > talkco_font = platform - > func_load_font ( assets , DQN_STRING8 ( " Talkco " ) , DQN_STRING8 ( " Data/Talkco.otf " ) , font_size ) ;
game - > talkco_font_large = platform - > func_load_font ( assets , DQN_STRING8 ( " Talkco " ) , DQN_STRING8 ( " Data/Talkco.otf " ) , DQN_CAST ( uint16_t ) ( font_size * 1.5f ) ) ;
game - > audio [ FP_GameAudio_TestAudio ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Test Audio " ) , DQN_STRING8 ( " Data/Audio/Purrple Cat - Moonwinds.qoa " ) ) ;
game - > audio [ FP_GameAudio_TerryHit ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Terry Hit " ) , DQN_STRING8 ( " Data/Audio/terry_hit.ogg " ) ) ;
game - > audio [ FP_GameAudio_Smooch ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Smooch " ) , DQN_STRING8 ( " Data/Audio/smooch.mp3 " ) ) ;
game - > audio [ FP_GameAudio_Woosh ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Woosh " ) , DQN_STRING8 ( " Data/Audio/woosh.ogg " ) ) ;
game - > audio [ FP_GameAudio_Ching ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Ching " ) , DQN_STRING8 ( " Data/Audio/ching.ogg " ) ) ;
game - > audio [ FP_GameAudio_Church ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Church " ) , DQN_STRING8 ( " Data/Audio/church.ogg " ) ) ;
game - > audio [ FP_GameAudio_Plane ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Plane " ) , DQN_STRING8 ( " Data/Audio/airport.ogg " ) ) ;
game - > audio [ FP_GameAudio_Club ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Club " ) , DQN_STRING8 ( " Data/Audio/club_terry.ogg " ) ) ;
game - > audio [ FP_GameAudio_Dog ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Dog " ) , DQN_STRING8 ( " Data/Audio/dog.ogg " ) ) ;
2023-10-08 08:00:13 +00:00
game - > audio [ FP_GameAudio_MerchantTerry ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Door " ) , DQN_STRING8 ( " Data/Audio/merchant_terry.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantGhost ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Ghost " ) , DQN_STRING8 ( " Data/Audio/merchant_ghost.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantGym ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Gym " ) , DQN_STRING8 ( " Data/Audio/merchant_gym.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantPhone ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Phone " ) , DQN_STRING8 ( " Data/Audio/merchant_tech.ogg " ) ) ;
2023-10-08 09:10:04 +00:00
game - > audio [ FP_GameAudio_Message ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Message " ) , DQN_STRING8 ( " Data/Audio/message.ogg " ) ) ;
2023-10-09 11:35:30 +00:00
game - > audio [ FP_GameAudio_Monkey ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Monkey " ) , DQN_STRING8 ( " Data/Audio/monkey.ogg " ) ) ;
game - > audio [ FP_GameAudio_PortalDestroy ] = platform - > func_load_audio ( assets , DQN_STRING8 ( " Portal Destroy " ) , DQN_STRING8 ( " Data/Audio/portal_destroy.ogg " ) ) ;
2023-10-08 05:14:31 +00:00
platform - > user_data = game ;
{
TELY_AssetSpriteSheet * sheet = & game - > hero_sprite_sheet ;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
Dqn_String8 sheet_path = Dqn_FsPath_ConvertF ( scratch . arena , " %.*s/adventurer-v1.5-sheet.png " , DQN_STRING_FMT ( assets - > textures_dir ) ) ;
sheet - > tex_handle = platform - > func_load_texture ( assets , DQN_STRING8 ( " Hero " ) , sheet_path ) ;
sheet - > sprite_count = 109 ;
sheet - > sprites_per_row = 7 ;
sheet - > sprite_size = Dqn_V2I_InitNx2 ( 50 , 37 ) ;
TELY_AssetSpriteAnimation hero_anims [ ] = {
{ DQN_STRING8 ( " Everything " ) , /*index*/ 0 , /*count*/ sheet - > sprite_count , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Idle " ) , /*index*/ 0 , /*count*/ 3 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 4.f ) } ,
{ DQN_STRING8 ( " Run " ) , /*index*/ 8 , /*count*/ 6 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 8.f ) } ,
{ DQN_STRING8 ( " Jump " ) , /*index*/ 14 , /*count*/ 10 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Floor slide " ) , /*index*/ 24 , /*count*/ 5 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Unknown " ) , /*index*/ 29 , /*count*/ 9 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Attack A " ) , /*index*/ 42 , /*count*/ 7 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Attack B " ) , /*index*/ 49 , /*count*/ 4 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 8.f ) } ,
{ DQN_STRING8 ( " Attack C " ) , /*index*/ 53 , /*count*/ 6 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Hurt A " ) , /*index*/ 59 , /*count*/ 5 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Hurt B " ) , /*index*/ 64 , /*count*/ 5 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Unsheath sword " ) , /*index*/ 69 , /*count*/ 4 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Sheath sword " ) , /*index*/ 73 , /*count*/ 4 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Air drift " ) , /*index*/ 77 , /*count*/ 2 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Air drop " ) , /*index*/ 79 , /*count*/ 2 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Ladder climb " ) , /*index*/ 81 , /*count*/ 4 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Chi push " ) , /*index*/ 85 , /*count*/ 8 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Leap slice A " ) , /*index*/ 93 , /*count*/ 7 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Leap slice B " ) , /*index*/ 100 , /*count*/ 3 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
{ DQN_STRING8 ( " Leap slice C " ) , /*index*/ 103 , /*count*/ 6 , /*ms_per_frame*/ DQN_CAST ( uint32_t ) ( 1000.f / 12.f ) } ,
} ;
game - > hero_sprite_anims = Dqn_Slice_Alloc < TELY_AssetSpriteAnimation > ( & platform - > arena , DQN_ARRAY_UCOUNT ( hero_anims ) , Dqn_ZeroMem_No ) ;
DQN_MEMCPY ( game - > hero_sprite_anims . data , & hero_anims , sizeof ( hero_anims [ 0 ] ) * DQN_ARRAY_UCOUNT ( hero_anims ) ) ;
}
// NOTE: Load sprite sheets ====================================================================
game - > atlas_sprite_sheet = FP_LoadSpriteSheetFromSpec ( platform , assets , & platform - > arena , DQN_STRING8 ( " atlas " ) ) ;
FP_PlayReset ( game , platform ) ;
2023-09-16 02:21:24 +00:00
}
2023-10-07 14:29:50 +00:00
struct FP_GetClosestPortalMonkeyResult
{
FP_GameEntityHandle entity ;
Dqn_f32 dist ;
} ;
FP_GetClosestPortalMonkeyResult FP_GetClosestPortalMonkey ( FP_Game * game , FP_GameEntityHandle handle )
{
// NOTE: Check if we are nearby a monkey and picking it up
FP_GetClosestPortalMonkeyResult result = { } ;
result . dist = DQN_F32_MAX ;
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityHandle portal_monkey_handle : game - > play . portal_monkeys ) {
2023-10-07 14:29:50 +00:00
FP_GameEntity * portal_monkey = FP_Game_GetEntity ( game , portal_monkey_handle ) ;
if ( FP_Game_IsNilEntity ( portal_monkey ) )
continue ;
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos ( game , handle ) ;
Dqn_V2 portal_monkey_pos = FP_Game_CalcEntityWorldPos ( game , portal_monkey_handle ) ;
Dqn_f32 dist_squared = Dqn_V2_LengthSq_V2x2 ( entity_pos , portal_monkey_pos ) ;
if ( dist_squared < result . dist ) {
result . dist = dist_squared ;
result . entity = portal_monkey_handle ;
}
}
return result ;
}
2023-10-14 01:01:22 +00:00
static void FP_AppendMobSpawnerWaypoints ( FP_Game * game , FP_GameEntityHandle src_handle , FP_GameEntityHandle dest_handle )
{
FP_GameEntity * src = FP_Game_GetEntity ( game , src_handle ) ;
FP_GameEntity * dest = FP_Game_GetEntity ( game , dest_handle ) ;
if ( FP_Game_IsNilEntity ( src ) | | FP_Game_IsNilEntity ( dest ) )
return ;
Dqn_f32 one_meter = FP_Game_MetersToPixelsNx1 ( game - > play , 1.f ) ;
for ( FP_GameEntity * waypoint_entity = src - > first_child ; waypoint_entity ; waypoint_entity = waypoint_entity - > next ) {
if ( ( waypoint_entity - > flags & FP_GameEntityFlag_MobSpawnerWaypoint ) = = 0 )
continue ;
// NOTE: Add the waypoint
FP_SentinelListLink < FP_GameWaypoint > * waypoint = FP_SentinelList_Make ( & dest - > waypoints , game - > play . chunk_pool ) ;
waypoint - > data . entity = waypoint_entity - > handle ;
waypoint - > data . arrive = FP_GameWaypointArrive_WhenWithinEntitySize ;
waypoint - > data . value = 1.5f ;
uint32_t min_vary = DQN_CAST ( uint32_t ) ( one_meter * .5f ) ;
uint32_t max_vary = DQN_CAST ( uint32_t ) ( one_meter * 2.f ) ;
waypoint - > data . offset + = Dqn_V2_InitNx2 ( DQN_CAST ( Dqn_f32 ) Dqn_PCG32_Range ( & game - > play . rng , min_vary , max_vary ) ,
DQN_CAST ( Dqn_f32 ) Dqn_PCG32_Range ( & game - > play . rng , min_vary , max_vary ) ) ;
if ( Dqn_PCG32_NextF32 ( & game - > play . rng ) > = .5f )
waypoint - > data . offset . x * = - 1 ;
if ( Dqn_PCG32_NextF32 ( & game - > play . rng ) > = .5f )
waypoint - > data . offset . y * = - 1 ;
}
}
2023-10-01 11:16:49 +00:00
void FP_EntityActionStateMachine ( FP_Game * game , TELY_Audio * audio , TELY_PlatformInput * input , FP_GameEntity * entity , Dqn_V2 * acceleration_meters_per_s )
2023-09-24 07:01:21 +00:00
{
2023-10-01 05:55:48 +00:00
TELY_AssetSpriteSheet * sheet = & game - > atlas_sprite_sheet ;
2023-09-24 12:00:08 +00:00
FP_GameEntityAction * action = & entity - > action ;
2023-10-08 05:14:31 +00:00
bool const we_are_clicked_entity = entity - > handle = = game - > play . clicked_entity ;
2023-09-24 13:08:30 +00:00
bool const entity_has_velocity = entity - > velocity . x | | entity - > velocity . y ;
2023-10-08 02:22:08 +00:00
bool const entering_new_state = entity - > alive_time_s = = 0.f | | action - > state ! = action - > next_state ;
2023-10-08 05:14:31 +00:00
bool const action_has_finished = ! entering_new_state & & game - > play . clock_ms > = action - > end_at_clock_ms ;
2023-09-24 13:08:30 +00:00
action - > state = action - > next_state ;
2023-09-24 07:01:21 +00:00
2023-10-02 11:38:36 +00:00
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , entity - > type , entity - > action . state , entity - > direction ) ;
switch ( entity - > type ) {
2023-09-24 08:16:14 +00:00
case FP_EntityType_Terry : {
2023-10-02 11:38:36 +00:00
FP_EntityTerryState * state = DQN_CAST ( FP_EntityTerryState * ) & action - > state ;
2023-09-24 11:54:08 +00:00
2023-10-07 14:29:50 +00:00
{
FP_GameEntity * portal_monkey = FP_Game_GetEntity ( game , entity - > carried_monkey ) ;
if ( ! FP_Game_IsNilEntity ( portal_monkey ) ) {
Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
Dqn_f32 trig_t = DQN_CAST ( Dqn_f32 ) input - > timer_s * 2.f ;
Dqn_V2 offset = Dqn_V2_InitNx2 ( DQN_COSF ( trig_t ) , DQN_SINF ( trig_t ) ) * 18.f ;
portal_monkey - > local_pos = Dqn_Rect_InterpolatedPoint ( hit_box , Dqn_V2_InitNx2 ( 0.5f , - 0.75f ) ) + offset ;
}
}
2023-10-08 07:02:10 +00:00
if ( entity - > hp < = 0 & & * state ! = FP_EntityTerryState_DeadGhost ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_DeadGhost ) ;
break ;
}
2023-09-24 12:00:08 +00:00
switch ( * state ) {
case FP_EntityTerryState_Idle : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 13:08:30 +00:00
}
if ( we_are_clicked_entity ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Nil ) {
2023-10-07 14:29:50 +00:00
bool picked_up_monkey_this_frame = false ;
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
// NOTE: Check if we are nearby a monkey and picking it up
FP_GetClosestPortalMonkeyResult closest_monkey = FP_GetClosestPortalMonkey ( game , entity - > handle ) ;
2023-10-08 05:14:31 +00:00
if ( closest_monkey . dist < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 2.f ) ) ) {
2023-10-07 14:29:50 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) ) {
entity - > carried_monkey = closest_monkey . entity ;
picked_up_monkey_this_frame = true ;
2023-10-09 11:35:30 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Monkey ] , 1.f ) ;
2023-10-07 14:29:50 +00:00
}
}
2023-10-07 01:21:31 +00:00
}
2023-10-07 14:29:50 +00:00
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-07 14:29:50 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Attack ) ;
} else if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_K ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_Y ) ) {
2023-10-07 14:29:50 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_RangeAttack ) ;
}
} else {
if ( ! picked_up_monkey_this_frame & & TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) ) {
FP_GameEntity * portal_monkey = FP_Game_GetEntity ( game , entity - > carried_monkey ) ;
portal_monkey - > local_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
entity - > carried_monkey = { } ;
}
}
}
2023-10-07 01:21:31 +00:00
if ( action - > next_state = = action - > state & & ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Run ) ;
2023-09-24 08:05:28 +00:00
}
2023-09-24 07:01:21 +00:00
}
2023-09-24 12:00:08 +00:00
} break ;
2023-09-24 07:01:21 +00:00
2023-09-29 06:17:55 +00:00
case FP_EntityTerryState_Attack : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 13:08:30 +00:00
}
if ( action_has_finished ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Idle ) ;
2023-09-24 07:01:21 +00:00
}
2023-09-24 12:00:08 +00:00
} break ;
2023-09-24 07:01:21 +00:00
2023-10-05 10:10:50 +00:00
case FP_EntityTerryState_RangeAttack : {
2023-10-04 12:50:31 +00:00
if ( entering_new_state ) {
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-06 10:48:05 +00:00
entity - > terry_mobile_data_plan - = FP_TERRY_MOBILE_DATA_PER_RANGE_ATTACK ;
2023-10-04 12:50:31 +00:00
}
if ( action_has_finished ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Idle ) ;
}
} break ;
2023-09-24 12:00:08 +00:00
case FP_EntityTerryState_Run : {
2023-10-02 11:38:36 +00:00
if ( entering_new_state | | action - > sprite . anim - > label ! = render_data . anim_name ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 13:08:30 +00:00
}
2023-09-24 12:00:08 +00:00
if ( we_are_clicked_entity ) {
2023-10-07 14:29:50 +00:00
bool picked_up_monkey_this_frame = false ;
2023-10-08 05:14:31 +00:00
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Nil ) {
2023-10-07 14:29:50 +00:00
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
// NOTE: Check if we are nearby a monkey and picking it up
FP_GetClosestPortalMonkeyResult closest_monkey = FP_GetClosestPortalMonkey ( game , entity - > handle ) ;
2023-10-08 05:14:31 +00:00
if ( closest_monkey . dist < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 2.f ) ) ) {
2023-10-07 14:29:50 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) ) {
entity - > carried_monkey = closest_monkey . entity ;
picked_up_monkey_this_frame = true ;
2023-10-09 11:35:30 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Monkey ] , 1.f ) ;
2023-10-07 14:29:50 +00:00
}
}
}
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-07 14:29:50 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Attack ) ;
} else if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_K ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_Y ) ) {
2023-10-07 14:29:50 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_RangeAttack ) ;
}
2023-10-07 01:21:31 +00:00
}
}
2023-10-07 14:29:50 +00:00
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_LeftControl ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_A ) ) {
2023-10-07 14:29:50 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Dash ) ;
}
} else {
if ( ! picked_up_monkey_this_frame & & TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) ) {
FP_GameEntity * portal_monkey = FP_Game_GetEntity ( game , entity - > carried_monkey ) ;
portal_monkey - > local_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
entity - > carried_monkey = { } ;
}
2023-09-24 08:05:28 +00:00
}
2023-09-24 07:01:21 +00:00
}
2023-09-29 05:44:02 +00:00
if ( ! entity_has_velocity ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Idle ) ;
2023-09-24 12:00:08 +00:00
}
} break ;
2023-09-24 07:01:21 +00:00
2023-09-24 12:00:08 +00:00
case FP_EntityTerryState_Dash : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-01 11:16:49 +00:00
uint64_t duration_ms = 250 ;
2023-10-02 11:38:36 +00:00
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-01 11:16:49 +00:00
* acceleration_meters_per_s * = 35.f ;
2023-10-07 04:29:56 +00:00
2023-10-07 06:55:34 +00:00
entity - > stamina - = FP_TERRY_DASH_STAMINA_COST ;
2023-10-08 05:21:39 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Woosh ] , 1.f ) ;
2023-10-07 06:55:34 +00:00
2023-10-07 04:29:56 +00:00
#if 0
FP_GameRenderSprite * cosmetic_sprite = Dqn_FArray_Make ( & entity - > extra_cosmetic_anims , Dqn_ZeroMem_Yes ) ;
if ( cosmetic_sprite ) {
cosmetic_sprite - > asset = TELY_Asset_MakeAnimatedSprite ( sheet , g_anim_names . terry_ghost , TELY_AssetFlip_No ) ;
2023-10-08 05:14:31 +00:00
cosmetic_sprite - > started_at_clock_ms = game - > play . clock_ms ;
2023-10-07 04:29:56 +00:00
cosmetic_sprite - > height . meters = entity - > sprite_height . meters ;
uint32_t max_rng_dist_x = DQN_CAST ( uint32_t ) ( FP_Game_MetersToPixelsNx1 ( game , entity - > sprite_height . meters ) * 1.f ) ;
2023-10-08 05:14:31 +00:00
uint32_t rng_x = Dqn_PCG32_Range ( & game - > play . rng , DQN_CAST ( uint32_t ) 0 , max_rng_dist_x ) ;
2023-10-07 04:29:56 +00:00
cosmetic_sprite - > offset . x = rng_x - ( max_rng_dist_x * .5f ) ;
uint32_t max_rng_dist_y = DQN_CAST ( uint32_t ) ( FP_Game_MetersToPixelsNx1 ( game , entity - > sprite_height . meters ) * .25f ) ;
2023-10-08 05:14:31 +00:00
uint32_t rng_y = Dqn_PCG32_Range ( & game - > play . rng , DQN_CAST ( uint32_t ) 0 , max_rng_dist_y ) ;
2023-10-07 04:29:56 +00:00
cosmetic_sprite - > offset . y = - DQN_CAST ( Dqn_f32 ) rng_y ;
}
# endif
2023-09-24 13:08:30 +00:00
}
2023-09-24 07:01:21 +00:00
2023-10-08 05:14:31 +00:00
entity - > action . sprite_alpha = Dqn_PCG32_NextF32 ( & game - > play . rng ) ;
2023-10-07 04:29:56 +00:00
if ( action_has_finished ) {
2023-10-01 11:16:49 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Run ) ;
2023-10-07 04:29:56 +00:00
entity - > action . sprite_alpha = 1.f ;
}
2023-09-24 12:00:08 +00:00
} break ;
2023-10-08 07:02:10 +00:00
case FP_EntityTerryState_DeadGhost : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
entity - > action . sprite_play_once = true ;
entity - > faction = FP_GameEntityFaction_Nil ;
}
Dqn_f32 hp_t = entity - > hp / DQN_CAST ( Dqn_f32 ) entity - > hp_cap ;
if ( hp_t > = 0.9f )
entity - > action . sprite_alpha = Dqn_PCG32_NextF32 ( & game - > play . rng ) ;
if ( entity - > hp > = entity - > hp_cap ) {
entity - > faction = FP_GameEntityFaction_Friendly ;
entity - > action . sprite_alpha = 1.f ;
entity - > action . sprite_play_once = false ;
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Idle ) ;
}
} break ;
2023-09-24 07:01:21 +00:00
}
} break ;
2023-10-07 14:29:50 +00:00
2023-09-24 08:16:14 +00:00
case FP_EntityType_Smoochie : {
2023-09-24 11:54:08 +00:00
FP_EntitySmoochieState * state = DQN_CAST ( FP_EntitySmoochieState * ) & action - > state ;
2023-09-24 12:00:08 +00:00
switch ( * state ) {
case FP_EntitySmoochieState_Idle : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 13:08:30 +00:00
}
if ( we_are_clicked_entity ) {
2023-09-24 12:00:08 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Attack ) ;
2023-10-01 11:16:49 +00:00
} else if ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Run ) ;
2023-09-24 08:05:28 +00:00
}
2023-09-24 07:01:21 +00:00
}
2023-09-26 14:09:14 +00:00
if ( entity_has_velocity ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Run ) ;
2023-09-26 14:09:14 +00:00
}
2023-09-24 12:00:08 +00:00
} break ;
2023-09-29 06:17:55 +00:00
case FP_EntitySmoochieState_Attack : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-30 13:27:19 +00:00
// NOTE: Deal with this further down with the gameplay attack code.
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Smooch ] , 1.f ) ;
2023-09-24 13:08:30 +00:00
}
2023-09-26 13:58:48 +00:00
// NOTE: Check if the heart animation is playing
bool has_heart_flourish = false ;
DQN_FOR_UINDEX ( anim_index , entity - > extra_cosmetic_anims . size ) {
FP_GameRenderSprite * sprite = entity - > extra_cosmetic_anims . data + anim_index ;
if ( sprite - > asset . anim - > label = = g_anim_names . smoochie_attack_heart ) {
has_heart_flourish = true ;
break ;
}
}
// NOTE: If we don't have the anim playing make one
if ( ! has_heart_flourish ) {
FP_GameRenderSprite * cosmetic_sprite = Dqn_FArray_Make ( & entity - > extra_cosmetic_anims , Dqn_ZeroMem_Yes ) ;
if ( cosmetic_sprite ) {
cosmetic_sprite - > asset = TELY_Asset_MakeAnimatedSprite ( sheet , g_anim_names . smoochie_attack_heart , TELY_AssetFlip_No ) ;
2023-10-08 05:14:31 +00:00
cosmetic_sprite - > started_at_clock_ms = game - > play . clock_ms ;
2023-09-26 13:58:48 +00:00
cosmetic_sprite - > height . meters = entity - > sprite_height . meters * .35f ;
cosmetic_sprite - > loop = true ;
2023-10-08 05:14:31 +00:00
uint32_t max_rng_dist_x = DQN_CAST ( uint32_t ) ( FP_Game_MetersToPixelsNx1 ( game - > play , entity - > sprite_height . meters ) * 1.f ) ;
uint32_t rng_x = Dqn_PCG32_Range ( & game - > play . rng , DQN_CAST ( uint32_t ) 0 , max_rng_dist_x ) ;
2023-09-26 13:58:48 +00:00
cosmetic_sprite - > offset . x = rng_x - ( max_rng_dist_x * .5f ) ;
2023-10-08 05:14:31 +00:00
uint32_t max_rng_dist_y = DQN_CAST ( uint32_t ) ( FP_Game_MetersToPixelsNx1 ( game - > play , entity - > sprite_height . meters ) * .25f ) ;
uint32_t rng_y = Dqn_PCG32_Range ( & game - > play . rng , DQN_CAST ( uint32_t ) 0 , max_rng_dist_y ) ;
2023-09-26 13:58:48 +00:00
cosmetic_sprite - > offset . y = - DQN_CAST ( Dqn_f32 ) rng_y ;
}
}
2023-09-24 13:08:30 +00:00
if ( action_has_finished ) {
2023-09-26 13:58:48 +00:00
// NOTE: Ensure the heart animation terminates by removing the loop
DQN_FOR_UINDEX ( anim_index , entity - > extra_cosmetic_anims . size ) {
FP_GameRenderSprite * sprite = entity - > extra_cosmetic_anims . data + anim_index ;
if ( sprite - > asset . anim - > label = = g_anim_names . smoochie_attack_heart )
sprite - > loop = false ;
}
// NOTE: Transition out of the action
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Idle ) ;
2023-09-24 12:00:08 +00:00
}
} break ;
2023-09-26 13:58:48 +00:00
case FP_EntitySmoochieState_HurtSide : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 12:00:08 +00:00
}
2023-09-24 13:08:30 +00:00
if ( action_has_finished )
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Idle ) ;
2023-09-24 12:00:08 +00:00
} break ;
2023-09-24 07:01:21 +00:00
2023-09-28 11:38:26 +00:00
case FP_EntitySmoochieState_Death : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-05 10:10:50 +00:00
entity - > local_hit_box_size = { } ;
2023-09-28 11:38:26 +00:00
}
2023-10-01 11:16:49 +00:00
if ( action_has_finished )
2023-09-28 11:38:26 +00:00
FP_Game_DeleteEntity ( game , entity - > handle ) ;
} break ;
2023-09-24 12:00:08 +00:00
case FP_EntitySmoochieState_Run : {
2023-10-02 11:38:36 +00:00
if ( entering_new_state | | action - > sprite . anim - > label ! = render_data . anim_name ) {
TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite ( sheet , render_data . anim_name , render_data . flip ) ;
2023-09-24 13:08:30 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , sprite ) ;
}
2023-09-24 12:00:08 +00:00
if ( we_are_clicked_entity ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Attack ) ;
2023-09-24 08:05:28 +00:00
}
2023-09-24 07:01:21 +00:00
}
2023-09-26 13:58:48 +00:00
if ( ! entity_has_velocity ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Idle ) ;
2023-09-24 12:00:08 +00:00
}
} break ;
2023-09-24 07:01:21 +00:00
}
2023-09-28 11:38:26 +00:00
if ( entity - > is_dying & & * state ! = FP_EntitySmoochieState_Death ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Death ) ;
2023-09-28 11:38:26 +00:00
}
2023-09-24 07:01:21 +00:00
} break ;
2023-09-24 08:05:28 +00:00
2023-09-27 04:47:27 +00:00
case FP_EntityType_Clinger : {
FP_EntityClingerState * state = DQN_CAST ( FP_EntityClingerState * ) & action - > state ;
switch ( * state ) {
case FP_EntityClingerState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-27 04:47:27 +00:00
}
if ( we_are_clicked_entity ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Attack ) ;
2023-10-01 11:16:49 +00:00
} else if ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Run ) ;
2023-09-27 04:47:27 +00:00
}
}
if ( entity_has_velocity ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Run ) ;
2023-09-27 04:47:27 +00:00
}
} break ;
case FP_EntityClingerState_Attack : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-27 04:47:27 +00:00
}
if ( action_has_finished )
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Idle ) ;
2023-09-27 04:47:27 +00:00
} break ;
case FP_EntityClingerState_Death : {
2023-09-28 11:38:26 +00:00
if ( entering_new_state ) {
TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite ( sheet , g_anim_names . clinger_death , TELY_AssetFlip_No ) ;
uint64_t duration_ms = sprite . anim - > count * sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , sprite ) ;
2023-10-05 10:10:50 +00:00
entity - > local_hit_box_size = { } ;
2023-09-28 11:38:26 +00:00
}
2023-10-01 11:16:49 +00:00
if ( action_has_finished )
2023-09-28 11:38:26 +00:00
FP_Game_DeleteEntity ( game , entity - > handle ) ;
2023-09-27 04:47:27 +00:00
} break ;
case FP_EntityClingerState_Run : {
2023-10-02 11:38:36 +00:00
if ( entering_new_state | | action - > sprite . anim - > label ! = render_data . anim_name ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-27 04:47:27 +00:00
}
if ( we_are_clicked_entity ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Attack ) ;
2023-09-27 04:47:27 +00:00
}
}
if ( ! entity_has_velocity ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Idle ) ;
2023-09-27 04:47:27 +00:00
}
} break ;
}
2023-09-28 11:38:26 +00:00
if ( entity - > is_dying & & * state ! = FP_EntityClingerState_Death ) {
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Death ) ;
2023-09-28 11:38:26 +00:00
}
2023-09-27 04:47:27 +00:00
} break ;
2023-10-01 06:50:32 +00:00
case FP_EntityType_MerchantTerry : {
FP_EntityMerchantTerryState * state = DQN_CAST ( FP_EntityMerchantTerryState * ) & action - > state ;
2023-09-24 12:00:08 +00:00
switch ( * state ) {
2023-10-01 06:50:32 +00:00
case FP_EntityMerchantTerryState_Idle : {
2023-09-24 13:08:30 +00:00
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-01 06:50:32 +00:00
}
} break ;
}
} break ;
case FP_EntityType_MerchantPhoneCompany : {
FP_EntityMerchantPhoneCompanyState * state = DQN_CAST ( FP_EntityMerchantPhoneCompanyState * ) & action - > state ;
switch ( * state ) {
case FP_EntityMerchantPhoneCompanyState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-01 06:50:32 +00:00
}
} break ;
}
} break ;
case FP_EntityType_MerchantGym : {
FP_EntityMerchantGymState * state = DQN_CAST ( FP_EntityMerchantGymState * ) & action - > state ;
switch ( * state ) {
case FP_EntityMerchantGymState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-01 06:50:32 +00:00
}
} break ;
}
} break ;
case FP_EntityType_MerchantGraveyard : {
FP_EntityMerchantGraveyardState * state = DQN_CAST ( FP_EntityMerchantGraveyardState * ) & action - > state ;
switch ( * state ) {
case FP_EntityMerchantGraveyardState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-24 12:00:08 +00:00
}
2023-09-24 13:08:30 +00:00
2023-09-24 12:00:08 +00:00
} break ;
2023-09-24 08:05:28 +00:00
}
} break ;
2023-09-29 06:17:55 +00:00
2023-09-29 07:42:58 +00:00
case FP_EntityType_ClubTerry : {
FP_EntityClubTerryState * state = DQN_CAST ( FP_EntityClubTerryState * ) & action - > state ;
switch ( * state ) {
case FP_EntityClubTerryState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-29 07:42:58 +00:00
}
2023-09-30 06:51:59 +00:00
} break ;
case FP_EntityClubTerryState_PartyTime : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = 5000 ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-30 06:51:59 +00:00
}
2023-09-29 07:42:58 +00:00
2023-09-30 06:51:59 +00:00
if ( action_has_finished ) {
2023-10-06 09:48:20 +00:00
if ( ! FP_Game_IsNilEntityHandle ( game , entity - > building_patron ) ) {
FP_GameEntity * patron = FP_Game_GetEntity ( game , entity - > building_patron ) ;
patron - > flags & = ~ FP_GameEntityFlag_OccupiedInBuilding ;
2023-09-30 06:51:59 +00:00
patron - > base_acceleration_per_s . meters * = .5f ;
2023-10-10 21:40:21 +00:00
patron - > is_drunk = true ;
2023-09-30 06:51:59 +00:00
}
2023-10-06 09:48:20 +00:00
entity - > building_patron = { } ;
2023-10-01 05:47:40 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityClubTerryState_Idle ) ;
2023-09-30 06:51:59 +00:00
}
2023-09-29 07:42:58 +00:00
} break ;
}
2023-09-30 06:51:59 +00:00
2023-09-29 07:42:58 +00:00
} break ;
2023-09-30 09:14:35 +00:00
case FP_EntityType_Map : {
2023-10-04 12:50:31 +00:00
FP_EntityMapState * state = DQN_CAST ( FP_EntityMapState * ) & action - > state ;
2023-09-30 09:14:35 +00:00
switch ( * state ) {
case FP_EntityMapState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-09-30 09:14:35 +00:00
}
} break ;
}
} break ;
2023-10-01 05:11:08 +00:00
case FP_EntityType_Heart : {
2023-10-04 12:50:31 +00:00
FP_EntityHeartState * state = DQN_CAST ( FP_EntityHeartState * ) & action - > state ;
2023-10-01 05:11:08 +00:00
switch ( * state ) {
case FP_EntityHeartState_Idle : {
if ( entering_new_state ) {
2023-10-02 11:38:36 +00:00
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-01 05:11:08 +00:00
}
} break ;
}
} break ;
2023-10-04 12:50:31 +00:00
case FP_EntityType_AirportTerry : {
FP_EntityAirportTerryState * state = DQN_CAST ( FP_EntityAirportTerryState * ) & action - > state ;
switch ( * state ) {
case FP_EntityAirportTerryState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
} break ;
case FP_EntityAirportTerryState_FlyPassenger : {
2023-10-06 09:48:20 +00:00
if ( entering_new_state ) {
uint64_t duration_ms = 5000 ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( action_has_finished ) {
2023-10-07 09:31:01 +00:00
Dqn_V2 world_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
FP_GameEntityHandle plane_handle = FP_Entity_CreateAirportTerryPlane ( game , world_pos , " Aiport Terry Plane " ) ;
FP_GameEntity * plane = FP_Game_GetEntity ( game , plane_handle ) ;
// NOTE: Transfer the entity to the plane
plane - > building_patron = entity - > building_patron ;
2023-10-06 09:48:20 +00:00
entity - > building_patron = { } ;
2023-10-07 09:31:01 +00:00
// NOTE: Add a waypoint for the plane to the mob spawn
2023-10-14 01:01:22 +00:00
FP_GameEntityHandle mob_spawner = { } ;
Dqn_V2 mob_spawner_pos = { } ;
2023-10-07 09:31:01 +00:00
{
2023-10-14 01:01:22 +00:00
uint32_t mob_spawner_index = Dqn_PCG32_Range ( & game - > play . rng , 0 , DQN_CAST ( uint32_t ) game - > play . mob_spawners . size ) ;
mob_spawner = game - > play . mob_spawners . data [ mob_spawner_index ] ;
mob_spawner_pos = FP_Game_CalcEntityWorldPos ( game , mob_spawner ) ;
plane - > waypoints = FP_SentinelList_Init < FP_GameWaypoint > ( game - > play . chunk_pool ) ;
2023-10-07 09:31:01 +00:00
FP_SentinelListLink < FP_GameWaypoint > * link = FP_SentinelList_MakeBefore ( & plane - > waypoints ,
FP_SentinelList_Front ( & plane - > waypoints ) ,
2023-10-08 05:14:31 +00:00
game - > play . chunk_pool ) ;
2023-10-07 09:31:01 +00:00
FP_GameWaypoint * waypoint = & link - > data ;
waypoint - > entity = mob_spawner ;
waypoint - > type = FP_GameWaypointType_ClosestSide ;
}
2023-10-14 01:01:22 +00:00
// NOTE: Update the mob's waypoints to the mob spawner waypoints
FP_GameEntity * patron = FP_Game_GetEntity ( game , plane - > building_patron ) ;
FP_SentinelList_Clear ( & patron - > waypoints , game - > play . chunk_pool ) ;
FP_AppendMobSpawnerWaypoints ( game , entity - > handle , plane - > building_patron ) ;
2023-10-06 09:48:20 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityAirportTerryState_Idle ) ;
}
2023-10-04 12:50:31 +00:00
} break ;
}
} break ;
2023-10-07 09:31:01 +00:00
case FP_EntityType_AirportTerryPlane : {
FP_EntityAirportTerryPlaneState * state = DQN_CAST ( FP_EntityAirportTerryPlaneState * ) & action - > state ;
switch ( * state ) {
case FP_EntityAirportTerryPlaneState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( ! FP_Game_IsNilEntityHandle ( game , entity - > building_patron ) )
FP_Game_EntityTransitionState ( game , entity , FP_EntityAirportTerryPlaneState_FlyPassenger ) ;
} break ;
case FP_EntityAirportTerryPlaneState_FlyPassenger : {
if ( entity - > waypoints . size = = 0 ) {
FP_GameEntity * patron = FP_Game_GetEntity ( game , entity - > building_patron ) ;
patron - > local_pos = entity - > local_pos ;
2023-10-08 08:04:11 +00:00
patron - > flags & = ~ ( FP_GameEntityFlag_OccupiedInBuilding ) ;
2023-10-07 09:31:01 +00:00
FP_Game_DeleteEntity ( game , entity - > handle ) ;
return ;
}
} break ;
}
} break ;
2023-10-04 12:50:31 +00:00
case FP_EntityType_Catfish : {
FP_EntityCatfishState * state = DQN_CAST ( FP_EntityCatfishState * ) & action - > state ;
switch ( * state ) {
case FP_EntityCatfishState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( we_are_clicked_entity ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-04 12:50:31 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Attack ) ;
} else if ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Run ) ;
}
}
if ( entity_has_velocity ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Run ) ;
}
} break ;
case FP_EntityCatfishState_Attack : {
if ( entering_new_state ) {
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( action_has_finished )
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Idle ) ;
} break ;
case FP_EntityCatfishState_Death : {
if ( entering_new_state ) {
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-05 10:10:50 +00:00
entity - > local_hit_box_size = { } ;
2023-10-04 12:50:31 +00:00
}
if ( action_has_finished )
FP_Game_DeleteEntity ( game , entity - > handle ) ;
} break ;
case FP_EntityCatfishState_Run : {
if ( entering_new_state | | action - > sprite . anim - > label ! = render_data . anim_name ) {
TELY_AssetAnimatedSprite sprite = TELY_Asset_MakeAnimatedSprite ( sheet , render_data . anim_name , render_data . flip ) ;
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , sprite ) ;
}
if ( we_are_clicked_entity ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) | |
2023-10-09 10:35:51 +00:00
TELY_Platform_InputGamepadKeyIsPressed ( input , 0 , TELY_PlatformInputGamepadKey_X ) ) {
2023-10-04 12:50:31 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Attack ) ;
}
}
if ( ! entity_has_velocity ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Idle ) ;
}
} break ;
}
if ( entity - > is_dying & & * state ! = FP_EntityCatfishState_Death ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Death ) ;
}
} break ;
case FP_EntityType_ChurchTerry : {
FP_EntityChurchTerryState * state = DQN_CAST ( FP_EntityChurchTerryState * ) & action - > state ;
switch ( * state ) {
case FP_EntityChurchTerryState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
} break ;
2023-10-06 09:48:20 +00:00
case FP_EntityChurchTerryState_ConvertPatron : {
if ( entering_new_state ) {
uint64_t duration_ms = 5000 ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( action_has_finished ) {
if ( ! FP_Game_IsNilEntityHandle ( game , entity - > building_patron ) ) {
2023-10-07 08:14:09 +00:00
FP_GameEntity * patron = FP_Game_GetEntity ( game , entity - > building_patron ) ;
2023-10-08 08:04:11 +00:00
patron - > flags & = ~ ( FP_GameEntityFlag_OccupiedInBuilding | FP_GameEntityFlag_RespondsToBuildings ) ;
2023-10-07 08:14:09 +00:00
patron - > faction = FP_GameEntityFaction_Friendly ;
patron - > converted_faction = true ;
2023-10-06 09:48:20 +00:00
}
entity - > building_patron = { } ;
FP_Game_EntityTransitionState ( game , entity , FP_EntityChurchTerryState_Idle ) ;
}
} break ;
2023-10-04 12:50:31 +00:00
}
} break ;
case FP_EntityType_KennelTerry : {
FP_EntityKennelTerryState * state = DQN_CAST ( FP_EntityKennelTerryState * ) & action - > state ;
switch ( * state ) {
case FP_EntityKennelTerryState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
} break ;
}
} break ;
2023-09-29 06:17:55 +00:00
case FP_EntityType_Nil : break ;
2023-10-04 12:50:31 +00:00
2023-09-29 06:17:55 +00:00
case FP_EntityType_Count : DQN_INVALID_CODE_PATH ; break ;
2023-10-06 10:48:05 +00:00
case FP_EntityType_PhoneMessageProjectile : break ;
2023-10-07 14:29:50 +00:00
case FP_EntityType_MobSpawner : {
FP_EntityMobSpawnerState * state = DQN_CAST ( FP_EntityMobSpawnerState * ) & action - > state ;
switch ( * state ) {
case FP_EntityMobSpawnerState_Idle : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
}
if ( ! FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityMobSpawnerState_Shutdown ) ;
}
} break ;
case FP_EntityMobSpawnerState_Shutdown : {
if ( entering_new_state ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
entity - > action . sprite_play_once = true ;
2023-10-09 11:35:30 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_PortalDestroy ] , 1.f ) ;
2023-10-07 14:29:50 +00:00
FP_Game_DeleteEntity ( game , entity - > carried_monkey ) ;
}
} break ;
}
} break ;
2023-10-14 14:31:33 +00:00
case FP_EntityType_PortalMonkey : break ;
2023-09-30 09:14:35 +00:00
}
2023-10-07 06:55:34 +00:00
switch ( entity - > type ) {
case FP_EntityType_Nil :
case FP_EntityType_AirportTerry :
case FP_EntityType_ChurchTerry :
case FP_EntityType_ClubTerry :
case FP_EntityType_Heart :
case FP_EntityType_KennelTerry :
case FP_EntityType_Map :
case FP_EntityType_MerchantGraveyard :
case FP_EntityType_MerchantGym :
case FP_EntityType_MerchantPhoneCompany :
case FP_EntityType_MerchantTerry :
case FP_EntityType_PhoneMessageProjectile :
case FP_EntityType_Count : break ;
case FP_EntityType_Terry : /*FALLTHRU*/
case FP_EntityType_Catfish : /*FALLTHRU*/
case FP_EntityType_Clinger : /*FALLTHRU*/
case FP_EntityType_Smoochie : {
bool is_attacking = false ;
bool is_range_attack = false ;
if ( entity - > type = = FP_EntityType_Catfish ) {
is_attacking = entity - > action . state = = FP_EntityCatfishState_Attack ;
} else if ( entity - > type = = FP_EntityType_Clinger ) {
is_attacking = entity - > action . state = = FP_EntityClingerState_Attack ;
} else if ( entity - > type = = FP_EntityType_Smoochie ) {
is_attacking = entity - > action . state = = FP_EntitySmoochieState_Attack ;
} else {
DQN_ASSERT ( entity - > type = = FP_EntityType_Terry ) ;
is_range_attack = entity - > action . state = = FP_EntityTerryState_RangeAttack ;
is_attacking = is_range_attack | | entity - > action . state = = FP_EntityTerryState_Attack ;
}
if ( ! is_attacking ) {
entity - > attack_processed = false ;
entity - > attack_box_size = { } ;
break ;
}
// NOTE: Position the attack box
uint64_t duration_ms = action - > sprite . anim - > count * action - > sprite . anim - > ms_per_frame ;
uint64_t midpoint_clock_ms = action - > started_at_clock_ms + ( duration_ms / 2 ) ;
DQN_ASSERT ( duration_ms > = PHYSICS_STEP ) ;
DQN_ASSERT ( action - > sprite . anim ) ;
// NOTE: Adding an attack_processed bool to make sure things only fire once
2023-10-08 05:14:31 +00:00
if ( ! entity - > attack_processed & & game - > play . clock_ms > = midpoint_clock_ms ) {
2023-10-07 06:55:34 +00:00
// NOTE: Position the attack box
if ( is_range_attack ) {
2023-10-08 05:44:48 +00:00
Dqn_V2 dir_vector = { } ;
switch ( entity - > direction ) {
case FP_GameDirection_Left : dir_vector . x = - 1.f ; break ;
case FP_GameDirection_Right : dir_vector . x = + 1.f ; break ;
case FP_GameDirection_Up : dir_vector . y = - 1.f ; break ;
case FP_GameDirection_Down : dir_vector . y = + 1.f ; break ;
case FP_GameDirection_Count : break ;
}
2023-10-08 06:25:08 +00:00
Dqn_V2 projectile_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
2023-10-08 05:14:31 +00:00
Dqn_V2 projectile_acceleration = FP_Game_MetersToPixelsV2 ( game - > play , dir_vector * 0.25f ) ;
2023-10-07 06:55:34 +00:00
FP_Entity_CreatePhoneMessageProjectile ( game ,
entity - > handle ,
projectile_pos ,
projectile_acceleration ,
" Phone Message Projectile " ) ;
2023-10-08 09:10:04 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Message ] , 1.f /*volume*/ ) ;
2023-10-07 06:55:34 +00:00
} else {
2023-10-08 05:44:48 +00:00
Dqn_FArray < Dqn_Rect , FP_GameDirection_Count > attack_boxes = FP_Game_CalcEntityMeleeAttackBoxes ( game , entity - > handle ) ;
entity - > attack_box_size = attack_boxes . data [ entity - > direction ] . size ;
entity - > attack_box_offset = attack_boxes . data [ entity - > direction ] . pos - FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
2023-10-07 06:55:34 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_TerryHit ] , 1.f ) ;
}
entity - > attack_processed = true ;
} else {
entity - > attack_box_size = { } ;
}
} break ;
2023-10-07 14:29:50 +00:00
2023-10-07 09:31:01 +00:00
case FP_EntityType_AirportTerryPlane : break ;
2023-10-14 14:31:33 +00:00
case FP_EntityType_MobSpawner :
case FP_EntityType_PortalMonkey : break ;
2023-10-07 06:55:34 +00:00
}
2023-09-30 06:11:39 +00:00
}
2023-10-08 05:21:39 +00:00
void FP_Update ( TELY_Platform * platform , FP_Game * game , TELY_PlatformInput * input , TELY_Audio * audio )
2023-09-16 07:32:25 +00:00
{
2023-09-23 06:42:22 +00:00
Dqn_Profiler_ZoneScopeWithIndex ( " FP_Update " , FP_ProfileZone_FPUpdate ) ;
2023-10-15 11:48:53 +00:00
FP_GameCameraM2x3 const camera_xforms = FP_Game_CameraModelViewM2x3 ( game - > play . camera ) ;
2023-10-08 05:14:31 +00:00
game - > play . update_counter + + ;
game - > play . clock_ms = DQN_CAST ( uint64_t ) ( platform - > input . timer_s * 1000.f ) ;
2023-10-08 02:22:08 +00:00
Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex ( DQN_STRING8 ( " FP_Update: Entity loop " ) , FP_ProfileZone_FPUpdate_EntityLoop ) ;
2023-10-08 05:14:31 +00:00
if ( game - > play . state = = FP_GameState_Play ) {
2023-10-08 02:22:08 +00:00
Dqn_V2 dir_vector = { } ;
if ( TELY_Platform_InputKeyIsReleased ( input - > mouse_left ) )
2023-10-08 05:14:31 +00:00
game - > play . clicked_entity = game - > play . prev_active_entity ;
2023-10-08 02:22:08 +00:00
// NOTE: Keyboard movement input
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_W ) )
dir_vector . y = - 1.f ;
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_A ) )
dir_vector . x = - 1.f ;
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_S ) )
dir_vector . y = + 1.f ;
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_D ) )
dir_vector . x = + 1.f ;
// NOTE: Gamepad movement input
// NOTE: button_codes 0 should be the first gamepad connected, we can
// get this working with other gamepads later
uint32_t gamepad = 0 ;
if ( input - > button_codes [ gamepad ] ) {
dir_vector . x + = input - > left_stick [ gamepad ] . x ;
dir_vector . y + = input - > left_stick [ gamepad ] . y ;
2023-10-05 12:09:39 +00:00
}
2023-10-08 02:22:08 +00:00
// TODO(doyle): Some bug, diagonal is still faster
if ( dir_vector . x & & dir_vector . y ) {
dir_vector . x * = 0.7071067811865475244f ;
dir_vector . y * = 0.7071067811865475244f ;
2023-10-05 12:09:39 +00:00
}
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Escape ) )
2023-10-09 10:35:51 +00:00
game - > play . state = FP_GameState_Pause ;
2023-10-05 10:30:56 +00:00
2023-10-08 05:14:31 +00:00
if ( game - > play . clicked_entity . id ) {
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Delete ) )
2023-10-08 05:14:31 +00:00
FP_Game_DeleteEntity ( game , game - > play . clicked_entity ) ;
2023-09-29 05:18:38 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Building selector
Dqn_usize last_building_index = DQN_ARRAY_UCOUNT ( PLACEABLE_BUILDINGS ) - 1 ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Q ) ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . build_mode_building_index < = 0 ) {
game - > play . build_mode_building_index = last_building_index ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-08 05:14:31 +00:00
game - > play . build_mode_building_index - = 1 ;
2023-09-24 07:01:21 +00:00
}
2023-10-08 02:22:08 +00:00
}
2023-09-24 07:01:21 +00:00
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_E ) ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . build_mode_building_index > = last_building_index ) {
game - > play . build_mode_building_index = 0 ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-08 05:14:31 +00:00
game - > play . build_mode_building_index + = 1 ;
2023-09-24 09:11:44 +00:00
}
2023-09-16 02:21:24 +00:00
}
2023-10-08 02:22:08 +00:00
2023-09-26 14:09:14 +00:00
} else {
2023-10-08 02:22:08 +00:00
Dqn_f32 pan_speed = 5.f ;
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_Space ) )
pan_speed * = 2.5f ;
2023-10-08 05:14:31 +00:00
game - > play . camera . world_pos + = dir_vector * pan_speed ;
2023-09-16 02:21:24 +00:00
}
2023-10-08 02:22:08 +00:00
// NOTE: Handle input ==========================================================================
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityIterator it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & it , game - > play . root_entity ) ; ) {
2023-10-08 02:22:08 +00:00
FP_GameEntity * entity = it . entity ;
entity - > alive_time_s + = PHYSICS_STEP ;
2023-10-07 08:14:09 +00:00
2023-10-08 02:22:08 +00:00
if ( entity - > flags & FP_GameEntityFlag_OccupiedInBuilding )
continue ;
// NOTE: Move entity by keyboard and gamepad ===============================================
Dqn_V2 acceleration_meters_per_s = entity - > constant_acceleration_per_s ;
2023-10-08 05:14:31 +00:00
if ( game - > play . clicked_entity = = entity - > handle ) {
2023-10-08 02:22:08 +00:00
if ( entity - > flags & ( FP_GameEntityFlag_MoveByKeyboard | FP_GameEntityFlag_MoveByGamepad ) ) {
bool move_entity = true ;
switch ( entity - > type ) {
case FP_EntityType_Terry : {
auto * state = DQN_CAST ( FP_EntityTerryState * ) & entity - > action . state ;
move_entity = * state = = FP_EntityTerryState_Run | | * state = = FP_EntityTerryState_Idle ;
2023-10-08 07:02:10 +00:00
if ( * state = = FP_EntityTerryState_DeadGhost ) {
FP_GameEntityAction const * action = & entity - > action ;
uint64_t const elapsed_ms = game - > play . clock_ms - action - > started_at_clock_ms ;
uint16_t const raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / action - > sprite . anim - > ms_per_frame ) ;
if ( raw_anim_frame > = action - > sprite . anim - > count ) {
move_entity = true ;
}
}
2023-10-08 02:22:08 +00:00
} break ;
case FP_EntityType_Smoochie : {
auto * state = DQN_CAST ( FP_EntitySmoochieState * ) & entity - > action . state ;
move_entity = * state = = FP_EntitySmoochieState_Run | | * state = = FP_EntitySmoochieState_Idle ;
} break ;
case FP_EntityType_Clinger : {
auto * state = DQN_CAST ( FP_EntityClingerState * ) & entity - > action . state ;
move_entity = * state = = FP_EntityClingerState_Run | | * state = = FP_EntityClingerState_Idle ;
} break ;
case FP_EntityType_Nil : break ;
case FP_EntityType_ClubTerry : break ;
case FP_EntityType_Count : break ;
case FP_EntityType_Map : break ;
case FP_EntityType_MerchantTerry : break ;
case FP_EntityType_MerchantGraveyard : break ;
case FP_EntityType_MerchantPhoneCompany : break ;
case FP_EntityType_Heart : break ;
case FP_EntityType_MerchantGym : break ;
case FP_EntityType_AirportTerry : break ;
case FP_EntityType_Catfish : break ;
case FP_EntityType_ChurchTerry : break ;
case FP_EntityType_KennelTerry : break ;
case FP_EntityType_PhoneMessageProjectile : break ;
case FP_EntityType_AirportTerryPlane : break ;
2023-10-14 14:31:33 +00:00
case FP_EntityType_MobSpawner :
case FP_EntityType_PortalMonkey : break ;
2023-10-08 02:22:08 +00:00
}
if ( move_entity ) {
acceleration_meters_per_s = dir_vector * entity - > base_acceleration_per_s . meters ;
if ( TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_Space ) )
acceleration_meters_per_s * = 2.5f ;
2023-10-07 08:14:09 +00:00
}
}
2023-10-08 02:22:08 +00:00
} else {
if ( entity - > velocity . x )
entity - > direction = entity - > velocity . x > 0.f ? FP_GameDirection_Right : FP_GameDirection_Left ;
else if ( entity - > velocity . y )
entity - > direction = entity - > velocity . y > 0.f ? FP_GameDirection_Down : FP_GameDirection_Up ;
}
// NOTE: Determine AI movement =============================================================
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
2023-10-08 08:04:11 +00:00
if ( entity - > flags & FP_GameEntityFlag_Aggros & & entity - > faction ! = FP_GameEntityFaction_Nil ) {
FP_GameFindClosestEntityResult closest_defender = { } ;
closest_defender . dist_squared = DQN_F32_MAX ;
closest_defender . pos = Dqn_V2_InitNx1 ( DQN_F32_MAX ) ;
FP_GameEntityFaction enemy_faction =
entity - > faction = = FP_GameEntityFaction_Friendly
? FP_GameEntityFaction_Foe
: FP_GameEntityFaction_Friendly ;
for ( FP_GameEntityIterator defender_it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & defender_it , game - > play . root_entity ) ; ) {
FP_GameEntity * it_entity = defender_it . entity ;
Dqn_V2 pos = FP_Game_CalcEntityWorldPos ( game , it_entity - > handle ) ;
Dqn_f32 dist = Dqn_V2_LengthSq_V2x2 ( pos , entity_pos ) ;
if ( it_entity - > faction ! = enemy_faction )
continue ;
2023-09-30 06:11:39 +00:00
2023-10-08 08:04:11 +00:00
if ( dist < closest_defender . dist_squared ) {
closest_defender . pos = pos ;
closest_defender . dist_squared = dist ;
closest_defender . entity = it_entity - > handle ;
2023-10-08 02:22:08 +00:00
}
2023-10-08 08:04:11 +00:00
}
2023-09-29 04:50:40 +00:00
2023-10-08 08:04:11 +00:00
Dqn_f32 aggro_dist_threshold = FP_Game_MetersToPixelsNx1 ( game - > play , 4.f ) ;
Dqn_f32 dist_to_defender = DQN_SQRTF ( closest_defender . dist_squared ) ;
if ( dist_to_defender > ( aggro_dist_threshold * 1.5f ) ) {
for ( FP_SentinelListLink < FP_GameWaypoint > * link = nullptr ; FP_SentinelList_Iterate < FP_GameWaypoint > ( & entity - > waypoints , & link ) ; ) {
FP_GameEntity * maybe_terry = FP_Game_GetEntity ( game , link - > data . entity ) ;
if ( maybe_terry - > type = = FP_EntityType_Terry ) {
link = FP_SentinelList_Erase ( & entity - > waypoints , link , game - > play . chunk_pool ) ;
2023-10-01 05:11:08 +00:00
}
2023-10-08 08:04:11 +00:00
}
} else if ( dist_to_defender < aggro_dist_threshold ) {
bool has_waypoint_to_defender = false ;
for ( FP_SentinelListLink < FP_GameWaypoint > * link = nullptr ;
! has_waypoint_to_defender & & FP_SentinelList_Iterate < FP_GameWaypoint > ( & entity - > waypoints , & link ) ; ) {
has_waypoint_to_defender = link - > data . entity = = closest_defender . entity ;
}
2023-10-08 02:22:08 +00:00
2023-10-08 08:04:11 +00:00
if ( ! has_waypoint_to_defender ) {
FP_GameEntity * defender = FP_Game_GetEntity ( game , closest_defender . entity ) ;
FP_SentinelListLink < FP_GameWaypoint > * link = FP_SentinelList_MakeBefore ( & entity - > waypoints ,
FP_SentinelList_Front ( & entity - > waypoints ) ,
game - > play . chunk_pool ) ;
FP_GameWaypoint * waypoint = & link - > data ;
waypoint - > entity = defender - > handle ;
waypoint - > type = FP_GameWaypointType_ClosestSide ;
2023-10-07 08:20:28 +00:00
}
2023-09-25 13:06:39 +00:00
}
2023-10-08 08:04:11 +00:00
}
2023-09-25 13:06:39 +00:00
2023-10-09 13:06:59 +00:00
// NOTE: Make waypoint to building =====================================================
// We can queue up to ente a building if we respond to the building AND we aren't
// already queuing up in a building already.
if ( entity - > flags & FP_GameEntityFlag_RespondsToBuildings & & FP_Game_IsNilEntityHandle ( game , entity - > queued_at_building ) ) {
2023-10-08 08:04:11 +00:00
FP_GameFindClosestEntityResult closest_building = { } ;
closest_building . dist_squared = DQN_F32_MAX ;
closest_building . pos = Dqn_V2_InitNx1 ( DQN_F32_MAX ) ;
2023-10-06 09:48:20 +00:00
2023-10-08 08:04:11 +00:00
for ( FP_GameEntityIterator building_it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & building_it , game - > play . root_entity ) ; ) {
FP_GameEntity * it_entity = building_it . entity ;
if ( it_entity - > type ! = FP_EntityType_ClubTerry & &
it_entity - > type ! = FP_EntityType_AirportTerry & &
it_entity - > type ! = FP_EntityType_ChurchTerry )
continue ;
2023-09-29 12:44:27 +00:00
2023-10-09 13:06:59 +00:00
// NOTE: Already converted, we cannot attend church again
2023-10-09 10:35:51 +00:00
if ( entity - > converted_faction & & it_entity - > type = = FP_EntityType_ChurchTerry )
continue ;
2023-10-10 21:40:21 +00:00
// NOTE: Already drunk, we are not allowed to enter the nightclub again
if ( entity - > is_drunk & & it_entity - > type = = FP_EntityType_ClubTerry )
continue ;
2023-10-09 13:06:59 +00:00
// NOTE: The queue to enter the building is completely full skip
2023-10-09 10:35:51 +00:00
if ( it_entity - > building_queue . size = = Dqn_FArray_Max ( & it_entity - > building_queue ) )
2023-10-08 08:04:11 +00:00
continue ;
2023-10-07 08:14:09 +00:00
2023-10-09 13:06:59 +00:00
// NOTE: Entity is already in the building queue, skip
if ( Dqn_FArray_Find < FP_GameEntityHandle > ( & it_entity - > building_queue , entity - > handle ) . data )
continue ;
2023-10-08 08:04:11 +00:00
bool already_visited_building = false ;
for ( FP_SentinelListLink < FP_GameEntityHandle > * link_it = { } ;
! already_visited_building & & FP_SentinelList_Iterate ( & entity - > buildings_visited , & link_it ) ;
) {
FP_GameEntityHandle visit_item = link_it - > data ;
already_visited_building = visit_item = = it_entity - > handle ;
}
2023-10-06 09:48:20 +00:00
2023-10-08 08:04:11 +00:00
if ( already_visited_building )
continue ;
2023-10-06 09:48:20 +00:00
2023-10-08 08:04:11 +00:00
Dqn_V2 pos = FP_Game_CalcEntityWorldPos ( game , it_entity - > handle ) ;
Dqn_f32 dist = Dqn_V2_LengthSq_V2x2 ( pos , entity_pos ) ;
if ( dist < closest_building . dist_squared ) {
closest_building . pos = pos ;
closest_building . dist_squared = dist ;
closest_building . entity = it_entity - > handle ;
2023-10-06 09:48:20 +00:00
}
2023-10-08 08:04:11 +00:00
}
2023-10-06 09:48:20 +00:00
2023-10-08 08:04:11 +00:00
if ( ! FP_Game_IsNilEntityHandle ( game , closest_building . entity ) & &
closest_building . dist_squared < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 5.f ) ) ) {
2023-10-06 09:48:20 +00:00
2023-10-08 08:04:11 +00:00
bool has_waypoint_to_building = false ;
for ( FP_SentinelListLink < FP_GameWaypoint > * link = nullptr ;
! has_waypoint_to_building & & FP_SentinelList_Iterate < FP_GameWaypoint > ( & entity - > waypoints , & link ) ; ) {
has_waypoint_to_building = link - > data . entity = = closest_building . entity ;
}
2023-09-30 06:11:39 +00:00
2023-10-08 08:04:11 +00:00
if ( ! has_waypoint_to_building ) {
2023-10-14 14:31:33 +00:00
FP_SentinelListLink < FP_GameWaypoint > * link = FP_SentinelList_MakeBefore ( & entity - > waypoints , FP_SentinelList_Front ( & entity - > waypoints ) , game - > play . chunk_pool ) ;
FP_GameWaypoint * waypoint = & link - > data ;
waypoint - > entity = closest_building . entity ;
waypoint - > type = FP_GameWaypointType_Queue ;
2023-10-09 13:06:59 +00:00
// NOTE: Add the entity to the building queue
FP_GameEntity * building = FP_Game_GetEntity ( game , closest_building . entity ) ;
Dqn_FArray_Add ( & building - > building_queue , entity - > handle ) ;
// NOTE: Remember the building we are queued at
entity - > queued_at_building = building - > handle ;
}
}
}
// NOTE: Building queue ================================================================
for ( Dqn_usize index = 0 ; index < entity - > building_queue . size ; index + + ) {
FP_GameEntityHandle queue_entity_handle = entity - > building_queue . data [ index ] ;
// NOTE: Delete dead entities
if ( FP_Game_IsNilEntityHandle ( game , queue_entity_handle ) ) {
index = Dqn_FArray_EraseRange ( & entity - > building_queue , index , 1 /*count*/ , Dqn_ArrayErase_Stable ) . it_index ;
continue ;
}
// NOTE: Delete far away entities
FP_GameEntity * queue_entity = FP_Game_GetEntity ( game , queue_entity_handle ) ;
Dqn_V2 queue_entity_p = FP_Game_CalcEntityWorldPos ( game , queue_entity_handle ) ;
Dqn_V2 building_p = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
Dqn_f32 dist_sq = Dqn_V2_LengthSq_V2x2 ( queue_entity_p , building_p ) ;
Dqn_f32 threshold_sq = DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 10.f ) ) ;
if ( dist_sq > = threshold_sq | | queue_entity - > converted_faction ) {
// NOTE: Remove the entity from the building
queue_entity - > queued_at_building = { } ;
// NOTE: Remove it from the queue
index = Dqn_FArray_EraseRange ( & entity - > building_queue , index , 1 /*count*/ , Dqn_ArrayErase_Stable ) . it_index ;
// NOTE: Make sure the entity doesnt' try and revisit
FP_SentinelList_Add ( & queue_entity - > buildings_visited , game - > play . chunk_pool , entity - > handle ) ;
// NOTE: Remove the waypoint from the entity
if ( queue_entity - > waypoints . size ) {
for ( FP_SentinelListLink < FP_GameWaypoint > * link = nullptr ;
FP_SentinelList_Iterate < FP_GameWaypoint > ( & queue_entity - > waypoints , & link ) ; ) {
if ( link - > data . entity = = entity - > handle ) {
FP_SentinelList_Erase ( & queue_entity - > waypoints , link , game - > play . chunk_pool ) ;
2023-10-07 08:14:09 +00:00
}
2023-10-08 02:22:08 +00:00
}
2023-10-09 13:06:59 +00:00
}
}
}
2023-10-08 08:04:11 +00:00
2023-10-09 13:06:59 +00:00
#if 0
// NOTE: Bubble sort entities in the building queue by distance to the building ========
// For example the closest entity will be assigned the first queue slot to the building
if ( entity - > building_queue . size & & game - > play . clock_ms > = entity - > building_queue_next_sort_timestamp_ms ) {
// NOTE: We only sort the queue for the building every second. This prevents the queue
// from stagnating incase some entity has locked a position in the queue but had
// some dodgy physics that sent it far away
entity - > building_queue_next_sort_timestamp_ms = game - > play . clock_ms + 1000 ;
for ( bool swapped = true ; swapped ; ) {
swapped = false ;
for ( Dqn_usize index = 0 ; index < ( entity - > building_queue . size - 1 ) ; index + + ) {
FP_GameEntityHandle left_handle = entity - > building_queue . data [ index + 0 ] ;
FP_GameEntityHandle right_handle = entity - > building_queue . data [ index + 1 ] ;
Dqn_V2 left_world_p = FP_Game_CalcEntityWorldPos ( game , left_handle ) ;
Dqn_V2 right_world_p = FP_Game_CalcEntityWorldPos ( game , right_handle ) ;
Dqn_f32 left_dist_sq = Dqn_V2_LengthSq_V2x2 ( entity_pos , left_world_p ) ;
Dqn_f32 right_dist_sq = Dqn_V2_LengthSq_V2x2 ( entity_pos , right_world_p ) ;
if ( left_dist_sq > ( right_dist_sq * 1.1f ) ) {
DQN_SWAP ( entity - > building_queue . data [ index + 0 ] , entity - > building_queue . data [ index + 1 ] ) ;
swapped = true ;
}
2023-09-29 11:42:27 +00:00
}
}
2023-10-08 08:04:11 +00:00
}
2023-10-09 13:06:59 +00:00
# endif
2023-09-29 11:42:27 +00:00
2023-10-09 13:06:59 +00:00
// NOTE: Handle waypoints ==============================================================
2023-10-08 08:04:11 +00:00
while ( entity - > waypoints . size ) {
FP_SentinelListLink < FP_GameWaypoint > * waypoint_link = entity - > waypoints . sentinel - > next ;
FP_GameWaypoint const * waypoint = & waypoint_link - > data ;
FP_GameEntity * waypoint_entity = FP_Game_GetEntity ( game , waypoint_link - > data . entity ) ;
if ( FP_Game_IsNilEntity ( waypoint_entity ) ) {
FP_SentinelList_Erase ( & entity - > waypoints , waypoint_link , game - > play . chunk_pool ) ;
continue ;
}
2023-10-01 05:11:08 +00:00
2023-10-08 08:04:11 +00:00
// NOTE: We found a waypoint that is valid to move towards
Dqn_V2 target_pos = FP_Game_CalcWaypointWorldPos ( game , entity - > handle , waypoint ) ;
Dqn_V2 entity_to_waypoint = target_pos - entity_pos ;
2023-10-01 05:11:08 +00:00
2023-10-08 08:04:11 +00:00
// NOTE: Check if we've arrived at the waypoint
Dqn_f32 dist_to_waypoint_sq = Dqn_V2_LengthSq ( entity_to_waypoint ) ;
2023-10-01 05:11:08 +00:00
2023-10-08 08:04:11 +00:00
// NOTE: Calculate the approaching direction
FP_GameDirection approach_dir = FP_GameDirection_Up ;
{
Dqn_V2 dir_vectors [ FP_GameDirection_Count ] = { } ;
dir_vectors [ FP_GameDirection_Up ] = Dqn_V2_InitNx2 ( + 0 , - 1 ) ;
dir_vectors [ FP_GameDirection_Down ] = Dqn_V2_InitNx2 ( + 0 , + 1 ) ;
dir_vectors [ FP_GameDirection_Left ] = Dqn_V2_InitNx2 ( - 1 , + 0 ) ;
dir_vectors [ FP_GameDirection_Right ] = Dqn_V2_InitNx2 ( + 1 , + 0 ) ;
Dqn_V2 target_entity_pos = FP_Game_CalcEntityWorldPos ( game , waypoint_entity - > handle ) ;
Dqn_V2 entity_to_target = target_entity_pos - entity_pos ;
Dqn_V2 entity_to_target_norm = Dqn_V2_Normalise ( entity_to_target ) ;
Dqn_f32 approach_dir_scalar_projection_onto_entity_to_waypoint_vector = - 1.1f ;
DQN_FOR_UINDEX ( dir_index , FP_GameDirection_Count ) {
Dqn_V2 attack_dir = dir_vectors [ dir_index ] ;
Dqn_f32 scalar_projection = Dqn_V2_Dot ( attack_dir , entity_to_target_norm ) ;
if ( scalar_projection > approach_dir_scalar_projection_onto_entity_to_waypoint_vector ) {
approach_dir = DQN_CAST ( FP_GameDirection ) dir_index ;
approach_dir_scalar_projection_onto_entity_to_waypoint_vector = scalar_projection ;
2023-10-08 02:22:08 +00:00
}
2023-10-01 05:11:08 +00:00
}
}
2023-10-08 08:04:11 +00:00
Dqn_f32 arrival_threshold = { } ;
switch ( waypoint - > arrive ) {
case FP_GameWaypointArrive_Default : {
if ( approach_dir = = FP_GameDirection_Up | | approach_dir = = FP_GameDirection_Down )
arrival_threshold = 10.f ;
else
arrival_threshold = 10.f ;
} break ;
case FP_GameWaypointArrive_WhenWithinEntitySize : {
arrival_threshold = DQN_MAX ( waypoint_entity - > local_hit_box_size . w , waypoint_entity - > local_hit_box_size . h ) * waypoint_link - > data . value ;
} break ;
}
2023-09-16 14:37:26 +00:00
2023-10-08 08:04:11 +00:00
// NOTE: We haven't arrived yet, calculate an acceleration vector to the waypoint
if ( dist_to_waypoint_sq > DQN_SQUARED ( arrival_threshold ) ) {
Dqn_V2 entity_to_waypoint_norm = Dqn_V2_Normalise ( entity_to_waypoint ) ;
acceleration_meters_per_s = entity_to_waypoint_norm * ( entity - > base_acceleration_per_s . meters * 0.25f ) ;
2023-10-08 09:05:40 +00:00
2023-10-08 09:51:54 +00:00
if ( entity - > type = = FP_EntityType_Smoochie ) {
if ( waypoint_entity - > type = = FP_EntityType_Terry ) {
if ( dist_to_waypoint_sq < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 2.f ) ) & &
! entity - > smoochie_has_teleported ) {
if ( entity - > smoochie_teleport_timestamp = = 0 ) {
entity - > smoochie_teleport_timestamp = game - > play . clock_ms + DQN_CAST ( Dqn_usize ) ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * 5000 ) ;
}
if ( game - > play . clock_ms < entity - > smoochie_teleport_timestamp ) {
Dqn_usize time_to_teleport_ms = entity - > smoochie_teleport_timestamp - game - > play . clock_ms ;
if ( time_to_teleport_ms < 2000 )
entity - > action . sprite_alpha = Dqn_PCG32_NextF32 ( & game - > play . rng ) ;
} else if ( game - > play . clock_ms > = entity - > smoochie_teleport_timestamp ) {
Dqn_Rect waypoint_rect = FP_Game_CalcEntityWorldHitBox ( game , waypoint_entity - > handle ) ;
entity - > smoochie_has_teleported = true ;
entity - > action . sprite_alpha = 1.f ;
Dqn_FArray < Dqn_V2 , FP_GameDirection_Count > teleport_pos = { } ;
if ( waypoint_entity - > direction ! = FP_GameDirection_Up ) {
Dqn_FArray_Add ( & teleport_pos , Dqn_Rect_InterpolatedPoint ( waypoint_rect , Dqn_V2_InitNx2 ( 0.5f , - 1.2f ) ) ) ;
}
if ( waypoint_entity - > direction ! = FP_GameDirection_Down ) {
Dqn_FArray_Add ( & teleport_pos , Dqn_Rect_InterpolatedPoint ( waypoint_rect , Dqn_V2_InitNx2 ( 0.5f , + 1.2f ) ) ) ;
}
if ( waypoint_entity - > direction ! = FP_GameDirection_Left ) {
Dqn_FArray_Add ( & teleport_pos , Dqn_Rect_InterpolatedPoint ( waypoint_rect , Dqn_V2_InitNx2 ( - 1.2f , + 0.5f ) ) ) ;
}
if ( waypoint_entity - > direction ! = FP_GameDirection_Right ) {
Dqn_FArray_Add ( & teleport_pos , Dqn_Rect_InterpolatedPoint ( waypoint_rect , Dqn_V2_InitNx2 ( + 1.2f , + 0.5f ) ) ) ;
}
uint32_t teleport_index = Dqn_PCG32_Range ( & game - > play . rng , 0 , DQN_CAST ( uint32_t ) teleport_pos . size ) ;
entity - > local_pos = teleport_pos . data [ teleport_index ] ;
}
}
}
} else if ( entity - > type = = FP_EntityType_Clinger ) {
2023-10-09 13:06:59 +00:00
if ( game - > play . clock_ms > = entity - > clinger_next_dash_timestamp & & dist_to_waypoint_sq > ( DQN_SQUARED ( arrival_threshold * 4.f ) ) ) {
2023-10-08 09:51:54 +00:00
entity - > clinger_next_dash_timestamp = game - > play . clock_ms + 2000 ;
acceleration_meters_per_s = entity_to_waypoint_norm * entity - > base_acceleration_per_s . meters * 45.f ;
}
2023-10-08 09:05:40 +00:00
}
2023-10-08 08:04:11 +00:00
break ;
}
2023-09-23 06:42:22 +00:00
2023-10-08 08:04:11 +00:00
// NOTE: We have arrived at the waypoint
bool aggro = false ;
if ( entity - > flags & FP_GameEntityFlag_Aggros ) {
aggro | = entity - > faction = = FP_GameEntityFaction_Friendly & & waypoint_entity - > faction & FP_GameEntityFaction_Foe ;
aggro | = entity - > faction = = FP_GameEntityFaction_Foe & & waypoint_entity - > faction & FP_GameEntityFaction_Friendly ;
}
2023-09-26 13:58:48 +00:00
2023-10-14 14:31:33 +00:00
bool building_response = ( ( entity - > flags & FP_GameEntityFlag_RespondsToBuildings ) & & ( waypoint_entity - > type = = FP_EntityType_ClubTerry ) ) | |
( waypoint_entity - > type = = FP_EntityType_AirportTerry ) | |
( waypoint_entity - > type = = FP_EntityType_ChurchTerry ) ;
2023-10-09 13:06:59 +00:00
if ( aggro | | building_response ) {
2023-10-08 08:04:11 +00:00
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 ( building_response ) {
FP_GameEntity * building = waypoint_entity ;
2023-10-09 13:06:59 +00:00
can_attack = false ;
DQN_ASSERTF (
waypoint - > type = = FP_GameWaypointType_Queue ,
" There's nothing stopping us from supporting other "
" waypoint types to buildings, but, for this game "
" we only ever make mobs queue at the building " ) ;
DQN_ASSERTF (
building - > building_queue . size ,
" An entity should only be forming a waypoint to "
" the building if there was space in the queue and "
" they were added the the queue " ) ;
2023-10-08 08:04:11 +00:00
if ( FP_Game_IsNilEntityHandle ( game , building - > building_patron ) ) {
2023-10-09 13:06:59 +00:00
if ( waypoint_entity - > building_queue . data [ 0 ] = = entity - > handle ) {
// NOTE: This entity is front-in-line in the queue to enter the building, we can enter!
building - > building_patron = entity - > handle ;
entity - > queued_at_building = { } ;
// NOTE: Remove them from the queue
Dqn_FArray_EraseRange ( & waypoint_entity - > building_queue , 0 /*index*/ , 1 /*count*/ , Dqn_ArrayErase_Stable ) ;
2023-10-14 01:01:22 +00:00
Dqn_Rect building_hit_box = FP_Game_CalcEntityWorldHitBox ( game , building - > handle ) ;
Dqn_V2 exit_pos = Dqn_Rect_InterpolatedPoint ( building_hit_box , Dqn_V2_InitNx2 ( 0.5f , 1.1f ) ) ;
2023-10-09 13:06:59 +00:00
if ( building - > type = = FP_EntityType_ClubTerry ) {
FP_Game_EntityTransitionState ( game , building , FP_EntityClubTerryState_PartyTime ) ;
entity - > local_pos = exit_pos ; // TODO(doyle): Only works when parent world pos is 0,0
} else if ( building - > type = = FP_EntityType_AirportTerry ) {
FP_Game_EntityTransitionState ( game , building , FP_EntityAirportTerryState_FlyPassenger ) ;
} else {
DQN_ASSERT ( building - > type = = FP_EntityType_ChurchTerry ) ;
FP_Game_EntityTransitionState ( game , building , FP_EntityChurchTerryState_ConvertPatron ) ;
}
2023-09-26 13:58:48 +00:00
2023-10-09 13:06:59 +00:00
entity - > flags | = FP_GameEntityFlag_OccupiedInBuilding ;
FP_SentinelList_Erase ( & entity - > waypoints , waypoint_link , game - > play . chunk_pool ) ;
// NOTE: Add the building to the entity's visit list to prevent them from re-entering
FP_SentinelList_Add ( & entity - > buildings_visited , game - > play . chunk_pool , building - > handle ) ;
}
2023-10-08 08:04:11 +00:00
}
2023-10-08 02:22:08 +00:00
}
2023-09-24 14:43:22 +00:00
2023-10-08 08:04:11 +00:00
if ( can_attack ) {
switch ( entity - > type ) {
case FP_EntityType_Terry : /*FALLTHRU*/
case FP_EntityType_Smoochie : /*FALLTHRU*/
case FP_EntityType_Catfish : /*FALLTHRU*/
case FP_EntityType_Clinger : {
// TODO(doyle): We should check if it's valid to enter this new state
// from the entity's current state
if ( entity - > type = = FP_EntityType_Terry ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Attack ) ;
} else if ( entity - > type = = FP_EntityType_Smoochie ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Attack ) ;
} else if ( entity - > type = = FP_EntityType_Catfish ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Attack ) ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-08 08:04:11 +00:00
DQN_ASSERT ( entity - > type = = FP_EntityType_Clinger ) ;
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Attack ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-08 08:04:11 +00:00
entity - > direction = approach_dir ;
} break ;
case FP_EntityType_Nil : break ;
case FP_EntityType_ClubTerry : break ;
case FP_EntityType_Map : break ;
case FP_EntityType_Heart : break ;
case FP_EntityType_MerchantTerry : break ;
case FP_EntityType_MerchantGraveyard : break ;
case FP_EntityType_MerchantGym : break ;
case FP_EntityType_MerchantPhoneCompany : break ;
case FP_EntityType_AirportTerry : break ;
case FP_EntityType_ChurchTerry : break ;
case FP_EntityType_KennelTerry : break ;
case FP_EntityType_PhoneMessageProjectile : break ;
case FP_EntityType_Count : DQN_INVALID_CODE_PATH ; break ;
2023-10-14 14:31:33 +00:00
case FP_EntityType_AirportTerryPlane :
case FP_EntityType_MobSpawner :
case FP_EntityType_PortalMonkey : break ;
2023-09-29 06:53:20 +00:00
}
2023-10-08 02:22:08 +00:00
}
2023-10-08 08:04:11 +00:00
// NOTE: Aggro makes the entity attack, we will exit here preserving the waypoint in the entity.
break ;
} else {
FP_SentinelList_Erase ( & entity - > waypoints , waypoint_link , game - > play . chunk_pool ) ;
2023-09-26 13:58:48 +00:00
}
2023-09-16 14:37:26 +00:00
}
2023-10-08 02:22:08 +00:00
// NOTE: Move entity by mouse ==============================================================
2023-10-08 05:14:31 +00:00
if ( game - > play . active_entity = = entity - > handle & & entity - > flags & FP_GameEntityFlag_MoveByMouse ) {
2023-10-15 11:48:53 +00:00
Dqn_V2 mouse_p_delta = input - > mouse_p_delta * ( Dqn_V2_One / game - > play . camera . scale ) ;
2023-10-08 02:22:08 +00:00
entity - > velocity = { } ;
acceleration_meters_per_s = { } ;
2023-10-15 11:48:53 +00:00
entity - > local_pos + = mouse_p_delta ;
2023-10-08 02:22:08 +00:00
}
2023-09-29 05:28:11 +00:00
2023-10-08 05:14:31 +00:00
if ( game - > play . clicked_entity = = entity - > handle ) {
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Nil | | game - > play . in_game_menu = = FP_GameInGameMenu_Build ) {
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_H ) )
2023-10-08 05:14:31 +00:00
game - > play . in_game_menu = DQN_CAST ( FP_GameInGameMenu ) ( DQN_CAST ( uint32_t ) game - > play . in_game_menu ^ FP_GameInGameMenu_Build ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-15 11:48:53 +00:00
if ( entity - > flags & FP_GameEntityFlag_CameraTracking ) {
FP_GameCamera * camera = & game - > play . camera ;
camera - > world_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) * camera - > scale ;
}
2023-10-08 02:22:08 +00:00
2023-10-08 05:14:31 +00:00
FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS [ game - > play . build_mode_building_index ] ;
2023-10-15 11:48:53 +00:00
game - > play . build_mode_can_place_building = false ;
2023-10-08 02:22:08 +00:00
uint8_t * inventory_count = nullptr ;
if ( placeable_building . type = = FP_EntityType_ChurchTerry )
inventory_count = & entity - > inventory . churchs ;
else if ( placeable_building . type = = FP_EntityType_KennelTerry )
inventory_count = & entity - > inventory . kennels ;
else if ( placeable_building . type = = FP_EntityType_ClubTerry )
inventory_count = & entity - > inventory . clubs ;
else if ( placeable_building . type = = FP_EntityType_AirportTerry )
inventory_count = & entity - > inventory . airports ;
bool have_building_inventory = inventory_count & & ( * inventory_count ) > 0 ;
2023-10-08 05:14:31 +00:00
if ( have_building_inventory & & game - > play . in_game_menu = = FP_GameInGameMenu_Build ) {
2023-10-08 02:22:08 +00:00
Dqn_Rect dest_rect = FP_Game_GetBuildingPlacementRectForEntity ( game , placeable_building , entity - > handle ) ;
Dqn_V2 placement_pos = Dqn_Rect_Center ( dest_rect ) ;
for ( FP_GameEntityIterator zone_it = { } ;
2023-10-08 05:14:31 +00:00
FP_Game_DFSPreOrderWalkEntityTree ( game , & zone_it , game - > play . root_entity ) ;
2023-10-08 02:22:08 +00:00
) {
FP_GameEntity * zone = zone_it . entity ;
bool is_building = zone - > type = = FP_EntityType_KennelTerry | |
zone - > type = = FP_EntityType_AirportTerry | |
zone - > type = = FP_EntityType_ChurchTerry | |
zone - > type = = FP_EntityType_ClubTerry ;
Dqn_Rect zone_hit_box = FP_Game_CalcEntityWorldHitBox ( game , zone - > handle ) ;
if ( is_building ) {
if ( Dqn_Rect_Intersects ( zone_hit_box , dest_rect ) ) {
2023-10-08 05:14:31 +00:00
game - > play . build_mode_can_place_building = false ;
2023-10-08 02:22:08 +00:00
break ;
}
2023-10-05 12:09:39 +00:00
}
2023-10-08 02:22:08 +00:00
if ( ( zone - > flags & FP_GameEntityFlag_BuildZone ) = = 0 )
continue ;
2023-10-03 10:05:16 +00:00
2023-10-08 02:22:08 +00:00
zone_hit_box . pos + = dest_rect . size * .5f ;
zone_hit_box . size - = dest_rect . size ;
zone_hit_box . size = Dqn_V2_Max ( zone_hit_box . size , Dqn_V2_Zero ) ;
2023-10-03 10:05:16 +00:00
2023-10-08 05:14:31 +00:00
game - > play . build_mode_can_place_building | = Dqn_Rect_ContainsPoint ( zone_hit_box , placement_pos ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-03 10:05:16 +00:00
2023-10-08 05:14:31 +00:00
if ( game - > play . build_mode_can_place_building & &
2023-10-08 02:22:08 +00:00
TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_J ) ) {
if ( placeable_building . type = = FP_EntityType_ClubTerry ) {
FP_Entity_CreateClubTerry ( game , placement_pos , " Club Terry " ) ;
2023-10-08 08:00:13 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Club ] , 1.f ) ;
2023-10-08 02:22:08 +00:00
} else if ( placeable_building . type = = FP_EntityType_ChurchTerry ) {
FP_Entity_CreateChurchTerry ( game , placement_pos , " Church Terry " ) ;
2023-10-08 05:21:39 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Church ] , 1.f ) ;
2023-10-08 02:22:08 +00:00
} else if ( placeable_building . type = = FP_EntityType_AirportTerry ) {
FP_Entity_CreateAirportTerry ( game , placement_pos , " Airport Terry " ) ;
2023-10-08 08:00:13 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Plane ] , 1.f ) ;
2023-10-08 02:22:08 +00:00
} else {
DQN_ASSERT ( placeable_building . type = = FP_EntityType_KennelTerry ) ;
FP_Entity_CreateKennelTerry ( game , placement_pos , " Kennel Terry " ) ;
2023-10-08 08:00:13 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Dog ] , 1.f ) ;
2023-10-08 02:22:08 +00:00
}
( * inventory_count ) - - ;
2023-10-05 12:09:39 +00:00
}
2023-10-08 02:22:08 +00:00
}
}
2023-10-07 04:04:37 +00:00
2023-10-08 02:22:08 +00:00
if ( ! FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) & & entity - > type ! = FP_EntityType_MobSpawner ) {
2023-10-08 05:14:31 +00:00
FP_GameFindClosestEntityResult closest_portal = FP_Game_FindClosestEntityWithType ( game , entity - > carried_monkey , game - > play . mob_spawners . data , game - > play . mob_spawners . size ) ;
if ( closest_portal . dist_squared < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 1.f ) ) ) {
2023-10-08 02:22:08 +00:00
FP_GameEntity * portal = FP_Game_GetEntity ( game , closest_portal . entity ) ;
portal - > carried_monkey = entity - > carried_monkey ;
entity - > carried_monkey = { } ;
2023-10-02 11:38:36 +00:00
}
2023-10-08 22:01:56 +00:00
acceleration_meters_per_s * = 0.5f ; // TODO(doyle): Penalise the player
2023-10-08 02:22:08 +00:00
} else {
acceleration_meters_per_s * = 1.f ; // TODO(doyle): Penalise the player
2023-10-02 11:38:36 +00:00
}
2023-10-01 11:16:49 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Left-shift lets us strafe in the same direction
if ( ! TELY_Platform_InputScanCodeIsDown ( input , TELY_PlatformInputScanCode_LeftShift ) ) {
if ( acceleration_meters_per_s . x )
entity - > direction = acceleration_meters_per_s . x > 0.f ? FP_GameDirection_Right : FP_GameDirection_Left ;
else if ( acceleration_meters_per_s . y )
entity - > direction = acceleration_meters_per_s . y > 0.f ? FP_GameDirection_Down : FP_GameDirection_Up ;
2023-10-07 14:29:50 +00:00
}
2023-10-08 02:22:08 +00:00
// NOTE: Tick the state machine
// NOTE: This can delete the entity! Take caution
FP_GameEntityHandle entity_handle = entity - > handle ;
FP_EntityActionStateMachine ( game , & platform - > audio , input , entity , & acceleration_meters_per_s ) ;
// NOTE: Core equations of motion ==========================================================
FP_Game_MoveEntity ( game , entity_handle , acceleration_meters_per_s ) ;
}
2023-09-16 02:21:24 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: If all enemies for the current wave have been spawned and the cooldown has elapsed
// start the next wave.
2023-10-08 05:14:31 +00:00
if ( game - > play . enemies_spawned_this_wave > = game - > play . enemies_per_wave & & game - > play . clock_ms > = game - > play . wave_cooldown_timestamp_ms ) {
2023-10-09 21:53:19 +00:00
game - > play . enemies_per_wave = DQN_MAX ( 5 * DQN_CAST ( uint32_t ) game - > play . mob_spawners . size , DQN_CAST ( uint32_t ) ( game - > play . enemies_per_wave * 1.5 ) ) ;
2023-10-08 05:14:31 +00:00
game - > play . enemies_spawned_this_wave = 0 ;
game - > play . current_wave + + ;
2023-10-08 02:22:08 +00:00
}
2023-10-07 09:56:14 +00:00
}
2023-09-29 05:28:11 +00:00
// NOTE: Update entity =========================================================================
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityIterator it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & it , game - > play . root_entity ) ; ) {
2023-09-29 05:28:11 +00:00
FP_GameEntity * entity = it . entity ;
2023-10-05 10:10:50 +00:00
if ( entity - > flags & FP_GameEntityFlag_TTL ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . clock_ms > = entity - > ttl_end_timestamp ) {
2023-10-05 10:10:50 +00:00
FP_Game_DeleteEntity ( game , entity - > handle ) ;
continue ;
}
}
2023-10-07 06:55:34 +00:00
// NOTE: Recover mobile data
entity - > terry_mobile_data_plan =
2023-10-08 05:14:31 +00:00
DQN_MIN ( entity - > terry_mobile_data_plan + DQN_CAST ( Dqn_usize ) ( FP_TERRY_MOBILE_DATA_PER_RANGE_ATTACK * .25f * PHYSICS_STEP ) ,
2023-10-07 06:55:34 +00:00
entity - > terry_mobile_data_plan_cap ) ;
// NOTE: Recover hp & stamina
2023-10-08 08:04:11 +00:00
if ( game - > play . update_counter % 4 = = 0 ) {
entity - > stamina = DQN_MIN ( entity - > stamina + 1 , entity - > stamina_cap ) ;
}
2023-10-07 06:55:34 +00:00
2023-10-08 08:04:11 +00:00
if ( entity - > flags & FP_GameEntityFlag_RecoversHP ) {
if ( game - > play . update_counter % entity - > hp_recover_every_n_ticks = = 0 ) {
entity - > hp = DQN_MIN ( entity - > hp + 1 , entity - > hp_cap ) ;
}
2023-10-06 10:48:05 +00:00
}
2023-09-29 05:28:11 +00:00
// NOTE: Derive dynmamic bounding boxes ====================================================
2023-09-23 02:33:59 +00:00
if ( entity - > flags & FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox ) {
2023-09-16 02:21:24 +00:00
Dqn_Rect children_bbox = { } ;
// TODO(doyle): Is the hit box supposed to include the containing
// entity itself? Not sure
2023-09-17 10:24:07 +00:00
children_bbox . pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
2023-09-16 02:21:24 +00:00
2023-09-17 10:24:07 +00:00
for ( FP_GameEntityIterator child_it = { } ; FP_Game_DFSPreOrderWalkEntityTree ( game , & child_it , entity ) ; ) {
FP_GameEntity * child = child_it . entity ;
2023-09-16 02:21:24 +00:00
DQN_ASSERT ( child ! = entity ) ;
2023-09-17 10:24:07 +00:00
Dqn_Rect bbox = FP_Game_CalcEntityWorldBoundingBox ( game , child - > handle ) ;
2023-09-16 02:21:24 +00:00
children_bbox = Dqn_Rect_Union ( children_bbox , bbox ) ;
}
Dqn_Rect padded_bbox = Dqn_Rect_Expand ( children_bbox , 16.f ) ;
entity - > local_hit_box_offset = padded_bbox . pos - entity - > local_pos + ( padded_bbox . size * .5f ) ;
entity - > local_hit_box_size = padded_bbox . size ;
}
2023-09-23 06:42:22 +00:00
// NOTE: Mob spawner =======================================================================
2023-10-15 11:48:53 +00:00
if ( entity - > type = = FP_EntityType_MobSpawner & & 0 ) {
2023-09-24 14:43:22 +00:00
// NOTE: Flush any spawn entities that are dead
for ( FP_SentinelListLink < FP_GameEntityHandle > * link = nullptr ; FP_SentinelList_Iterate < FP_GameEntityHandle > ( & entity - > spawn_list , & link ) ; ) {
FP_GameEntity * spawned_entity = FP_Game_GetEntity ( game , link - > data ) ;
if ( FP_Game_IsNilEntity ( spawned_entity ) ) // NOTE: Entity is dead remove it from the linked list
2023-10-08 05:14:31 +00:00
link = FP_SentinelList_Erase ( & entity - > spawn_list , link , game - > play . chunk_pool ) ;
2023-09-24 09:11:44 +00:00
}
2023-10-08 08:48:17 +00:00
if ( entity - > action . state ! = FP_EntityMobSpawnerState_Shutdown & &
game - > play . enemies_spawned_this_wave < game - > play . enemies_per_wave & & entity - > spawn_list . size < entity - > spawn_cap ) { // NOTE: Spawn new entities
if ( input - > timer_s > = entity - > next_spawn_timestamp_s ) {
Dqn_usize spawn_count = DQN_MIN ( game - > play . current_wave + 1 , 8 ) ;
for ( Dqn_usize spawn_index = 0 ; spawn_index < spawn_count ; spawn_index + + ) {
uint16_t hp_adjustment = DQN_CAST ( uint16_t ) game - > play . current_wave ;
2023-10-09 21:53:19 +00:00
entity - > next_spawn_timestamp_s = DQN_CAST ( uint64_t ) ( input - > timer_s + 2.5f ) ;
2023-10-08 08:48:17 +00:00
FP_SentinelListLink < FP_GameEntityHandle > * link = FP_SentinelList_Make ( & entity - > spawn_list , game - > play . chunk_pool ) ;
2023-10-14 01:01:22 +00:00
Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
Dqn_Rect entity_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
Dqn_f32 step_y = ( entity_hit_box . size . h / spawn_count ) * 1.5f ;
Dqn_f32 mob_y_offset = ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * step_y ) + ( step_y * spawn_index ) ;
2023-10-08 08:48:17 +00:00
2023-10-08 09:51:54 +00:00
if ( Dqn_PCG32_NextF32 ( & game - > play . rng ) > = .5f )
mob_y_offset * = - 1 ;
Dqn_V2 mob_world_pos = Dqn_V2_InitNx2 ( entity_world_pos . x , entity_world_pos . y + mob_y_offset ) ;
2023-10-08 08:48:17 +00:00
Dqn_f32 mob_choice = Dqn_PCG32_NextF32 ( & game - > play . rng ) ;
if ( mob_choice < = 0.33f )
link - > data = FP_Entity_CreateClinger ( game , mob_world_pos , " Clinger " ) ;
else if ( mob_choice < = 0.66f )
link - > data = FP_Entity_CreateSmoochie ( game , mob_world_pos , " Smoochie " ) ;
else
link - > data = FP_Entity_CreateCatfish ( game , mob_world_pos , " Catfish " ) ;
// NOTE: Setup the mob with waypoints
FP_GameEntity * mob = FP_Game_GetEntity ( game , link - > data ) ;
mob - > waypoints = FP_SentinelList_Init < FP_GameWaypoint > ( game - > play . chunk_pool ) ;
mob - > flags | = FP_GameEntityFlag_Aggros ;
mob - > flags | = FP_GameEntityFlag_RespondsToBuildings ;
2023-10-12 11:46:26 +00:00
mob - > hp_cap * = hp_adjustment ;
2023-10-08 08:48:17 +00:00
mob - > hp = mob - > hp_cap ;
2023-10-14 01:01:22 +00:00
FP_AppendMobSpawnerWaypoints ( game , entity - > handle , mob - > handle ) ;
2023-10-08 08:48:17 +00:00
game - > play . enemies_spawned_this_wave + + ;
if ( game - > play . enemies_spawned_this_wave > = game - > play . enemies_per_wave )
game - > play . wave_cooldown_timestamp_ms = game - > play . clock_ms + 30'000 ;
2023-09-24 09:11:44 +00:00
}
2023-09-24 04:20:27 +00:00
}
}
2023-09-23 06:42:22 +00:00
}
2023-09-16 07:32:25 +00:00
2023-09-29 05:28:11 +00:00
// NOTE: Do attacks ============================================================================
{
Dqn_Profiler_ZoneScopeWithIndex ( " FP_Update: Attacks " , FP_ProfileZone_FPUpdate_Attacks ) ;
FP_GameEntity * attacker = entity ;
2023-09-16 07:32:25 +00:00
2023-09-29 05:28:11 +00:00
// NOTE: Resolve attack boxes
if ( ! Dqn_V2_Area ( attacker - > attack_box_size ) )
continue ;
2023-09-16 07:32:25 +00:00
2023-10-14 14:31:33 +00:00
Dqn_Rect attacker_box = FP_Game_CalcEntityAttackWorldHitBox ( game , attacker - > handle ) ;
2023-10-07 08:14:09 +00:00
FP_GameEntityFaction enemy_faction =
entity - > faction = = FP_GameEntityFaction_Friendly
? FP_GameEntityFaction_Foe
: FP_GameEntityFaction_Friendly ;
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityIterator defender_it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & defender_it , game - > play . root_entity ) ; ) {
2023-09-29 05:28:11 +00:00
FP_GameEntity * defender = defender_it . entity ;
if ( defender - > handle = = attacker - > handle )
continue ;
2023-09-16 07:32:25 +00:00
2023-09-29 05:28:11 +00:00
if ( ( defender - > flags & FP_GameEntityFlag_Attackable ) = = 0 )
continue ;
2023-09-29 05:18:38 +00:00
2023-10-07 06:55:34 +00:00
// NOTE: Projectiles can't hurt the owner that spawned it
if ( attacker - > projectile_owner = = defender - > handle )
continue ;
2023-10-07 08:14:09 +00:00
if ( defender - > faction ! = enemy_faction )
2023-09-29 06:53:20 +00:00
continue ;
2023-09-29 05:28:11 +00:00
Dqn_Rect defender_box = FP_Game_CalcEntityWorldHitBox ( game , defender - > handle ) ;
if ( ! Dqn_Rect_Intersects ( attacker_box , defender_box ) )
continue ;
2023-09-16 07:32:25 +00:00
2023-09-29 05:28:11 +00:00
// NOTE: Do HP =========================================================================
2023-10-08 08:09:34 +00:00
if ( game - > play . player = = defender - > handle | | game - > play . heart = = defender - > handle ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . god_mode )
continue ;
}
2023-10-08 06:35:57 +00:00
defender - > hp = defender - > hp > = attacker - > base_attack ? defender - > hp - attacker - > base_attack : 0 ;
2023-10-06 10:57:34 +00:00
if ( defender - > hp < = 0 ) {
2023-10-08 06:35:57 +00:00
if ( ! defender - > is_dying ) {
FP_GameEntity * coin_receiver = FP_Game_GetEntity ( game , attacker - > projectile_owner ) ;
if ( FP_Game_IsNilEntity ( coin_receiver ) )
coin_receiver = attacker ;
coin_receiver - > coins + = 1 ;
}
2023-10-08 05:14:31 +00:00
defender - > is_dying = true ;
2023-10-06 10:57:34 +00:00
}
2023-09-29 05:28:11 +00:00
// NOTE: Kickback ======================================================================
2023-10-05 10:10:50 +00:00
#if 0
2023-09-29 05:28:11 +00:00
Dqn_V2 defender_world_pos = Dqn_Rect_Center ( defender_box ) ;
Dqn_V2 attack_dir_vector = { } ;
if ( attacker_world_pos . x < defender_world_pos . x )
attack_dir_vector . x = 1.f ;
else
attack_dir_vector . x = - 1.f ;
2023-09-29 05:58:03 +00:00
Dqn_V2 attack_acceleration_meters_per_s = attack_dir_vector * 60.f ;
2023-09-29 05:44:02 +00:00
FP_Game_MoveEntity ( game , defender - > handle , attack_acceleration_meters_per_s ) ;
2023-10-05 10:10:50 +00:00
# endif
2023-09-29 05:28:11 +00:00
}
2023-09-16 07:32:25 +00:00
}
}
2023-09-30 12:27:25 +00:00
2023-10-08 05:14:31 +00:00
bool all_portals_shutdown = true ;
for ( FP_GameEntityHandle portal_handle : game - > play . mob_spawners ) {
FP_GameEntity * portal = FP_Game_GetEntity ( game , portal_handle ) ;
all_portals_shutdown & = portal - > action . state = = FP_EntityMobSpawnerState_Shutdown ;
}
if ( all_portals_shutdown ) {
game - > play . state = FP_GameState_WinGame ;
}
2023-10-08 08:04:11 +00:00
{
FP_GameEntity * heart = FP_Game_GetEntity ( game , game - > play . heart ) ;
FP_GameEntity * player = FP_Game_GetEntity ( game , game - > play . player ) ;
if ( heart - > hp < = 0 ) {
player - > hp = 0 ;
game - > play . state = FP_GameState_LoseGame ;
}
}
2023-10-08 05:14:31 +00:00
if ( ! FP_Game_IsNilEntityHandle ( game , game - > play . clicked_entity ) ) {
2023-10-15 11:48:53 +00:00
Dqn_V2 window_size = Dqn_V2_InitV2I ( platform - > core . window_size ) ;
FP_GamePlay * play = & game - > play ;
FP_GameCamera * camera = & play - > camera ;
camera - > scale = window_size / camera - > size ;
Dqn_V2 camera_size_screen = camera - > size * camera - > scale ;
Dqn_V2 map_world_size = play - > map - > local_hit_box_size ;
Dqn_V2 map_screen_size = map_world_size * camera - > scale ;
Dqn_V2 half_map_screen_size = map_screen_size * .5f ;
camera - > world_pos . x = DQN_MIN ( camera - > world_pos . x , half_map_screen_size . w - ( camera_size_screen . w * .5f ) ) ;
camera - > world_pos . x = DQN_MAX ( camera - > world_pos . x , - half_map_screen_size . w + ( camera_size_screen . w * .5f ) ) ;
camera - > world_pos . y = DQN_MAX ( camera - > world_pos . y , - half_map_screen_size . h + ( camera_size_screen . h * .5f ) ) ;
camera - > world_pos . y = DQN_MIN ( camera - > world_pos . y , half_map_screen_size . h - ( camera_size_screen . h * .5f ) ) ;
2023-09-30 12:27:25 +00:00
}
2023-09-29 05:28:11 +00:00
Dqn_Profiler_EndZone ( update_zone ) ;
2023-09-16 07:32:25 +00:00
}
2023-10-08 05:21:39 +00:00
void FP_Render ( FP_Game * game , TELY_Platform * platform , TELY_Renderer * renderer , TELY_Audio * audio )
2023-09-16 07:32:25 +00:00
{
2023-09-23 06:42:22 +00:00
Dqn_Profiler_ZoneScopeWithIndex ( " FP_Render " , FP_ProfileZone_FPRender ) ;
2023-10-08 02:22:08 +00:00
TELY_PlatformInput * input = & platform - > input ;
TELY_RFui * rfui = & game - > rfui ;
TELY_Assets * assets = & platform - > assets ;
TELY_Render_ClearColourV3 ( renderer , TELY_COLOUR_BLACK_MIDNIGHT_V4 . rgb ) ;
TELY_Render_PushFont ( renderer , game - > jetbrains_mono_font ) ;
TELY_RFui_FrameSetup ( rfui , & platform - > frame_arena ) ;
TELY_RFui_PushFont ( rfui , game - > jetbrains_mono_font ) ;
TELY_RFui_PushLabelColourV4 ( rfui , TELY_COLOUR_BLACK_MIDNIGHT_V4 ) ;
2023-10-15 11:48:53 +00:00
FP_GameCameraM2x3 camera_xforms = FP_Game_CameraModelViewM2x3 ( game - > play . camera ) ;
TELY_Render_PushTransform ( renderer , camera_xforms . model_view ) ;
Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2 ( camera_xforms . view_model , input - > mouse_p ) ;
2023-09-16 07:32:25 +00:00
2023-09-16 09:06:16 +00:00
// NOTE: Draw tiles ============================================================================
2023-10-08 05:14:31 +00:00
Dqn_usize tile_count_x = DQN_CAST ( Dqn_usize ) ( platform - > core . window_size . w / game - > play . tile_size ) ;
Dqn_usize tile_count_y = DQN_CAST ( Dqn_usize ) ( platform - > core . window_size . h / game - > play . tile_size ) ;
2023-09-16 09:06:16 +00:00
for ( Dqn_usize x = 0 ; x < tile_count_x ; x + + ) {
2023-10-08 05:14:31 +00:00
Dqn_V2 start = Dqn_V2_InitNx2 ( ( x + 1 ) * game - > play . tile_size , 0 ) ;
2023-09-16 09:06:16 +00:00
Dqn_V2 end = Dqn_V2_InitNx2 ( start . x , platform - > core . window_size . h ) ;
TELY_Render_LineColourV4 ( renderer , start , end , TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , .25f ) , 1.f ) ;
}
for ( Dqn_usize y = 0 ; y < tile_count_y ; y + + ) {
2023-10-08 05:14:31 +00:00
Dqn_V2 start = Dqn_V2_InitNx2 ( 0 , ( y + 1 ) * game - > play . tile_size ) ;
2023-09-16 09:06:16 +00:00
Dqn_V2 end = Dqn_V2_InitNx2 ( platform - > core . window_size . w , start . y ) ;
TELY_Render_LineColourV4 ( renderer , start , end , TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , .25f ) , 1.f ) ;
}
2023-10-02 12:41:08 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_BLACK_V4 ) ;
2023-09-16 09:06:16 +00:00
// NOTE: Draw entities =========================================================================
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityIterator it = { } ; FP_Game_DFSPostOrderWalkEntityTree ( game , & it , game - > play . root_entity ) ; ) {
2023-09-17 10:24:07 +00:00
FP_GameEntity * entity = it . entity ;
2023-10-06 09:48:20 +00:00
if ( entity - > flags & FP_GameEntityFlag_OccupiedInBuilding )
2023-09-30 06:51:59 +00:00
continue ;
2023-09-16 02:21:24 +00:00
// NOTE: Render shapes in entity ===========================================================
2023-09-30 07:49:49 +00:00
Dqn_Rect world_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
2023-10-01 05:20:05 +00:00
Dqn_V2 world_pos = FP_Game_CalcEntityWorldPos ( game , entity - > handle ) ;
2023-09-17 10:24:07 +00:00
for ( FP_GameShape const & shape_ : entity - > shapes ) {
FP_GameShape const * shape = & shape_ ;
2023-09-16 02:21:24 +00:00
Dqn_V2 local_to_world_p1 = world_pos + shape - > p1 ;
Dqn_V2 local_to_world_p2 = world_pos + shape - > p2 ;
switch ( shape - > type ) {
2023-09-17 10:24:07 +00:00
case FP_GameShapeType_None : {
2023-09-16 02:21:24 +00:00
} break ;
2023-09-17 10:24:07 +00:00
case FP_GameShapeType_Circle : {
2023-09-16 02:21:24 +00:00
TELY_Render_CircleColourV4 ( renderer , local_to_world_p1 , shape - > circle_radius , shape - > render_mode , shape - > colour ) ;
} break ;
2023-09-17 10:24:07 +00:00
case FP_GameShapeType_Rect : {
2023-09-16 02:21:24 +00:00
Dqn_Rect rect = Dqn_Rect_InitV2x2 ( local_to_world_p1 , local_to_world_p2 - local_to_world_p1 ) ;
rect . pos - = rect . size * .5f ;
TELY_Render_RectColourV4 ( renderer , rect , shape - > render_mode , shape - > colour ) ;
} break ;
2023-09-17 10:24:07 +00:00
case FP_GameShapeType_Line : {
2023-09-16 02:21:24 +00:00
TELY_Render_LineColourV4 ( renderer , local_to_world_p1 , local_to_world_p2 , shape - > colour , shape - > line_thickness ) ;
} break ;
}
}
// NOTE: Render entity sprites =============================================================
2023-09-24 11:54:08 +00:00
if ( entity - > action . sprite . anim ) {
2023-10-08 01:26:36 +00:00
FP_GameEntityAction const * action = & entity - > action ;
TELY_AssetAnimatedSprite const sprite = action - > sprite ;
2023-10-08 05:14:31 +00:00
uint64_t const elapsed_ms = game - > play . clock_ms - action - > started_at_clock_ms ;
2023-10-08 01:26:36 +00:00
uint16_t const raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / sprite . anim - > ms_per_frame ) ;
DQN_ASSERTF ( sprite . anim - > count , " We will modulo by 0 or overflow to UINT64_MAX " ) ;
// TODO(doyle): So many ways to create and get sprite data .. its a mess
// I want to override per sprite anim height, we currently use the one
// in the entity which is not correct.
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , entity - > type , action - > state , entity - > direction ) ;
2023-09-24 13:08:30 +00:00
2023-10-07 14:29:50 +00:00
uint16_t anim_frame = 0 ;
2023-10-08 01:26:36 +00:00
if ( action - > sprite_play_once )
anim_frame = DQN_MIN ( raw_anim_frame , ( sprite . anim - > count - 1 ) ) ;
else
anim_frame = raw_anim_frame % sprite . anim - > count ;
2023-09-16 02:21:24 +00:00
2023-09-24 13:08:30 +00:00
Dqn_usize sprite_index = sprite . anim - > index + anim_frame ;
2023-09-24 01:34:04 +00:00
Dqn_Rect src_rect = { } ;
2023-09-24 13:08:30 +00:00
switch ( sprite . sheet - > type ) {
2023-09-23 11:21:08 +00:00
case TELY_AssetSpriteSheetType_Uniform : {
2023-09-24 13:08:30 +00:00
Dqn_usize sprite_sheet_row = sprite_index / sprite . sheet - > sprites_per_row ;
Dqn_usize sprite_sheet_column = sprite_index % sprite . sheet - > sprites_per_row ;
src_rect . pos . x = DQN_CAST ( Dqn_f32 ) ( sprite_sheet_column * sprite . sheet - > sprite_size . w ) ;
src_rect . pos . y = DQN_CAST ( Dqn_f32 ) ( sprite_sheet_row * sprite . sheet - > sprite_size . y ) ;
src_rect . size . w = DQN_CAST ( Dqn_f32 ) sprite . sheet - > sprite_size . w ;
src_rect . size . h = DQN_CAST ( Dqn_f32 ) sprite . sheet - > sprite_size . h ;
2023-09-23 11:21:08 +00:00
} break ;
case TELY_AssetSpriteSheetType_Rects : {
2023-09-24 13:08:30 +00:00
DQN_ASSERT ( sprite_index < sprite . sheet - > rects . size ) ;
src_rect = sprite . sheet - > rects . data [ sprite_index ] ;
2023-09-23 11:21:08 +00:00
} break ;
}
2023-09-16 02:21:24 +00:00
2023-10-08 05:14:31 +00:00
Dqn_f32 sprite_in_meters = FP_Game_PixelsToMetersNx1 ( game - > play , src_rect . size . y ) ;
2023-10-08 01:26:36 +00:00
Dqn_f32 size_scale = render_data . height . meters / sprite_in_meters ;
2023-09-25 14:07:39 +00:00
2023-09-16 02:21:24 +00:00
Dqn_Rect dest_rect = { } ;
2023-09-25 14:07:39 +00:00
dest_rect . size = src_rect . size * size_scale ;
2023-09-16 02:46:28 +00:00
dest_rect . pos = world_pos - ( dest_rect . size * .5f ) ;
2023-09-16 02:21:24 +00:00
2023-09-24 13:08:30 +00:00
if ( sprite . flip & TELY_AssetFlip_X )
2023-09-16 02:21:24 +00:00
dest_rect . size . w * = - 1.f ; // NOTE: Flip the texture horizontally
2023-09-24 13:08:30 +00:00
if ( sprite . flip & TELY_AssetFlip_Y )
dest_rect . size . h * = - 1.f ; // NOTE: Flip the texture vertically
2023-09-30 07:49:49 +00:00
TELY_Render_TextureColourV4 ( renderer ,
sprite . sheet - > tex_handle ,
src_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
2023-09-30 07:50:57 +00:00
0.f /*rotate radians*/ ,
2023-10-07 04:29:56 +00:00
TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , entity - > action . sprite_alpha ) ) ;
2023-10-07 08:14:09 +00:00
if ( entity - > converted_faction ) {
Dqn_V2 label_p = Dqn_Rect_InterpolatedPoint ( dest_rect , Dqn_V2_InitNx2 ( 0 , - 0.25f ) ) ;
TELY_Render_TextF ( renderer , label_p , Dqn_V2_InitNx2 ( 0.f , 0.1f ) , " CONVERTED " ) ;
}
2023-09-16 02:21:24 +00:00
}
2023-09-26 13:58:48 +00:00
DQN_FOR_UINDEX ( anim_index , entity - > extra_cosmetic_anims . size ) {
FP_GameRenderSprite * sprite = entity - > extra_cosmetic_anims . data + anim_index ;
2023-10-08 05:14:31 +00:00
uint64_t elapsed_ms = game - > play . clock_ms - sprite - > started_at_clock_ms ;
2023-09-26 13:58:48 +00:00
uint16_t raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / sprite - > asset . anim - > ms_per_frame ) ;
if ( raw_anim_frame > sprite - > asset . anim - > count & & ! sprite - > loop ) {
anim_index = Dqn_FArray_EraseRange ( & entity - > extra_cosmetic_anims , anim_index , 1 , Dqn_ArrayErase_Unstable ) . it_index ;
continue ;
}
uint16_t anim_frame = raw_anim_frame % sprite - > asset . anim - > count ;
Dqn_usize sprite_index = sprite - > asset . anim - > index + anim_frame ;
Dqn_Rect src_rect = { } ;
switch ( sprite - > asset . sheet - > type ) {
case TELY_AssetSpriteSheetType_Uniform : {
Dqn_usize sprite_sheet_row = sprite_index / sprite - > asset . sheet - > sprites_per_row ;
Dqn_usize sprite_sheet_column = sprite_index % sprite - > asset . sheet - > sprites_per_row ;
src_rect . pos . x = DQN_CAST ( Dqn_f32 ) ( sprite_sheet_column * sprite - > asset . sheet - > sprite_size . w ) ;
src_rect . pos . y = DQN_CAST ( Dqn_f32 ) ( sprite_sheet_row * sprite - > asset . sheet - > sprite_size . y ) ;
src_rect . size . w = DQN_CAST ( Dqn_f32 ) sprite - > asset . sheet - > sprite_size . w ;
src_rect . size . h = DQN_CAST ( Dqn_f32 ) sprite - > asset . sheet - > sprite_size . h ;
} break ;
case TELY_AssetSpriteSheetType_Rects : {
DQN_ASSERT ( sprite_index < sprite - > asset . sheet - > rects . size ) ;
src_rect = sprite - > asset . sheet - > rects . data [ sprite_index ] ;
} break ;
}
2023-10-08 05:14:31 +00:00
Dqn_f32 sprite_in_meters = FP_Game_PixelsToMetersNx1 ( game - > play , src_rect . size . y ) ;
2023-09-26 13:58:48 +00:00
Dqn_f32 size_scale = sprite - > height . meters / sprite_in_meters ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = src_rect . size * size_scale ;
dest_rect . pos = world_pos - ( dest_rect . size * .5f ) + sprite - > offset ;
if ( sprite - > asset . flip & TELY_AssetFlip_X )
dest_rect . size . w * = - 1.f ; // NOTE: Flip the texture horizontally
if ( sprite - > asset . flip & TELY_AssetFlip_Y )
dest_rect . size . h * = - 1.f ; // NOTE: Flip the texture vertically
2023-09-30 07:49:49 +00:00
TELY_Render_TextureColourV4 ( renderer ,
sprite - > asset . sheet - > tex_handle ,
src_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-08 08:04:11 +00:00
if ( entity - > handle ! = game - > play . player & & entity - > handle ! = game - > play . heart & & entity - > hp ! = entity - > hp_cap & & entity - > hp ) {
2023-10-08 06:25:08 +00:00
Dqn_f32 bar_height = 12.f ;
2023-10-14 14:31:33 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.f , - 0.2f ) ) ;
Dqn_f32 health_t = entity - > hp / DQN_CAST ( Dqn_f32 ) entity - > hp_cap ;
Dqn_Rect health_rect = Dqn_Rect_InitNx4 ( draw_p . x , draw_p . y , DQN_CAST ( Dqn_f32 ) entity - > hp_cap , bar_height ) ;
Dqn_Rect curr_health_rect = Dqn_Rect_InitNx4 ( draw_p . x , draw_p . y , DQN_CAST ( Dqn_f32 ) entity - > hp_cap * health_t , bar_height ) ;
2023-10-08 06:25:08 +00:00
TELY_Render_RectColourV4 ( renderer , curr_health_rect , TELY_RenderShapeMode_Fill , TELY_COLOUR_RED_TOMATO_V4 ) ;
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 ( renderer , health_rect , TELY_RenderShapeMode_Line , TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 1.f ;
}
2023-10-06 09:48:20 +00:00
if ( entity - > type = = FP_EntityType_ClubTerry | |
entity - > type = = FP_EntityType_AirportTerry | |
entity - > type = = FP_EntityType_ChurchTerry ) {
FP_GameEntityAction const * action = & entity - > action ;
2023-10-14 14:31:33 +00:00
bool draw_timer = ( entity - > type = = FP_EntityType_ClubTerry & & action - > state = = FP_EntityClubTerryState_PartyTime ) | |
( entity - > type = = FP_EntityType_AirportTerry & & action - > state = = FP_EntityAirportTerryState_FlyPassenger ) | |
( entity - > type = = FP_EntityType_ChurchTerry & & action - > state = = FP_EntityChurchTerryState_ConvertPatron ) ;
2023-10-06 09:48:20 +00:00
if ( draw_timer ) {
Dqn_f32 duration = action - > end_at_clock_ms - DQN_CAST ( Dqn_f32 ) action - > started_at_clock_ms ;
2023-10-08 05:14:31 +00:00
Dqn_f32 elapsed = DQN_CAST ( Dqn_f32 ) ( game - > play . clock_ms - action - > started_at_clock_ms ) ;
2023-09-30 07:49:49 +00:00
Dqn_f32 t01 = DQN_MIN ( 1.f , elapsed / duration ) ;
Dqn_Rect rect = { } ;
rect . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0 , - 0.3f ) ) ;
rect . size = Dqn_V2_InitNx2 ( world_hit_box . size . w * t01 , 16.f ) ;
TELY_Render_RectColourV4 ( renderer , rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , 1.0f ) ) ;
}
2023-09-26 13:58:48 +00:00
}
2023-10-08 05:14:31 +00:00
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Build ) {
2023-10-05 12:09:39 +00:00
if ( entity - > flags & FP_GameEntityFlag_BuildZone )
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_BLUE_CADET_V4 , 0.5f ) ) ;
}
2023-10-08 05:14:31 +00:00
if ( game - > play . debug_ui ) {
2023-10-05 12:09:39 +00:00
// NOTE: Render waypoint entities ======================================================
2023-10-07 14:29:50 +00:00
if ( entity - > type = = FP_EntityType_MobSpawner ) {
2023-10-05 10:30:56 +00:00
Dqn_V2 start = world_pos ;
for ( FP_GameEntity * waypoint_entity = entity - > first_child ; waypoint_entity ; waypoint_entity = waypoint_entity - > next ) {
if ( ( waypoint_entity - > flags & FP_GameEntityFlag_MobSpawnerWaypoint ) = = 0 )
continue ;
2023-09-24 14:43:22 +00:00
2023-10-05 10:30:56 +00:00
Dqn_V2 end = FP_Game_CalcEntityWorldPos ( game , waypoint_entity - > handle ) ;
TELY_Render_LineColourV4 ( renderer , start , end , TELY_COLOUR_BLUE_CADET_V4 , 2.f ) ;
start = end ;
}
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Line , TELY_COLOUR_BLUE_CADET_V4 ) ;
2023-10-07 14:29:50 +00:00
}
2023-09-16 07:32:25 +00:00
2023-10-08 08:48:17 +00:00
// NOTE: Draw the waypoints that the entity is moving along
Dqn_V2 start = world_pos ;
for ( FP_SentinelListLink < FP_GameWaypoint > * link = nullptr ; FP_SentinelList_Iterate ( & entity - > waypoints , & link ) ; ) {
Dqn_V2 end = FP_Game_CalcWaypointWorldPos ( game , entity - > handle , & link - > data ) ;
TELY_Render_LineColourV4 ( renderer , start , end , TELY_COLOUR_WHITE_PALE_GOLDENROD_V4 , 2.f ) ;
start = end ;
}
2023-10-05 10:30:56 +00:00
if ( entity - > flags & FP_GameEntityFlag_MobSpawnerWaypoint )
TELY_Render_CircleColourV4 ( renderer , world_pos , 16.f /*radius*/ , TELY_RenderShapeMode_Line , TELY_COLOUR_BLUE_CADET_V4 ) ;
if ( entity - > flags & FP_GameEntityFlag_BuildZone ) {
{
Dqn_V2 line_p1 = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0 , 0 ) ) ;
Dqn_V2 line_p2 = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 1 , 1 ) ) ;
TELY_Render_LineColourV4 ( renderer , line_p1 , line_p2 , TELY_COLOUR_BLACK_V4 , 2.f ) ;
}
{
Dqn_V2 line_p1 = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0 , 1 ) ) ;
Dqn_V2 line_p2 = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 1 , 0 ) ) ;
TELY_Render_LineColourV4 ( renderer , line_p1 , line_p2 , TELY_COLOUR_BLACK_V4 , 2.f ) ;
}
}
// NOTE: Render attack box =================================================================
{
2023-10-08 05:44:48 +00:00
Dqn_FArray < Dqn_Rect , FP_GameDirection_Count > attack_boxes = FP_Game_CalcEntityMeleeAttackBoxes ( game , entity - > handle ) ;
for ( Dqn_Rect box : attack_boxes )
TELY_Render_RectColourV4 ( renderer , box , TELY_RenderShapeMode_Line , TELY_COLOUR_RED_TOMATO_V4 ) ;
2023-10-05 10:30:56 +00:00
}
// NOTE: Render world position =============================================================
TELY_Render_CircleColourV4 ( renderer , world_pos , 4.f , TELY_RenderShapeMode_Fill , TELY_COLOUR_RED_TOMATO_V4 ) ;
// NOTE: Render hot/active entity ==========================================================
2023-10-08 05:14:31 +00:00
if ( game - > play . clicked_entity = = entity - > handle ) {
2023-10-05 10:30:56 +00:00
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Line , TELY_COLOUR_WHITE_PALE_GOLDENROD_V4 ) ;
2023-10-08 05:14:31 +00:00
} else if ( game - > play . hot_entity = = entity - > handle | | ( entity - > flags & FP_GameEntityFlag_DrawHitBox ) ) {
Dqn_V4 hot_colour = game - > play . hot_entity = = entity - > handle ? TELY_COLOUR_RED_TOMATO_V4 : TELY_Colour_V4Alpha ( TELY_COLOUR_YELLOW_SANDY_V4 , .5f ) ;
2023-10-05 10:30:56 +00:00
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Line , hot_colour ) ;
}
2023-10-08 05:14:31 +00:00
if ( game - > play . hot_entity = = entity - > handle ) {
2023-10-05 10:30:56 +00:00
if ( entity - > name . size ) {
2023-10-08 05:14:31 +00:00
Dqn_V2I player_tile = Dqn_V2I_InitNx2 ( world_pos . x / game - > play . tile_size , world_pos . y / game - > play . tile_size ) ;
2023-10-05 10:30:56 +00:00
Dqn_f32 line_height = TELY_Render_FontHeight ( renderer , & platform - > assets ) ;
Dqn_V2 draw_p = world_mouse_p ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " %.*s " , DQN_STRING_FMT ( entity - > name ) ) ; draw_p . y + = line_height ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " World Pos: (%.1f, %.1f) " , world_pos . x , world_pos . y ) ; draw_p . y + = line_height ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " Hit Box Size: %.1fx%.1f " , world_hit_box . size . x , world_hit_box . size . y ) ; draw_p . y + = line_height ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " Tile: %I32dx%I32d " , player_tile . x , player_tile . y ) ; draw_p . y + = line_height ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " World Mouse Pos: (%.1f, %.1f) " , world_mouse_p . x , world_mouse_p . y ) ; draw_p . y + = line_height ;
2023-10-07 08:14:09 +00:00
Dqn_String8 faction = { } ;
switch ( entity - > faction ) {
case FP_GameEntityFaction_Nil : faction = DQN_STRING8 ( " Nil " ) ; break ;
case FP_GameEntityFaction_Friendly : faction = DQN_STRING8 ( " Friendly " ) ; break ;
case FP_GameEntityFaction_Foe : faction = DQN_STRING8 ( " Foe " ) ; break ;
}
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " Faction: %.*s " , DQN_STRING_FMT ( faction ) ) ; draw_p . y + = line_height ;
2023-10-05 10:30:56 +00:00
}
}
}
2023-10-06 10:48:05 +00:00
}
2023-09-16 02:21:24 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Render overlay UI =====================================================================
2023-10-14 14:31:33 +00:00
if ( ! game - > play . debug_hide_hud & & ( game - > play . state = = FP_GameState_Pause | | game - > play . state = = FP_GameState_Play ) ) {
2023-10-06 12:30:09 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Render the merchant menus =========================================================
2023-10-08 05:14:31 +00:00
FP_GameEntity * player = FP_Game_GetEntity ( game , game - > play . player ) ;
Dqn_V2 player_pos = FP_Game_CalcEntityWorldPos ( game , game - > play . player ) ;
2023-10-08 02:22:08 +00:00
{
2023-10-08 08:00:13 +00:00
static bool sound_played_flags [ 4 ] = { false , false , false , false } ;
2023-10-08 02:22:08 +00:00
FP_GameInventory * invent = & player - > inventory ;
struct FP_MerchantToMenuMapping {
FP_GameEntityHandle merchant ;
Dqn_String8 menu_anim ;
Dqn_String8 building ;
Dqn_V2 building_offset01 ;
uint8_t * inventory_count ;
uint32_t * building_base_price ;
uint32_t * upgrade_base_price ;
2023-10-08 08:00:13 +00:00
FP_GameAudio audio_type ;
bool * sound_played ;
2023-10-08 02:22:08 +00:00
} merchants [ ] = {
2023-10-08 08:00:13 +00:00
{ game - > play . merchant_terry , g_anim_names . merchant_terry_menu , g_anim_names . club_terry_dark , Dqn_V2_InitNx2 ( 0.015f , + 0.04f ) , & invent - > clubs , & invent - > clubs_base_price , & invent - > health_base_price , FP_GameAudio_MerchantTerry , & sound_played_flags [ 0 ] } ,
{ game - > play . merchant_graveyard , g_anim_names . merchant_graveyard_menu , g_anim_names . church_terry_dark , Dqn_V2_InitNx2 ( 0.04f , - 0.15f ) , & invent - > churchs , & invent - > churchs_base_price , & invent - > stamina_base_price , FP_GameAudio_MerchantGhost , & sound_played_flags [ 1 ] } ,
{ game - > play . merchant_gym , g_anim_names . merchant_gym_menu , g_anim_names . kennel_terry , Dqn_V2_InitNx2 ( 0 , + 0 ) , & invent - > kennels , & invent - > kennels_base_price , & invent - > attack_base_price , FP_GameAudio_MerchantGym , & sound_played_flags [ 2 ] } ,
{ game - > play . merchant_phone_company , g_anim_names . merchant_phone_company_menu , g_anim_names . airport_terry , Dqn_V2_InitNx2 ( 0 , - 0.1f ) , & invent - > airports , & invent - > airports_base_price , & invent - > mobile_plan_base_price , FP_GameAudio_MerchantPhone , & sound_played_flags [ 3 ] } ,
2023-10-07 01:21:31 +00:00
} ;
2023-10-08 02:22:08 +00:00
bool activated_merchant = false ;
for ( FP_MerchantToMenuMapping mapping : merchants ) {
FP_GameEntityHandle merchant_handle = mapping . merchant ;
Dqn_V2 world_pos = FP_Game_CalcEntityWorldPos ( game , merchant_handle ) ;
Dqn_f32 dist_squared = Dqn_V2_LengthSq_V2x2 ( world_pos , player_pos ) ;
2023-10-06 12:55:57 +00:00
2023-10-08 08:00:13 +00:00
if ( dist_squared > DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 4 ) ) ) {
* mapping . sound_played = false ;
2023-10-08 02:22:08 +00:00
continue ;
2023-10-08 08:00:13 +00:00
}
2023-10-06 12:55:57 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Render animated merchant menu =============================
activated_merchant = true ;
Dqn_Rect merchant_menu_rect = { } ;
{
2023-10-08 05:14:31 +00:00
FP_GameRenderSprite * sprite = & game - > play . player_merchant_menu ;
2023-10-08 02:22:08 +00:00
if ( ! sprite - > asset . anim | | sprite - > asset . anim - > label ! = mapping . menu_anim ) {
sprite - > asset = TELY_Asset_MakeAnimatedSprite ( & game - > atlas_sprite_sheet , mapping . menu_anim , TELY_AssetFlip_No ) ;
2023-10-08 05:14:31 +00:00
sprite - > started_at_clock_ms = game - > play . clock_ms ;
2023-10-07 01:21:31 +00:00
}
2023-10-07 04:04:37 +00:00
2023-10-08 05:14:31 +00:00
uint64_t elapsed_ms = game - > play . clock_ms - sprite - > started_at_clock_ms ;
2023-10-08 02:22:08 +00:00
uint16_t raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / sprite - > asset . anim - > ms_per_frame ) ;
uint16_t anim_frame = raw_anim_frame % sprite - > asset . anim - > count ;
2023-10-07 01:21:31 +00:00
2023-10-08 02:22:08 +00:00
Dqn_usize sprite_index = sprite - > asset . anim - > index + anim_frame ;
Dqn_Rect src_rect = sprite - > asset . sheet - > rects . data [ sprite_index ] ;
2023-10-07 01:21:31 +00:00
2023-10-08 02:22:08 +00:00
merchant_menu_rect . size = src_rect . size ;
merchant_menu_rect . pos = world_pos - ( merchant_menu_rect . size * .5f ) - Dqn_V2_InitNx2 ( 0.f , src_rect . size . y ) ;
Dqn_f32 sin_t = DQN_SINF ( DQN_CAST ( Dqn_f32 ) input - > timer_s * 3.f ) ;
merchant_menu_rect . pos . y + = sin_t * 4.f ;
2023-10-07 01:21:31 +00:00
TELY_Render_TextureColourV4 ( renderer ,
2023-10-08 02:22:08 +00:00
sprite - > asset . sheet - > tex_handle ,
src_rect ,
merchant_menu_rect ,
2023-10-07 01:21:31 +00:00
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
2023-10-08 02:22:08 +00:00
TELY_COLOUR_WHITE_V4 ) ;
2023-10-08 08:00:13 +00:00
if ( activated_merchant & & ! * mapping . sound_played ) {
TELY_Audio_Play ( audio , game - > audio [ mapping . audio_type ] , 1.f ) ;
* mapping . sound_played = true ;
}
2023-10-06 12:55:57 +00:00
}
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_WHITE_V4 ) ;
TELY_Render_PushFont ( renderer , game - > talkco_font_large ) ;
DQN_DEFER {
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
} ;
// NOTE: Render the merchant button for buildings ==================
uint64_t const buy_duration_ms = 500 ;
2023-10-07 01:21:31 +00:00
{
2023-10-08 02:22:08 +00:00
bool const have_enough_coins = player - > coins > = * mapping . building_base_price ;
// NOTE: Buy trigger + animation ===============================
{
TELY_PlatformInputScanCode key = TELY_PlatformInputScanCode_J ;
bool trigger_buy_anim = false ;
if ( have_enough_coins ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , key ) ) {
2023-10-08 05:14:31 +00:00
game - > play . player_trigger_purchase_building_timestamp = game - > play . clock_ms + buy_duration_ms ;
2023-10-08 02:22:08 +00:00
} else if ( TELY_Platform_InputScanCodeIsDown ( input , key ) ) {
trigger_buy_anim = true ;
2023-10-08 05:14:31 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_building_timestamp )
game - > play . player_trigger_purchase_building_timestamp = game - > play . clock_ms ;
2023-10-08 02:22:08 +00:00
} else if ( TELY_Platform_InputScanCodeIsReleased ( input , key ) ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_building_timestamp ) {
2023-10-08 02:22:08 +00:00
if ( mapping . inventory_count ) {
player - > coins - = * mapping . building_base_price ;
2023-10-08 05:21:39 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Ching ] , 1.f ) ;
2023-10-09 21:53:19 +00:00
* mapping . building_base_price * = 1 ;
2023-10-08 02:22:08 +00:00
// NOTE: Raise the prices of everything else
2023-10-09 21:53:19 +00:00
invent - > airports_base_price * = 1 ;
invent - > clubs_base_price * = 1 ;
invent - > kennels_base_price * = 1 ;
invent - > churchs_base_price * = 1 ;
2023-10-08 02:22:08 +00:00
( * mapping . inventory_count ) + + ;
}
} else {
2023-10-08 05:14:31 +00:00
game - > play . player_trigger_purchase_building_timestamp = UINT64_MAX ;
2023-10-07 06:55:34 +00:00
}
}
2023-10-06 12:30:09 +00:00
2023-10-08 02:22:08 +00:00
if ( trigger_buy_anim ) {
2023-10-08 05:14:31 +00:00
uint64_t start_buy_time = game - > play . player_trigger_purchase_building_timestamp - buy_duration_ms ;
uint64_t elapsed_time = game - > play . clock_ms - start_buy_time ;
2023-10-07 01:21:31 +00:00
2023-10-08 02:22:08 +00:00
Dqn_f32 buy_t = DQN_MIN ( elapsed_time / DQN_CAST ( Dqn_f32 ) buy_duration_ms , 1.f ) ;
Dqn_Rect buy_lerp_rect = { } ;
buy_lerp_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.297f , 0.215f ) ) ;
buy_lerp_rect . size . w = ( merchant_menu_rect . size . w * 0.38f ) * buy_t ;
buy_lerp_rect . size . h = merchant_menu_rect . size . h * .611f ;
2023-10-07 01:21:31 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_RectColourV4 ( renderer , buy_lerp_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_BLUE_CADET_V4 , 0.5f ) ) ;
}
2023-10-07 06:55:34 +00:00
}
2023-10-08 02:22:08 +00:00
}
// NOTE: Render the (A) button =================================
Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , .5f ) ;
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . merchant_button_a ) ;
Dqn_Rect button_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = button_rect . size * 1.5f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.345f , 0.5f ) ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
button_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
tex_mod_colour ) ;
TELY_Render_TextF ( renderer , Dqn_Rect_InterpolatedPoint ( dest_rect , Dqn_V2_InitNx2 ( 0.5f , - 1 ) ) , Dqn_V2_InitNx2 ( 0.5 , 0.f ) , " $%u " , * mapping . building_base_price ) ;
}
// NOTE: Render the merchant shop item building ================
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , mapping . building ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * 0.35f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.42f , 0.25f ) + mapping . building_offset01 ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
tex_mod_colour ) ;
2023-10-07 01:21:31 +00:00
}
}
2023-10-08 02:22:08 +00:00
// NOTE: Render the merchant button for buildings
2023-10-07 01:21:31 +00:00
{
2023-10-08 02:22:08 +00:00
bool const have_enough_coins = player - > coins > = * mapping . upgrade_base_price ;
// NOTE: Buy trigger + animation ===============================
{
TELY_PlatformInputScanCode key = TELY_PlatformInputScanCode_K ;
bool trigger_buy_anim = false ;
if ( have_enough_coins ) {
if ( TELY_Platform_InputScanCodeIsPressed ( input , key ) ) {
2023-10-08 05:14:31 +00:00
game - > play . player_trigger_purchase_upgrade_timestamp = game - > play . clock_ms + buy_duration_ms ;
2023-10-08 02:22:08 +00:00
} else if ( TELY_Platform_InputScanCodeIsDown ( input , key ) ) {
trigger_buy_anim = true ;
2023-10-08 05:14:31 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_upgrade_timestamp )
game - > play . player_trigger_purchase_upgrade_timestamp = game - > play . clock_ms ;
2023-10-08 02:22:08 +00:00
} else if ( TELY_Platform_InputScanCodeIsReleased ( input , key ) ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_upgrade_timestamp ) {
2023-10-08 02:22:08 +00:00
player - > coins - = * mapping . upgrade_base_price ;
2023-10-09 21:53:19 +00:00
* mapping . upgrade_base_price * = 1 ;
2023-10-08 05:21:39 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Ching ] , 1.f ) ;
2023-10-08 02:22:08 +00:00
2023-10-08 05:14:31 +00:00
if ( mapping . merchant = = game - > play . merchant_terry ) {
2023-10-08 02:22:08 +00:00
// TODO(doyle): Attack damage? Or increase attack range?
2023-10-12 11:46:26 +00:00
player - > base_attack + = DQN_CAST ( uint32_t ) ( FP_DEFAULT_DAMAGE * 1.2f ) ;
2023-10-08 05:14:31 +00:00
} else if ( mapping . merchant = = game - > play . merchant_graveyard ) {
2023-10-08 06:35:57 +00:00
player - > stamina_cap + = DQN_CAST ( uint32_t ) ( FP_TERRY_DASH_STAMINA_COST * .5f ) ;
2023-10-08 05:14:31 +00:00
} else if ( mapping . merchant = = game - > play . merchant_gym ) {
2023-10-08 02:22:08 +00:00
player - > hp_cap + = FP_DEFAULT_DAMAGE ;
player - > hp = player - > hp_cap ;
2023-10-08 05:14:31 +00:00
} else if ( mapping . merchant = = game - > play . merchant_phone_company ) {
2023-10-08 02:22:08 +00:00
player - > terry_mobile_data_plan_cap + = DQN_KILOBYTES ( 1 ) ;
}
} else {
2023-10-08 05:14:31 +00:00
game - > play . player_trigger_purchase_upgrade_timestamp = UINT64_MAX ;
2023-10-08 02:22:08 +00:00
}
}
if ( trigger_buy_anim ) {
2023-10-08 05:14:31 +00:00
uint64_t start_buy_time = game - > play . player_trigger_purchase_upgrade_timestamp - buy_duration_ms ;
uint64_t elapsed_time = game - > play . clock_ms - start_buy_time ;
2023-10-07 01:21:31 +00:00
2023-10-08 02:22:08 +00:00
Dqn_f32 buy_t = DQN_MIN ( elapsed_time / DQN_CAST ( Dqn_f32 ) buy_duration_ms , 1.f ) ;
Dqn_Rect buy_lerp_rect = { } ;
buy_lerp_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.68f , 0.215f ) ) ;
buy_lerp_rect . size . w = ( merchant_menu_rect . size . w * 0.211f ) * buy_t ;
buy_lerp_rect . size . h = merchant_menu_rect . size . h * .611f ;
TELY_Render_RectColourV4 ( renderer , buy_lerp_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , 0.5f ) ) ;
}
}
}
// NOTE: Render the (B) button =================================
Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , .5f ) ;
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . merchant_button_b ) ;
Dqn_Rect button_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = button_rect . size * 1.5f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.75f , 0.5f ) ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
button_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
tex_mod_colour ) ;
TELY_Render_TextF ( renderer , Dqn_Rect_InterpolatedPoint ( dest_rect , Dqn_V2_InitNx2 ( 0.5f , - 1 ) ) , Dqn_V2_InitNx2 ( 0.5 , 0.f ) , " $%u " , * mapping . upgrade_base_price ) ;
}
2023-10-07 01:21:31 +00:00
}
}
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
if ( activated_merchant ) {
2023-10-08 05:14:31 +00:00
game - > play . in_game_menu = FP_GameInGameMenu_Merchant ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-08 05:14:31 +00:00
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Merchant ) {
game - > play . in_game_menu = FP_GameInGameMenu_Nil ;
2023-10-08 02:22:08 +00:00
}
2023-10-06 12:16:55 +00:00
}
}
2023-10-09 10:35:51 +00:00
// NOTE: Render player avatar HUD ==========================================================
2023-10-08 02:22:08 +00:00
Dqn_Rect player_avatar_rect = { } ;
player_avatar_rect . pos = Dqn_V2_InitNx1 ( 32.f ) ;
Dqn_V2 next_pos = { } ;
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-06 10:48:05 +00:00
2023-10-08 02:22:08 +00:00
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , FP_EntityType_Terry , FP_EntityTerryState_Idle , FP_GameDirection_Down ) ;
player_avatar_rect . size = render_data . render_size ;
2023-10-06 10:48:05 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_TextureColourV4 ( renderer ,
render_data . sheet - > tex_handle ,
render_data . sheet_rect ,
player_avatar_rect ,
Dqn_V2_Zero ,
0.f ,
TELY_COLOUR_WHITE_V4 ) ;
2023-10-06 10:48:05 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_PushFont ( renderer , game - > talkco_font ) ;
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
2023-10-06 10:48:05 +00:00
2023-10-08 02:22:08 +00:00
next_pos = Dqn_Rect_InterpolatedPoint ( player_avatar_rect , Dqn_V2_InitNx2 ( 1.f , 0 ) ) ;
Dqn_f32 font_height = TELY_Render_FontHeight ( renderer , & platform - > assets ) ;
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Health bar ====================================================
Dqn_f32 bar_height = font_height * .75f ;
Dqn_Rect health_icon_rect = { } ;
2023-10-07 12:05:46 +00:00
{
2023-10-08 02:22:08 +00:00
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . icon_health ) ;
Dqn_Rect icon_tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
{
health_icon_rect . size = icon_tex_rect . size * .4f ;
health_icon_rect . pos = Dqn_V2_InitNx2 ( next_pos . x - health_icon_rect . size . x * .25f , next_pos . y - ( health_icon_rect . size . y * .35f ) ) ;
}
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
Dqn_f32 bar_x = next_pos . x + ( health_icon_rect . size . x * .25f ) ;
Dqn_f32 health_t = player - > hp / DQN_CAST ( Dqn_f32 ) player - > hp_cap ;
Dqn_Rect health_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , DQN_CAST ( Dqn_f32 ) player - > hp_cap , bar_height ) ;
Dqn_Rect curr_health_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , DQN_CAST ( Dqn_f32 ) player - > hp_cap * health_t , bar_height ) ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_RectColourV4 ( renderer , curr_health_rect , TELY_RenderShapeMode_Fill , TELY_COLOUR_RED_TOMATO_V4 ) ;
2023-10-07 06:55:34 +00:00
2023-10-08 02:22:08 +00:00
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 (
renderer ,
health_rect ,
TELY_RenderShapeMode_Line ,
TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 4.f ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the heart icon shadow
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
Dqn_Rect_InitV2x2 ( health_icon_rect . pos - ( cmd - > thickness * 2.f ) , health_icon_rect . size + ( cmd - > thickness * 4.f ) ) ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_BLACK_V4 ) ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the heart icon
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
health_icon_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-07 06:55:34 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Stamina bar ===================================================
next_pos . y + = health_icon_rect . size . h * .8f ;
Dqn_Rect stamina_icon_rect = { } ;
2023-10-07 12:05:46 +00:00
{
2023-10-08 02:22:08 +00:00
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . icon_stamina ) ;
Dqn_Rect icon_tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
{
stamina_icon_rect . size = icon_tex_rect . size * .35f ;
stamina_icon_rect . pos = Dqn_V2_InitNx2 ( next_pos . x - stamina_icon_rect . size . x * .25f , next_pos . y - ( stamina_icon_rect . size . y * .35f ) ) ;
}
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
Dqn_f32 bar_x = next_pos . x + ( stamina_icon_rect . size . x * .25f ) ;
Dqn_f32 stamina_t = player - > stamina / DQN_CAST ( Dqn_f32 ) player - > stamina_cap ;
Dqn_Rect stamina_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , DQN_CAST ( Dqn_f32 ) player - > stamina_cap , bar_height ) ;
Dqn_Rect curr_stamina_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , DQN_CAST ( Dqn_f32 ) player - > stamina_cap * stamina_t , bar_height ) ;
TELY_Render_RectColourV4 (
renderer ,
curr_stamina_rect ,
TELY_RenderShapeMode_Fill ,
TELY_COLOUR_YELLOW_SANDY_V4 ) ;
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 (
renderer ,
stamina_rect ,
TELY_RenderShapeMode_Line ,
TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 4.f ;
// NOTE: Draw the icon shadow
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
Dqn_Rect_InitV2x2 ( stamina_icon_rect . pos - ( cmd - > thickness * 2.f ) , stamina_icon_rect . size + ( cmd - > thickness * 4.f ) ) ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_BLACK_V4 ) ;
2023-10-07 06:55:34 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the icon
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
stamina_icon_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
// NOTE: Mobile data bar ===================================================
next_pos . y + = stamina_icon_rect . size . h * .8f ;
Dqn_Rect phone_icon_rect = { } ;
2023-10-07 12:05:46 +00:00
{
2023-10-08 02:22:08 +00:00
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . icon_phone ) ;
Dqn_Rect icon_tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
{
phone_icon_rect . size = icon_tex_rect . size * .35f ;
phone_icon_rect . pos = Dqn_V2_InitNx2 ( next_pos . x - phone_icon_rect . size . x * .25f , next_pos . y - ( phone_icon_rect . size . y * .35f ) ) ;
}
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
Dqn_f32 pixels_per_kb = 15.5f ;
Dqn_f32 bar_width = DQN_CAST ( Dqn_f32 ) player - > terry_mobile_data_plan_cap / DQN_KILOBYTES ( 1 ) * pixels_per_kb ;
Dqn_f32 bar_x = next_pos . x + ( phone_icon_rect . size . x * .25f ) ;
Dqn_f32 data_plan_t = player - > terry_mobile_data_plan / DQN_CAST ( Dqn_f32 ) player - > terry_mobile_data_plan_cap ;
Dqn_Rect data_plan_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , bar_width , bar_height ) ;
Dqn_Rect curr_data_plan_rect = Dqn_Rect_InitNx4 ( bar_x , next_pos . y , bar_width * data_plan_t , bar_height ) ;
TELY_Render_RectColourV4 (
renderer ,
curr_data_plan_rect ,
TELY_RenderShapeMode_Fill ,
TELY_COLOUR_BLUE_CADET_V4 ) ;
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 (
renderer ,
data_plan_rect ,
TELY_RenderShapeMode_Line ,
TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 4.f ;
// NOTE: Draw the icon shadow
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
Dqn_Rect_InitV2x2 ( phone_icon_rect . pos - ( cmd - > thickness * 2.f ) , phone_icon_rect . size + ( cmd - > thickness * 4.f ) ) ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_BLACK_V4 ) ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the icon
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
phone_icon_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
2023-10-07 12:05:46 +00:00
}
2023-10-08 02:22:08 +00:00
next_pos . y + = phone_icon_rect . size . h * .8f ;
Dqn_Rect money_icon_rect = { } ;
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . icon_money ) ;
Dqn_Rect icon_tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
{
money_icon_rect . size = icon_tex_rect . size * .35f ;
money_icon_rect . pos = Dqn_V2_InitNx2 ( next_pos . x - money_icon_rect . size . x * .25f , next_pos . y - ( money_icon_rect . size . y * .15f ) ) ;
}
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_TextF ( renderer ,
Dqn_V2_InitNx2 ( next_pos . x + money_icon_rect . size . x * .75f , money_icon_rect . pos . y ) ,
Dqn_V2_InitNx2 ( 0 , - 0.5 ) ,
2023-10-15 09:41:42 +00:00
" $%zu " ,
2023-10-08 02:22:08 +00:00
player - > coins ) ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the icon shadow
Dqn_f32 thickness = 4.f ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
Dqn_Rect_InitV2x2 ( money_icon_rect . pos - ( thickness * 2.f ) , money_icon_rect . size + ( thickness * 4.f ) ) ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_BLACK_V4 ) ;
2023-10-07 12:05:46 +00:00
2023-10-08 02:22:08 +00:00
// NOTE: Draw the icon
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
money_icon_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
next_pos . y + = money_icon_rect . size . h ;
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
#if 0
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [H] Build Menu " ) ;
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [Shift+WASD] Strafe " ) ;
2023-10-06 12:16:55 +00:00
2023-10-08 02:22:08 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [Ctrl+WASD] Dash " ) ;
2023-10-05 12:09:39 +00:00
2023-10-08 02:22:08 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [J|K] Melee/Range " ) ;
# endif
}
2023-10-09 10:35:51 +00:00
// NOTE: Render the wave ===================================================================
2023-10-08 02:22:08 +00:00
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-07 09:56:14 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_PushFont ( renderer , game - > talkco_font_large ) ;
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
2023-10-07 09:56:14 +00:00
2023-10-08 02:22:08 +00:00
uint64_t time_until_next_wave_ms = 0 ;
2023-10-08 05:14:31 +00:00
if ( game - > play . enemies_spawned_this_wave > = game - > play . enemies_per_wave ) {
if ( game - > play . wave_cooldown_timestamp_ms > game - > play . clock_ms ) {
time_until_next_wave_ms = game - > play . wave_cooldown_timestamp_ms - game - > play . clock_ms ;
2023-10-08 02:22:08 +00:00
}
2023-10-07 09:56:14 +00:00
}
2023-10-08 02:22:08 +00:00
Dqn_f32 mid_x = platform - > core . window_size . x * .5f ;
if ( time_until_next_wave_ms ) {
TELY_Render_TextF ( renderer ,
Dqn_V2_InitNx2 ( mid_x , player_avatar_rect . pos . y ) ,
Dqn_V2_InitNx1 ( 0.5f ) ,
" %.1fs remaining until Wave %u! " ,
2023-10-08 05:14:31 +00:00
( time_until_next_wave_ms / 1000.f ) , game - > play . current_wave + 1 ) ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-08 05:14:31 +00:00
TELY_Render_TextF ( renderer , Dqn_V2_InitNx2 ( mid_x , player_avatar_rect . pos . y ) , Dqn_V2_InitNx1 ( 0.5f ) , " Wave %u " , game - > play . current_wave ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-07 09:56:14 +00:00
}
2023-10-08 05:14:31 +00:00
if ( ! FP_Game_IsNilEntityHandle ( game , game - > play . clicked_entity ) ) {
2023-10-09 10:35:51 +00:00
// NOTE: Render building blueprint =====================================================
2023-10-08 05:14:31 +00:00
if ( game - > play . in_game_menu = = FP_GameInGameMenu_Build ) {
FP_GameEntity * entity = FP_Game_GetEntity ( game , game - > play . clicked_entity ) ;
FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS [ game - > play . build_mode_building_index ] ;
2023-10-08 02:22:08 +00:00
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , placeable_building . type , placeable_building . state , entity - > direction ) ;
Dqn_Rect dest_rect = FP_Game_GetBuildingPlacementRectForEntity ( game , placeable_building , entity - > handle ) ;
2023-10-02 11:38:36 +00:00
2023-10-08 05:14:31 +00:00
Dqn_V4 colour = game - > play . build_mode_can_place_building ?
2023-10-08 02:22:08 +00:00
TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , 0.5f ) :
TELY_Colour_V4Alpha ( TELY_COLOUR_RED_V4 , 0.5f ) ;
2023-10-05 12:09:39 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_RectColourV4 ( renderer , dest_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_BLUE_CADET_V4 , 0.5f ) ) ;
2023-10-05 12:09:39 +00:00
TELY_Render_TextureColourV4 ( renderer ,
render_data . sheet - > tex_handle ,
render_data . sheet_rect ,
2023-10-08 02:22:08 +00:00
dest_rect ,
2023-10-05 12:09:39 +00:00
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
2023-10-08 02:22:08 +00:00
colour ) ;
2023-10-05 12:09:39 +00:00
}
2023-09-16 07:32:25 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Render the building selector UI ===============================================
2023-10-08 02:22:08 +00:00
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-08 05:14:31 +00:00
game - > play . build_mode_building_index = DQN_CLAMP ( game - > play . build_mode_building_index , 0 , DQN_ARRAY_UCOUNT ( PLACEABLE_BUILDINGS ) - 1 ) ;
2023-10-08 02:22:08 +00:00
Dqn_f32 building_ui_size = 64.f ;
Dqn_f32 padding = 10.f ;
Dqn_f32 start_x = player_avatar_rect . pos . x ;
DQN_FOR_UINDEX ( building_index , DQN_ARRAY_UCOUNT ( PLACEABLE_BUILDINGS ) ) {
FP_GamePlaceableBuilding building = PLACEABLE_BUILDINGS [ building_index ] ;
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , building . type , building . state , FP_GameDirection_Down ) ;
Dqn_Rect rect = Dqn_Rect_InitNx4 ( start_x + ( building_index * building_ui_size ) + ( padding * building_index ) ,
next_pos . y ,
building_ui_size ,
building_ui_size ) ;
Dqn_V4 texture_colour = TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , .5f ) ;
Dqn_V4 outline_colour = TELY_COLOUR_WHITE_PALE_GOLDENROD_V4 ;
2023-10-08 05:14:31 +00:00
if ( game - > play . build_mode_building_index = = building_index ) {
2023-10-08 02:22:08 +00:00
outline_colour = TELY_COLOUR_RED_TOMATO_V4 ;
texture_colour . a = 1.f ;
}
TELY_Render_RectColourV4 ( renderer , rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , 0.5f ) ) ;
TELY_Render_TextureColourV4 ( renderer ,
render_data . sheet - > tex_handle ,
render_data . sheet_rect ,
rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
texture_colour ) ;
uint32_t building_count = 0 ;
if ( building . type = = FP_EntityType_ClubTerry )
building_count = player - > inventory . clubs ;
else if ( building . type = = FP_EntityType_AirportTerry )
building_count = player - > inventory . airports ;
else if ( building . type = = FP_EntityType_KennelTerry )
building_count = player - > inventory . kennels ;
else if ( building . type = = FP_EntityType_ChurchTerry )
building_count = player - > inventory . churchs ;
TELY_Render_PushFont ( renderer , game - > talkco_font ) ;
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
Dqn_V2 label_p = Dqn_Rect_InterpolatedPoint ( rect , Dqn_V2_InitNx2 ( 0.5f , 1.25f ) ) ;
TELY_Render_TextF ( renderer , label_p , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) , " x %u " , building_count ) ;
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 ( renderer , rect , TELY_RenderShapeMode_Line , outline_colour ) ;
cmd - > thickness = 2.f ;
}
2023-09-16 07:32:25 +00:00
}
}
2023-09-16 02:21:24 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Render the heart health ===========================================================
2023-10-08 08:04:11 +00:00
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
Dqn_f32 font_height = TELY_Render_FontHeight ( renderer , assets ) ;
Dqn_f32 bar_height = font_height * 1.25f ;
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . heart ) ;
2023-10-09 10:35:51 +00:00
FP_GameEntity * heart = FP_Game_GetEntity ( game , game - > play . heart ) ;
Dqn_f32 max_width = platform - > core . window_size . x * .5f ;
Dqn_V2 draw_p = Dqn_V2_InitNx2 ( platform - > core . window_size . x * .25f , player_avatar_rect . pos . y + bar_height ) ;
Dqn_f32 health_t = heart - > hp / DQN_CAST ( Dqn_f32 ) heart - > hp_cap ;
Dqn_Rect health_rect = Dqn_Rect_InitNx4 ( draw_p . x , draw_p . y , max_width , bar_height ) ;
Dqn_Rect curr_health_rect = Dqn_Rect_InitNx4 ( draw_p . x , draw_p . y , max_width * health_t , bar_height ) ;
2023-10-08 08:04:11 +00:00
TELY_Render_RectColourV4 ( renderer , curr_health_rect , TELY_RenderShapeMode_Fill , TELY_COLOUR_RED_TOMATO_V4 ) ;
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 (
renderer ,
health_rect ,
TELY_RenderShapeMode_Line ,
TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 4.f ;
// NOTE: Draw the heart icon
Dqn_Rect icon_tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect heart_icon_rect = { } ;
heart_icon_rect . size = icon_tex_rect . size * .4f ;
heart_icon_rect . pos = Dqn_V2_InitNx2 ( draw_p . x - ( heart_icon_rect . size . w * .7f ) , draw_p . y - ( heart_icon_rect . size . y * .4f ) ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
icon_tex_rect ,
heart_icon_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
TELY_Render_PushFont ( renderer , game - > talkco_font_large ) ;
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
TELY_Render_TextF ( renderer , Dqn_Rect_InterpolatedPoint ( heart_icon_rect , Dqn_V2_InitNx2 ( 1.f , 0.65f ) ) , Dqn_V2_Zero , " Terry's Heart " ) ;
}
}
2023-10-09 10:35:51 +00:00
}
2023-10-08 08:04:11 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Add scanlines into the game for A E S T H E T I C S ===================================
if ( game - > play . state = = FP_GameState_Play ) {
2023-10-08 02:22:08 +00:00
Dqn_V2 screen_size = Dqn_V2_InitNx2 ( platform - > core . window_size . w , platform - > core . window_size . h ) ;
Dqn_f32 scanline_gap = 4.0f ;
Dqn_f32 scanline_thickness = 3.0f ;
2023-10-15 10:02:28 +00:00
FP_GameRenderScanlines ( renderer , scanline_gap , scanline_thickness , screen_size ) ;
2023-09-23 07:26:18 +00:00
}
2023-10-09 10:35:51 +00:00
// NOTE: Render the other game state modes =====================================================
if ( game - > play . state = = FP_GameState_IntroScreen | | game - > play . state = = FP_GameState_WinGame | | game - > play . state = = FP_GameState_LoseGame | | game - > play . state = = FP_GameState_Pause ) {
2023-10-12 13:05:41 +00:00
2023-10-09 10:35:51 +00:00
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
Dqn_V2I inset = platform - > core . window_size * .05f ;
Dqn_f32 min_inset = DQN_CAST ( Dqn_f32 ) DQN_MIN ( inset . x , inset . y ) ;
Dqn_V2 draw_p = Dqn_V2_InitNx2 ( min_inset , min_inset ) ;
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_WHITE_V4 ) ;
2023-10-14 01:19:03 +00:00
Dqn_V4 bg_colour = TELY_Colour_V4InitRGBAU32 ( 0x301010FF ) ; // NOTE: Maroon
2023-10-12 13:05:41 +00:00
TELY_Render_RectColourV4 (
renderer ,
Dqn_Rect_InitNx4 ( 0 , 0 , DQN_CAST ( Dqn_f32 ) platform - > core . window_size . x , DQN_CAST ( Dqn_f32 ) platform - > core . window_size . y ) ,
TELY_RenderShapeMode_Fill ,
bg_colour ) ;
2023-10-14 01:19:03 +00:00
Dqn_Rect window_rect = Dqn_Rect_InitNx4 ( 0 , 0 , DQN_CAST ( Dqn_f32 ) platform - > core . window_size . w , DQN_CAST ( Dqn_f32 ) platform - > core . window_size . h ) ;
if ( game - > play . state = = FP_GameState_IntroScreen | | game - > play . state = = FP_GameState_LoseGame ) {
2023-10-12 13:05:41 +00:00
Dqn_f32 tex_scalar = 0.f ;
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_terry ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_f32 desired_width = window_rect . size . x ;
tex_scalar = desired_width / tex_rect . size . w ;
}
2023-10-14 02:21:38 +00:00
// NOTE: Draw title text ===========================================================
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_title ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ) - ( dest_rect . size * .5f ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-14 01:19:03 +00:00
if ( game - > play . state = = FP_GameState_IntroScreen ) {
// NOTE: Draw terry logo ===========================================================
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_terry ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ) - ( dest_rect . size * .5f ) ;
2023-10-12 13:05:41 +00:00
2023-10-14 01:19:03 +00:00
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-12 13:05:41 +00:00
2023-10-14 01:19:03 +00:00
// NOTE: Draw title subtitle =======================================================
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_subtitle ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ) - ( dest_rect . size * .5f ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
} else {
// NOTE: Draw end screen logo ======================================================
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . end_screen ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ) - ( dest_rect . size * .5f ) ;
2023-10-12 13:05:41 +00:00
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-14 01:19:03 +00:00
// NOTE: Draw cupid arrows around logo =================================================
2023-10-12 13:05:41 +00:00
{
2023-10-14 01:19:03 +00:00
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_arrows ) ;
2023-10-12 13:05:41 +00:00
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ) - ( dest_rect . size * .5f ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
dest_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
2023-10-09 10:35:51 +00:00
2023-10-14 01:19:03 +00:00
2023-10-09 10:35:51 +00:00
TELY_Render_PushFont ( renderer , game - > inter_regular_font ) ;
2023-10-12 13:05:41 +00:00
Dqn_f32 t = ( DQN_SINF ( DQN_CAST ( Dqn_f32 ) input - > timer_s * 5.f ) + 1.f ) / 2.f ;
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , t ) ) ;
2023-10-14 01:19:03 +00:00
TELY_Render_TextF ( renderer , Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.925f ) ) , Dqn_V2_InitNx1 ( 0.5f ) , " Press enter to %s " , game - > play . state = = FP_GameState_Play ? " start " : " restart " ) ;
2023-10-09 10:35:51 +00:00
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-12 13:05:41 +00:00
TELY_Render_PopFont ( renderer ) ;
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Return ) ) {
if ( game - > play . state = = FP_GameState_LoseGame )
FP_PlayReset ( game , platform ) ;
2023-10-09 10:35:51 +00:00
game - > play . state = FP_GameState_Play ;
2023-10-14 01:19:03 +00:00
}
2023-10-12 13:05:41 +00:00
2023-10-09 10:35:51 +00:00
} else if ( game - > play . state = = FP_GameState_Pause ) {
TELY_Render_PushFont ( renderer , game - > inter_regular_font_large ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Paused " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PushFont ( renderer , game - > inter_regular_font ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Press enter to resume " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Return ) )
game - > play . state = FP_GameState_Play ;
} else {
DQN_ASSERT ( game - > play . state = = FP_GameState_WinGame ) ;
TELY_Render_PushFont ( renderer , game - > inter_regular_font_large ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Terry has been saved " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " from his terrible calamity " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PushFont ( renderer , game - > inter_regular_font ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " He lives for yet another day and another love " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Press enter to restart " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_Return ) )
FP_PlayReset ( game , platform ) ;
}
2023-10-14 01:19:03 +00:00
Dqn_f32 scanline_gap = 4.0f ;
Dqn_f32 scanline_thickness = 3.0f ;
2023-10-15 10:02:28 +00:00
FP_GameRenderScanlines ( renderer , scanline_gap , scanline_thickness , window_rect . size ) ;
2023-10-09 10:35:51 +00:00
}
// NOTE: Debug UI ==============================================================================
2023-10-08 05:14:31 +00:00
if ( game - > play . debug_ui ) {
2023-10-08 06:25:08 +00:00
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-14 14:31:33 +00:00
2023-09-23 06:42:22 +00:00
// NOTE: Info bar ==========================================================================
2023-10-14 14:31:33 +00:00
Dqn_f32 next_y = 10.f ;
TELY_RFui_PushLabelColourV4 ( rfui , TELY_COLOUR_BLACK_V4 ) ;
2023-09-23 06:42:22 +00:00
{
TELY_RFuiResult info_bar = TELY_RFui_Row ( rfui , DQN_STRING8 ( " Info Bar " ) ) ;
info_bar . widget - > semantic_position [ TELY_RFuiAxis_X ] . kind = TELY_RFuiPositionKind_Absolute ;
2023-10-14 14:31:33 +00:00
info_bar . widget - > semantic_position [ TELY_RFuiAxis_X ] . value = next_y ;
2023-09-23 06:42:22 +00:00
info_bar . widget - > semantic_position [ TELY_RFuiAxis_Y ] . kind = TELY_RFuiPositionKind_Absolute ;
2023-10-14 14:31:33 +00:00
info_bar . widget - > semantic_position [ TELY_RFuiAxis_Y ] . value = next_y ;
2023-09-23 06:42:22 +00:00
TELY_RFui_PushParent ( rfui , info_bar . widget ) ;
DQN_DEFER { TELY_RFui_PopParent ( rfui ) ; } ;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
TELY_RFui_TextF ( rfui , " TELY " ) ;
if ( Dqn_String8_IsValid ( platform - > core . os_name ) ) {
TELY_RFui_TextF ( rfui , " | %.*s " , DQN_STRING_FMT ( platform - > core . os_name ) ) ;
}
2023-09-20 13:35:38 +00:00
2023-09-23 06:42:22 +00:00
TELY_RFui_TextF ( rfui ,
" | %dx%d %.1fHz | TSC %.1f GHz " ,
2023-09-30 11:36:27 +00:00
platform - > core . window_size . w ,
platform - > core . window_size . h ,
2023-09-23 06:42:22 +00:00
platform - > core . display . refresh_rate ,
platform - > core . tsc_per_second / 1'000'000'000 .0 ) ;
if ( platform - > core . ram_mb )
TELY_RFui_TextF ( rfui , " | RAM %.1fGB " , platform - > core . ram_mb / 1024.0 ) ;
TELY_RFui_TextF ( rfui ,
" | Work %04.1fms/f (%04.1f%%) | %05.1f FPS | Frame %'I64u | Timer %.1fs " ,
input - > work_ms ,
input - > work_ms * 100.0 / input - > delta_ms ,
1000.0 / input - > delta_ms ,
input - > frame_counter ,
input - > timer_s ) ;
2023-09-30 12:27:25 +00:00
2023-09-20 13:35:38 +00:00
}
2023-10-15 11:48:53 +00:00
if ( 0 ) {
next_y + = TELY_Asset_GetFont ( assets , TELY_RFui_ActiveFont ( rfui ) ) - > pixel_height ;
2023-10-14 14:31:33 +00:00
TELY_RFuiResult bar = TELY_RFui_Column ( rfui , DQN_STRING8 ( " Memory bar " ) ) ;
bar . widget - > semantic_position [ TELY_RFuiAxis_X ] . kind = TELY_RFuiPositionKind_Absolute ;
bar . widget - > semantic_position [ TELY_RFuiAxis_X ] . value = 10.f ;
bar . widget - > semantic_position [ TELY_RFuiAxis_Y ] . kind = TELY_RFuiPositionKind_Absolute ;
bar . widget - > semantic_position [ TELY_RFuiAxis_Y ] . value = next_y ;
TELY_RFui_PushParent ( rfui , bar . widget ) ;
DQN_DEFER { TELY_RFui_PopParent ( rfui ) ; } ;
{
Dqn_ArenaInfo arena_info = Dqn_Arena_Info ( & platform - > arena ) ;
TELY_RFui_TextF ( rfui ,
" Platform Arena[%I64u]: %_$$d/%_$$d (HWM %_$$d, COMMIT %_$$d) " ,
platform - > arena . blocks ,
arena_info . used ,
arena_info . capacity ,
arena_info . used_hwm ,
arena_info . commit ) ;
next_y + = TELY_Asset_GetFont ( assets , TELY_RFui_ActiveFont ( rfui ) ) - > pixel_height ;
}
for ( Dqn_ArenaCatalogItem * item = g_dqn_library - > arena_catalog . sentinel . next ; item ! = & g_dqn_library - > arena_catalog . sentinel ; item = item - > next ) {
if ( item ! = g_dqn_library - > arena_catalog . sentinel . next )
next_y + = TELY_Asset_GetFont ( assets , TELY_RFui_ActiveFont ( rfui ) ) - > pixel_height ;
Dqn_Arena * arena = item - > arena ;
Dqn_ArenaInfo arena_info = Dqn_Arena_Info ( arena ) ;
TELY_RFui_TextF ( rfui ,
" %.*s[%I64u]: %_$$d/%_$$d (HWM %_$$d, COMMIT %_$$d) " ,
DQN_STRING_FMT ( arena - > label ) ,
arena - > blocks ,
arena_info . used ,
arena_info . capacity ,
arena_info . used_hwm ,
arena_info . commit ) ;
}
}
// NOTE: Profiler
next_y + = TELY_Asset_GetFont ( assets , TELY_RFui_ActiveFont ( rfui ) ) - > pixel_height ;
2023-09-23 06:42:22 +00:00
{
TELY_RFuiResult profiler_layout = TELY_RFui_Column ( rfui , DQN_STRING8 ( " Profiler Bar " ) ) ;
profiler_layout . widget - > semantic_position [ TELY_RFuiAxis_X ] . kind = TELY_RFuiPositionKind_Absolute ;
profiler_layout . widget - > semantic_position [ TELY_RFuiAxis_X ] . value = 10.f ;
profiler_layout . widget - > semantic_position [ TELY_RFuiAxis_Y ] . kind = TELY_RFuiPositionKind_Absolute ;
2023-10-14 14:31:33 +00:00
profiler_layout . widget - > semantic_position [ TELY_RFuiAxis_Y ] . value = next_y ;
2023-09-23 06:42:22 +00:00
TELY_RFui_PushParent ( rfui , profiler_layout . widget ) ;
DQN_DEFER { TELY_RFui_PopParent ( rfui ) ; } ;
2023-10-08 05:14:31 +00:00
TELY_RFui_TextF ( rfui , " Camera: %.1f, %.1f " , game - > play . camera . world_pos . x , game - > play . camera . world_pos . y ) ;
2023-10-15 11:48:53 +00:00
TELY_RFui_TextF ( rfui , " Mouse: %.1f, %.1f " , input - > mouse_p . x , input - > mouse_p . y ) ;
2023-09-30 12:27:25 +00:00
2023-10-15 08:04:58 +00:00
// TODO(doyle): On emscripten we need to use Dqn_OS_PerfCounterNow() however those
// require OS functions which need to be exposed into the platform layer.
#if 0
Dqn_f64 const tsc_frequency = DQN_CAST ( Dqn_f64 ) TELY_Platform_PerfCounterFrequency ( & platform - > core ) ;
Dqn_ProfilerAnchor * anchors = Dqn_Profiler_AnchorBuffer ( Dqn_ProfilerAnchorBuffer_Back ) ;
2023-09-23 06:42:22 +00:00
for ( size_t anchor_index = 1 ; anchor_index < DQN_PROFILER_ANCHOR_BUFFER_SIZE ; anchor_index + + ) {
Dqn_ProfilerAnchor const * anchor = anchors + anchor_index ;
if ( ! anchor - > hit_count )
continue ;
2023-09-20 13:35:38 +00:00
2023-09-23 06:42:22 +00:00
uint64_t tsc_exclusive = anchor - > tsc_exclusive ;
uint64_t tsc_inclusive = anchor - > tsc_inclusive ;
2023-10-15 08:04:58 +00:00
Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / tsc_frequency ;
2023-09-23 06:42:22 +00:00
if ( tsc_exclusive = = tsc_inclusive ) {
TELY_RFui_TextF ( rfui ,
" %.*s[%u]: %.1fms " ,
DQN_STRING_FMT ( anchor - > name ) ,
anchor - > hit_count ,
tsc_exclusive_milliseconds ) ;
} else {
2023-10-15 08:04:58 +00:00
Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / tsc_frequency ;
2023-09-23 06:42:22 +00:00
TELY_RFui_TextF ( rfui ,
" %.*s[%u]: %.1f/%.1fms " ,
DQN_STRING_FMT ( anchor - > name ) ,
anchor - > hit_count ,
tsc_exclusive_milliseconds ,
tsc_inclusive_milliseconds ) ;
}
}
2023-10-15 08:04:58 +00:00
# endif
2023-09-23 06:42:22 +00:00
}
2023-10-14 14:31:33 +00:00
TELY_RFui_Flush ( rfui , renderer , input , assets ) ;
2023-09-20 13:35:38 +00:00
}
2023-10-08 05:14:31 +00:00
2023-10-08 05:17:40 +00:00
if ( game - > play . state = = FP_GameState_Play ) {
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-08 05:14:31 +00:00
2023-10-08 05:17:40 +00:00
FP_GameEntity * player = FP_Game_GetEntity ( game , game - > play . player ) ;
Dqn_V2 draw_p = Dqn_V2_InitNx2 ( 32.f , platform - > core . window_size . h * .5f ) ;
TELY_Render_PushFont ( renderer , game - > inter_regular_font ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " DEBUG MENU " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-09 13:06:59 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F1 Debug info " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F2 Add coins x10,000 " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F3 Win game " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-14 01:19:03 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F4 Lose game " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F5 Reset game " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F6 Increase health " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F7 Increase stamina " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F8 Increase mobile data " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F9 %s god mode " , game - > play . god_mode ? " Disable " : " Enable " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-14 14:31:33 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F10 %s noclip " , player - > flags & FP_GameEntityFlag_NoClip ? " Disable " : " Enable " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-14 01:19:03 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F11 Building inventory +1 " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " F12 %s by enemies " , player - > faction = = FP_GameEntityFaction_Nil ? " Attacked " : " Ignored " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-14 14:31:33 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " 1 %s HUD " , game - > play . debug_hide_hud ? " Show " : " Hide " ) ; draw_p . y + = TELY_Render_FontHeight ( renderer , assets ) ;
2023-10-08 05:17:40 +00:00
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F1 ) )
game - > play . debug_ui = ! game - > play . debug_ui ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F2 ) ) {
if ( ! FP_Game_IsNilEntity ( player ) )
player - > coins + = 10'000 ;
}
2023-10-08 05:14:31 +00:00
2023-10-08 05:17:40 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F3 ) )
game - > play . state = FP_GameState_WinGame ;
2023-10-08 05:14:31 +00:00
2023-10-08 05:17:40 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F4 ) )
2023-10-14 01:19:03 +00:00
game - > play . state = FP_GameState_LoseGame ;
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F5 ) )
2023-10-08 05:17:40 +00:00
FP_PlayReset ( game , platform ) ;
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F6 ) ) {
2023-10-14 14:31:33 +00:00
if ( ! FP_Game_IsNilEntity ( player ) ) {
2023-10-08 05:17:40 +00:00
player - > hp_cap + = FP_DEFAULT_DAMAGE ;
2023-10-08 08:48:17 +00:00
player - > hp = player - > hp_cap ;
2023-10-14 14:31:33 +00:00
}
2023-10-08 05:17:40 +00:00
}
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F7 ) ) {
2023-10-08 08:48:17 +00:00
if ( ! FP_Game_IsNilEntity ( player ) ) {
2023-10-08 05:17:40 +00:00
player - > stamina_cap + = DQN_CAST ( uint16_t ) ( FP_TERRY_DASH_STAMINA_COST * .5f ) ;
2023-10-08 08:48:17 +00:00
player - > stamina = player - > stamina_cap ;
}
2023-10-08 05:17:40 +00:00
}
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F8 ) ) {
2023-10-08 08:48:17 +00:00
if ( ! FP_Game_IsNilEntity ( player ) ) {
2023-10-08 05:17:40 +00:00
player - > terry_mobile_data_plan_cap + = DQN_KILOBYTES ( 1 ) ;
2023-10-08 08:48:17 +00:00
player - > terry_mobile_data_plan = player - > terry_mobile_data_plan_cap ;
}
2023-10-08 05:17:40 +00:00
}
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F9 ) )
2023-10-08 05:17:40 +00:00
game - > play . god_mode = ! game - > play . god_mode ;
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F10 ) )
2023-10-08 05:17:40 +00:00
player - > flags ^ = FP_GameEntityFlag_NoClip ;
2023-10-08 05:14:31 +00:00
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F11 ) ) {
2023-10-09 13:06:59 +00:00
player - > inventory . clubs + = 1 ;
player - > inventory . airports + = 1 ;
player - > inventory . churchs + = 1 ;
player - > inventory . kennels + = 1 ;
}
2023-10-14 01:19:03 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_F12 ) ) {
2023-10-09 13:06:59 +00:00
player - > faction = player - > faction = = FP_GameEntityFaction_Nil
? FP_GameEntityFaction_Friendly
: FP_GameEntityFaction_Nil ;
}
2023-10-14 14:31:33 +00:00
if ( TELY_Platform_InputScanCodeIsPressed ( input , TELY_PlatformInputScanCode_1 ) )
game - > play . debug_hide_hud = ! game - > play . debug_hide_hud ;
2023-10-08 05:17:40 +00:00
}
2023-10-08 02:22:08 +00:00
}
2023-10-14 06:21:23 +00:00
FP_DLL_FUNCTION
2023-10-08 02:22:08 +00:00
void TELY_DLL_FrameUpdate ( void * user_data )
{
TELY_Platform * platform = DQN_CAST ( TELY_Platform * ) user_data ;
TELY_PlatformInput * input = & platform - > input ;
TELY_Assets * assets = & platform - > assets ;
TELY_Renderer * renderer = & platform - > renderer ;
FP_Game * game = DQN_CAST ( FP_Game * ) platform - > user_data ;
// =============================================================================================
2023-10-08 05:14:31 +00:00
game - > play . prev_clicked_entity = game - > play . clicked_entity ;
game - > play . prev_hot_entity = game - > play . hot_entity ;
game - > play . prev_active_entity = game - > play . active_entity ;
game - > play . hot_entity = { } ;
game - > play . active_entity = { } ;
Dqn_FArray_Clear ( & game - > play . parent_entity_stack ) ;
Dqn_FArray_Add ( & game - > play . parent_entity_stack , game - > play . root_entity - > handle ) ;
2023-10-08 02:22:08 +00:00
// =============================================================================================
TELY_Audio * audio = & platform - > audio ;
#if 0
if ( audio - > playback_size = = 0 ) {
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_TestAudio ] , 1.f /*volume*/ ) ;
}
# endif
// =============================================================================================
2023-10-08 05:14:31 +00:00
if ( game - > play . state = = FP_GameState_Play ) {
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputKeyWasDown ( input - > mouse_left ) & & TELY_Platform_InputKeyIsDown ( input - > mouse_left ) ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . prev_active_entity . id )
game - > play . active_entity = game - > play . prev_active_entity ;
2023-10-08 02:22:08 +00:00
} else {
2023-10-15 11:48:53 +00:00
FP_GameCameraM2x3 const camera_xforms = FP_Game_CameraModelViewM2x3 ( game - > play . camera ) ;
Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2 ( camera_xforms . view_model , input - > mouse_p ) ;
2023-10-08 05:14:31 +00:00
for ( FP_GameEntityIterator it = { } ; FP_Game_DFSPreOrderWalkEntityTree ( game , & it , game - > play . root_entity ) ; ) {
2023-10-08 02:22:08 +00:00
FP_GameEntity * entity = it . entity ;
if ( entity - > local_hit_box_size . x < = 0 | | entity - > local_hit_box_size . y < = 0 )
continue ;
if ( ( entity - > flags & FP_GameEntityFlag_Clickable ) = = 0 )
continue ;
Dqn_Rect world_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
if ( ! Dqn_Rect_ContainsPoint ( world_hit_box , world_mouse_p ) )
continue ;
2023-10-08 05:14:31 +00:00
game - > play . hot_entity = entity - > handle ;
2023-10-08 02:22:08 +00:00
if ( TELY_Platform_InputKeyIsPressed ( input - > mouse_left ) ) {
2023-10-08 05:14:31 +00:00
game - > play . active_entity = entity - > handle ;
game - > play . clicked_entity = entity - > handle ;
2023-10-08 02:22:08 +00:00
}
}
}
}
2023-10-08 05:14:31 +00:00
for ( game - > play . delta_s_accumulator + = DQN_CAST ( Dqn_f32 ) input - > delta_s ;
game - > play . delta_s_accumulator > PHYSICS_STEP ;
game - > play . delta_s_accumulator - = PHYSICS_STEP ) {
2023-10-08 05:21:39 +00:00
FP_Update ( platform , game , input , audio ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-08 05:21:39 +00:00
FP_Render ( game , platform , renderer , audio ) ;
2023-09-20 13:35:38 +00:00
2023-09-30 09:09:15 +00:00
TELY_Audio_MixPlaybackSamples ( audio , assets ) ;
2023-09-16 02:21:24 +00:00
}