fp: Add key binding support for p2

This commit is contained in:
doyle 2023-10-21 00:04:13 +11:00
parent 13af438612
commit 85fe5c8ff7
3 changed files with 314 additions and 194 deletions

2
External/tely vendored

@ -1 +1 @@
Subproject commit c1056434f07a1f2b12378fee6c5ed33941e2bfcb
Subproject commit e1f11120a2a3a2673317db52d73ae30a71faf172

View File

@ -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 <BTN> 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 <B>"); 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, "<Gamepad: Start> 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 <B> or <Gamepad: Start> 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);

View File

@ -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