From 85fe5c8ff7a947e7508184d5e6b731dce7ff8490 Mon Sep 17 00:00:00 2001 From: doyle Date: Sat, 21 Oct 2023 00:04:13 +1100 Subject: [PATCH] fp: Add key binding support for p2 --- External/tely | 2 +- feely_pona.cpp | 476 ++++++++++++++++++++++++++++------------------ feely_pona_game.h | 30 +-- 3 files changed, 314 insertions(+), 194 deletions(-) diff --git a/External/tely b/External/tely index c105643..e1f1112 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit c1056434f07a1f2b12378fee6c5ed33941e2bfcb +Subproject commit e1f11120a2a3a2673317db52d73ae30a71faf172 diff --git a/feely_pona.cpp b/feely_pona.cpp index af2de43..8367e19 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -89,6 +89,83 @@ static TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, return result; } +static void FP_SetDefaultGamepadBindings(FP_GameControls *controls) +{ + controls->up.gamepad_key = TELY_PlatformInputGamepadKey_DUp; + controls->down.gamepad_key = TELY_PlatformInputGamepadKey_DDown; + controls->left.gamepad_key = TELY_PlatformInputGamepadKey_DLeft; + controls->right.gamepad_key = TELY_PlatformInputGamepadKey_DRight; + controls->attack.gamepad_key = TELY_PlatformInputGamepadKey_X; + controls->range_attack.gamepad_key = TELY_PlatformInputGamepadKey_Y; + controls->build_mode.gamepad_key = TELY_PlatformInputGamepadKey_L3; + controls->strafe.gamepad_key = TELY_PlatformInputGamepadKey_B; + controls->dash.gamepad_key = TELY_PlatformInputGamepadKey_A; + controls->buy_building.gamepad_key = TELY_PlatformInputGamepadKey_LeftBumper; + controls->buy_upgrade.gamepad_key = TELY_PlatformInputGamepadKey_RightBumper; +} + +static bool FP_ListenForNewPlayer(TELY_PlatformInput *input, FP_Game *game) +{ + bool result = false; + if (game->play.players.size == 2) + return result; + + FP_GamePlay *play = &game->play; + uint32_t gamepad_index = 0; + if (play->players.size) { + FP_GameEntityHandle first_player_handle = play->players.data[0]; + FP_GameEntity *player = FP_Game_GetEntity(game, first_player_handle); + if (player->controls.mode == FP_GameControlMode_Gamepad) + gamepad_index = 1; + } + + bool keyboard_pressed = TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_B); + bool gamepad_pressed = TELY_Platform_InputGamepadKeyIsPressed(input, gamepad_index, TELY_PlatformInputGamepadKey_Start); + + if (keyboard_pressed || gamepad_pressed) { + FP_GameEntityHandle terry_handle = FP_Entity_CreateTerry(game, Dqn_V2_InitNx2(1380, 11), "Terry"); + Dqn_FArray_Add(&play->players, terry_handle); + + FP_GameEntity *terry = FP_Game_GetEntity(game, terry_handle); + FP_GameControls *controls = &terry->controls; + if (keyboard_pressed) { + controls->mode = FP_GameControlMode_Keyboard; + if (play->players.size == 1) { + controls->up.scan_code = TELY_PlatformInputScanCode_W; + controls->down.scan_code = TELY_PlatformInputScanCode_S; + controls->left.scan_code = TELY_PlatformInputScanCode_A; + controls->right.scan_code = TELY_PlatformInputScanCode_D; + controls->attack.scan_code = TELY_PlatformInputScanCode_G; + controls->range_attack.scan_code = TELY_PlatformInputScanCode_H; + controls->build_mode.scan_code = TELY_PlatformInputScanCode_F; + controls->strafe.scan_code = TELY_PlatformInputScanCode_J; + controls->dash.scan_code = TELY_PlatformInputScanCode_V; + controls->buy_building.scan_code = TELY_PlatformInputScanCode_T; + controls->buy_upgrade.scan_code = TELY_PlatformInputScanCode_Y; + } else { + controls->up.scan_code = TELY_PlatformInputScanCode_O; + controls->down.scan_code = TELY_PlatformInputScanCode_L; + controls->left.scan_code = TELY_PlatformInputScanCode_K; + controls->right.scan_code = TELY_PlatformInputScanCode_Semicolon; + controls->attack.scan_code = TELY_PlatformInputScanCode_Right; + controls->range_attack.scan_code = TELY_PlatformInputScanCode_Left; + controls->build_mode.scan_code = TELY_PlatformInputScanCode_Up; + controls->strafe.scan_code = TELY_PlatformInputScanCode_N; + controls->dash.scan_code = TELY_PlatformInputScanCode_Down; + controls->buy_building.scan_code = TELY_PlatformInputScanCode_Backslash; + controls->buy_upgrade.scan_code = TELY_PlatformInputScanCode_Apostrophe; + } + } else { + controls->mode = FP_GameControlMode_Gamepad; + controls->gamepad_index = gamepad_index; + FP_SetDefaultGamepadBindings(controls); + } + + result = true; + } + return result; +} + static void FP_PlayReset(FP_Game *game, TELY_Platform *platform) { FP_GamePlay *play = &game->play; @@ -240,47 +317,6 @@ static void FP_PlayReset(FP_Game *game, TELY_Platform *platform) Dqn_FArray_Add(&play->portal_monkeys, monkey_c); } - // NOTE: Hero ================================================================================== - { - FP_GameEntityHandle terry_handle = FP_Entity_CreateTerry(game, Dqn_V2_InitNx2(1434, 11), "Terry"); - Dqn_FArray_Add(&game->play.players, terry_handle); - play->clicked_entity = terry_handle; - - FP_GameEntity *terry = FP_Game_GetEntity(game, terry_handle); - FP_GameControls *controls = &terry->controls; - controls->up.scan_code = TELY_PlatformInputScanCode_W; - controls->down.scan_code = TELY_PlatformInputScanCode_S; - controls->left.scan_code = TELY_PlatformInputScanCode_A; - controls->right.scan_code = TELY_PlatformInputScanCode_D; - controls->attack.scan_code = TELY_PlatformInputScanCode_J; - controls->range_attack.scan_code = TELY_PlatformInputScanCode_K; - controls->build_mode.scan_code = TELY_PlatformInputScanCode_H; - controls->strafe.scan_code = TELY_PlatformInputScanCode_L; - controls->dash.scan_code = TELY_PlatformInputScanCode_N; - controls->buy_building.scan_code = TELY_PlatformInputScanCode_U; - controls->buy_upgrade.scan_code = TELY_PlatformInputScanCode_I; - } - - { - FP_GameEntityHandle terry_handle = FP_Entity_CreateTerry(game, Dqn_V2_InitNx2(1380, 11), "Terry 2"); - Dqn_FArray_Add(&game->play.players, terry_handle); - play->clicked_entity = terry_handle; - - FP_GameEntity *terry = FP_Game_GetEntity(game, terry_handle); - FP_GameControls *controls = &terry->controls; - controls->up.scan_code = TELY_PlatformInputScanCode_Up; - controls->down.scan_code = TELY_PlatformInputScanCode_Down; - controls->left.scan_code = TELY_PlatformInputScanCode_Left; - controls->right.scan_code = TELY_PlatformInputScanCode_Right; - controls->attack.scan_code = TELY_PlatformInputScanCode_J; - controls->range_attack.scan_code = TELY_PlatformInputScanCode_K; - controls->build_mode.scan_code = TELY_PlatformInputScanCode_H; - controls->strafe.scan_code = TELY_PlatformInputScanCode_L; - controls->dash.scan_code = TELY_PlatformInputScanCode_N; - controls->buy_building.scan_code = TELY_PlatformInputScanCode_U; - controls->buy_upgrade.scan_code = TELY_PlatformInputScanCode_I; - } - { Dqn_V2 base_top_left_pos = Dqn_V2_InitNx2(1018, -335); Dqn_V2 base_bottom_right_pos = Dqn_V2_InitNx2(2050, +351); @@ -434,6 +470,28 @@ static void FP_AppendMobSpawnerWaypoints(FP_Game *game, FP_GameEntityHandle src_ } } +bool FP_Game_KeyBindIsPressed(TELY_PlatformInput const *input, FP_GameControls const *controls, FP_GameKeyBind key_bind) +{ + bool result = false; + if (controls->mode == FP_GameControlMode_Keyboard) { + result = TELY_Platform_InputScanCodeIsPressed(input, key_bind.scan_code); + } else { + result = TELY_Platform_InputGamepadKeyIsPressed(input, controls->gamepad_index, key_bind.gamepad_key); + } + return result; +} + +bool FP_Game_KeyBindIsDown(TELY_PlatformInput const *input, FP_GameControls const *controls, FP_GameKeyBind key_bind) +{ + bool result = false; + if (controls->mode == FP_GameControlMode_Keyboard) { + result = TELY_Platform_InputScanCodeIsDown(input, key_bind.scan_code); + } else { + result = TELY_Platform_InputGamepadKeyIsDown(input, controls->gamepad_index, key_bind.gamepad_key); + } + return result; +} + void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 *acceleration_meters_per_s) { @@ -488,11 +546,9 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { + if (FP_Game_KeyBindIsPressed(input, controls, controls->attack)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, controls->range_attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_Y)) { + } else if (FP_Game_KeyBindIsPressed(input, controls, controls->range_attack)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } } else { @@ -553,23 +609,19 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { + if (FP_Game_KeyBindIsPressed(input, controls, controls->attack)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, controls->range_attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_Y)) { + } else if (FP_Game_KeyBindIsPressed(input, controls, controls->range_attack)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } } } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->dash.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_A)) { + if (FP_Game_KeyBindIsPressed(input, controls, controls->dash)) FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash); - } } else { - if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { + if (!picked_up_monkey_this_frame && FP_Game_KeyBindIsPressed(input, controls, controls->attack)) { FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey); portal_monkey->local_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); entity->carried_monkey = {}; @@ -647,13 +699,10 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } - if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { - 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); - } + 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); } if (entity_has_velocity) { @@ -742,8 +791,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { + if (FP_Game_KeyBindIsPressed(input, controls, controls->attack)) { FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack); } } @@ -768,13 +816,10 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } - if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { - 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); - } + 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); } if (entity_has_velocity) { @@ -810,12 +855,8 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } - if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack); - } - } + if (FP_Game_KeyBindIsPressed(input, controls, controls->attack)) + FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack); if (!entity_has_velocity) { FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Idle); @@ -1021,13 +1062,10 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } - if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack); - } else if (acceleration_meters_per_s->x || acceleration_meters_per_s->y) { - FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Run); - } + if (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); } if (entity_has_velocity) { @@ -1062,12 +1100,8 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform FP_Game_EntityActionReset(game, entity->handle, duration_ms, render_data.sprite); } - if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || - TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack); - } - } + if (FP_Game_KeyBindIsPressed(input, controls, controls->attack)) + FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack); if (!entity_has_velocity) { FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Idle); @@ -1275,13 +1309,13 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input FP_GameControls const *controls = &entity->controls; Dqn_V2 dir_vector = {}; { - if (TELY_Platform_InputScanCodeIsDown(input, controls->up.scan_code)) + if (FP_Game_KeyBindIsDown(input, controls, controls->up)) dir_vector.y = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, controls->left.scan_code)) + if (FP_Game_KeyBindIsDown(input, controls, controls->left)) dir_vector.x = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, controls->down.scan_code)) + if (FP_Game_KeyBindIsDown(input, controls, controls->down)) dir_vector.y = +1.f; - if (TELY_Platform_InputScanCodeIsDown(input, controls->right.scan_code)) + if (FP_Game_KeyBindIsDown(input, controls, controls->right)) dir_vector.x = +1.f; // NOTE: Gamepad movement input @@ -1763,7 +1797,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input // NOTE: Toggle menu =============================================================== if (entity->in_game_menu == FP_GameInGameMenu_Nil || entity->in_game_menu == FP_GameInGameMenu_Build) { - if (TELY_Platform_InputScanCodeIsPressed(input, controls->build_mode.scan_code)) + if (FP_Game_KeyBindIsPressed(input, controls, controls->build_mode)) entity->in_game_menu = DQN_CAST(FP_GameInGameMenu)(DQN_CAST(uint32_t)entity->in_game_menu ^ FP_GameInGameMenu_Build); } @@ -1839,8 +1873,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input entity->build_mode_can_place_building |= Dqn_Rect_ContainsPoint(zone_hit_box, placement_pos); } - if (entity->build_mode_can_place_building && - TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { + if (entity->build_mode_can_place_building && FP_Game_KeyBindIsPressed(input, controls, controls->attack)) { if (placeable_building.type == FP_EntityType_ClubTerry) { FP_Entity_CreateClubTerry(game, placement_pos, "Club Terry"); TELY_Audio_Play(audio, game->audio[FP_GameAudio_Club], 1.f); @@ -1873,7 +1906,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input acceleration_meters_per_s *= 1.f; // TODO(doyle): Penalise the player } - if (!TELY_Platform_InputScanCodeIsDown(input, controls->strafe.scan_code)) { + if (!FP_Game_KeyBindIsPressed(input, controls, controls->strafe)) { 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) @@ -1894,8 +1927,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input if (entity->handle != player) continue; - Dqn_V2 new_entity_pos = entity_pos; - Dqn_Rect camera_view_rect = Dqn_Rect_InitV2x2(camera->world_pos_target - (camera->size * .5f) * camera->scale, camera->size * camera->scale); + Dqn_V2 new_entity_pos = entity_pos; + Dqn_Rect camera_view_rect = Dqn_Rect_InitV2x2((camera->size * -.5f) + (camera->world_pos / camera->scale), camera->size); Dqn_Rect entity_hit_box = FP_Game_CalcEntityWorldHitBox(game, entity_handle); Dqn_Rect playable_bounds = Dqn_Rect_ExpandV2(camera_view_rect, -entity_hit_box.size); @@ -2167,6 +2200,33 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input Dqn_Profiler_EndZone(update_zone); } +static Dqn_String8 FP_ScanCodeToLabel(Dqn_Arena *arena, TELY_PlatformInputScanCode scan_code) +{ + Dqn_String8 result = {}; + Dqn_Allocator allocator = Dqn_Arena_Allocator(arena); + if (scan_code >= TELY_PlatformInputScanCode_A && scan_code <= TELY_PlatformInputScanCode_Z) { + char scan_code_ch = DQN_CAST(char)('A' + (scan_code - TELY_PlatformInputScanCode_A)); + result = Dqn_String8_InitF(allocator, "[%c]", scan_code_ch); + } else { + if (scan_code == TELY_PlatformInputScanCode_Up) { + result = Dqn_String8_InitF(allocator, "[Up]"); + } else if (scan_code == TELY_PlatformInputScanCode_Down) { + result = Dqn_String8_InitF(allocator, "[Down]"); + } else if (scan_code == TELY_PlatformInputScanCode_Left) { + result = Dqn_String8_InitF(allocator, "[Left]"); + } else if (scan_code == TELY_PlatformInputScanCode_Right) { + result = Dqn_String8_InitF(allocator, "[Right]"); + } else if (scan_code == TELY_PlatformInputScanCode_Semicolon) { + result = Dqn_String8_InitF(allocator, "[;]"); + } else if (scan_code == TELY_PlatformInputScanCode_Apostrophe) { + result = Dqn_String8_InitF(allocator, "[']"); + } else if (scan_code == TELY_PlatformInputScanCode_Backslash) { + result = Dqn_String8_InitF(allocator, "[/]"); + } + } + return result; +} + void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, TELY_Audio *audio) { Dqn_Profiler_ZoneScopeWithIndex("FP_Render", FP_ProfileZone_FPRender); @@ -2523,24 +2583,26 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, } if (entity->type == FP_EntityType_Billboard) { - for (FP_GameEntityHandle player_handle : game->play.players) { - FP_GameEntity const *player = FP_Game_GetEntity(game, player_handle); - FP_GameControls const *controls = &player->controls; + 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; + + FP_EntityBillboardState state = DQN_CAST(FP_EntityBillboardState) entity->action.state; + FP_GameKeyBind const *key_bind = nullptr; + Dqn_V2 draw_p = {}; + Dqn_V4 colour = TELY_COLOUR_BLACK_V4; - FP_EntityBillboardState state = DQN_CAST(FP_EntityBillboardState) entity->action.state; - FP_GameKeyBind const *key_bind = nullptr; - Dqn_V2 draw_p = {}; - Dqn_V4 colour = TELY_COLOUR_BLACK_V4; switch (state) { case FP_EntityBillboardState_Attack: { key_bind = &controls->attack; - draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.65f, 0.2f)); + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.6f, 0.2f)); colour = colour_accent_yellow; } break; case FP_EntityBillboardState_Dash: { key_bind = &controls->dash; - draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.62f, -0.07f)); + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.505f, -0.08f)); colour = TELY_Colour_V4InitRGBU32(0xFFE726); } break; @@ -2549,28 +2611,61 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, case FP_EntityBillboardState_RangeAttack: { key_bind = &controls->range_attack; - draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.33f, -0.2f)); + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.20f, -0.13f)); colour = TELY_Colour_V4InitRGBU32(0x364659); } break; case FP_EntityBillboardState_Strafe: { key_bind = &controls->dash; - draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.52f, -0.15f)); + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.36f, -0.15f)); colour = TELY_Colour_V4InitRGBU32(0xFF68A8); } break; } + draw_p.y += TELY_Render_FontHeight(renderer, assets) * player_index; if (key_bind) { TELY_Render_PushColourV4(renderer, colour); - TELY_Render_PushFont(renderer, game->talkco_font_xlarge); + TELY_Render_PushFont(renderer, game->talkco_font); DQN_DEFER { TELY_Render_PopFont(renderer); TELY_Render_PopColourV4(renderer); }; - if (key_bind->scan_code >= TELY_PlatformInputScanCode_A && key_bind->scan_code <= TELY_PlatformInputScanCode_Z) { - char scan_code_ch = DQN_CAST(char)('A' + (key_bind->scan_code - TELY_PlatformInputScanCode_A)); - TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx1(0.5f), "[%c]", scan_code_ch); + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 player_prefix = Dqn_String8_InitF(scratch.allocator, "P%zu", player_index); + if (controls->mode == FP_GameControlMode_Gamepad) { + Dqn_String8 tex_name = {}; + if (key_bind->gamepad_key == TELY_PlatformInputGamepadKey_A) + tex_name = g_anim_names.merchant_button_a; + else if (key_bind->gamepad_key == TELY_PlatformInputGamepadKey_B) + tex_name = g_anim_names.merchant_button_b; + else if (key_bind->gamepad_key == TELY_PlatformInputGamepadKey_X) + tex_name = g_anim_names.merchant_button_x; + else if (key_bind->gamepad_key == TELY_PlatformInputGamepadKey_Y) + 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]; + + Dqn_V2 text_size = TELY_Asset_MeasureText(TELY_Render_Font(renderer, assets), player_prefix); + TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0, +1.f), "P%zu", player_index); + + 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 { + Dqn_String8 key_bind_label = FP_ScanCodeToLabel(scratch.arena, key_bind->scan_code); + TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx2(0, +1.f), "%.*s %.*s", DQN_STRING_FMT(player_prefix), DQN_STRING_FMT(key_bind_label)); } } } @@ -2616,6 +2711,31 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, } } + Dqn_V2 const player_avatar_base_pos[] = { + Dqn_V2_InitNx1(32.f), + Dqn_V2_InitNx2(platform->core.window_size.x - 320.f, 32.f), + }; + 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"); + if (game->play.players.size == 1) { + // NOTE: We show the Press to join for the remaining 2nd player + TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity()); + TELY_Render_PushFont(renderer, game->talkco_font); + DQN_DEFER { + TELY_Render_PopFont(renderer); + TELY_Render_PopTransform(renderer); + }; + + Dqn_f32 font_height = TELY_Render_FontHeight(renderer, assets); + Dqn_V2 base_p = player_avatar_base_pos[game->play.players.size]; + TELY_Render_TextF(renderer, base_p, Dqn_V2_Zero, "Press "); base_p.y += font_height; + TELY_Render_TextF(renderer, base_p, Dqn_V2_Zero, "or"); base_p.y += font_height; + TELY_Render_TextF(renderer, base_p, Dqn_V2_Zero, " to join!"); base_p.y += font_height; + FP_ListenForNewPlayer(input, game); + } + + // NOTE: Render the player(s) HUD and merchant menu interaction ============================ Dqn_Rect first_player_avatar_rect = {}; DQN_FOR_UINDEX (player_index, game->play.players.size) { FP_GameEntityHandle player_handle = game->play.players.data[player_index]; @@ -2679,6 +2799,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, 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. + DQN_ASSERTF(player_index <= 2, "We only handle 2 players gracefully"); if (player_index == 0) *mapping.menu_pos = top_rect.pos; else @@ -2732,8 +2853,8 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, uint64_t const buy_duration_ms = 500; Dqn_V4 keybind_btn_shadow_colour = Dqn_V4_InitNx4(0.4f, 0.4f, 0.4f, 1.f); { - bool const have_enough_coins = player->coins >= *mapping.building_base_price; - FP_GameKeyBind key_bind = player->controls.buy_building; + bool const have_enough_coins = player->coins >= *mapping.building_base_price; + FP_GameKeyBind key_bind = player->controls.buy_building; // NOTE: Buy trigger + animation =============================== { bool trigger_buy_anim = false; @@ -2784,44 +2905,41 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, // NOTE: Render the (A) button ================================= Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha(TELY_COLOUR_RED_TOMATO_V4, .5f); { - // NOTE: Render the gamepad button - Dqn_Rect gamepad_btn_rect = {}; - { + // 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]; - gamepad_btn_rect.size = button_rect.size * 1.5f; - gamepad_btn_rect.pos = Dqn_Rect_InterpolatedPoint(merchant_menu_rect, Dqn_V2_InitNx2(0.345f, 0.41f)); + 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, - gamepad_btn_rect, + key_bind_rect, Dqn_V2_Zero /*rotate origin*/, 0.f /*rotation*/, tex_mod_colour); - } + } else { + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + TELY_AssetFont const *font = TELY_Render_Font(renderer, assets); + Dqn_String8 key_bind_label = FP_ScanCodeToLabel(scratch.arena, key_bind.scan_code); + interact_btn_rect.size = TELY_Asset_MeasureText(font, key_bind_label); - // NOTE: Render the $ cost - TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(gamepad_btn_rect, Dqn_V2_InitNx2(0.5f, -0.8f)), Dqn_V2_InitNx2(0.5, 0.f), "$%u", *mapping.building_base_price); - - // NOTE: Render the keyboard binding ======================================= - { - DQN_ASSERT(key_bind.scan_code >= TELY_PlatformInputScanCode_A && key_bind.scan_code <= TELY_PlatformInputScanCode_Z); - char scan_code_ch = DQN_CAST(char)('A' + (key_bind.scan_code - TELY_PlatformInputScanCode_A)); - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - TELY_AssetFont const *font = TELY_Render_Font(renderer, assets); - Dqn_String8 key_bind_label = Dqn_String8_InitF(scratch.allocator, "[%c]", scan_code_ch); - - Dqn_Rect key_bind_rect = {}; - key_bind_rect.size = TELY_Asset_MeasureText(font, key_bind_label); - key_bind_rect.pos = Dqn_Rect_InterpolatedPoint(gamepad_btn_rect, Dqn_V2_InitNx2(-0.1f, 1.1f)); - key_bind_rect = Dqn_Rect_Expand(key_bind_rect, 2.f); + Dqn_Rect key_bind_rect = interact_btn_rect; + key_bind_rect.pos.y += interact_btn_rect.size.y * .3f; + 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_RectColourV4(renderer, key_bind_rect, TELY_RenderShapeMode_Fill, TELY_Colour_V4Alpha(keybind_btn_shadow_colour, 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 + TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(interact_btn_rect, Dqn_V2_InitNx2(0.5f, -0.8f)), Dqn_V2_InitNx2(0.5, 0.f), "$%u", *mapping.building_base_price); } // NOTE: Render the merchant shop item building ================ @@ -2830,7 +2948,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, 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.45f, 0.25f) + mapping.building_offset01); + 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, @@ -2895,32 +3013,47 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, // NOTE: Render the (B) button ================================= Dqn_V4 tex_mod_colour = have_enough_coins ? TELY_COLOUR_WHITE_V4 : TELY_Colour_V4Alpha(TELY_COLOUR_RED_TOMATO_V4, .5f); { - Dqn_V2 interp_pos01 = {}; - Dqn_Rect gamepad_btn_rect = {}; - // NOTE: Render the gamepad button - { + Dqn_V2 interp_pos01 = Dqn_V2_InitNx2(0.7f, 0.41f); + Dqn_Rect interact_btn_rect = {}; + interact_btn_rect.pos = Dqn_Rect_InterpolatedPoint(merchant_menu_rect, interp_pos01); + // NOTE: Render the interact button ==================================== + if (player->controls.mode == FP_GameControlMode_Gamepad) { TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.merchant_button_b); Dqn_Rect button_rect = game->atlas_sprite_sheet.rects.data[anim->index]; - interp_pos01 = Dqn_V2_InitNx2(0.71f, 0.41f); - gamepad_btn_rect.size = button_rect.size * 1.5f; - gamepad_btn_rect.pos = Dqn_Rect_InterpolatedPoint(merchant_menu_rect, interp_pos01); + 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, - gamepad_btn_rect, + key_bind_rect, Dqn_V2_Zero /*rotate origin*/, 0.f /*rotation*/, tex_mod_colour); + } else { + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + TELY_AssetFont const *font = TELY_Render_Font(renderer, assets); + Dqn_String8 key_bind_label = FP_ScanCodeToLabel(scratch.arena, key_bind.scan_code); + interact_btn_rect.size = TELY_Asset_MeasureText(font, key_bind_label); + Dqn_Rect key_bind_rect = interact_btn_rect; + key_bind_rect.pos.y += interact_btn_rect.size.y * .3f; + + 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 building + // 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.12f, 0.15f)) - (dest_rect.size * .5f); + 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, @@ -2931,27 +3064,8 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, } // NOTE: Render the $ cost - TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(gamepad_btn_rect, Dqn_V2_InitNx2(1.f, -0.8f)), Dqn_V2_InitNx2(0.5, 0.f), "$%u", *mapping.upgrade_base_price); + TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(interact_btn_rect, Dqn_V2_InitNx2(1.f, -0.8f)), Dqn_V2_InitNx2(0.5, 0.f), "$%u", *mapping.upgrade_base_price); - // NOTE: Render the keyboard binding ======================================= - { - DQN_ASSERT(key_bind.scan_code >= TELY_PlatformInputScanCode_A && key_bind.scan_code <= TELY_PlatformInputScanCode_Z); - char scan_code_ch = DQN_CAST(char)('A' + (key_bind.scan_code - TELY_PlatformInputScanCode_A)); - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - TELY_AssetFont const *font = TELY_Render_Font(renderer, assets); - Dqn_String8 key_bind_label = Dqn_String8_InitF(scratch.allocator, "[%c]", scan_code_ch); - - Dqn_Rect key_bind_rect = {}; - key_bind_rect.pos = Dqn_Rect_InterpolatedPoint(gamepad_btn_rect, Dqn_V2_InitNx2(-0.1f, 1.1f)); - key_bind_rect.size = TELY_Asset_MeasureText(font, key_bind_label); - key_bind_rect = Dqn_Rect_Expand(key_bind_rect, 2.f); - - TELY_Render_PushColourV4(renderer, TELY_Colour_V4Alpha(colour_accent_yellow, tex_mod_colour.a)); - TELY_Render_RectColourV4(renderer, key_bind_rect, TELY_RenderShapeMode_Fill, TELY_Colour_V4Alpha(keybind_btn_shadow_colour, 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); - } } } } @@ -2967,12 +3081,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, // NOTE: Render player avatar HUD ========================================================== Dqn_Rect player_avatar_rect = {}; - if (player_index) { - player_avatar_rect.pos = Dqn_V2_InitNx2(platform->core.window_size.x - 320.f, 32.f); - } else { - player_avatar_rect.pos = Dqn_V2_InitNx1(32.f); - } - + player_avatar_rect.pos = player_avatar_base_pos[player_index]; Dqn_V2 next_pos = {}; { TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity()); @@ -3340,9 +3449,12 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, } } + // NOTE: Debug show camera bounds + #if 0 FP_GameCamera *camera = &game->play.camera; - Dqn_Rect camera_view_rect = Dqn_Rect_InitV2x2(camera->world_pos - (camera->size * .5f) * camera->scale, camera->size * camera->scale); + Dqn_Rect camera_view_rect = Dqn_Rect_InitV2x2((camera->size * -.5f) + (camera->world_pos / camera->scale), camera->size); TELY_Render_RectColourV4(renderer, Dqn_Rect_Expand(camera_view_rect, -5.f), TELY_RenderShapeMode_Line, TELY_COLOUR_RED_V4); + #endif // NOTE: Add scanlines into the game for A E S T H E T I C S =================================== @@ -3469,15 +3581,15 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, TELY_Render_PushFont(renderer, game->inter_regular_font); 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)); - TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.925f)), Dqn_V2_InitNx1(0.5f), "Press enter to %s", game->play.state == FP_GameState_IntroScreen ? "start" : "restart"); + TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.925f)), Dqn_V2_InitNx1(0.5f), "Press or to %s", game->play.state == FP_GameState_IntroScreen ? "start" : "restart"); + TELY_Render_PopColourV4(renderer); TELY_Render_PopFont(renderer); - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Return)) { - if (game->play.state == FP_GameState_LoseGame) - FP_PlayReset(game, platform); + if (game->play.state == FP_GameState_LoseGame) + FP_PlayReset(game, platform); + if (FP_ListenForNewPlayer(input, game)) game->play.state = FP_GameState_Play; - } } else if (game->play.state == FP_GameState_Pause) { TELY_Render_PushFont(renderer, game->inter_regular_font_large); diff --git a/feely_pona_game.h b/feely_pona_game.h index a031e34..6c5faf6 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -171,19 +171,27 @@ struct FP_GameKeyBind TELY_PlatformInputGamepadKey gamepad_key; }; +enum FP_GameControlMode +{ + FP_GameControlMode_Keyboard, + FP_GameControlMode_Gamepad, +}; + struct FP_GameControls { - FP_GameKeyBind up; - FP_GameKeyBind down; - FP_GameKeyBind left; - FP_GameKeyBind right; - FP_GameKeyBind attack; - FP_GameKeyBind buy_building; - FP_GameKeyBind buy_upgrade; - FP_GameKeyBind range_attack; - FP_GameKeyBind build_mode; - FP_GameKeyBind strafe; - FP_GameKeyBind dash; + FP_GameControlMode mode; + uint32_t gamepad_index; + FP_GameKeyBind up; + FP_GameKeyBind down; + FP_GameKeyBind left; + FP_GameKeyBind right; + FP_GameKeyBind attack; + FP_GameKeyBind buy_building; + FP_GameKeyBind buy_upgrade; + FP_GameKeyBind range_attack; + FP_GameKeyBind build_mode; + FP_GameKeyBind strafe; + FP_GameKeyBind dash; }; struct FP_GameEntity