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-10-22 11:50:18 +00:00
Dqn_V2 const FP_TARGET_VIEWPORT_SIZE = Dqn_V2_InitNx2 ( 1824 , 1046 ) ;
2023-10-22 10:17:25 +00:00
2023-10-24 12:41:15 +00:00
static FP_ParticleDescriptor FP_DefaultFloatUpParticleDescriptor ( Dqn_Str8 anim_name , Dqn_usize duration_ms )
2023-10-22 11:41:21 +00:00
{
FP_ParticleDescriptor result = { } ;
result . anim_name = anim_name ;
result . velocity . y = - 16.f ;
result . velocity_variance . y = ( result . velocity . y * .5f ) ;
result . velocity_variance . x = DQN_ABS ( result . velocity . y ) ;
result . colour_begin = TELY_COLOUR_WHITE_V4 ;
result . colour_end = TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , 0.f ) ;
result . duration_ms = duration_ms ;
return result ;
}
2023-10-23 11:13:03 +00:00
static void FP_EmitParticle ( FP_Game * game , FP_ParticleDescriptor descriptor , Dqn_usize count )
2023-10-22 10:17:25 +00:00
{
DQN_FOR_UINDEX ( index , count ) {
2023-10-23 11:13:03 +00:00
uint32_t particle_index = game - > play . particle_next_index + + & ( DQN_ARRAY_UCOUNT ( game - > play . particles ) - 1 ) ;
FP_Particle * particle = game - > play . particles + particle_index ;
2023-10-22 10:17:25 +00:00
if ( particle - > alive )
continue ;
2023-10-24 12:41:15 +00:00
particle - > anim_name = Dqn_Str8_Copy ( Dqn_Arena_Allocator ( game - > frame_arena ) , descriptor . anim_name ) ;
2023-10-22 10:17:25 +00:00
particle - > alive = true ;
particle - > pos = descriptor . pos ;
2023-10-23 11:13:03 +00:00
particle - > velocity . x = descriptor . velocity . x + ( descriptor . velocity_variance . x * ( Dqn_PCG32_NextF32 ( & game - > play . rng ) - 0.5f ) ) ;
particle - > velocity . y = descriptor . velocity . y + ( descriptor . velocity_variance . y * ( Dqn_PCG32_NextF32 ( & game - > play . rng ) - 0.5f ) ) ;
2023-10-22 10:17:25 +00:00
particle - > colour_begin = descriptor . colour_begin ;
particle - > colour_end = descriptor . colour_end ;
2023-10-23 11:13:03 +00:00
particle - > start_ms = game - > play . clock_ms ;
particle - > end_ms = game - > play . clock_ms + descriptor . duration_ms ;
2023-10-22 10:17:25 +00:00
}
}
2023-10-29 03:59:53 +00:00
static TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec ( TELY_OS * os , TELY_Assets * assets , Dqn_Arena * arena , Dqn_Str8 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 =========================================================
2023-10-24 12:41:15 +00:00
Dqn_Str8 sprite_spec_path = Dqn_FsPath_ConvertF ( scratch . arena , " %.*s/%.*s.txt " , DQN_STR_FMT ( assets - > textures_dir ) , DQN_STR_FMT ( sheet_name ) ) ;
2023-10-29 03:59:53 +00:00
Dqn_Str8 sprite_spec_buffer = os - > funcs . load_file ( scratch . arena , sprite_spec_path ) ;
2023-10-24 12:41:15 +00:00
Dqn_Str8SplitAllocResult lines = Dqn_Str8_SplitAlloc ( scratch . allocator , sprite_spec_buffer , DQN_STR8 ( " \n " ) ) ;
Dqn_usize sprite_rect_index = 0 ;
Dqn_usize sprite_anim_index = 0 ;
2023-09-24 04:20:27 +00:00
DQN_FOR_UINDEX ( line_index , lines . size ) {
2023-10-24 12:41:15 +00:00
Dqn_Str8 line = lines . data [ line_index ] ;
Dqn_Str8SplitAllocResult line_splits = Dqn_Str8_SplitAlloc ( scratch . allocator , line , DQN_STR8 ( " ; " ) ) ;
2023-09-24 04:20:27 +00:00
if ( line_index = = 0 ) {
DQN_ASSERTF ( line_splits . size = = 4 , " Expected 4 splits for @file lines " ) ;
2023-10-24 12:41:15 +00:00
DQN_ASSERT ( Dqn_Str8_StartsWith ( line_splits . data [ 0 ] , DQN_STR8 ( " @file " ) , Dqn_Str8EqCase_Sensitive ) ) ;
2023-09-24 04:20:27 +00:00
// NOTE: Sprite sheet path
2023-10-24 12:41:15 +00:00
Dqn_Str8 sprite_sheet_path = Dqn_FsPath_ConvertF ( scratch . arena , " %.*s/%.*s " , DQN_STR_FMT ( assets - > textures_dir ) , DQN_STR_FMT ( line_splits . data [ 1 ] ) ) ;
2023-10-29 03:59:53 +00:00
result . tex_handle = os - > funcs . load_texture ( assets , sheet_name , sprite_sheet_path ) ;
2023-10-24 12:41:15 +00:00
DQN_ASSERTF ( Dqn_Fs_Exists ( sprite_sheet_path ) , " Required file does not exist '%.*s' " , DQN_STR_FMT ( sprite_sheet_path ) ) ;
2023-09-24 04:20:27 +00:00
// NOTE: Total sprite frame count
2023-10-24 12:41:15 +00:00
Dqn_Str8ToU64Result total_frame_count = Dqn_Str8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
2023-09-24 04:20:27 +00:00
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
2023-10-24 12:41:15 +00:00
Dqn_Str8ToU64Result total_anim_count = Dqn_Str8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
2023-09-24 04:20:27 +00:00
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 ;
}
2023-10-24 12:41:15 +00:00
if ( Dqn_Str8_StartsWith ( line , DQN_STR8 ( " @anim " ) ) ) {
2023-09-24 04:20:27 +00:00
DQN_ASSERTF ( line_splits . size = = 4 , " Expected 4 splits for @anim lines " ) ;
2023-10-24 12:41:15 +00:00
Dqn_Str8 anim_name = line_splits . data [ 1 ] ;
Dqn_Str8ToU64Result frames_per_second = Dqn_Str8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
Dqn_Str8ToU64Result frame_count = Dqn_Str8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
2023-09-24 04:20:27 +00:00
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 + + ;
2023-10-24 12:41:15 +00:00
anim - > label = Dqn_Str8_Copy ( allocator , anim_name ) ;
2023-09-24 04:20:27 +00:00
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-10-24 12:41:15 +00:00
Dqn_Str8ToU64Result x = Dqn_Str8_ToU64 ( line_splits . data [ 0 ] , 0 ) ;
Dqn_Str8ToU64Result y = Dqn_Str8_ToU64 ( line_splits . data [ 1 ] , 0 ) ;
Dqn_Str8ToU64Result w = Dqn_Str8_ToU64 ( line_splits . data [ 2 ] , 0 ) ;
Dqn_Str8ToU64Result h = Dqn_Str8_ToU64 ( line_splits . data [ 3 ] , 0 ) ;
2023-09-24 04:20:27 +00:00
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-10-20 13:04:13 +00:00
static void FP_SetDefaultGamepadBindings ( FP_GameControls * controls )
{
2023-10-21 05:30:15 +00:00
// NOTE: Note up/down/left/right uses analog sticks, non-negotiable.
2023-12-01 05:46:58 +00:00
controls - > attack . gamepad_key = TELY_InputGamepadKey_X ;
controls - > range_attack . gamepad_key = TELY_InputGamepadKey_Y ;
controls - > build_mode . gamepad_key = TELY_InputGamepadKey_L3 ;
controls - > strafe . gamepad_key = TELY_InputGamepadKey_B ;
controls - > dash . gamepad_key = TELY_InputGamepadKey_A ;
controls - > buy_building . gamepad_key = TELY_InputGamepadKey_LeftBumper ;
controls - > buy_upgrade . gamepad_key = TELY_InputGamepadKey_RightBumper ;
controls - > move_building_ui_cursor_left . gamepad_key = TELY_InputGamepadKey_DLeft ;
controls - > move_building_ui_cursor_right . gamepad_key = TELY_InputGamepadKey_DRight ;
2023-10-20 13:04:13 +00:00
}
2023-12-01 05:46:58 +00:00
static FP_ListenForNewPlayerResult FP_ListenForNewPlayer ( TELY_Input * input , FP_Game * game , bool tutorial_is_allowed )
2023-10-20 13:04:13 +00:00
{
2023-10-29 12:45:45 +00:00
FP_ListenForNewPlayerResult result = { } ;
2023-10-20 13:04:13 +00:00
if ( game - > play . players . size = = 2 )
return result ;
2023-10-22 09:11:33 +00:00
FP_GamePlay * play = & game - > play ;
bool keyboard_already_allocated = false ;
uint32_t gamepad_index = 0 ;
2023-10-20 13:04:13 +00:00
if ( play - > players . size ) {
FP_GameEntityHandle first_player_handle = play - > players . data [ 0 ] ;
FP_GameEntity * player = FP_Game_GetEntity ( game , first_player_handle ) ;
2023-10-22 09:11:33 +00:00
if ( player - > controls . mode = = FP_GameControlMode_Gamepad ) {
2023-10-20 13:04:13 +00:00
gamepad_index = 1 ;
2023-10-22 09:11:33 +00:00
} else {
// NOTE: We only allow one player to use the keyboard, everyone
// else must use a gamepad.
keyboard_already_allocated = true ;
}
2023-10-20 13:04:13 +00:00
}
2023-12-01 05:46:58 +00:00
bool keyboard_pressed = ! keyboard_already_allocated & & TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_B ) ;
bool gamepad_pressed = TELY_Input_GamepadKeyIsPressed ( input , gamepad_index , TELY_InputGamepadKey_Start ) ;
2023-10-20 13:04:13 +00:00
2023-10-29 12:45:45 +00:00
if ( tutorial_is_allowed ) {
2023-12-01 05:46:58 +00:00
if ( ! keyboard_already_allocated & & TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_T ) ) {
2023-10-29 12:45:45 +00:00
keyboard_pressed = true ;
result . tutorial_requested = true ;
}
2023-12-01 05:46:58 +00:00
if ( TELY_Input_GamepadKeyIsPressed ( input , gamepad_index , TELY_InputGamepadKey_Select ) ) {
2023-10-29 12:45:45 +00:00
gamepad_pressed = true ;
result . tutorial_requested = true ;
}
}
2023-10-20 13:04:13 +00:00
if ( keyboard_pressed | | gamepad_pressed ) {
2023-10-21 05:30:15 +00:00
FP_GameEntityHandle terry_handle = { } ;
if ( play - > players . size ) {
2023-10-21 15:14:01 +00:00
FP_GameEntityHandle player = play - > players . data [ play - > players . size - 1 ] ;
Dqn_V2 player_pos = FP_Game_CalcEntityWorldPos ( game , player ) ;
terry_handle = FP_Entity_CreatePerry ( game , player_pos , " Perry " ) ;
game - > play . perry_joined = FP_GamePerryJoins_Enters ;
2023-10-21 05:30:15 +00:00
} else {
terry_handle = FP_Entity_CreateTerry ( game , Dqn_V2_InitNx2 ( 1380 , 11 ) , " Perry " ) ;
}
2023-10-20 13:04:13 +00:00
Dqn_FArray_Add ( & play - > players , terry_handle ) ;
FP_GameEntity * terry = FP_Game_GetEntity ( game , terry_handle ) ;
FP_GameControls * controls = & terry - > controls ;
if ( keyboard_pressed ) {
2023-10-22 09:11:33 +00:00
controls - > mode = FP_GameControlMode_Keyboard ;
2023-12-01 05:46:58 +00:00
controls - > up . scan_key = TELY_InputScanKey_W ;
controls - > down . scan_key = TELY_InputScanKey_S ;
controls - > left . scan_key = TELY_InputScanKey_A ;
controls - > right . scan_key = TELY_InputScanKey_D ;
controls - > attack . scan_key = TELY_InputScanKey_J ;
controls - > range_attack . scan_key = TELY_InputScanKey_K ;
controls - > build_mode . scan_key = TELY_InputScanKey_H ;
controls - > strafe . scan_key = TELY_InputScanKey_L ;
controls - > dash . scan_key = TELY_InputScanKey_N ;
controls - > buy_building . scan_key = TELY_InputScanKey_U ;
controls - > buy_upgrade . scan_key = TELY_InputScanKey_I ;
controls - > move_building_ui_cursor_left . scan_key = TELY_InputScanKey_Q ;
controls - > move_building_ui_cursor_right . scan_key = TELY_InputScanKey_E ;
2023-10-20 13:04:13 +00:00
} else {
2023-10-22 09:11:33 +00:00
controls - > mode = FP_GameControlMode_Gamepad ;
controls - > gamepad_index = gamepad_index ;
2023-10-20 13:04:13 +00:00
FP_SetDefaultGamepadBindings ( controls ) ;
}
2023-10-29 12:45:45 +00:00
result . yes = true ;
2023-10-20 13:04:13 +00:00
}
2023-10-29 12:45:45 +00:00
2023-10-20 13:04:13 +00:00
return result ;
}
2023-10-29 12:45:45 +00:00
uint64_t const FP_COOLDOWN_WAVE_TIME_MS = 30'000 ;
2023-10-29 03:59:53 +00:00
static void FP_PlayReset ( FP_Game * game , TELY_OS * os )
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 ;
2023-10-25 09:17:30 +00:00
DQN_MEMSET ( play , 0 , sizeof ( * play ) ) ;
2023-09-16 02:21:24 +00:00
2023-10-29 03:59:53 +00:00
play - > chunk_pool = & os - > chunk_pool ;
2023-10-08 05:14:31 +00:00
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 ) ;
2023-10-29 03:59:53 +00:00
Dqn_PCG32_Seed ( & play - > rng , os - > core . epoch_time ) ;
2023-09-16 02:21:24 +00:00
2023-10-29 12:45:45 +00:00
// NOTE: Seed the shuffled list with indexes
DQN_FOR_UINDEX ( index , DQN_ARRAY_UCOUNT ( play - > monkey_spawn_shuffled_list ) ) {
play - > monkey_spawn_shuffled_list [ index ] = DQN_CAST ( uint8_t ) index ;
}
// NOTE: Fisher yates shuffle the list
2023-11-26 03:03:46 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 6293 ) // Ill-formed for loop (potential wrapping index)
2023-10-29 12:45:45 +00:00
for ( Dqn_usize index = DQN_ARRAY_UCOUNT ( play - > monkey_spawn_shuffled_list ) - 1 ; index < DQN_ARRAY_UCOUNT ( play - > monkey_spawn_shuffled_list ) ; index - - ) {
2023-11-26 03:03:46 +00:00
DQN_MSVC_WARNING_POP
2023-10-29 12:45:45 +00:00
uint32_t swap_index = Dqn_PCG32_Range ( & play - > rng , 0 , DQN_CAST ( uint32_t ) index + 1 ) ;
DQN_SWAP ( play - > monkey_spawn_shuffled_list [ swap_index ] , play - > monkey_spawn_shuffled_list [ index ] ) ;
}
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 = F P_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 ;
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 ,
2023-10-24 12:41:15 +00:00
DQN_STR8 ( " Left Wall " ) ,
2023-10-01 10:15:05 +00:00
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 ,
2023-10-24 12:41:15 +00:00
DQN_STR8 ( " Right Wall " ) ,
2023-10-01 10:15:05 +00:00
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 ,
2023-10-24 12:41:15 +00:00
DQN_STR8 ( " Top Wall " ) ,
2023-10-01 10:15:05 +00:00
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 ,
2023-10-24 12:41:15 +00:00
DQN_STR8 ( " Bottom Wall " ) ,
2023-10-01 10:15:05 +00:00
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 ) ;
{
2023-10-29 06:30:08 +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 ) ;
Dqn_V2 bottom_lane_mob_spawner_pos = Dqn_V2_InitNx2 ( mid_lane_mob_spawner_pos . x , mid_lane_mob_spawner_pos . y + 932.f ) ;
Dqn_V2 top_lane_mob_spawner_pos = Dqn_V2_InitNx2 ( mid_lane_mob_spawner_pos . x , mid_lane_mob_spawner_pos . y - 915.f ) ;
Dqn_usize spawn_cap = 16 ;
// NOTE: Top lane spawner ===================================================================
{
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 ) ;
Dqn_FArray_Add ( & play - > mob_spawners , mob_spawner ) ;
}
2023-10-07 14:29:50 +00:00
// NOTE: Mid lane mob spawner ==================================================================
{
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 ===================================================================
{
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
}
}
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
2023-10-16 13:35:41 +00:00
// NOTE: Create billboads ======================================================================
2023-10-16 22:00:04 +00:00
FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( 511 , 451 ) , FP_EntityBillboardState_Dash , " Dash Billboard " ) ;
FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( 511 , - 451 ) , FP_EntityBillboardState_Attack , " Attack Billboard " ) ;
FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( 2047 , - 720 ) , FP_EntityBillboardState_RangeAttack , " Range Attack Billboard " ) ;
FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( - 936 , - 500 ) , FP_EntityBillboardState_Monkey , " Monkey Billboard " ) ;
FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( 1898 , 771 ) , FP_EntityBillboardState_Strafe , " Strafe Billboard " ) ;
2023-10-29 12:45:45 +00:00
play - > billboard_build = FP_Entity_CreateBillboard ( game , Dqn_V2_InitNx2 ( 1068 , 619 ) , FP_EntityBillboardState_Build , " Build Billboard " ) ;
2023-10-16 13:35:41 +00:00
2023-10-15 10:02:28 +00:00
// NOTE: Camera ================================================================================
2023-10-17 12:56:56 +00:00
play - > camera . world_pos = { } ;
2023-10-15 11:48:53 +00:00
play - > camera . scale = Dqn_V2_InitNx1 ( 1 ) ;
2023-10-22 11:50:18 +00:00
play - > camera . size = FP_TARGET_VIEWPORT_SIZE ;
2023-10-08 05:14:31 +00:00
}
2023-10-29 03:59:53 +00:00
TELY_OS_DLL_FUNCTION
void TELY_OS_DLLReload ( TELY_OS * os )
2023-10-08 05:14:31 +00:00
{
2023-10-29 03:59:53 +00:00
Dqn_Library_SetPointer ( os - > core . dqn_lib ) ;
2023-10-29 04:14:07 +00:00
g_tely = os - > funcs ;
2023-10-29 03:59:53 +00:00
os - > funcs . set_window_title ( DQN_STR8 ( " Terry Cherry " ) ) ;
2023-10-08 05:14:31 +00:00
}
2023-10-29 03:59:53 +00:00
TELY_OS_DLL_FUNCTION
void TELY_OS_DLLInit ( TELY_OS * os )
2023-10-08 05:14:31 +00:00
{
2023-10-29 03:59:53 +00:00
TELY_OS_DLLReload ( os ) ;
FP_UnitTests ( os ) ;
2023-10-08 05:14:31 +00:00
// NOTE: TELY Game =============================================================================
2023-10-29 03:59:53 +00:00
TELY_Assets * assets = & os - > assets ;
2023-11-26 12:45:50 +00:00
assets - > chunk_pool = & os - > chunk_pool ;
2023-10-29 03:59:53 +00:00
FP_Game * game = Dqn_Arena_New ( & os - > arena , FP_Game , Dqn_ZeroMem_Yes ) ;
Dqn_f32 font_scalar = FP_TARGET_VIEWPORT_SIZE . w / DQN_CAST ( Dqn_f32 ) os - > core . window_size . x ;
game - > font_size = DQN_CAST ( uint16_t ) ( 18 * font_scalar ) ;
2023-10-29 12:45:45 +00:00
game - > large_font_size = DQN_CAST ( uint16_t ) ( game - > font_size * 5.f ) ;
2023-10-29 03:59:53 +00:00
game - > large_talkco_font_size = DQN_CAST ( uint16_t ) ( game - > font_size * 1.5f ) ;
game - > xlarge_talkco_font_size = DQN_CAST ( uint16_t ) ( game - > font_size * 2.f ) ;
game - > inter_regular_font = TELY_Asset_LoadFont ( assets , DQN_STR8 ( " Inter (Regular) " ) , DQN_STR8 ( " Data/Fonts/Inter-Regular.otf " ) ) ;
game - > inter_italic_font = TELY_Asset_LoadFont ( assets , DQN_STR8 ( " Inter (Italic) " ) , DQN_STR8 ( " Data/Fonts/Inter-Italic.otf " ) ) ;
game - > jetbrains_mono_font = TELY_Asset_LoadFont ( assets , DQN_STR8 ( " JetBrains Mono NL (Regular) " ) , DQN_STR8 ( " Data/Fonts/JetBrainsMonoNL-Regular.ttf " ) ) ;
game - > talkco_font = TELY_Asset_LoadFont ( assets , DQN_STR8 ( " Talkco " ) , DQN_STR8 ( " Data/Fonts/Talkco.otf " ) ) ;
2023-11-26 12:45:50 +00:00
game - > audio [ FP_GameAudio_TerryHit ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Terry Hit " ) , DQN_STR8 ( " Data/Audio/terry_hit.ogg " ) ) ;
game - > audio [ FP_GameAudio_Ching ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Ching " ) , DQN_STR8 ( " Data/Audio/ching.ogg " ) ) ;
game - > audio [ FP_GameAudio_Church ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Church " ) , DQN_STR8 ( " Data/Audio/church.ogg " ) ) ;
game - > audio [ FP_GameAudio_Club ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Club " ) , DQN_STR8 ( " Data/Audio/club_terry.ogg " ) ) ;
game - > audio [ FP_GameAudio_Dog ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Dog " ) , DQN_STR8 ( " Data/Audio/dog.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantGhost ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Ghost " ) , DQN_STR8 ( " Data/Audio/merchant_ghost.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantGym ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Gym " ) , DQN_STR8 ( " Data/Audio/merchant_gym.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantPhone ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Phone " ) , DQN_STR8 ( " Data/Audio/merchant_tech.ogg " ) ) ;
game - > audio [ FP_GameAudio_MerchantTerry ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Door " ) , DQN_STR8 ( " Data/Audio/merchant_terry.ogg " ) ) ;
game - > audio [ FP_GameAudio_Message ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Message " ) , DQN_STR8 ( " Data/Audio/message.ogg " ) ) ;
game - > audio [ FP_GameAudio_Monkey ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Monkey " ) , DQN_STR8 ( " Data/Audio/monkey.ogg " ) ) ;
game - > audio [ FP_GameAudio_Plane ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Plane " ) , DQN_STR8 ( " Data/Audio/airport.ogg " ) ) ;
game - > audio [ FP_GameAudio_PortalDestroy ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Portal Destroy " ) , DQN_STR8 ( " Data/Audio/portal_destroy.ogg " ) ) ;
game - > audio [ FP_GameAudio_Smooch ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Smooch " ) , DQN_STR8 ( " Data/Audio/smooch.ogg " ) ) ;
game - > audio [ FP_GameAudio_Woosh ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Woosh " ) , DQN_STR8 ( " Data/Audio/woosh.ogg " ) ) ;
game - > audio [ FP_GameAudio_GameStart ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Game Start " ) , DQN_STR8 ( " Data/Audio/game_start.ogg " ) ) ;
game - > audio [ FP_GameAudio_PerryStart ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Perry Start " ) , DQN_STR8 ( " Data/Audio/perry_start.ogg " ) ) ;
game - > audio [ FP_GameAudio_Ambience1 ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Ambience one " ) , DQN_STR8 ( " Data/Audio/ambience_1.ogg " ) ) ;
game - > audio [ FP_GameAudio_Ambience2 ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Ambience two " ) , DQN_STR8 ( " Data/Audio/ambience_2.ogg " ) ) ;
game - > audio [ FP_GameAudio_Music1 ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Music one " ) , DQN_STR8 ( " Data/Audio/music_1.ogg " ) ) ;
game - > audio [ FP_GameAudio_Music2 ] = TELY_Asset_LoadAudio ( assets , os , DQN_STR8 ( " Music two " ) , DQN_STR8 ( " Data/Audio/music_2.ogg " ) ) ;
2023-10-08 05:14:31 +00:00
// NOTE: Load sprite sheets ====================================================================
2023-10-29 03:59:53 +00:00
os - > user_data = game ;
game - > atlas_sprite_sheet = FP_LoadSpriteSheetFromSpec ( os , assets , & os - > arena , DQN_STR8 ( " atlas " ) ) ;
FP_PlayReset ( game , os ) ;
2023-10-16 13:35:41 +00:00
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-12-01 05:46:58 +00:00
static void FP_EntityActionStateMachine ( FP_Game * game , TELY_Audio * audio , TELY_Input * input , FP_GameEntity * entity , Dqn_V2 * acceleration_meters_per_s )
2023-09-24 07:01:21 +00:00
{
2023-10-19 13:20:41 +00:00
TELY_AssetSpriteSheet * sheet = & game - > atlas_sprite_sheet ;
FP_GameEntityAction * action = & entity - > action ;
bool const we_are_clicked_entity = entity - > handle = = game - > play . clicked_entity ;
bool const entity_has_velocity = entity - > velocity . x | | entity - > velocity . y ;
bool const entering_new_state = entity - > alive_time_s = = 0.f | | action - > state ! = action - > next_state ;
bool const action_has_finished = ! entering_new_state & & game - > play . clock_ms > = action - > end_at_clock_ms ;
FP_GameControls const * controls = & entity - > controls ;
action - > state = action - > next_state ;
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , entity - > type , entity - > action . state , entity - > direction ) ;
2023-09-24 07:01:21 +00:00
2023-10-02 11:38:36 +00:00
switch ( entity - > type ) {
2023-10-21 05:30:15 +00:00
case FP_EntityType_Perry : /*FALLTHRU*/
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-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 ) ;
2023-10-29 12:45:45 +00:00
Dqn_f32 trig_t = DQN_CAST ( Dqn_f32 ) game - > play . clock_ms / 1000.f * 2.f ;
2023-10-07 14:29:50 +00:00
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
}
2023-10-19 13:20:41 +00:00
if ( entity - > in_game_menu ! = FP_GameInGameMenu_Build ) {
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 ) ;
if ( closest_monkey . dist < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 2.f ) ) ) {
2023-10-21 05:30:15 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
entity - > carried_monkey = closest_monkey . entity ;
picked_up_monkey_this_frame = true ;
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-19 13:20:41 +00:00
}
2023-10-07 01:21:31 +00:00
2023-10-19 13:20:41 +00:00
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Attack ) ;
2023-10-20 13:04:13 +00:00
} else if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > range_attack ) ) {
2023-10-19 13:20:41 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_RangeAttack ) ;
}
} else {
2023-10-21 05:30:15 +00:00
if ( ! picked_up_monkey_this_frame & & FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
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 14:29:50 +00:00
}
}
2023-10-19 13:20:41 +00:00
}
2023-10-07 01:21:31 +00:00
2023-10-19 13:20:41 +00:00
if ( action - > next_state = = action - > state & & ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Run ) ;
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 ;
2023-10-22 09:22:24 +00:00
entity - > attack_direction = entity - > direction ;
2023-10-02 11:38:36 +00:00
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-22 09:22:24 +00:00
entity - > attack_direction = entity - > direction ;
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
2023-10-19 13:20:41 +00:00
bool picked_up_monkey_this_frame = false ;
if ( entity - > in_game_menu ! = FP_GameInGameMenu_Build ) {
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 ) ;
if ( closest_monkey . dist < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 2.f ) ) ) {
2023-10-21 05:30:15 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
entity - > carried_monkey = closest_monkey . entity ;
picked_up_monkey_this_frame = true ;
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 ) ) {
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Attack ) ;
2023-10-20 13:04:13 +00:00
} else if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > range_attack ) ) {
2023-10-19 13:20:41 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_RangeAttack ) ;
2023-10-07 14:29:50 +00:00
}
2023-09-24 08:05:28 +00:00
}
2023-09-24 07:01:21 +00:00
}
2023-10-19 13:20:41 +00:00
if ( FP_Game_IsNilEntityHandle ( game , entity - > carried_monkey ) ) {
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > dash ) )
2023-10-19 13:20:41 +00:00
FP_Game_EntityTransitionState ( game , entity , FP_EntityTerryState_Dash ) ;
} else {
2023-10-20 13:04:13 +00:00
if ( ! picked_up_monkey_this_frame & & FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-19 13:20:41 +00:00
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-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-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-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
}
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Attack ) ;
} else if ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntitySmoochieState_Run ) ;
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-10-22 09:22:24 +00:00
entity - > attack_direction = entity - > direction ;
2023-10-02 11:38:36 +00:00
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 ) {
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
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
}
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
FP_Game_EntityTransitionState ( game , entity , FP_EntityClingerState_Attack ) ;
} else if ( acceleration_meters_per_s - > x | | acceleration_meters_per_s - > y ) {
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-22 09:22:24 +00:00
uint64_t duration_ms = render_data . sprite . anim - > count * render_data . sprite . anim - > ms_per_frame ;
entity - > attack_direction = entity - > direction ;
2023-10-02 11:38:36 +00:00
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 ) {
2023-10-18 10:48:12 +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 ) ;
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
}
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) )
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 ) ;
2023-10-22 11:41:21 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Plane ] , 1.f ) ;
2023-10-06 09:48:20 +00:00
}
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 ) ;
}
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
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 ) ;
2023-10-04 12:50:31 +00:00
}
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 ) ;
2023-10-22 09:22:24 +00:00
entity - > attack_direction = entity - > direction ;
2023-10-04 12:50:31 +00:00
}
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 ) {
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER ;
2023-10-18 10:48:12 +00:00
FP_Game_EntityActionReset ( game , entity - > handle , duration_ms , render_data . sprite ) ;
2023-10-04 12:50:31 +00:00
}
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) )
FP_Game_EntityTransitionState ( game , entity , FP_EntityCatfishState_Attack ) ;
2023-10-04 12:50:31 +00:00
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-22 11:41:21 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Church ] , 1.f ) ;
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-10-16 13:35:41 +00:00
case FP_EntityType_Billboard : 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*/
2023-10-21 05:30:15 +00:00
case FP_EntityType_Perry : /*FALLTHRU*/
2023-10-07 06:55:34 +00:00
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 {
2023-10-21 05:30:15 +00:00
DQN_ASSERT ( entity - > type = = FP_EntityType_Terry | | entity - > type = = FP_EntityType_Perry ) ;
2023-10-07 06:55:34 +00:00
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 ) ;
2023-10-19 13:20:41 +00:00
DQN_ASSERT ( duration_ms > = FP_GAME_PHYSICS_STEP ) ;
2023-10-07 06:55:34 +00:00
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 = { } ;
2023-10-22 09:22:24 +00:00
switch ( entity - > attack_direction ) {
2023-10-08 05:44:48 +00:00
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 ) ;
2023-10-22 09:22:24 +00:00
entity - > attack_box_size = attack_boxes . data [ entity - > attack_direction ] . size ;
entity - > attack_box_offset = attack_boxes . data [ entity - > attack_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-16 13:35:41 +00:00
case FP_EntityType_Billboard : break ;
2023-10-07 06:55:34 +00:00
}
2023-09-30 06:11:39 +00:00
}
2023-12-01 05:46:58 +00:00
static void FP_Update ( TELY_OS * os , FP_Game * game , TELY_Input * 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-29 12:45:45 +00:00
if ( game - > play . state = = FP_GameState_Pause )
return ;
2023-09-23 06:42:22 +00:00
2023-10-08 05:14:31 +00:00
game - > play . update_counter + + ;
2023-10-29 12:45:45 +00:00
game - > play . clock_ms + = DQN_CAST ( uint64_t ) ( os - > input . delta_ms ) ;
2023-10-29 06:30:08 +00:00
2023-10-24 12:41:15 +00:00
Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex ( DQN_STR8 ( " FP_Update: Entity loop " ) , FP_ProfileZone_FPUpdate_EntityLoop ) ;
2023-10-21 15:14:01 +00:00
if ( game - > play . state = = FP_GameState_Play & & game - > play . perry_joined ! = FP_GamePerryJoins_Enters ) {
2023-10-21 05:30:15 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 4127 ) // Conditional expression is constant 'FP_DEVELOPER_MODE'
2023-12-01 05:46:58 +00:00
if ( FP_DEVELOPER_MODE & & TELY_Input_KeyIsReleased ( input - > mouse_keys [ TELY_InputMouseKey_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
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_Escape ) ) {
2023-10-09 10:35:51 +00:00
game - > play . state = FP_GameState_Pause ;
2023-10-29 12:45:45 +00:00
return ;
}
2023-10-05 10:30:56 +00:00
2023-10-21 05:30:15 +00:00
if ( FP_DEVELOPER_MODE & & game - > play . clicked_entity . id ) {
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_Delete ) )
2023-10-08 05:14:31 +00:00
FP_Game_DeleteEntity ( game , game - > play . clicked_entity ) ;
2023-09-16 02:21:24 +00:00
}
2023-10-21 05:30:15 +00:00
DQN_MSVC_WARNING_POP
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 ;
2023-10-19 13:20:41 +00:00
entity - > alive_time_s + = FP_GAME_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 ;
2023-10-19 13:20:41 +00:00
// NOTE: Calc direction vector from input
FP_GameControls const * controls = & entity - > controls ;
Dqn_V2 dir_vector = { } ;
{
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsDown ( input , controls , controls - > up ) )
2023-10-19 13:20:41 +00:00
dir_vector . y = - 1.f ;
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsDown ( input , controls , controls - > left ) )
2023-10-19 13:20:41 +00:00
dir_vector . x = - 1.f ;
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsDown ( input , controls , controls - > down ) )
2023-10-19 13:20:41 +00:00
dir_vector . y = + 1.f ;
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsDown ( input , controls , controls - > right ) )
2023-10-19 13:20:41 +00:00
dir_vector . x = + 1.f ;
// NOTE: Gamepad movement input
2023-10-21 05:30:15 +00:00
if ( controls - > mode = = FP_GameControlMode_Gamepad ) {
2023-12-01 05:46:58 +00:00
TELY_InputGamepad * gamepad = input - > gamepads + controls - > gamepad_index ;
2023-10-29 03:59:53 +00:00
dir_vector + = gamepad - > left_stick ;
2023-10-19 13:20:41 +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-08 02:22:08 +00:00
// NOTE: Move entity by keyboard and gamepad ===============================================
Dqn_V2 acceleration_meters_per_s = entity - > constant_acceleration_per_s ;
2023-10-19 13:20:41 +00:00
if ( entity - > flags & ( FP_GameEntityFlag_MoveByKeyboard | FP_GameEntityFlag_MoveByGamepad ) ) {
bool move_entity = true ;
switch ( entity - > type ) {
2023-10-21 05:30:15 +00:00
case FP_EntityType_Perry : /*FALLTHRU*/
2023-10-19 13:20:41 +00:00
case FP_EntityType_Terry : {
auto * state = DQN_CAST ( FP_EntityTerryState * ) & entity - > action . state ;
move_entity = * state = = FP_EntityTerryState_Run | | * state = = FP_EntityTerryState_Idle ;
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 07:02:10 +00:00
}
2023-10-19 13:20:41 +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 ;
2023-10-21 05:30:15 +00:00
case FP_EntityType_AirportTerryPlane : break ;
case FP_EntityType_MobSpawner : break ;
case FP_EntityType_PortalMonkey : break ;
case FP_EntityType_Billboard : break ;
2023-10-07 08:14:09 +00:00
}
2023-10-19 13:20:41 +00:00
2023-10-25 09:23:42 +00:00
if ( move_entity ) {
2023-10-21 05:30:15 +00:00
acceleration_meters_per_s + = dir_vector * entity - > base_acceleration_per_s . meters ;
2023-10-25 09:23:42 +00:00
if ( dir_vector . x )
entity - > direction = entity - > velocity . x > 0.f ? FP_GameDirection_Right : FP_GameDirection_Left ;
if ( dir_vector . y )
entity - > direction = entity - > velocity . y > 0.f ? FP_GameDirection_Down : FP_GameDirection_Up ;
}
} else {
2023-10-22 09:22:24 +00:00
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 ;
}
2023-10-08 02:22:08 +00:00
// 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 ) ;
2023-10-21 05:30:15 +00:00
if ( maybe_terry - > type = = FP_EntityType_Terry | | maybe_terry - > type = = FP_EntityType_Perry ) {
2023-10-08 08:04:11 +00:00
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
// 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 ) {
2023-10-21 05:30:15 +00:00
if ( waypoint_entity - > type = = FP_EntityType_Terry | | waypoint_entity - > type = = FP_EntityType_Perry ) {
2023-10-08 09:51:54 +00:00
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 ) {
2023-10-21 05:30:15 +00:00
case FP_EntityType_Perry : /*FALLTHRU*/
2023-10-08 08:04:11 +00:00
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
2023-10-21 05:30:15 +00:00
if ( entity - > type = = FP_EntityType_Terry | | entity - > type = = FP_EntityType_Perry ) {
2023-10-08 08:04:11 +00:00
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-10-16 13:35:41 +00:00
case FP_EntityType_Billboard : 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-19 13:20:41 +00:00
if ( entity - > flags & FP_GameEntityFlag_CameraTracking ) {
if ( ! Dqn_FArray_Find ( & game - > play . camera_tracking_entity , entity - > handle ) . data )
Dqn_FArray_Add ( & game - > play . camera_tracking_entity , entity - > handle ) ;
}
// NOTE: Building logic ================================================================
for ( FP_GameEntityHandle player : game - > play . players ) {
if ( player ! = entity - > handle )
continue ;
// NOTE: Toggle menu ===============================================================
if ( entity - > in_game_menu = = FP_GameInGameMenu_Nil | | entity - > in_game_menu = = FP_GameInGameMenu_Build ) {
2023-10-20 13:04:13 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > build_mode ) )
2023-10-19 13:20:41 +00:00
entity - > in_game_menu = DQN_CAST ( FP_GameInGameMenu ) ( DQN_CAST ( uint32_t ) entity - > in_game_menu ^ FP_GameInGameMenu_Build ) ;
}
// NOTE: Building selector =========================================================
Dqn_usize const last_building_index = DQN_ARRAY_UCOUNT ( PLACEABLE_BUILDINGS ) - 1 ;
2023-10-21 05:30:15 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > move_building_ui_cursor_left ) ) {
2023-10-19 13:20:41 +00:00
if ( entity - > build_mode_building_index < = 0 ) {
entity - > build_mode_building_index = last_building_index ;
} else {
entity - > build_mode_building_index - = 1 ;
}
2023-10-08 02:22:08 +00:00
}
2023-10-21 05:33:22 +00:00
if ( FP_Game_KeyBindIsPressed ( input , controls , controls - > move_building_ui_cursor_right ) ) {
2023-10-19 13:20:41 +00:00
if ( entity - > build_mode_building_index > = last_building_index ) {
entity - > build_mode_building_index = 0 ;
} else {
entity - > build_mode_building_index + = 1 ;
}
2023-10-15 11:48:53 +00:00
}
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Builder blueprint =========================================================
FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS [ entity - > build_mode_building_index ] ;
entity - > 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-19 13:20:41 +00:00
if ( have_building_inventory & & entity - > 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
) {
2023-10-19 13:20:41 +00:00
FP_GameEntity * zone = zone_it . entity ;
bool cant_overlap_build = zone - > type = = FP_EntityType_KennelTerry | |
zone - > type = = FP_EntityType_AirportTerry | |
zone - > type = = FP_EntityType_ChurchTerry | |
zone - > type = = FP_EntityType_ClubTerry ;
// NOTE: We also can't overlap with the player incase of 2 players
if ( game - > play . players . size > 1 ) {
if ( ! cant_overlap_build ) {
cant_overlap_build = Dqn_FArray_Find ( & game - > play . players , zone_it . entity - > handle ) . data ;
}
}
2023-10-08 02:22:08 +00:00
Dqn_Rect zone_hit_box = FP_Game_CalcEntityWorldHitBox ( game , zone - > handle ) ;
2023-10-19 13:20:41 +00:00
if ( cant_overlap_build ) {
2023-10-08 02:22:08 +00:00
if ( Dqn_Rect_Intersects ( zone_hit_box , dest_rect ) ) {
2023-10-19 13:20:41 +00:00
entity - > 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-19 13:20:41 +00:00
entity - > 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-20 13:04:13 +00:00
if ( entity - > build_mode_can_place_building & & FP_Game_KeyBindIsPressed ( input , controls , controls - > attack ) ) {
2023-10-08 02:22:08 +00:00
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-22 11:43:36 +00:00
if ( ! FP_Game_KeyBindIsDown ( input , controls , controls - > strafe ) ) {
2023-10-08 02:22:08 +00:00
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 ;
2023-10-29 03:59:53 +00:00
FP_EntityActionStateMachine ( game , & os - > audio , input , entity , & acceleration_meters_per_s ) ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Limit the entity to within bounds of the camera only in multiplayer ===========
bool entity_is_oob_with_camera = false ;
Dqn_V2 entity_oob_move = { } ;
if ( game - > play . players . size > 1 ) {
FP_GameCamera * camera = & game - > play . camera ;
for ( FP_GameEntityHandle player : game - > play . players ) {
if ( entity - > handle ! = player )
continue ;
2023-10-20 13:06:28 +00:00
Dqn_Rect const camera_view_rect = Dqn_Rect_InitV2x2 ( ( camera - > size * - .5f ) + ( camera - > world_pos / camera - > scale ) , camera - > size ) ;
Dqn_Rect const entity_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity_handle ) ;
Dqn_Rect const playable_bounds = Dqn_Rect_ExpandV2 ( camera_view_rect , - entity_hit_box . size ) ;
Dqn_V2 new_entity_pos = entity_pos ;
new_entity_pos . x = DQN_MAX ( new_entity_pos . x , playable_bounds . pos . x ) ;
new_entity_pos . y = DQN_MAX ( new_entity_pos . y , playable_bounds . pos . y ) ;
new_entity_pos . x = DQN_MIN ( new_entity_pos . x , playable_bounds . pos . x + playable_bounds . size . x ) ;
new_entity_pos . y = DQN_MIN ( new_entity_pos . y , playable_bounds . pos . y + playable_bounds . size . y ) ;
2023-10-19 13:20:41 +00:00
if ( new_entity_pos ! = entity_pos ) {
entity_is_oob_with_camera = true ;
Dqn_V2 delta_pos = new_entity_pos - entity_pos ;
entity_oob_move = delta_pos ;
}
}
}
if ( entity_is_oob_with_camera ) {
if ( FP_Game_CanEntityMoveToPosition ( game , entity_handle , entity_oob_move ) . yes )
entity - > local_pos + = entity_oob_move ;
} else {
// NOTE: Core equations of motion ==================================================
FP_Game_MoveEntity ( game , entity_handle , acceleration_meters_per_s ) ;
}
2023-10-08 02:22:08 +00:00
}
2023-09-16 02:21:24 +00:00
2023-10-29 12:45:45 +00:00
// NOTE: Typically, the game starts the cooldown for the next wave after
// all enemies are spawned which makes the game fast paced. However, to
// give the player a break to reorganise, every 3 waves- we don't start
// the wave countdown until all enemies are killed.
bool all_enemies_spawned = game - > play . enemies_spawned_this_wave > = game - > play . enemies_per_wave ;
bool advance_to_next_wave = false ;
2023-11-01 21:30:37 +00:00
bool current_wave_is_break_for_player = game - > play . current_wave % 1 = = 0 ;
bool monkey_spawn = game - > play . current_wave % 3 = = 0 ;
2023-10-29 12:45:45 +00:00
if ( current_wave_is_break_for_player ) {
Dqn_usize enemy_count = 0 ;
for ( FP_GameEntityHandle spawner_handle : game - > play . mob_spawners ) {
FP_GameEntity * spawner = FP_Game_GetEntity ( game , spawner_handle ) ;
2023-10-29 12:55:36 +00:00
// NOTE: Count all the mobs spawned that are a foe
// (e.g. churches that convert mobs don't count).
for ( FP_SentinelListLink < FP_GameEntityHandle > * link = nullptr ; FP_SentinelList_Iterate < FP_GameEntityHandle > ( & spawner - > spawn_list , & link ) ; ) {
FP_GameEntity * mob = FP_Game_GetEntity ( game , link - > data ) ;
if ( mob - > faction = = FP_GameEntityFaction_Foe )
enemy_count + + ;
}
2023-10-29 12:45:45 +00:00
}
bool all_enemies_killed = enemy_count = = 0 ;
advance_to_next_wave = all_enemies_spawned & & all_enemies_killed ;
} else {
advance_to_next_wave = all_enemies_spawned ;
}
if ( advance_to_next_wave ) {
// NOTE: If the cooldown timestamp is 0, the wave is complete and we
// haven't given the player cooldown yet, so we assign a timestamp for that
if ( game - > play . wave_cooldown_timestamp_ms = = 0 ) {
game - > play . wave_cooldown_timestamp_ms = game - > play . clock_ms + FP_COOLDOWN_WAVE_TIME_MS ;
2023-11-21 11:47:54 +00:00
TELY_Audio_Fade ( audio , game - > bg_music1 , TELY_AudioEffectFade_Out , 2000 /*fade_duration_ms*/ ) ;
game - > bg_music2 = TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Music2 ] , 1.f ) ;
TELY_Audio_Fade ( audio , game - > bg_music2 , TELY_AudioEffectFade_In , 2000 /*fade_duration_ms*/ ) ;
2023-10-29 12:45:45 +00:00
} else {
// NOTE: Check if cooldown has elapsed, the next wave can start if so
if ( game - > play . clock_ms > game - > play . wave_cooldown_timestamp_ms ) {
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 ) ) ;
game - > play . enemies_spawned_this_wave = 0 ; // Important! Reset the spawn count
game - > play . wave_cooldown_timestamp_ms = 0 ; // Important! We reset the timestamp for the next wave
2023-11-21 11:47:54 +00:00
TELY_Audio_Fade ( audio , game - > bg_music2 , TELY_AudioEffectFade_Out , 2000 /*fade_duration_ms*/ ) ;
game - > bg_music1 = TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Music1 ] , 1.f ) ;
TELY_Audio_Fade ( audio , game - > bg_music1 , TELY_AudioEffectFade_In , 2000 /*fade_duration_ms*/ ) ;
2023-10-29 12:45:45 +00:00
2023-11-01 21:30:37 +00:00
if ( monkey_spawn & & game - > play . current_wave ! = 0 ) {
2023-10-29 12:45:45 +00:00
// NOTE: We spawn a monkey at these wave intervals;
if ( game - > play . monkey_spawn_count < 3 ) {
DQN_ASSERT ( game - > play . monkey_spawn_count < DQN_ARRAY_UCOUNT ( game - > play . monkey_spawn_shuffled_list ) ) ;
uint8_t spawn_pos_index = game - > play . monkey_spawn_shuffled_list [ game - > play . monkey_spawn_count + + ] ;
DQN_ASSERT ( spawn_pos_index < DQN_ARRAY_UCOUNT ( FP_MONKEY_SPAWN_LOCATIONS ) ) ;
Dqn_V2 spawn_pos = FP_MONKEY_SPAWN_LOCATIONS [ spawn_pos_index ] ;
FP_GameEntityHandle portal_monkey = FP_Entity_CreatePortalMonkey ( game , spawn_pos , " Portal Monkey " ) ;
Dqn_FArray_Add ( & game - > play . portal_monkeys , portal_monkey ) ;
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Monkey ] , 1.f ) ;
}
}
game - > play . current_wave + + ;
2023-11-13 21:44:10 +00:00
//TELY_Audio_Stop(audio, game->audio[FP_GameAudio_Music2]);
2023-10-29 12:45:45 +00:00
}
}
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-19 13:20:41 +00:00
DQN_MIN ( entity - > terry_mobile_data_plan + DQN_CAST ( Dqn_usize ) ( FP_TERRY_MOBILE_DATA_PER_RANGE_ATTACK * .25f * FP_GAME_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-10-18 09:34:38 +00:00
entity - > trauma01 = DQN_MAX ( 0.f , entity - > trauma01 - 0.05f ) ;
2023-10-22 11:41:21 +00:00
Dqn_Rect world_hit_box = FP_Game_CalcEntityWorldHitBox ( game , entity - > handle ) ;
if ( entity - > is_drunk ) {
if ( game - > play . clock_ms > entity - > drunk_particles_end_ms ) {
Dqn_usize duration_ms = 2000 ;
FP_ParticleDescriptor particle_desc = FP_DefaultFloatUpParticleDescriptor ( g_anim_names . particle_drunk , duration_ms ) ;
particle_desc . velocity_variance . x * = 2.f ;
particle_desc . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.5f , - 0.1f ) ) ;
particle_desc . colour_begin . a = .8f ;
entity - > drunk_particles_end_ms = game - > play . clock_ms + duration_ms ;
2023-10-23 11:13:03 +00:00
FP_EmitParticle ( game , particle_desc , 3 ) ;
2023-10-22 11:41:21 +00:00
}
}
2023-10-18 09:34:38 +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-29 06:30:08 +00:00
if ( entity - > type = = FP_EntityType_MobSpawner & & ! game - > play . debug_disable_mobs & & game - > play . state ! = FP_GameState_Tutorial ) {
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
2023-10-29 12:45:45 +00:00
if ( game - > play . clock_ms > = entity - > next_spawn_timestamp_s ) {
2023-10-08 08:48:17 +00:00
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-29 12:45:45 +00:00
entity - > next_spawn_timestamp_s = DQN_CAST ( uint64_t ) ( game - > play . clock_ms + 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-11-01 21:30:37 +00:00
mob - > hp_cap * = hp_adjustment * 2 ;
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 + + ;
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-10-22 10:17:25 +00:00
// NOTE: Emit hit particles ========================================================
FP_ParticleDescriptor particle_desc = { } ;
Dqn_usize particle_selector = Dqn_PCG32_Range ( & game - > play . rng , 0 , 3 ) ;
if ( particle_selector = = 0 ) {
particle_desc . anim_name = g_anim_names . particle_hit_1 ;
} else if ( particle_selector = = 1 ) {
particle_desc . anim_name = g_anim_names . particle_hit_2 ;
} else {
particle_desc . anim_name = g_anim_names . particle_hit_3 ;
DQN_ASSERT ( particle_selector = = 2 ) ;
}
particle_desc . pos = Dqn_Rect_InterpolatedPoint ( defender_box , Dqn_V2_InitNx2 ( 0.5f , 0.0f ) ) ;
particle_desc . velocity . y = - 16.f ;
particle_desc . velocity_variance . y = ( particle_desc . velocity . y * .5f ) ;
particle_desc . velocity_variance . x = DQN_ABS ( particle_desc . velocity . y ) ;
particle_desc . colour_begin = TELY_COLOUR_WHITE_V4 ;
particle_desc . colour_end = TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , 0.f ) ;
particle_desc . duration_ms = 1000 ;
2023-10-23 11:13:03 +00:00
FP_EmitParticle ( game , particle_desc , Dqn_PCG32_Range ( & game - > play . rng , 1 , 3 ) ) ;
2023-10-22 10:17:25 +00:00
// NOTE: God mode override =========================================================
2023-10-19 13:20:41 +00:00
if ( game - > play . heart = = defender - > handle ) {
2023-10-08 05:14:31 +00:00
if ( game - > play . god_mode )
continue ;
}
2023-10-23 09:54:28 +00:00
bool god_mode_override = false ;
bool attacker_is_player = false ;
2023-10-19 13:20:41 +00:00
if ( game - > play . god_mode ) {
for ( FP_GameEntityHandle player : game - > play . players ) {
2023-10-23 09:54:28 +00:00
god_mode_override | = player = = defender - > handle ;
attacker_is_player | = player = = attacker - > handle ;
2023-10-19 13:20:41 +00:00
}
}
if ( god_mode_override )
continue ;
2023-10-22 10:17:25 +00:00
// NOTE: Do hit logic ==============================================================
2023-10-18 09:34:38 +00:00
defender - > hp = defender - > hp > = attacker - > base_attack ? defender - > hp - attacker - > base_attack : 0 ;
2023-10-22 06:26:30 +00:00
defender - > hit_on_clock_ms = game - > play . clock_ms ;
2023-10-18 09:34:38 +00:00
defender - > trauma01 = 1.f - ( defender - > hp / DQN_CAST ( Dqn_f32 ) defender - > hp_cap ) ;
2023-10-17 23:28:15 +00:00
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 ;
2023-10-17 23:28:15 +00:00
coin_receiver - > coins + = 2 ;
2023-10-08 06:35:57 +00:00
}
2023-10-08 05:14:31 +00:00
defender - > is_dying = true ;
2023-10-06 10:57:34 +00:00
}
2023-10-23 09:54:28 +00:00
// NOTE: Interrupt hit entity =====================================================
if ( attacker_is_player ) {
switch ( defender - > type ) {
case FP_EntityType_Catfish : {
FP_Game_EntityTransitionState ( game , defender , FP_EntityCatfishState_Idle ) ;
defender - > last_attack_timestamp = game - > play . clock_ms ;
} break ;
case FP_EntityType_Terry : {
FP_Game_EntityTransitionState ( game , defender , FP_EntityTerryState_Idle ) ;
defender - > last_attack_timestamp = game - > play . clock_ms ;
} break ;
case FP_EntityType_Perry : {
FP_Game_EntityTransitionState ( game , defender , FP_EntityTerryState_Idle ) ;
defender - > last_attack_timestamp = game - > play . clock_ms ;
} break ;
case FP_EntityType_Clinger : {
FP_Game_EntityTransitionState ( game , defender , FP_EntityClingerState_Idle ) ;
defender - > last_attack_timestamp = game - > play . clock_ms ;
} break ;
case FP_EntityType_Smoochie : {
FP_Game_EntityTransitionState ( game , defender , FP_EntitySmoochieState_Idle ) ;
defender - > last_attack_timestamp = game - > play . clock_ms ;
} break ;
case FP_EntityType_Nil : break ;
case FP_EntityType_AirportTerry : break ;
case FP_EntityType_AirportTerryPlane : break ;
case FP_EntityType_ChurchTerry : break ;
case FP_EntityType_ClubTerry : break ;
case FP_EntityType_Heart : break ;
case FP_EntityType_KennelTerry : break ;
case FP_EntityType_Map : break ;
case FP_EntityType_MerchantGraveyard : break ;
case FP_EntityType_MerchantGym : break ;
case FP_EntityType_MerchantPhoneCompany : break ;
case FP_EntityType_MerchantTerry : break ;
case FP_EntityType_MobSpawner : break ;
case FP_EntityType_PortalMonkey : break ;
case FP_EntityType_PhoneMessageProjectile : break ;
case FP_EntityType_Billboard : break ;
case FP_EntityType_Count : break ;
}
}
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-19 13:20:41 +00:00
// NOTE: Game over check =======================================================================
2023-10-08 08:04:11 +00:00
{
FP_GameEntity * heart = FP_Game_GetEntity ( game , game - > play . heart ) ;
2023-10-19 13:20:41 +00:00
if ( heart - > hp < = 0 )
2023-10-08 08:04:11 +00:00
game - > play . state = FP_GameState_LoseGame ;
}
2023-10-21 15:14:01 +00:00
game - > play . global_camera_trauma01 = DQN_MAX ( 0.f , game - > play . global_camera_trauma01 - 0.05f ) ;
2023-10-22 10:17:25 +00:00
// NOTE: Update all particles ==================================================================
for ( FP_Particle & particle_ : game - > play . particles ) {
FP_Particle * particle = & particle_ ;
if ( game - > play . clock_ms > = particle - > end_ms )
particle - > alive = false ;
if ( ! particle - > alive )
continue ;
particle - > pos + = particle - > velocity * DQN_CAST ( Dqn_f32 ) input - > delta_s ;
}
2023-10-17 12:49:20 +00:00
// NOTE: Camera ================================================================================
2023-10-19 13:20:41 +00:00
FP_GamePlay * play = & game - > play ;
FP_GameCamera * camera = & play - > camera ;
2023-10-29 06:30:08 +00:00
if ( game - > play . state = = FP_GameState_Tutorial ) {
Dqn_f32 arrival_dist = Dqn_V2_LengthSq ( camera - > world_pos_target - camera - > world_pos ) ;
bool camera_arrived = arrival_dist < DQN_SQUARED ( 5.f ) ;
switch ( game - > play . tutorial_state ) {
case FP_GameStateTutorial_ShowPlayer : {
camera - > world_pos_target = FP_Game_CalcEntityWorldPos ( game , game - > play . players . data [ 0 ] ) * game - > play . camera . scale ;
if ( camera_arrived ) {
2023-10-29 12:45:45 +00:00
game - > play . tutorial_state = DQN_CAST ( FP_GameStateTutorial ) ( DQN_CAST ( uint32_t ) game - > play . tutorial_state + 1 ) ;
game - > play . tutorial_wait_end_time_ms = game - > play . clock_ms + 3000 ;
2023-10-29 06:30:08 +00:00
}
} break ;
case FP_GameStateTutorial_ShowPortalOneWait : /*FALLTHRU*/
case FP_GameStateTutorial_ShowPortalTwoWait : /*FALLTHRU*/
case FP_GameStateTutorial_ShowPortalThreeWait : /*FALLTHRU*/
2023-10-29 12:45:45 +00:00
case FP_GameStateTutorial_ShowBillboardBuildWait : /*FALLTHRU*/
2023-10-29 06:30:08 +00:00
case FP_GameStateTutorial_ShowPlayerWait : {
2023-10-29 12:45:45 +00:00
if ( game - > play . clock_ms > game - > play . tutorial_wait_end_time_ms ) {
2023-10-29 06:30:08 +00:00
game - > play . tutorial_state = DQN_CAST ( FP_GameStateTutorial ) ( DQN_CAST ( uint32_t ) game - > play . tutorial_state + 1 ) ;
}
} break ;
case FP_GameStateTutorial_ShowPortalOne : /*FALLTHRU*/
case FP_GameStateTutorial_ShowPortalTwo : /*FALLTHRU*/
case FP_GameStateTutorial_ShowPortalThree : {
if ( game - > play . tutorial_state = = FP_GameStateTutorial_ShowPortalOne ) {
camera - > world_pos_target = FP_Game_CalcEntityWorldPos ( game , game - > play . mob_spawners . data [ 0 ] ) * game - > play . camera . scale ;
} else if ( game - > play . tutorial_state = = FP_GameStateTutorial_ShowPortalTwo ) {
camera - > world_pos_target = FP_Game_CalcEntityWorldPos ( game , game - > play . mob_spawners . data [ 1 ] ) * game - > play . camera . scale ;
} else {
DQN_ASSERT ( game - > play . tutorial_state = = FP_GameStateTutorial_ShowPortalThree ) ;
camera - > world_pos_target = FP_Game_CalcEntityWorldPos ( game , game - > play . mob_spawners . data [ 2 ] ) * game - > play . camera . scale ;
}
if ( camera_arrived ) {
game - > play . tutorial_state = DQN_CAST ( FP_GameStateTutorial ) ( DQN_CAST ( uint32_t ) game - > play . tutorial_state + 1 ) ;
2023-10-29 12:45:45 +00:00
game - > play . tutorial_wait_end_time_ms = game - > play . clock_ms + 2000 ;
}
} break ;
case FP_GameStateTutorial_ShowBillboardBuild : {
camera - > world_pos_target = FP_Game_CalcEntityWorldPos ( game , game - > play . billboard_build ) * game - > play . camera . scale ;
if ( camera_arrived ) {
game - > play . tutorial_state = DQN_CAST ( FP_GameStateTutorial ) ( DQN_CAST ( uint32_t ) game - > play . tutorial_state + 1 ) ;
game - > play . tutorial_wait_end_time_ms = game - > play . clock_ms + 4000 ;
2023-10-29 06:30:08 +00:00
}
} break ;
case FP_GameStateTutorial_Count : {
game - > play . state = FP_GameState_Play ;
} break ;
}
} else {
camera - > world_pos_target = { } ;
for ( FP_GameEntityHandle camera_entity : game - > play . camera_tracking_entity ) {
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos ( game , camera_entity ) * game - > play . camera . scale ;
camera - > world_pos_target + = entity_pos ;
}
if ( game - > play . camera_tracking_entity . size )
camera - > world_pos_target / = DQN_CAST ( Dqn_f32 ) game - > play . camera_tracking_entity . size ;
}
2023-10-19 13:20:41 +00:00
2023-10-22 09:02:35 +00:00
// NOTE: Clamp camera to map bounds ============================================================
{
2023-10-29 06:30:08 +00:00
Dqn_V2 window_size = Dqn_V2_InitV2I ( os - > core . window_size ) ;
2023-10-15 11:48:53 +00:00
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 ;
2023-10-17 12:49:20 +00:00
camera - > world_pos_target . x = DQN_MIN ( camera - > world_pos_target . x , half_map_screen_size . w - ( camera_size_screen . w * .5f ) ) ;
camera - > world_pos_target . x = DQN_MAX ( camera - > world_pos_target . x , - half_map_screen_size . w + ( camera_size_screen . w * .5f ) ) ;
camera - > world_pos_target . y = DQN_MAX ( camera - > world_pos_target . y , - half_map_screen_size . h + ( camera_size_screen . h * .5f ) ) ;
camera - > world_pos_target . y = DQN_MIN ( camera - > world_pos_target . y , half_map_screen_size . h - ( camera_size_screen . h * .5f ) ) ;
2023-09-30 12:27:25 +00:00
}
2023-10-29 06:30:08 +00:00
camera - > world_pos + = ( camera - > world_pos_target - camera - > world_pos ) * DQN_MIN ( 1.f , ( 5.f * DQN_CAST ( Dqn_f32 ) input - > delta_s ) ) ;
2023-09-29 05:28:11 +00:00
Dqn_Profiler_EndZone ( update_zone ) ;
2023-09-16 07:32:25 +00:00
}
2023-12-01 05:46:58 +00:00
static Dqn_Str8 FP_ScanKeyToLabel ( Dqn_Arena * arena , TELY_InputScanKey scan_key )
2023-10-20 13:04:13 +00:00
{
2023-10-24 12:41:15 +00:00
Dqn_Str8 result = { } ;
2023-10-20 13:04:13 +00:00
Dqn_Allocator allocator = Dqn_Arena_Allocator ( arena ) ;
2023-12-01 05:46:58 +00:00
if ( scan_key > = TELY_InputScanKey_A & & scan_key < = TELY_InputScanKey_Z ) {
char scan_key_ch = DQN_CAST ( char ) ( ' A ' + ( scan_key - TELY_InputScanKey_A ) ) ;
2023-10-29 03:59:53 +00:00
result = Dqn_Str8_InitF ( allocator , " [%c] " , scan_key_ch ) ;
2023-10-20 13:04:13 +00:00
} else {
2023-12-01 05:46:58 +00:00
if ( scan_key = = TELY_InputScanKey_Up ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [Up] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Down ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [Down] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Left ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [Left] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Right ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [Right] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Semicolon ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [;] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Apostrophe ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " ['] " ) ;
2023-12-01 05:46:58 +00:00
} else if ( scan_key = = TELY_InputScanKey_Backslash ) {
2023-10-24 12:41:15 +00:00
result = Dqn_Str8_InitF ( allocator , " [/] " ) ;
2023-10-20 13:04:13 +00:00
}
}
return result ;
}
2023-10-23 11:13:03 +00:00
static void FP_DrawBillboardKeyBindHint ( TELY_Renderer * renderer ,
TELY_Assets * assets ,
FP_Game * game ,
Dqn_usize player_index ,
FP_GameControlMode mode ,
FP_GameKeyBind key_bind ,
Dqn_V2 draw_p ,
bool draw_player_prefix )
{
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
2023-10-24 12:41:15 +00:00
Dqn_Str8 player_prefix = { } ;
2023-10-23 11:13:03 +00:00
if ( draw_player_prefix )
2023-10-24 12:41:15 +00:00
player_prefix = Dqn_Str8_InitF ( scratch . allocator , " P%zu " , player_index ) ;
2023-10-23 11:13:03 +00:00
if ( mode = = FP_GameControlMode_Gamepad ) {
2023-10-24 12:41:15 +00:00
Dqn_Str8 tex_name = { } ;
2023-12-01 05:46:58 +00:00
if ( key_bind . gamepad_key = = TELY_InputGamepadKey_A )
2023-10-23 11:13:03 +00:00
tex_name = g_anim_names . merchant_button_a ;
2023-12-01 05:46:58 +00:00
else if ( key_bind . gamepad_key = = TELY_InputGamepadKey_B )
2023-10-23 11:13:03 +00:00
tex_name = g_anim_names . merchant_button_b ;
2023-12-01 05:46:58 +00:00
else if ( key_bind . gamepad_key = = TELY_InputGamepadKey_X )
2023-10-23 11:13:03 +00:00
tex_name = g_anim_names . merchant_button_x ;
2023-12-01 05:46:58 +00:00
else if ( key_bind . gamepad_key = = TELY_InputGamepadKey_Y )
2023-10-23 11:13:03 +00:00
tex_name = g_anim_names . merchant_button_y ;
if ( tex_name . size ) {
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , tex_name ) ;
Dqn_Rect button_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
2023-10-29 03:59:53 +00:00
Dqn_V2 text_size = TELY_Asset_MeasureText ( assets , TELY_Render_ActiveFont ( renderer ) , player_prefix ) ;
2023-10-23 11:13:03 +00:00
TELY_Render_Text ( renderer , draw_p , Dqn_V2_InitNx2 ( 0 , + 1.f ) , player_prefix ) ;
Dqn_Rect gamepad_btn_rect = { } ;
gamepad_btn_rect . size = button_rect . size ;
gamepad_btn_rect . pos = Dqn_V2_InitNx2 ( draw_p . x + ( text_size . x * 1.25f ) , draw_p . y - button_rect . size . y ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
button_rect ,
gamepad_btn_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_COLOUR_WHITE_V4 ) ;
}
} else {
2023-10-29 03:59:53 +00:00
Dqn_Str8 key_bind_label = FP_ScanKeyToLabel ( scratch . arena , key_bind . scan_key ) ;
2023-10-24 12:41:15 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0 , + 1.f ) , " %.*s%.*s " , DQN_STR_FMT ( player_prefix ) , DQN_STR_FMT ( key_bind_label ) ) ;
2023-10-23 11:13:03 +00:00
}
}
2023-10-29 03:59:53 +00:00
static void FP_Render ( FP_Game * game , TELY_OS * os , 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-12-01 05:46:58 +00:00
TELY_Input * input = & os - > input ;
2023-10-08 02:22:08 +00:00
TELY_RFui * rfui = & game - > rfui ;
2023-10-29 03:59:53 +00:00
TELY_Assets * assets = & os - > assets ;
2023-10-08 02:22:08 +00:00
TELY_Render_ClearColourV3 ( renderer , TELY_COLOUR_BLACK_MIDNIGHT_V4 . rgb ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > jetbrains_mono_font , game - > font_size ) ;
TELY_RFui_FrameSetup ( rfui , & os - > frame_arena ) ;
TELY_RFui_PushFontSize ( rfui , game - > jetbrains_mono_font , game - > font_size ) ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
FP_GameCamera shake_camera = game - > play . camera ;
2023-10-18 09:34:38 +00:00
{
2023-10-19 13:20:41 +00:00
Dqn_f32 trauma01 = 0.f ;
for ( FP_GameEntityHandle camera_entity_handle : game - > play . camera_tracking_entity ) {
FP_GameEntity * camera_entity = FP_Game_GetEntity ( game , camera_entity_handle ) ;
trauma01 = DQN_MAX ( trauma01 , DQN_SQUARED ( camera_entity - > trauma01 ) ) ;
2023-10-18 09:34:38 +00:00
}
2023-10-19 13:20:41 +00:00
// NOTE: The heart shake is trauma^3 to emphasise the severity of losing heart health
FP_GameEntity * heart = FP_Game_GetEntity ( game , game - > play . heart ) ;
2023-10-25 21:38:34 +00:00
trauma01 = DQN_MAX ( trauma01 , DQN_SQUARED ( heart - > trauma01 ) * heart - > trauma01 * 5 ) ;
2023-10-21 15:14:01 +00:00
trauma01 = DQN_MAX ( trauma01 , DQN_SQUARED ( game - > play . global_camera_trauma01 ) ) ;
2023-10-19 13:20:41 +00:00
// NOTE: Calculate camera position based on camera shake
2023-10-18 09:34:38 +00:00
Dqn_f32 max_shake_dist = 400.f ;
Dqn_f32 half_shake_dist = max_shake_dist * .5f ;
Dqn_V2 shake_offset = { } ;
shake_offset . x = ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * max_shake_dist - half_shake_dist ) * trauma01 ;
shake_offset . y = ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * max_shake_dist - half_shake_dist ) * trauma01 ;
Dqn_f32 interp_rate = 5.0f * DQN_CAST ( Dqn_f32 ) input - > delta_s ;
shake_camera . world_pos + = shake_offset * interp_rate ;
}
FP_GameCameraM2x3 camera_xforms = FP_Game_CameraModelViewM2x3 ( shake_camera ) ;
2023-10-15 11:48:53 +00:00
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-29 03:59:53 +00:00
Dqn_usize tile_count_x = DQN_CAST ( Dqn_usize ) ( os - > core . window_size . w / game - > play . tile_size ) ;
Dqn_usize tile_count_y = DQN_CAST ( Dqn_usize ) ( os - > 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-10-29 03:59:53 +00:00
Dqn_V2 end = Dqn_V2_InitNx2 ( start . x , os - > core . window_size . h ) ;
2023-09-16 09:06:16 +00:00
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-10-29 03:59:53 +00:00
Dqn_V2 end = Dqn_V2_InitNx2 ( os - > core . window_size . w , start . y ) ;
2023-09-16 09:06:16 +00:00
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-19 13:20:41 +00:00
bool render_build_mode = false ;
for ( FP_GameEntityHandle player : game - > play . players ) {
FP_GameEntity * entity = FP_Game_GetEntity ( game , player ) ;
render_build_mode = entity - > in_game_menu = = FP_GameInGameMenu_Build ; ;
if ( render_build_mode )
break ;
}
2023-10-18 11:59:40 +00:00
Dqn_V4 const colour_accent_yellow = TELY_Colour_V4InitRGBU32 ( 0xFFE726 ) ;
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 ;
}
}
2023-10-22 06:24:40 +00:00
if ( entity - > flags & FP_GameEntityFlag_HasShadow ) {
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . shadow_tight_circle ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect shadow_rect = { } ;
shadow_rect . size = tex_rect . size * .5f ;
shadow_rect . pos = Dqn_V2_InitNx2 ( world_hit_box . pos . x - ( shadow_rect . size . w * .5f ) + ( shadow_rect . size . w * .2f ) , world_hit_box . pos . y + world_hit_box . size . h ) ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
tex_rect ,
shadow_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , .8f ) ) ;
}
2023-09-16 02:21:24 +00:00
// 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-10-22 06:24:40 +00:00
dest_rect . pos = world_pos - ( dest_rect . size * .5f ) + render_data . offset ;
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-10-17 13:19:09 +00:00
Dqn_V4 sprite_colour = TELY_COLOUR_WHITE_V4 ;
2023-10-18 09:34:38 +00:00
if ( entity - > hit_on_clock_ms ) {
DQN_ASSERT ( game - > play . clock_ms > = entity - > hit_on_clock_ms ) ;
Dqn_usize ms_since_hit = game - > play . clock_ms - entity - > hit_on_clock_ms ;
Dqn_usize const HIT_CONFIRM_DURATION_MS = 8 * 16 ;
if ( ms_since_hit < HIT_CONFIRM_DURATION_MS ) {
2023-10-17 13:19:09 +00:00
sprite_colour = TELY_COLOUR_RED_V4 ;
2023-10-18 09:34:38 +00:00
sprite_colour . g = ( ( 1.f / HIT_CONFIRM_DURATION_MS ) * ms_since_hit ) ;
sprite_colour . b = ( ( 1.f / HIT_CONFIRM_DURATION_MS ) * ms_since_hit ) ;
2023-10-17 13:19:09 +00:00
}
}
sprite_colour . a * = entity - > action . sprite_alpha ;
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-17 13:19:09 +00:00
sprite_colour ) ;
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-29 12:45:45 +00:00
uint64_t elapsed_ms = game - > play . clock_ms - sprite - > started_at_clock_ms ;
uint16_t raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / sprite - > asset . anim - > ms_per_frame ) ;
2023-09-26 13:58:48 +00:00
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-22 11:41:21 +00:00
if ( entity - > is_drunk ) {
bool show_martini = entity - > handle . id & 1 ;
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , show_martini ? g_anim_names . particle_drunk_martini : g_anim_names . particle_drunk_bottle ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size ;
if ( show_martini ) {
dest_rect . size * = 1.5f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box ,
Dqn_V2_InitNx2 ( 0.0f , 0.0f ) ) - ( Dqn_V2_InitNx2 ( dest_rect . size . x * .6f , dest_rect . size . y * .7f ) ) ;
2023-10-29 12:45:45 +00:00
dest_rect . pos . y + = ( DQN_SINF ( DQN_CAST ( Dqn_f32 ) game - > play . clock_ms / 1000.f * 2.f ) + 1.f / 2.f ) * dest_rect . size . y * .005f ;
2023-10-22 11:41:21 +00:00
} else {
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box ,
Dqn_V2_InitNx2 ( 0.0f , 0.0f ) ) - ( Dqn_V2_InitNx2 ( dest_rect . size . x * .7f , dest_rect . size . y * .9f ) ) ;
}
2023-10-29 12:45:45 +00:00
dest_rect . pos . y + = ( DQN_SINF ( DQN_CAST ( Dqn_f32 ) game - > play . clock_ms / 1000.f * 2.f ) + 1.f / 2.f ) * dest_rect . size . y * .1f ;
2023-10-22 11:41:21 +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 ) ;
}
if ( entity - > converted_faction ) {
bool show_halo = entity - > handle . id & 1 ;
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , show_halo ? g_anim_names . particle_church_halo : g_anim_names . particle_church_cross ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size ;
if ( show_halo ) {
dest_rect . size * = .6f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box ,
Dqn_V2_InitNx2 ( 0.5f , 0.0f ) ) - ( Dqn_V2_InitNx2 ( dest_rect . size . x * .5f , dest_rect . size . y * 1.2f ) ) ;
} else {
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( world_hit_box ,
Dqn_V2_InitNx2 ( 0.5f , 0.0f ) ) - ( Dqn_V2_InitNx2 ( dest_rect . size . x * .5f , dest_rect . size . y * .9f ) ) ;
}
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-19 13:20:41 +00:00
if ( entity - > handle ! = game - > play . heart & & entity - > hp ! = entity - > hp_cap & & entity - > hp ) {
// NOTE: We don't draw health bar for the player either
bool current_entity_is_a_player = false ;
for ( FP_GameEntityHandle player : game - > play . players ) {
current_entity_is_a_player | = ( entity - > handle = = player ) ;
}
if ( ! current_entity_is_a_player ) {
Dqn_f32 bar_height = 12.f ;
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 ) ;
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-08 06:25:08 +00:00
}
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-19 13:20:41 +00:00
if ( render_build_mode ) {
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-23 13:07:59 +00:00
if ( ! game - > play . debug_hide_bounding_rectangles ) {
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-23 13:07:59 +00:00
if ( ! game - > play . debug_hide_bounding_rectangles ) {
if ( game - > play . clicked_entity = = entity - > handle ) {
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Line , TELY_COLOUR_WHITE_PALE_GOLDENROD_V4 ) ;
2023-10-05 10:30:56 +00:00
2023-10-23 13:07:59 +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 ) ;
TELY_Render_RectColourV4 ( renderer , world_hit_box , TELY_RenderShapeMode_Line , hot_colour ) ;
}
2023-10-05 10:30:56 +00:00
}
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-29 04:14:07 +00:00
Dqn_f32 line_height = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-05 10:30:56 +00:00
Dqn_V2 draw_p = world_mouse_p ;
2023-10-24 12:41:15 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " %.*s " , DQN_STR_FMT ( entity - > name ) ) ; draw_p . y + = line_height ;
2023-10-05 10:30:56 +00:00
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
2023-10-24 12:41:15 +00:00
Dqn_Str8 faction = { } ;
2023-10-07 08:14:09 +00:00
switch ( entity - > faction ) {
2023-10-24 12:41:15 +00:00
case FP_GameEntityFaction_Nil : faction = DQN_STR8 ( " Nil " ) ; break ;
case FP_GameEntityFaction_Friendly : faction = DQN_STR8 ( " Friendly " ) ; break ;
case FP_GameEntityFaction_Foe : faction = DQN_STR8 ( " Foe " ) ; break ;
2023-10-07 08:14:09 +00:00
}
2023-10-24 12:41:15 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx2 ( 0.f , 1 ) , " Faction: %.*s " , DQN_STR_FMT ( faction ) ) ; draw_p . y + = line_height ;
2023-10-05 10:30:56 +00:00
}
}
}
2023-10-16 13:35:41 +00:00
if ( entity - > type = = FP_EntityType_Billboard ) {
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > font_size ) ;
2023-10-23 11:13:03 +00:00
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
2023-10-20 13:04:13 +00:00
DQN_FOR_UINDEX ( player_index , game - > play . players . size ) {
FP_GameEntityHandle player_handle = game - > play . players . data [ player_index ] ;
FP_GameEntity const * player = FP_Game_GetEntity ( game , player_handle ) ;
FP_GameControls const * controls = & player - > controls ;
2023-10-23 11:13:03 +00:00
FP_EntityBillboardState state = DQN_CAST ( FP_EntityBillboardState ) entity - > action . state ;
2023-10-19 13:20:41 +00:00
switch ( state ) {
case FP_EntityBillboardState_Attack : {
2023-10-23 11:13:03 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.6f , 0.2f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , colour_accent_yellow ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > attack , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-19 13:20:41 +00:00
} break ;
2023-10-16 13:35:41 +00:00
2023-10-19 13:20:41 +00:00
case FP_EntityBillboardState_Dash : {
2023-10-23 11:13:03 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.505f , - 0.08f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4InitRGBU32 ( 0xFFE726 ) ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > dash , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-19 13:20:41 +00:00
} break ;
2023-10-16 13:35:41 +00:00
2023-10-19 13:20:41 +00:00
case FP_EntityBillboardState_Monkey : {
} break ;
2023-10-16 13:35:41 +00:00
2023-10-19 13:20:41 +00:00
case FP_EntityBillboardState_RangeAttack : {
2023-10-23 11:13:03 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.20f , - 0.13f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4InitRGBU32 ( 0x364659 ) ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > range_attack , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-19 13:20:41 +00:00
} break ;
2023-10-16 13:35:41 +00:00
2023-10-19 13:20:41 +00:00
case FP_EntityBillboardState_Strafe : {
2023-10-23 11:13:03 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.36f , - 0.15f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4InitRGBU32 ( 0xFF68A8 ) ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > strafe , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-19 13:20:41 +00:00
} break ;
2023-10-16 13:35:41 +00:00
2023-10-23 11:13:03 +00:00
case FP_EntityBillboardState_Build : {
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.065f , 0.2f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , colour_accent_yellow ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > build_mode , draw_p , true /*draw_player_prefix*/ ) ;
2023-10-19 13:20:41 +00:00
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-16 13:35:41 +00:00
2023-10-23 11:13:03 +00:00
draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.35f , 0.2f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4InitRGBU32 ( 0xFF68A8 ) ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > move_building_ui_cursor_left , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-10-20 13:04:13 +00:00
2023-10-23 11:13:03 +00:00
draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.44f , 0.2f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4InitRGBU32 ( 0xFF68A8 ) ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > move_building_ui_cursor_right , draw_p , false /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
draw_p = Dqn_Rect_InterpolatedPoint ( world_hit_box , Dqn_V2_InitNx2 ( 0.665f , 0.2f ) ) ;
2023-10-29 04:14:07 +00:00
draw_p . y + = TELY_Render_FontSize ( renderer ) * player_index * os - > core . dpi_scale ;
2023-10-23 11:13:03 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_BLACK_V4 ) ;
FP_DrawBillboardKeyBindHint ( renderer , assets , game , player_index , controls - > mode , controls - > attack , draw_p , true /*draw_player_prefix*/ ) ;
TELY_Render_PopColourV4 ( renderer ) ;
} break ;
2023-10-19 13:20:41 +00:00
}
2023-10-16 13:35:41 +00:00
}
}
2023-10-06 10:48:05 +00:00
}
2023-09-16 02:21:24 +00:00
2023-10-21 15:14:01 +00:00
// NOTE: Draw perry ============================================================================
if ( game - > play . perry_joined = = FP_GamePerryJoins_Enters ) {
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
// NOTE: Shake the world ===================================================================
Dqn_Rect window_rect = { } ;
2023-10-29 03:59:53 +00:00
window_rect . size = Dqn_V2_InitV2I ( os - > core . window_size ) ;
2023-10-21 15:14:01 +00:00
Dqn_f32 tex_scalar = { } ;
{
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 * .25f ;
tex_scalar = desired_width / tex_rect . size . w ;
}
game - > play . perry_join_bg_alpha + = ( 1.f - game - > play . perry_join_bg_alpha ) * ( DQN_CAST ( Dqn_f32 ) input - > delta_s * 8.f ) ;
game - > play . perry_join_bg_alpha = DQN_MIN ( game - > play . perry_join_bg_alpha , .8f ) ;
TELY_Render_RectColourV4 ( renderer ,
window_rect ,
TELY_RenderShapeMode_Fill ,
TELY_Colour_V4Alpha ( TELY_COLOUR_BLACK_V4 , game - > play . perry_join_bg_alpha ) ) ;
if ( game - > play . perry_join_bg_alpha > 0.5f ) {
if ( ! game - > play . perry_join_splash_screen_shake_triggered ) {
game - > play . global_camera_trauma01 = 1.f ;
game - > play . perry_join_splash_screen_shake_triggered = true ;
game - > play . perry_join_flash_alpha = 1.f ;
2023-10-29 12:45:45 +00:00
game - > play . perry_join_splash_screen_end_ms = game - > play . clock_ms + 2000 ;
2023-11-05 21:29:55 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_PerryStart ] , 1.f ) ;
2023-10-21 15:14:01 +00:00
}
}
if ( game - > play . perry_join_splash_screen_shake_triggered ) {
game - > play . perry_join_flash_alpha - = game - > play . perry_join_bg_alpha * ( DQN_CAST ( Dqn_f32 ) input - > delta_s * 4.f ) ;
game - > play . perry_join_flash_alpha = DQN_MAX ( game - > play . perry_join_flash_alpha , .0f ) ;
TELY_Render_RectColourV4 ( renderer ,
window_rect ,
TELY_RenderShapeMode_Fill ,
TELY_Colour_V4Alpha ( TELY_COLOUR_WHITE_V4 , game - > play . perry_join_flash_alpha ) ) ;
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . intro_screen_perry_joins_the_fight ) ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
2023-10-29 12:45:45 +00:00
Dqn_f32 sin_t = ( DQN_SINF ( DQN_CAST ( Dqn_f32 ) game - > play . clock_ms / 1000.f * 3.f ) + 1 ) / 2.f ;
2023-10-21 15:14:01 +00:00
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * ( tex_scalar * 1.5f ) + ( tex_rect . size * ( 0.005f * sin_t ) ) ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 1 , 1 ) ) - dest_rect . size ;
Dqn_f32 max_shake_dist = 50.f ;
Dqn_f32 half_shake_dist = max_shake_dist * .5f ;
Dqn_V2 shake_offset = { } ;
shake_offset . x = ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * max_shake_dist - half_shake_dist ) * game - > play . global_camera_trauma01 ;
shake_offset . y = ( Dqn_PCG32_NextF32 ( & game - > play . rng ) * max_shake_dist - half_shake_dist ) * game - > play . global_camera_trauma01 ;
dest_rect . pos + = shake_offset ;
2023-10-29 12:45:45 +00:00
if ( game - > play . clock_ms > game - > play . perry_join_splash_screen_end_ms ) {
2023-10-21 15:14:01 +00:00
game - > play . perry_join_splash_pos_offset . x - = dest_rect . size . x * ( 12.f * DQN_CAST ( Dqn_f32 ) input - > delta_s ) ;
dest_rect . pos + = game - > play . perry_join_splash_pos_offset ;
if ( dest_rect . pos . x < - dest_rect . size . x )
game - > play . perry_joined = FP_GamePerryJoins_PostEnter ;
}
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
// NOTE: Render overlay UI =====================================================================
2023-10-29 06:30:08 +00:00
if ( ! game - > play . debug_hide_hud & & ( game - > play . state = = FP_GameState_Pause | | game - > play . state = = FP_GameState_Play | | game - > play . state = = FP_GameState_Tutorial ) ) {
2023-10-06 12:30:09 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Render the merchant menus for each player =========================================
FP_GamePlay * play = & game - > play ;
FP_GameEntityHandle merchant_handles [ ] = {
play - > merchant_terry ,
play - > merchant_graveyard ,
play - > merchant_gym ,
play - > merchant_phone_company ,
} ;
2023-10-20 10:01:26 +00:00
// NOTE: Calculate which merchant sound trigger flags to reset =============================
FP_GameEntityHandle merchant_with_more_than_2_menus_open = { } ;
2023-10-19 13:20:41 +00:00
static bool sound_played_flags [ DQN_ARRAY_UCOUNT ( merchant_handles ) ] = { false , false , false , false } ;
DQN_FOR_UINDEX ( merchant_index , DQN_ARRAY_UCOUNT ( merchant_handles ) ) {
2023-10-20 10:01:26 +00:00
FP_GameEntityHandle merchant = merchant_handles [ merchant_index ] ;
Dqn_V2 merchant_pos = FP_Game_CalcEntityWorldPos ( game , merchant ) ;
Dqn_usize menu_activation_count = 0 ;
bool all_players_are_far_away_from_merchant_menu_trigger = true ;
2023-10-19 13:20:41 +00:00
DQN_FOR_UINDEX ( player_index , game - > play . players . size ) {
FP_GameEntityHandle player_handle = game - > play . players . data [ player_index ] ;
Dqn_V2 player_pos = FP_Game_CalcEntityWorldPos ( game , player_handle ) ;
Dqn_f32 dist_squared = Dqn_V2_LengthSq_V2x2 ( merchant_pos , player_pos ) ;
if ( dist_squared < DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 4 ) ) ) {
all_players_are_far_away_from_merchant_menu_trigger = false ;
2023-10-20 10:01:26 +00:00
menu_activation_count + + ;
2023-10-08 08:00:13 +00:00
}
2023-10-19 13:20:41 +00:00
}
2023-10-06 12:55:57 +00:00
2023-10-19 13:20:41 +00:00
if ( all_players_are_far_away_from_merchant_menu_trigger )
sound_played_flags [ merchant_index ] = false ;
2023-10-20 10:01:26 +00:00
if ( menu_activation_count > = 2 ) {
DQN_ASSERT ( merchant_with_more_than_2_menus_open . id = = 0 ) ;
merchant_with_more_than_2_menus_open = merchant ;
}
2023-10-19 13:20:41 +00:00
}
2023-10-07 01:21:31 +00:00
2023-10-20 13:04:13 +00:00
Dqn_V2 const player_avatar_base_pos [ ] = {
Dqn_V2_InitNx1 ( 32.f ) ,
2023-10-29 03:59:53 +00:00
Dqn_V2_InitNx2 ( os - > core . window_size . x - 320.f , 32.f ) ,
2023-10-20 13:04:13 +00:00
} ;
DQN_ASSERT ( game - > play . players . size < = DQN_ARRAY_UCOUNT ( player_avatar_base_pos ) ) ;
DQN_ASSERTF ( game - > play . players . size < = 2 , " We hardcode 2 player support " ) ;
2023-10-29 12:45:45 +00:00
if ( game - > play . players . size = = 1 & & game - > play . state ! = FP_GameState_Tutorial ) {
2023-10-20 13:04:13 +00:00
// NOTE: We show the Press <BTN> to join for the remaining 2nd player
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > font_size ) ;
2023-10-20 13:04:13 +00:00
DQN_DEFER {
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopTransform ( renderer ) ;
} ;
2023-10-22 09:11:33 +00:00
FP_GameEntity * first_player = FP_Game_GetEntity ( game , game - > play . players . data [ 0 ] ) ;
2023-10-24 12:41:15 +00:00
Dqn_Str8 join_game_key = { } ;
2023-10-22 09:11:33 +00:00
if ( first_player - > controls . mode = = FP_GameControlMode_Keyboard )
2023-10-24 12:41:15 +00:00
join_game_key = DQN_STR8 ( " <Gamepad: Start> " ) ;
2023-10-22 09:11:33 +00:00
else
2023-10-24 12:41:15 +00:00
join_game_key = DQN_STR8 ( " <Gamepad: B> " ) ;
2023-10-22 09:11:33 +00:00
2023-10-29 04:14:07 +00:00
Dqn_f32 font_height = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-20 13:04:13 +00:00
Dqn_V2 base_p = player_avatar_base_pos [ game - > play . players . size ] ;
2023-10-24 12:41:15 +00:00
TELY_Render_TextF ( renderer , base_p , Dqn_V2_Zero , " Press %.*s " , DQN_STR_FMT ( join_game_key ) ) ; base_p . y + = font_height ;
2023-10-29 12:45:45 +00:00
FP_ListenForNewPlayer ( input , game , false /*tutorial_is_allowed*/ ) ;
2023-10-20 13:04:13 +00:00
}
// NOTE: Render the player(s) HUD and merchant menu interaction ============================
2023-10-22 11:41:21 +00:00
FP_ParticleDescriptor dollar_buy_particle = FP_DefaultFloatUpParticleDescriptor ( g_anim_names . particle_purchase , 2000 /*duration*/ ) ;
Dqn_Rect first_player_avatar_rect = { } ;
2023-10-22 10:53:21 +00:00
2023-10-19 13:20:41 +00:00
DQN_FOR_UINDEX ( player_index , game - > play . players . size ) {
FP_GameEntityHandle player_handle = game - > play . players . data [ player_index ] ;
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
Dqn_V2 player_pos = FP_Game_CalcEntityWorldPos ( game , player_handle ) ;
{
FP_GameInventory * invent = & player - > inventory ;
struct FP_MerchantToMenuMapping {
FP_GameEntityHandle merchant ;
Dqn_V2 * menu_pos ;
2023-10-24 12:41:15 +00:00
Dqn_Str8 upgrade_icon ;
Dqn_Str8 menu_anim ;
Dqn_Str8 building ;
2023-10-19 13:20:41 +00:00
Dqn_V2 building_offset01 ;
uint8_t * inventory_count ;
uint32_t * building_base_price ;
uint32_t * upgrade_base_price ;
FP_GameAudio audio_type ;
bool * sound_played ;
} merchants [ ] = {
2023-10-20 10:01:26 +00:00
{ play - > merchant_terry , & player - > merchant_terry_menu_pos , g_anim_names . icon_attack , 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 ] } ,
{ play - > merchant_graveyard , & player - > merchant_graveyard_menu_pos , g_anim_names . icon_stamina , 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 ] } ,
{ play - > merchant_gym , & player - > merchant_gym_menu_pos , g_anim_names . icon_health , 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 ] } ,
{ play - > merchant_phone_company , & player - > merchant_phone_company_menu_pos , g_anim_names . icon_phone , 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-19 13:20:41 +00:00
} ;
2023-10-07 01:21:31 +00:00
2023-10-19 13:20:41 +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-18 10:48:12 +00:00
2023-10-19 13:20:41 +00:00
if ( dist_squared > DQN_SQUARED ( FP_Game_MetersToPixelsNx1 ( game - > play , 4 ) ) ) {
continue ;
}
2023-10-18 10:48:12 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Render animated merchant menu =============================
activated_merchant = true ;
Dqn_Rect merchant_menu_rect = { } ;
2023-10-18 10:48:12 +00:00
{
2023-10-19 13:20:41 +00:00
FP_GameRenderSprite * sprite = & game - > play . player_merchant_menu ;
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 ) ;
sprite - > started_at_clock_ms = game - > play . clock_ms ;
2023-10-18 10:48:12 +00:00
}
2023-10-19 13:20:41 +00:00
uint64_t elapsed_ms = game - > play . clock_ms - sprite - > started_at_clock_ms ;
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-06 12:16:55 +00:00
2023-10-19 13:20:41 +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-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
if ( * mapping . menu_pos = = Dqn_V2_Zero )
* mapping . menu_pos = world_pos ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
Dqn_Rect top_rect = Dqn_Rect_InitV2x2 ( world_pos - ( src_rect . size * .5f ) - Dqn_V2_InitNx2 ( 0.f , src_rect . size . h ) , src_rect . size ) ;
Dqn_Rect bottom_rect = Dqn_Rect_InitV2x2 ( world_pos - ( src_rect . size * .5f ) + Dqn_V2_InitNx2 ( 0.f , src_rect . size . h ) , src_rect . size ) ;
2023-10-08 02:22:08 +00:00
2023-10-20 10:01:26 +00:00
// NOTE: Position the merchant menu ========================================
if ( merchant_with_more_than_2_menus_open = = mapping . merchant ) {
// NOTE: More than 2 players have activated the same merchant, we just
// draw the menus and overlap with the players.
2023-10-20 13:04:13 +00:00
DQN_ASSERTF ( player_index < = 2 , " We only handle 2 players gracefully " ) ;
2023-10-20 10:01:26 +00:00
if ( player_index = = 0 )
* mapping . menu_pos = top_rect . pos ;
else
* mapping . menu_pos = bottom_rect . pos ;
} else {
// NOTE: Move the merchant menu if we overlap with it so as to not
// occlude the player. We *only* do this if the merchant is activated
// by 1 player only. If multiple players activate it, we just draw the
// menus-as-is.
Dqn_V2 target_pos = { } ;
2023-10-19 13:20:41 +00:00
Dqn_Rect camera_entity_hit_box = FP_Game_CalcEntityWorldHitBox ( game , player - > handle ) ;
if ( Dqn_Rect_Intersects ( top_rect , camera_entity_hit_box ) ) {
target_pos = bottom_rect . pos ;
} else {
target_pos = top_rect . pos ;
2023-10-07 06:55:34 +00:00
}
2023-10-06 12:30:09 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Interpolate the menu position
* mapping . menu_pos + = ( target_pos - * mapping . menu_pos ) * ( 24.f * DQN_CAST ( Dqn_f32 ) input - > delta_s ) ;
2023-10-07 06:55:34 +00:00
}
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
merchant_menu_rect . size = src_rect . size ;
merchant_menu_rect . pos = * mapping . menu_pos ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Bob the merchant menu
2023-10-29 12:45:45 +00:00
Dqn_f32 sin_t = DQN_SINF ( DQN_CAST ( Dqn_f32 ) game - > play . clock_ms / 1000.f * 3.f ) ;
2023-10-19 13:20:41 +00:00
merchant_menu_rect . pos . y + = sin_t * 4.f ;
2023-10-18 10:48:12 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_TextureColourV4 ( renderer ,
2023-10-19 13:20:41 +00:00
sprite - > asset . sheet - > tex_handle ,
src_rect ,
merchant_menu_rect ,
2023-10-08 02:22:08 +00:00
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
2023-10-19 13:20:41 +00:00
TELY_COLOUR_WHITE_V4 ) ;
if ( activated_merchant & & ! * mapping . sound_played ) {
TELY_Audio_Play ( audio , game - > audio [ mapping . audio_type ] , 1.f ) ;
* mapping . sound_played = true ;
}
2023-10-07 01:21:31 +00:00
}
2023-10-19 13:20:41 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_BLACK_V4 ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > large_talkco_font_size ) ;
2023-10-19 13:20:41 +00:00
DQN_DEFER {
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
} ;
// NOTE: Render the merchant button for buildings ==================
uint64_t const buy_duration_ms = 500 ;
Dqn_V4 keybind_btn_shadow_colour = Dqn_V4_InitNx4 ( 0.4f , 0.4f , 0.4f , 1.f ) ;
2023-10-08 02:22:08 +00:00
{
2023-10-20 13:04:13 +00:00
bool const have_enough_coins = player - > coins > = * mapping . building_base_price ;
FP_GameKeyBind key_bind = player - > controls . buy_building ;
2023-10-22 10:53:21 +00:00
// NOTE: Render the (A) button =================================
Dqn_V2 dollar_text_label_pos = { } ;
Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , .5f ) ;
{
// NOTE: Render the interaction button
Dqn_Rect interact_btn_rect = { } ;
interact_btn_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , Dqn_V2_InitNx2 ( 0.345f , 0.41f ) ) ;
if ( player - > controls . mode = = FP_GameControlMode_Gamepad ) {
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 ] ;
interact_btn_rect . size = button_rect . size * 1.5f ;
Dqn_Rect key_bind_rect = interact_btn_rect ;
key_bind_rect . pos . y + = interact_btn_rect . size . y * .3f ;
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
button_rect ,
key_bind_rect ,
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
tex_mod_colour ) ;
} else {
2023-10-29 03:59:53 +00:00
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
TELY_AssetFontSizeHandle font_size = TELY_Render_ActiveFont ( renderer ) ;
Dqn_Str8 key_bind_label = FP_ScanKeyToLabel ( scratch . arena , key_bind . scan_key ) ;
interact_btn_rect . size = TELY_Asset_MeasureText ( assets , font_size , key_bind_label ) ;
2023-10-22 10:53:21 +00:00
Dqn_Rect key_bind_rect = interact_btn_rect ;
2023-10-29 12:45:45 +00:00
key_bind_rect . pos . y + = interact_btn_rect . size . y * .8f ;
2023-10-22 10:53:21 +00:00
TELY_Render_RectColourV4 ( renderer , Dqn_Rect_Expand ( key_bind_rect , 2.f ) , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( keybind_btn_shadow_colour , tex_mod_colour . a ) ) ;
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4Alpha ( colour_accent_yellow , tex_mod_colour . a ) ) ;
TELY_Render_Text ( renderer , Dqn_Rect_InterpolatedPoint ( key_bind_rect , Dqn_V2_InitNx1 ( 0.5f ) ) , Dqn_V2_InitNx1 ( 0.5f ) , key_bind_label ) ;
TELY_Render_PopColourV4 ( renderer ) ;
}
// NOTE: Render the $ cost
2023-10-29 12:45:45 +00:00
dollar_text_label_pos = Dqn_Rect_InterpolatedPoint ( interact_btn_rect , Dqn_V2_InitNx2 ( 0.5f , - 1.5f ) ) ;
2023-10-22 10:53:21 +00:00
TELY_Render_TextF ( renderer , dollar_text_label_pos , Dqn_V2_InitNx2 ( 0.5 , 0.f ) , " $%u " , * mapping . building_base_price ) ;
}
// NOTE: Buy trigger + animation ===========================================
2023-10-19 13:20:41 +00:00
{
bool trigger_buy_anim = false ;
if ( have_enough_coins ) {
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
game - > play . player_trigger_purchase_building_timestamp = game - > play . clock_ms + buy_duration_ms ;
2023-12-01 05:46:58 +00:00
} else if ( TELY_Input_ScanKeyIsDown ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
trigger_buy_anim = true ;
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_building_timestamp )
game - > play . player_trigger_purchase_building_timestamp = game - > play . clock_ms ;
2023-12-01 05:46:58 +00:00
} else if ( TELY_Input_ScanKeyIsReleased ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_building_timestamp ) {
if ( mapping . inventory_count ) {
player - > coins - = * mapping . building_base_price ;
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Ching ] , 1.f ) ;
* mapping . building_base_price * = 1 ;
// NOTE: Raise the prices of everything else
invent - > airports_base_price * = 1 ;
invent - > clubs_base_price * = 1 ;
invent - > kennels_base_price * = 1 ;
invent - > churchs_base_price * = 1 ;
( * mapping . inventory_count ) + + ;
2023-10-22 10:53:21 +00:00
dollar_buy_particle . pos = dollar_text_label_pos ;
2023-10-23 11:13:03 +00:00
FP_EmitParticle ( game , dollar_buy_particle , 1 ) ;
2023-10-19 13:20:41 +00:00
}
} else {
game - > play . player_trigger_purchase_building_timestamp = UINT64_MAX ;
2023-10-08 02:22:08 +00:00
}
}
2023-10-19 13:20:41 +00:00
if ( trigger_buy_anim ) {
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-19 13:20:41 +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-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
TELY_Render_RectColourV4 ( renderer , buy_lerp_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_BLUE_CADET_V4 , 0.5f ) ) ;
}
2023-10-08 02:22:08 +00:00
}
2023-10-22 10:53:21 +00:00
}
2023-10-19 13:20:41 +00:00
2023-10-22 10:53:21 +00:00
// 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-08 02:22:08 +00:00
}
2023-10-22 10:53:21 +00:00
}
2023-10-08 02:22:08 +00:00
2023-10-22 10:53:21 +00:00
// NOTE: Render the merchant button for buildings
{
bool const have_enough_coins = player - > coins > = * mapping . upgrade_base_price ;
FP_GameKeyBind key_bind = player - > controls . buy_upgrade ;
// NOTE: Render the (B) button =================================
2023-10-19 13:20:41 +00:00
Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , .5f ) ;
2023-10-22 10:53:21 +00:00
Dqn_V2 dollar_text_label_pos = { } ;
2023-10-18 10:48:12 +00:00
{
2023-10-22 10:53:21 +00:00
Dqn_V2 interp_pos01 = Dqn_V2_InitNx2 ( 0.7f , 0.41f ) ;
2023-10-20 13:04:13 +00:00
Dqn_Rect interact_btn_rect = { } ;
2023-10-22 10:53:21 +00:00
interact_btn_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , interp_pos01 ) ;
// NOTE: Render the interact button ====================================
2023-10-20 13:04:13 +00:00
if ( player - > controls . mode = = FP_GameControlMode_Gamepad ) {
2023-10-22 10:53:21 +00:00
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , g_anim_names . merchant_button_b ) ;
2023-10-19 13:20:41 +00:00
Dqn_Rect button_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
2023-10-20 13:04:13 +00:00
interact_btn_rect . size = button_rect . size * 1.5f ;
Dqn_Rect key_bind_rect = interact_btn_rect ;
key_bind_rect . pos . y + = interact_btn_rect . size . y * .3f ;
2023-10-19 13:20:41 +00:00
TELY_Render_TextureColourV4 ( renderer ,
game - > atlas_sprite_sheet . tex_handle ,
button_rect ,
2023-10-20 13:04:13 +00:00
key_bind_rect ,
2023-10-19 13:20:41 +00:00
Dqn_V2_Zero /*rotate origin*/ ,
0.f /*rotation*/ ,
tex_mod_colour ) ;
2023-10-20 13:04:13 +00:00
} else {
2023-10-29 04:14:07 +00:00
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
TELY_AssetFontSizeHandle font_size = TELY_Render_ActiveFont ( renderer ) ;
Dqn_Str8 key_bind_label = FP_ScanKeyToLabel ( scratch . arena , key_bind . scan_key ) ;
interact_btn_rect . size = TELY_Asset_MeasureText ( assets , font_size , key_bind_label ) ;
2023-10-18 10:48:12 +00:00
2023-10-29 04:14:07 +00:00
Dqn_Rect key_bind_rect = interact_btn_rect ;
2023-10-29 12:45:45 +00:00
key_bind_rect . pos . y + = interact_btn_rect . size . y * .8f ;
2023-10-19 13:20:41 +00:00
2023-10-20 13:04:13 +00:00
TELY_Render_RectColourV4 ( renderer , Dqn_Rect_Expand ( key_bind_rect , 2.f ) , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( keybind_btn_shadow_colour , tex_mod_colour . a ) ) ;
2023-10-19 13:20:41 +00:00
TELY_Render_PushColourV4 ( renderer , TELY_Colour_V4Alpha ( colour_accent_yellow , tex_mod_colour . a ) ) ;
TELY_Render_Text ( renderer , Dqn_Rect_InterpolatedPoint ( key_bind_rect , Dqn_V2_InitNx1 ( 0.5f ) ) , Dqn_V2_InitNx1 ( 0.5f ) , key_bind_label ) ;
TELY_Render_PopColourV4 ( renderer ) ;
}
2023-10-20 13:04:13 +00:00
2023-10-22 10:53:21 +00:00
// NOTE: Render the icon ===============================================
{
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , mapping . upgrade_icon ) ;
Dqn_Rect button_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = button_rect . size * .75f ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( merchant_menu_rect , interp_pos01 + Dqn_V2_InitNx2 ( 0.135f , 0.15f ) ) - ( dest_rect . size * .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 ) ;
}
2023-10-20 13:04:13 +00:00
// NOTE: Render the $ cost
2023-10-29 12:45:45 +00:00
dollar_text_label_pos = Dqn_Rect_InterpolatedPoint ( interact_btn_rect , Dqn_V2_InitNx2 ( 1.f , - 1.5f ) ) ;
2023-10-22 10:53:21 +00:00
TELY_Render_TextF ( renderer , dollar_text_label_pos , Dqn_V2_InitNx2 ( 0.5 , 0.f ) , " $%u " , * mapping . upgrade_base_price ) ;
2023-10-18 10:48:12 +00:00
}
2023-10-08 02:22:08 +00:00
2023-10-22 10:53:21 +00:00
// NOTE: Buy trigger + animation ===========================================
2023-10-18 11:59:40 +00:00
{
2023-10-19 13:20:41 +00:00
bool trigger_buy_anim = false ;
if ( have_enough_coins ) {
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
game - > play . player_trigger_purchase_upgrade_timestamp = game - > play . clock_ms + buy_duration_ms ;
2023-12-01 05:46:58 +00:00
} else if ( TELY_Input_ScanKeyIsDown ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
trigger_buy_anim = true ;
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_upgrade_timestamp )
game - > play . player_trigger_purchase_upgrade_timestamp = game - > play . clock_ms ;
2023-12-01 05:46:58 +00:00
} else if ( TELY_Input_ScanKeyIsReleased ( input , key_bind . scan_key ) ) {
2023-10-19 13:20:41 +00:00
if ( game - > play . clock_ms > game - > play . player_trigger_purchase_upgrade_timestamp ) {
player - > coins - = * mapping . upgrade_base_price ;
* mapping . upgrade_base_price * = 1 ;
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_Ching ] , 1.f ) ;
2023-10-06 12:16:55 +00:00
2023-10-22 10:53:21 +00:00
dollar_buy_particle . pos = dollar_text_label_pos ;
2023-10-23 11:13:03 +00:00
FP_EmitParticle ( game , dollar_buy_particle , 1 ) ;
2023-10-22 10:53:21 +00:00
2023-10-19 13:20:41 +00:00
if ( mapping . merchant = = game - > play . merchant_terry ) {
player - > base_attack + = DQN_CAST ( uint32_t ) ( FP_DEFAULT_DAMAGE * 1.2f ) ;
} else if ( mapping . merchant = = game - > play . merchant_graveyard ) {
player - > stamina_cap + = DQN_CAST ( uint32_t ) ( FP_TERRY_DASH_STAMINA_COST * .5f ) ;
} else if ( mapping . merchant = = game - > play . merchant_gym ) {
player - > hp_cap + = FP_DEFAULT_DAMAGE ;
player - > hp = player - > hp_cap ;
} else if ( mapping . merchant = = game - > play . merchant_phone_company ) {
player - > terry_mobile_data_plan_cap + = DQN_KILOBYTES ( 1 ) ;
}
} else {
game - > play . player_trigger_purchase_upgrade_timestamp = UINT64_MAX ;
}
}
2023-10-06 12:16:55 +00:00
2023-10-19 13:20:41 +00:00
if ( trigger_buy_anim ) {
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-06 10:48:05 +00:00
2023-10-19 13:20:41 +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 ;
2023-10-06 10:48:05 +00:00
2023-10-19 13:20:41 +00:00
TELY_Render_RectColourV4 ( renderer , buy_lerp_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( TELY_COLOUR_RED_TOMATO_V4 , 0.5f ) ) ;
}
}
}
}
}
2023-10-07 06:55:34 +00:00
2023-10-19 13:20:41 +00:00
if ( activated_merchant ) {
player - > in_game_menu = FP_GameInGameMenu_Merchant ;
} else {
if ( player - > in_game_menu = = FP_GameInGameMenu_Merchant ) {
player - > in_game_menu = FP_GameInGameMenu_Nil ;
}
2023-10-08 02:22:08 +00:00
}
2023-10-19 13:20:41 +00:00
}
2023-10-07 12:05:46 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Render player avatar HUD ==========================================================
Dqn_Rect player_avatar_rect = { } ;
2023-10-20 13:04:13 +00:00
player_avatar_rect . pos = player_avatar_base_pos [ player_index ] ;
2023-10-19 13:20:41 +00:00
Dqn_V2 next_pos = { } ;
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-08 02:22:08 +00:00
2023-10-21 05:30:15 +00:00
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , player - > type , 0 /*state, usually 0 is idle*/ , FP_GameDirection_Down ) ;
2023-10-19 13:20:41 +00:00
player_avatar_rect . size = render_data . render_size ;
2023-10-07 06:55:34 +00:00
2023-10-08 02:22:08 +00:00
TELY_Render_TextureColourV4 ( renderer ,
2023-10-19 13:20:41 +00:00
render_data . sheet - > tex_handle ,
render_data . sheet_rect ,
player_avatar_rect ,
Dqn_V2_Zero ,
0.f ,
2023-10-08 02:22:08 +00:00
TELY_COLOUR_WHITE_V4 ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > font_size ) ;
2023-10-19 13:20:41 +00:00
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
next_pos = Dqn_Rect_InterpolatedPoint ( player_avatar_rect , Dqn_V2_InitNx2 ( 1.f , 0 ) ) ;
2023-10-29 04:14:07 +00:00
Dqn_f32 font_height = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-19 13:20:41 +00:00
// NOTE: Health bar ====================================================
Dqn_f32 bar_height = font_height * .75f ;
Dqn_Rect health_icon_rect = { } ;
2023-10-08 02:22:08 +00:00
{
2023-10-19 13:20:41 +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-19 13:20:41 +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-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
TELY_Render_RectColourV4 ( renderer , curr_health_rect , TELY_RenderShapeMode_Fill , TELY_COLOUR_RED_TOMATO_V4 ) ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
TELY_RenderCommandRect * cmd = TELY_Render_RectColourV4 (
renderer ,
health_rect ,
TELY_RenderShapeMode_Line ,
TELY_COLOUR_BLACK_V4 ) ;
cmd - > thickness = 4.f ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +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-19 13:20:41 +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 12:05:46 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Stamina bar ===================================================
next_pos . y + = health_icon_rect . size . h * .8f ;
Dqn_Rect stamina_icon_rect = { } ;
2023-10-08 02:22:08 +00:00
{
2023-10-19 13:20:41 +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 ) ) ;
}
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 ) ;
// 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 ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-07 12:05:46 +00:00
2023-10-19 13:20:41 +00:00
// NOTE: Mobile data bar ===================================================
next_pos . y + = stamina_icon_rect . size . h * .8f ;
Dqn_Rect phone_icon_rect = { } ;
{
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 ) ) ;
}
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-19 13:20:41 +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-19 13:20:41 +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-06 12:16:55 +00:00
2023-10-19 13:20:41 +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 ) ,
" $%zu " ,
player - > coins ) ;
2023-10-06 12:16:55 +00:00
2023-10-19 13:20:41 +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-06 12:16:55 +00:00
2023-10-19 13:20:41 +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-19 13:20:41 +00:00
next_pos . y + = money_icon_rect . size . h ;
2023-10-05 12:09:39 +00:00
2023-10-19 13:20:41 +00:00
#if 0
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [H] Build Menu " ) ;
2023-10-08 02:22:08 +00:00
2023-10-19 13:20:41 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [Shift+WASD] Strafe " ) ;
2023-10-07 09:56:14 +00:00
2023-10-19 13:20:41 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [Ctrl+WASD] Dash " ) ;
2023-10-07 09:56:14 +00:00
2023-10-19 13:20:41 +00:00
next_pos . y + = font_height ;
TELY_Render_TextF ( renderer , next_pos , Dqn_V2_Zero , " [J|K] Melee/Range " ) ;
# endif
2023-10-07 09:56:14 +00:00
2023-10-19 13:20:41 +00:00
if ( player_index = = 0 )
first_player_avatar_rect = player_avatar_rect ;
2023-10-08 02:22:08 +00:00
}
2023-10-07 09:56:14 +00:00
2023-10-09 10:35:51 +00:00
// NOTE: Render building blueprint =====================================================
2023-10-19 13:20:41 +00:00
if ( player - > in_game_menu = = FP_GameInGameMenu_Build ) {
FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS [ player - > build_mode_building_index ] ;
FP_EntityRenderData render_data = FP_Entity_GetRenderData ( game , placeable_building . type , placeable_building . state , player - > direction ) ;
Dqn_Rect dest_rect = FP_Game_GetBuildingPlacementRectForEntity ( game , placeable_building , player - > handle ) ;
2023-10-02 11:38:36 +00:00
2023-10-19 13:20:41 +00:00
Dqn_V4 colour = player - > 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-19 13:20:41 +00:00
player - > build_mode_building_index = DQN_CLAMP ( player - > 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-19 13:20:41 +00:00
if ( player - > 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 ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > font_size ) ;
2023-10-08 02:22:08 +00:00
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-19 13:20:41 +00:00
// NOTE: Render the wave ===================================================================
{
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > large_talkco_font_size ) ;
2023-10-19 13:20:41 +00:00
DQN_DEFER { TELY_Render_PopFont ( renderer ) ; } ;
uint64_t time_until_next_wave_ms = 0 ;
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-29 03:59:53 +00:00
Dqn_f32 mid_x = os - > core . window_size . x * .5f ;
2023-10-19 13:20:41 +00:00
if ( time_until_next_wave_ms ) {
TELY_Render_TextF ( renderer ,
Dqn_V2_InitNx2 ( mid_x , first_player_avatar_rect . pos . y ) ,
Dqn_V2_InitNx1 ( 0.5f ) ,
" %.1fs remaining until Wave %u! " ,
( time_until_next_wave_ms / 1000.f ) , game - > play . current_wave + 1 ) ;
} else {
TELY_Render_TextF ( renderer , Dqn_V2_InitNx2 ( mid_x , first_player_avatar_rect . pos . y ) , Dqn_V2_InitNx1 ( 0.5f ) , " Wave %u " , game - > play . current_wave ) ;
}
}
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 ) ; } ;
2023-10-29 04:14:07 +00:00
Dqn_f32 font_height = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-29 03:59:53 +00:00
Dqn_f32 bar_height = font_height * 1.25f ;
2023-10-08 08:04:11 +00:00
{
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 ) ;
2023-10-29 03:59:53 +00:00
Dqn_f32 max_width = os - > core . window_size . x * .5f ;
Dqn_V2 draw_p = Dqn_V2_InitNx2 ( os - > core . window_size . x * .25f , first_player_avatar_rect . pos . y + bar_height ) ;
2023-10-09 10:35:51 +00:00
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 ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > large_talkco_font_size ) ;
2023-10-08 08:04:11 +00:00
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-20 13:04:13 +00:00
// NOTE: Debug show camera bounds
#if 0
2023-10-19 13:20:41 +00:00
FP_GameCamera * camera = & game - > play . camera ;
2023-10-20 13:04:13 +00:00
Dqn_Rect camera_view_rect = Dqn_Rect_InitV2x2 ( ( camera - > size * - .5f ) + ( camera - > world_pos / camera - > scale ) , camera - > size ) ;
2023-10-19 13:20:41 +00:00
TELY_Render_RectColourV4 ( renderer , Dqn_Rect_Expand ( camera_view_rect , - 5.f ) , TELY_RenderShapeMode_Line , TELY_COLOUR_RED_V4 ) ;
2023-10-20 13:04:13 +00:00
# endif
2023-10-19 13:20:41 +00:00
2023-10-29 06:30:08 +00:00
// NOTE: Render the other game state modes =====================================================
Dqn_V4 const maroon_colour = TELY_Colour_V4InitRGBAU32 ( 0x301010FF ) ; // NOTE: Maroon
if ( game - > play . state = = FP_GameState_Tutorial ) {
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > large_talkco_font_size ) ;
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_WHITE_V4 ) ;
DQN_DEFER {
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopTransform ( renderer ) ;
} ;
// NOTE: Calculate text bounds
struct LineSize {
Dqn_Str8 line ;
Dqn_V2 size ;
} ;
2023-10-29 12:45:45 +00:00
Dqn_FArray < LineSize , 8 > lines = { } ;
if ( game - > play . tutorial_state = = FP_GameStateTutorial_ShowPlayer | | game - > play . tutorial_state = = FP_GameStateTutorial_ShowPlayerWait ) {
LineSize * line_size = Dqn_FArray_Make ( & lines , Dqn_ZeroMem_Yes ) ;
line_size - > line = DQN_STR8 ( " Defend Terry's heart from the oncoming cherries! " ) ;
} else if ( game - > play . tutorial_state = = FP_GameStateTutorial_ShowBillboardBuild | | game - > play . tutorial_state = = FP_GameStateTutorial_ShowBillboardBuildWait ) {
{
LineSize * line_size = Dqn_FArray_Make ( & lines , Dqn_ZeroMem_Yes ) ;
line_size - > line = DQN_STR8 ( " Lookout for billboards for tips! " ) ;
}
{
LineSize * line_size = Dqn_FArray_Make ( & lines , Dqn_ZeroMem_Yes ) ;
line_size - > line = DQN_STR8 ( " Build buildings to slow cherries down and afford more time " ) ;
}
} else {
LineSize * line_size = Dqn_FArray_Make ( & lines , Dqn_ZeroMem_Yes ) ;
line_size - > line = DQN_STR8 ( " Defeat the cherries spawning from the portals " ) ;
}
Dqn_f32 scaled_font_size = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
Dqn_V2 text_bounding_size = { } ;
text_bounding_size . y = lines . size * scaled_font_size ;
2023-10-29 06:30:08 +00:00
for ( LineSize & line_size_ : lines ) {
2023-10-29 12:45:45 +00:00
LineSize * line_size = & line_size_ ;
line_size - > size = TELY_Asset_MeasureText ( assets , TELY_Render_ActiveFont ( renderer ) , line_size - > line ) ;
text_bounding_size . x = DQN_MAX ( text_bounding_size . x , line_size - > size . x ) ;
2023-10-29 06:30:08 +00:00
}
// NOTE: Calculate terry avatar variables
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 = os - > core . window_size . x * .1f ;
Dqn_f32 tex_scalar = desired_width / tex_rect . size . w ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size * tex_scalar ;
2023-10-29 12:45:45 +00:00
dest_rect . pos = Dqn_V2_InitV2I ( os - > core . window_size ) * Dqn_V2_InitNx2 ( 0.5f , 0.8f ) - ( dest_rect . size * .5f ) - Dqn_V2_InitNx2 ( text_bounding_size . x * .5f , 0.f ) ;
2023-10-29 06:30:08 +00:00
2023-10-29 12:45:45 +00:00
Dqn_Rect terry_bounding_rect = dest_rect ;
2023-10-29 06:30:08 +00:00
// NOTE: Draw text
{
2023-10-29 12:45:45 +00:00
Dqn_V2 draw_p = Dqn_Rect_InterpolatedPoint ( terry_bounding_rect , Dqn_V2_InitNx2 ( 1.0f , 0.5f ) ) ;
Dqn_Rect bounding_rect = Dqn_Rect_InitV2x2 ( draw_p , text_bounding_size ) ;
bounding_rect = Dqn_Rect_ExpandV2 ( bounding_rect , Dqn_V2_InitNx2 ( scaled_font_size * 1.5f , scaled_font_size * .1f ) ) ;
2023-10-29 06:30:08 +00:00
TELY_Render_RectColourV4 ( renderer , Dqn_Rect_Expand ( bounding_rect , 5.f ) , TELY_RenderShapeMode_Fill , TELY_COLOUR_BLACK_V4 ) ;
TELY_Render_RectColourV4 ( renderer , bounding_rect , TELY_RenderShapeMode_Fill , TELY_Colour_V4Alpha ( maroon_colour , 1.f ) ) ;
for ( LineSize & line_size_ : lines ) {
LineSize * line_size = & line_size_ ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " %.*s " , DQN_STR_FMT ( line_size - > line ) ) ;
draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
}
}
// NOTE: Draw terry
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-29 12:45:45 +00:00
if ( game - > play . state = = FP_GameState_Pause ) {
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
TELY_Render_PushColourV4 ( renderer , TELY_COLOUR_WHITE_V4 ) ;
DQN_DEFER {
TELY_Render_PopColourV4 ( renderer ) ;
TELY_Render_PopTransform ( renderer ) ;
} ;
TELY_Render_RectColourV4 (
renderer ,
Dqn_Rect_InitNx4 ( 0 , 0 , DQN_CAST ( Dqn_f32 ) os - > core . window_size . x , DQN_CAST ( Dqn_f32 ) os - > core . window_size . y ) ,
TELY_RenderShapeMode_Fill ,
TELY_Colour_V4Alpha ( TELY_COLOUR_BLACK_V4 , .8f ) ) ;
Dqn_V2 draw_p = Dqn_V2_InitV2I ( os - > core . window_size ) * Dqn_V2_InitNx2 ( 0.5f , 0.5f ) ;
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > xlarge_talkco_font_size ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx1 ( 0.5f ) , " Paused " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PushFontSize ( renderer , game - > talkco_font , game - > large_talkco_font_size ) ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_InitNx1 ( 0.5f ) , " Press enter to resume " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_Return ) )
2023-10-29 12:45:45 +00:00
game - > play . state = FP_GameState_Play ;
}
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 ===================================
2023-10-29 06:30:08 +00:00
if ( game - > play . state = = FP_GameState_Play | | game - > play . state = = FP_GameState_Tutorial ) {
2023-10-29 03:59:53 +00:00
Dqn_V2 screen_size = Dqn_V2_InitNx2 ( os - > core . window_size . w , os - > core . window_size . h ) ;
2023-10-08 02:22:08 +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 , screen_size ) ;
2023-09-23 07:26:18 +00:00
}
2023-10-29 12:45:45 +00:00
if ( game - > play . state = = FP_GameState_IntroScreen | | game - > play . state = = FP_GameState_WinGame | | game - > play . state = = FP_GameState_LoseGame ) {
2023-10-09 10:35:51 +00:00
TELY_Render_PushTransform ( renderer , Dqn_M2x3_Identity ( ) ) ;
DQN_DEFER { TELY_Render_PopTransform ( renderer ) ; } ;
2023-10-29 03:59:53 +00:00
Dqn_V2I inset = os - > core . window_size * .05f ;
2023-10-09 10:35:51 +00:00
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-29 06:30:08 +00:00
Dqn_V4 bg_colour = maroon_colour ;
2023-10-12 13:05:41 +00:00
TELY_Render_RectColourV4 (
renderer ,
2023-10-29 03:59:53 +00:00
Dqn_Rect_InitNx4 ( 0 , 0 , DQN_CAST ( Dqn_f32 ) os - > core . window_size . x , DQN_CAST ( Dqn_f32 ) os - > core . window_size . y ) ,
2023-10-12 13:05:41 +00:00
TELY_RenderShapeMode_Fill ,
bg_colour ) ;
2023-10-29 03:59:53 +00:00
Dqn_Rect window_rect = Dqn_Rect_InitNx4 ( 0 , 0 , DQN_CAST ( Dqn_f32 ) os - > core . window_size . w , DQN_CAST ( Dqn_f32 ) os - > core . window_size . h ) ;
2023-10-14 01:19:03 +00:00
if ( game - > play . state = = FP_GameState_IntroScreen | | game - > play . state = = FP_GameState_LoseGame ) {
2023-10-16 13:35:41 +00:00
Dqn_f32 tex_scalar = 0.f ;
2023-10-12 13:05:41 +00:00
{
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 ] ;
2023-10-16 13:35:41 +00:00
Dqn_f32 desired_width = window_rect . size . x * .25f ;
2023-10-12 13:05:41 +00:00
tex_scalar = desired_width / tex_rect . size . w ;
}
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-21 15:14:01 +00:00
2023-10-14 01:19:03 +00:00
} 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 = { } ;
2023-10-16 13:35:41 +00:00
dest_rect . size = tex_rect . size * ( tex_scalar * 1.5f ) ;
2023-10-14 01:19:03 +00:00
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-16 13:35:41 +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 * 1.5f ) ;
dest_rect . pos = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.20f ) ) - ( 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 ) ;
}
// NOTE: Draw title subtitle ===========================================================
if ( game - > play . state = = FP_GameState_IntroScreen ) {
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.8f ) ) - ( 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
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > inter_regular_font , game - > font_size ) ;
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-29 12:45:45 +00:00
Dqn_V2 text_p = Dqn_Rect_InterpolatedPoint ( window_rect , Dqn_V2_InitNx2 ( 0.5f , 0.925f ) ) ;
TELY_Render_TextF ( renderer , text_p , Dqn_V2_InitNx1 ( 0.5f ) , " Press <B> or <Gamepad: Start> to %s " , game - > play . state = = FP_GameState_IntroScreen ? " start " : " restart " ) ;
text_p . y + = TELY_Render_ActiveFont ( renderer ) . size * os - > core . dpi_scale ;
TELY_Render_TextF ( renderer , text_p , Dqn_V2_InitNx1 ( 0.5f ) , " Press <T> or <Gamepad: Select> for the tutorial " ) ;
text_p . y + = TELY_Render_ActiveFont ( renderer ) . size * os - > core . dpi_scale ;
2023-10-20 13:04:13 +00:00
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-20 13:04:13 +00:00
if ( game - > play . state = = FP_GameState_LoseGame )
2023-10-29 03:59:53 +00:00
FP_PlayReset ( game , os ) ;
2023-10-29 06:30:08 +00:00
2023-10-29 12:45:45 +00:00
FP_ListenForNewPlayerResult new_player = FP_ListenForNewPlayer ( input , game , true /*tutorial_is_allowed*/ ) ;
if ( new_player . yes ) {
if ( new_player . tutorial_requested ) {
game - > play . state = FP_GameState_Tutorial ;
2023-11-05 21:29:55 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_GameStart ] , 1.f ) ;
2023-10-29 12:45:45 +00:00
} else {
game - > play . state = FP_GameState_Play ;
2023-11-05 21:29:55 +00:00
TELY_Audio_Play ( audio , game - > audio [ FP_GameAudio_GameStart ] , 1.f ) ;
2023-10-29 12:45:45 +00:00
}
}
2023-10-09 10:35:51 +00:00
} else {
DQN_ASSERT ( game - > play . state = = FP_GameState_WinGame ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > inter_regular_font , game - > large_font_size ) ;
2023-10-29 04:14:07 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Terry has been saved " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " from his terrible calamity " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-09 10:35:51 +00:00
TELY_Render_PopFont ( renderer ) ;
2023-10-29 03:59:53 +00:00
TELY_Render_PushFontSize ( renderer , game - > inter_regular_font , game - > font_size ) ;
2023-10-29 04:14:07 +00:00
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " He lives for yet another day and another love " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
TELY_Render_TextF ( renderer , draw_p , Dqn_V2_Zero , " Press enter to restart " ) ; draw_p . y + = TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-09 10:35:51 +00:00
TELY_Render_PopFont ( renderer ) ;
TELY_Render_PopColourV4 ( renderer ) ;
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_Return ) )
2023-10-29 03:59:53 +00:00
FP_PlayReset ( game , os ) ;
2023-10-09 10:35:51 +00:00
}
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
}
2023-10-22 10:53:21 +00:00
for ( FP_Particle & particle_ : game - > play . particles ) {
FP_Particle * particle = & particle_ ;
if ( ! particle - > alive )
continue ;
Dqn_usize elapsed_ms = game - > play . clock_ms - particle - > start_ms ;
Dqn_usize duration_ms = particle - > end_ms - particle - > start_ms ;
Dqn_f32 t = DQN_MIN ( 1.f , elapsed_ms / DQN_CAST ( Dqn_f32 ) duration_ms ) ;
Dqn_V4 colour = { } ;
colour . r = Dqn_Lerp_F32 ( particle - > colour_begin . r , t , particle - > colour_end . r ) ;
colour . b = Dqn_Lerp_F32 ( particle - > colour_begin . b , t , particle - > colour_end . b ) ;
colour . g = Dqn_Lerp_F32 ( particle - > colour_begin . g , t , particle - > colour_end . g ) ;
colour . a = Dqn_Lerp_F32 ( particle - > colour_begin . a , t , particle - > colour_end . a ) ;
if ( particle - > anim_name . size ) {
TELY_AssetSpriteAnimation * anim = TELY_Asset_GetSpriteAnimation ( & game - > atlas_sprite_sheet , particle - > anim_name ) ;
uint16_t const raw_anim_frame = DQN_CAST ( uint16_t ) ( elapsed_ms / anim - > ms_per_frame ) ;
uint16_t anim_frame = raw_anim_frame % anim - > count ;
Dqn_Rect tex_rect = game - > atlas_sprite_sheet . rects . data [ anim - > index + anim_frame ] ;
Dqn_Rect dest_rect = { } ;
dest_rect . size = tex_rect . size ;
dest_rect . pos = particle - > pos - ( tex_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*/ ,
colour ) ;
} else {
TELY_Render_CircleColourV4 ( renderer , particle - > pos , 20.f , TELY_RenderShapeMode_Fill , colour ) ;
}
}
2023-10-09 10:35:51 +00:00
// NOTE: Debug UI ==============================================================================
2023-12-01 05:46:58 +00:00
if ( TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F1 ) )
2023-10-23 13:07:59 +00:00
game - > play . debug_ui = ! game - > play . debug_ui ;
2023-10-21 05:30:15 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 4127 ) // Conditional expression is constant 'FP_DEVELOPER_MODE'
if ( FP_DEVELOPER_MODE & & game - > play . debug_ui ) {
DQN_MSVC_WARNING_POP
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 ;
2023-09-23 06:42:22 +00:00
{
2023-10-24 11:36:08 +00:00
TELY_RFui_PushPadding ( rfui , Dqn_V2_InitNx1 ( 2 ) ) ;
DQN_DEFER { TELY_RFui_PopPadding ( rfui ) ; } ;
TELY_RFui_PushMargin ( rfui , Dqn_V2_InitNx1 ( 1 ) ) ;
DQN_DEFER { TELY_RFui_PopMargin ( rfui ) ; } ;
2023-10-24 12:41:15 +00:00
TELY_RFuiResult info_column = TELY_RFui_ColumnReverse ( rfui , DQN_STR8 ( " Info Column " ) ) ;
2023-10-23 13:07:59 +00:00
TELY_RFui_PushParent ( rfui , info_column . widget ) ;
2023-09-23 06:42:22 +00:00
DQN_DEFER { TELY_RFui_PopParent ( rfui ) ; } ;
2023-10-24 11:36:08 +00:00
Dqn_V4 bg_colour = TELY_Colour_V4Alpha ( TELY_RFui_ActiveBackgroundColourV4 ( rfui ) , .7f ) ;
TELY_RFui_PushBackgroundColourV4 ( rfui , bg_colour ) ;
DQN_DEFER { TELY_RFui_PopBackgroundColourV4 ( rfui ) ; } ;
2023-10-23 13:07:59 +00:00
info_column . widget - > semantic_position [ TELY_RFuiAxis_X ] . kind = TELY_RFuiPositionKind_Absolute ;
info_column . widget - > semantic_position [ TELY_RFuiAxis_X ] . value = next_y ;
info_column . widget - > semantic_position [ TELY_RFuiAxis_Y ] . kind = TELY_RFuiPositionKind_Absolute ;
2023-10-29 04:14:07 +00:00
info_column . widget - > semantic_position [ TELY_RFuiAxis_Y ] . value = os - > core . window_size . h - TELY_Render_FontSize ( renderer ) * os - > core . dpi_scale ;
2023-10-23 13:07:59 +00:00
{
2023-10-24 12:41:15 +00:00
TELY_RFuiResult row = TELY_RFui_Row ( rfui , DQN_STR8 ( " Info Bar " ) ) ;
2023-10-23 13:07:59 +00:00
TELY_RFui_PushParent ( rfui , row . widget ) ;
DQN_DEFER { TELY_RFui_PopParent ( rfui ) ; } ;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch ( nullptr ) ;
2023-10-24 12:41:15 +00:00
Dqn_Str8Builder builder = { } ;
2023-10-24 11:36:08 +00:00
builder . allocator = scratch . allocator ;
2023-10-24 12:41:15 +00:00
Dqn_Str8Builder_AppendF ( & builder , " TELY " ) ;
2023-10-29 03:59:53 +00:00
if ( Dqn_Str8_IsValid ( os - > core . os_name ) ) {
Dqn_Str8Builder_AppendF ( & builder , " | %.*s " , DQN_STR_FMT ( os - > core . os_name ) ) ;
2023-10-23 13:07:59 +00:00
}
2023-10-24 12:41:15 +00:00
Dqn_Str8Builder_AppendF ( & builder ,
2023-10-23 13:07:59 +00:00
" | %dx%d %.1fHz | TSC %.1f GHz " ,
2023-10-29 03:59:53 +00:00
os - > core . window_size . w ,
os - > core . window_size . h ,
os - > core . display . refresh_rate ,
os - > core . tsc_per_second / 1'000'000'000 .0 ) ;
2023-10-23 13:07:59 +00:00
2023-10-29 03:59:53 +00:00
if ( os - > core . ram_mb )
Dqn_Str8Builder_AppendF ( & builder , " | RAM %.1fGB " , os - > core . ram_mb / 1024.0 ) ;
2023-10-23 13:07:59 +00:00
2023-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 6272 6271 )
// warning C6272: Non-float passed as argument '6' when float is required in call to 'Dqn_Str8Builder_AppendF' Actual type: 'unsigned __int64'.
// warning C6271: Extra argument passed to 'Dqn_Str8Builder_AppendF'.
2023-10-24 12:41:15 +00:00
Dqn_Str8Builder_AppendF ( & builder ,
2023-10-23 13:07:59 +00:00
" | 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-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_POP
2023-10-24 11:36:08 +00:00
2023-10-24 12:41:15 +00:00
Dqn_Str8 text = Dqn_Str8Builder_Build ( & builder , scratch . allocator ) ;
TELY_RFui_TextBackgroundF ( rfui , " %.*s " , DQN_STR_FMT ( text ) ) ;
2023-10-23 13:07:59 +00:00
}
2023-10-24 11:36:08 +00:00
TELY_RFui_TextBackgroundF ( rfui , " Mouse: %.1f, %.1f " , input - > mouse_p . x , input - > mouse_p . y ) ;
TELY_RFui_TextBackgroundF ( rfui , " Camera: %.1f, %.1f " , game - > play . camera . world_pos . x , game - > play . camera . world_pos . y ) ;
TELY_RFui_TextBackgroundF ( rfui , " Debug Info " ) ;
2023-10-23 13:07:59 +00:00
2023-10-24 11:36:08 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F1 Debug info " ) . clicked )
2023-10-23 13:07:59 +00:00
game - > play . debug_ui = ! game - > play . debug_ui ;
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F2 Add coins x10,000 " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F2 ) ) {
2023-10-23 13:07:59 +00:00
for ( FP_GameEntityHandle player_handle : game - > play . players ) {
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
player - > coins + = 10'000 ;
}
2023-09-23 06:42:22 +00:00
}
2023-09-20 13:35:38 +00:00
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F3 Win game " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F3 ) )
2023-10-23 13:07:59 +00:00
game - > play . state = FP_GameState_WinGame ;
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F4 Lose game " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F4 ) )
2023-10-23 13:07:59 +00:00
game - > play . state = FP_GameState_LoseGame ;
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F5 Reset game " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F5 ) )
2023-10-29 03:59:53 +00:00
FP_PlayReset ( game , os ) ;
2023-09-23 06:42:22 +00:00
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F6 Increase health " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F6 ) ) {
2023-10-23 13:07:59 +00:00
for ( FP_GameEntityHandle player_handle : game - > play . players ) {
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
player - > hp_cap + = FP_DEFAULT_DAMAGE ;
player - > hp = player - > hp_cap ;
}
}
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F7 Increase stamina " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F7 ) ) {
2023-10-23 13:07:59 +00:00
for ( FP_GameEntityHandle player_handle : game - > play . players ) {
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
player - > stamina_cap + = DQN_CAST ( uint16_t ) ( FP_TERRY_DASH_STAMINA_COST * .5f ) ;
player - > stamina = player - > stamina_cap ;
}
}
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F8 Increase mobile data " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F8 ) ) {
2023-10-23 13:07:59 +00:00
for ( FP_GameEntityHandle player_handle : game - > play . players ) {
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
player - > terry_mobile_data_plan_cap + = DQN_KILOBYTES ( 1 ) ;
player - > terry_mobile_data_plan = player - > terry_mobile_data_plan_cap ;
}
}
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F9 %s god mode " , game - > play . god_mode ? " Disable " : " Enable " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F9 ) )
2023-10-23 13:07:59 +00:00
game - > play . god_mode = ! game - > play . god_mode ;
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " F11 Building inventory +1 " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_F11 ) ) {
2023-10-23 13:07:59 +00:00
for ( FP_GameEntityHandle player_handle : game - > play . players ) {
FP_GameEntity * player = FP_Game_GetEntity ( game , player_handle ) ;
player - > inventory . clubs + = 1 ;
player - > inventory . airports + = 1 ;
player - > inventory . churchs + = 1 ;
player - > inventory . kennels + = 1 ;
}
}
2023-09-23 06:42:22 +00:00
2023-12-01 05:46:58 +00:00
if ( TELY_RFui_ButtonF ( rfui , " 1 %s HUD " , game - > play . debug_hide_hud ? " Show " : " Hide " ) . clicked | | TELY_Input_ScanKeyIsPressed ( input , TELY_InputScanKey_1 ) )
2023-10-23 13:07:59 +00:00
game - > play . debug_hide_hud = ! game - > play . debug_hide_hud ;
2023-09-30 12:27:25 +00:00
2023-10-23 13:07:59 +00:00
if ( TELY_RFui_ButtonF ( rfui , " %s bounding rects " , game - > play . debug_hide_bounding_rectangles ? " Show " : " Hide " ) . clicked )
game - > play . debug_hide_bounding_rectangles = ! game - > play . debug_hide_bounding_rectangles ;
2023-10-24 11:36:08 +00:00
if ( TELY_RFui_ButtonF ( rfui , " %s mob spawning " , game - > play . debug_disable_mobs ? " Enable " : " Disable " ) . clicked )
game - > play . debug_disable_mobs = ! game - > play . debug_disable_mobs ;
2023-09-20 13:35:38 +00:00
}
2023-10-15 11:48:53 +00:00
if ( 0 ) {
2023-10-29 03:59:53 +00:00
next_y + = TELY_RFui_ActiveFont ( rfui ) . size ;
2023-10-24 12:41:15 +00:00
TELY_RFuiResult bar = TELY_RFui_Column ( rfui , DQN_STR8 ( " Memory bar " ) ) ;
2023-10-14 14:31:33 +00:00
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 ) ; } ;
{
2023-10-29 03:59:53 +00:00
Dqn_ArenaInfo arena_info = Dqn_Arena_Info ( & os - > arena ) ;
2023-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 6271 ) // warning C6271: Extra argument passed to 'TELY_RFui_TextF'.
2023-10-14 14:31:33 +00:00
TELY_RFui_TextF ( rfui ,
" Platform Arena[%I64u]: %_$$d/%_$$d (HWM %_$$d, COMMIT %_$$d) " ,
2023-10-29 03:59:53 +00:00
os - > arena . blocks ,
2023-10-14 14:31:33 +00:00
arena_info . used ,
arena_info . capacity ,
arena_info . used_hwm ,
arena_info . commit ) ;
2023-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_POP
2023-10-29 03:59:53 +00:00
next_y + = TELY_RFui_ActiveFont ( rfui ) . size ;
2023-10-14 14:31:33 +00:00
}
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 )
2023-10-29 03:59:53 +00:00
next_y + = TELY_RFui_ActiveFont ( rfui ) . size ;
2023-10-14 14:31:33 +00:00
Dqn_Arena * arena = item - > arena ;
Dqn_ArenaInfo arena_info = Dqn_Arena_Info ( arena ) ;
2023-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE ( 6271 ) // warning C6271: Extra argument passed to 'TELY_RFui_TextF'.
2023-10-14 14:31:33 +00:00
TELY_RFui_TextF ( rfui ,
" %.*s[%I64u]: %_$$d/%_$$d (HWM %_$$d, COMMIT %_$$d) " ,
2023-10-24 12:41:15 +00:00
DQN_STR_FMT ( arena - > label ) ,
2023-10-14 14:31:33 +00:00
arena - > blocks ,
arena_info . used ,
arena_info . capacity ,
arena_info . used_hwm ,
arena_info . commit ) ;
2023-10-25 09:17:30 +00:00
DQN_MSVC_WARNING_POP
2023-10-14 14:31:33 +00:00
}
}
// NOTE: Profiler
2023-10-23 13:07:59 +00:00
if ( 0 ) {
2023-10-29 03:59:53 +00:00
next_y + = TELY_RFui_ActiveFont ( rfui ) . size ;
2023-10-24 12:41:15 +00:00
TELY_RFuiResult profiler_layout = TELY_RFui_Column ( rfui , DQN_STR8 ( " Profiler Bar " ) ) ;
2023-09-23 06:42:22 +00:00
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-15 08:04:58 +00:00
// TODO(doyle): On emscripten we need to use Dqn_OS_PerfCounterNow() however those
2023-10-29 03:59:53 +00:00
// require OS functions which need to be exposed into the os layer.
2023-10-15 08:04:58 +00:00
#if 0
2023-10-29 03:59:53 +00:00
Dqn_f64 const tsc_frequency = DQN_CAST ( Dqn_f64 ) TELY_OS_PerfCounterFrequency ( & os - > core ) ;
2023-10-15 08:04:58 +00:00
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 " ,
2023-10-24 12:41:15 +00:00
DQN_STR_FMT ( anchor - > name ) ,
2023-09-23 06:42:22 +00:00
anchor - > hit_count ,
tsc_exclusive_milliseconds ) ;
} else {
2023-10-24 12:41:15 +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 " ,
2023-10-24 12:41:15 +00:00
DQN_STR_FMT ( anchor - > name ) ,
2023-09-23 06:42:22 +00:00
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 02:22:08 +00:00
}
2023-10-29 03:59:53 +00:00
TELY_OS_DLL_FUNCTION
void TELY_OS_DLLFrameUpdate ( TELY_OS * os )
2023-10-08 02:22:08 +00:00
{
2023-12-01 05:46:58 +00:00
TELY_Input * input = & os - > input ;
2023-10-29 03:59:53 +00:00
TELY_Renderer * renderer = & os - > renderer ;
FP_Game * game = DQN_CAST ( FP_Game * ) os - > user_data ;
2023-10-08 02:22:08 +00:00
// =============================================================================================
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
// =============================================================================================
2023-10-29 06:30:08 +00:00
if ( game - > play . state = = FP_GameState_Play | | game - > play . state = = FP_GameState_Tutorial ) {
2023-12-01 05:46:58 +00:00
if ( TELY_Input_KeyWasDown ( input - > mouse_keys [ TELY_InputMouseKey_Left ] ) & & TELY_Input_KeyIsDown ( input - > mouse_keys [ TELY_InputMouseKey_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-25 09:17:30 +00:00
DQN_ASSERT ( it . 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-12-01 05:46:58 +00:00
if ( TELY_Input_KeyIsPressed ( input - > mouse_keys [ TELY_InputMouseKey_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-11-21 11:47:54 +00:00
TELY_Audio * audio = & os - > audio ;
2023-10-08 05:14:31 +00:00
for ( game - > play . delta_s_accumulator + = DQN_CAST ( Dqn_f32 ) input - > delta_s ;
2023-10-19 13:20:41 +00:00
game - > play . delta_s_accumulator > FP_GAME_PHYSICS_STEP ;
game - > play . delta_s_accumulator - = FP_GAME_PHYSICS_STEP ) {
2023-10-29 03:59:53 +00:00
FP_Update ( os , game , input , audio ) ;
2023-10-08 02:22:08 +00:00
}
2023-10-29 03:59:53 +00:00
FP_Render ( game , os , renderer , audio ) ;
2023-09-16 02:21:24 +00:00
}