fp: Add monkeys and carrying to portal

This commit is contained in:
doyle 2023-10-08 01:29:50 +11:00
parent 7e1bcee5df
commit 631579d29b
32 changed files with 393 additions and 89 deletions

BIN
Data/Textures/atlas.png (Stored with Git LFS)

Binary file not shown.

BIN
Data/Textures/atlas.txt (Stored with Git LFS)

Binary file not shown.

BIN
Data/Textures/atlas/particle_church_cross.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_church_halo.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_drunk_bottle.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_drunk_martini.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_hit_1_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_hit_2_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/particle_hit_3_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_10.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_11.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_12.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_13.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_14.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_2.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_4.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_6.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_7.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_8.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_break_9.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Data/Textures/atlas/portal_monk_1.png (Stored with Git LFS)

Binary file not shown.

BIN
Data/Textures/atlas/portal_monk_2.png (Stored with Git LFS)

Binary file not shown.

BIN
Data/Textures/sprite_spec.txt (Stored with Git LFS)

Binary file not shown.

View File

@ -424,40 +424,10 @@ void TELY_DLL_Init(void *user_data)
} }
} }
// NOTE: Hero ==================================================================================
{
FP_GameEntityHandle terry = FP_Entity_CreateTerry(game, Dqn_V2_InitNx2(1434, 11), "Terry");
game->clicked_entity = terry;
game->player = terry;
}
{
Dqn_V2 base_top_left_pos = Dqn_V2_InitNx2(1018, -335);
Dqn_V2 base_bottom_right_pos = Dqn_V2_InitNx2(2050, +351);
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);
game->merchant_terry = FP_Entity_CreateMerchantTerry(game, base_top_left, "Merchant");
game->merchant_graveyard = FP_Entity_CreateMerchantGraveyard(game, base_bottom_left, "Graveyard");
game->merchant_gym = FP_Entity_CreateMerchantGym(game, base_bottom_right, "Gym");
game->merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany(game, base_top_right, "PhoneCompany");
}
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");
game->tile_size = 37;
Dqn_V2I max_tile = platform->core.window_size / game->tile_size;
// NOTE: Mid lane mob spawner ==================================================================
Dqn_V2 base_mid_p = Dqn_V2_InitNx2(1580, 0.f); Dqn_V2 base_mid_p = Dqn_V2_InitNx2(1580, 0.f);
Dqn_V2 mid_lane_mob_spawner_pos = Dqn_V2_InitNx2(game->map->local_hit_box_size.w * -0.5f, 0.f); {
// NOTE: Mid lane mob spawner ==================================================================
Dqn_V2 mid_lane_mob_spawner_pos = Dqn_V2_InitNx2(game->map->local_hit_box_size.w * -0.5f + 128.f, 0.f);
Dqn_usize spawn_cap = 16; Dqn_usize spawn_cap = 16;
{ {
FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, mid_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, mid_lane_mob_spawner_pos, spawn_cap, "Mob spawner");
@ -490,10 +460,53 @@ void TELY_DLL_Init(void *user_data)
Dqn_FArray_Add(&game->mob_spawners, mob_spawner); Dqn_FArray_Add(&game->mob_spawners, mob_spawner);
} }
#endif #endif
}
// NOTE: Monkey ============================================================
{
Dqn_V2 monkey_base_p = Dqn_V2_InitNx2(base_mid_p.x + 400.f, base_mid_p.y + 16.f);
FP_GameEntityHandle monkey_a = FP_Entity_CreatePortalMonkey(game, Dqn_V2_InitNx2(monkey_base_p.x, monkey_base_p.y), "Monkey A");
FP_GameEntityHandle monkey_b = FP_Entity_CreatePortalMonkey(game, Dqn_V2_InitNx2(monkey_base_p.x, monkey_base_p.y + 100.f), "Monkey B");
FP_GameEntityHandle monkey_c = FP_Entity_CreatePortalMonkey(game, Dqn_V2_InitNx2(monkey_base_p.x, monkey_base_p.y - 100.f), "Monkey C");
Dqn_FArray_Add(&game->portal_monkeys, monkey_a);
Dqn_FArray_Add(&game->portal_monkeys, monkey_b);
Dqn_FArray_Add(&game->portal_monkeys, monkey_c);
}
// NOTE: Hero ==================================================================================
{
FP_GameEntityHandle terry = FP_Entity_CreateTerry(game, Dqn_V2_InitNx2(1434, 11), "Terry");
game->clicked_entity = terry;
game->player = terry;
}
{
Dqn_V2 base_top_left_pos = Dqn_V2_InitNx2(1018, -335);
Dqn_V2 base_bottom_right_pos = Dqn_V2_InitNx2(2050, +351);
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);
game->merchant_terry = FP_Entity_CreateMerchantTerry(game, base_top_left, "Merchant");
game->merchant_graveyard = FP_Entity_CreateMerchantGraveyard(game, base_bottom_left, "Graveyard");
game->merchant_gym = FP_Entity_CreateMerchantGym(game, base_bottom_right, "Gym");
game->merchant_phone_company = FP_Entity_CreateMerchantPhoneCompany(game, base_top_right, "PhoneCompany");
}
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");
game->tile_size = 37;
Dqn_V2I max_tile = platform->core.window_size / game->tile_size;
// NOTE: Heart
FP_Entity_CreateHeart(game, base_mid_p, "Heart"); FP_Entity_CreateHeart(game, base_mid_p, "Heart");
uint16_t font_size = 18; uint16_t font_size = 18;
game->camera.scale = Dqn_V2_InitNx1(1); game->camera.scale = Dqn_V2_InitNx1(1);
game->inter_regular_font = platform->func_load_font(assets, DQN_STRING8("Inter (Regular)"), DQN_STRING8("Data/Inter-Regular.otf"), font_size); game->inter_regular_font = platform->func_load_font(assets, DQN_STRING8("Inter (Regular)"), DQN_STRING8("Data/Inter-Regular.otf"), font_size);
@ -506,6 +519,36 @@ void TELY_DLL_Init(void *user_data)
game->audio[FP_GameAudio_Smooch] = platform->func_load_audio(assets, DQN_STRING8("Smooch"), DQN_STRING8("Data/Audio/smooch.mp3")); game->audio[FP_GameAudio_Smooch] = platform->func_load_audio(assets, DQN_STRING8("Smooch"), DQN_STRING8("Data/Audio/smooch.mp3"));
} }
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;
FP_GameEntityHandle best_portal_monkey = {};
for (FP_GameEntityHandle portal_monkey_handle : game->portal_monkeys) {
FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, portal_monkey_handle);
if (FP_Game_IsNilEntity(portal_monkey))
continue;
if (portal_monkey->action.state == FP_EntityPortalMonkeyState_DisablingPortal)
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;
}
void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 *acceleration_meters_per_s) void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_PlatformInput *input, FP_GameEntity *entity, Dqn_V2 *acceleration_meters_per_s)
{ {
TELY_AssetSpriteSheet *sheet = &game->atlas_sprite_sheet; TELY_AssetSpriteSheet *sheet = &game->atlas_sprite_sheet;
@ -529,6 +572,16 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
case FP_EntityType_Terry: { case FP_EntityType_Terry: {
FP_EntityTerryState *state = DQN_CAST(FP_EntityTerryState *) & action->state; FP_EntityTerryState *state = DQN_CAST(FP_EntityTerryState *) & action->state;
{
FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey);
if (!FP_Game_IsNilEntity(portal_monkey)) {
Dqn_Rect hit_box = FP_Game_CalcEntityWorldHitBox(game, entity->handle);
Dqn_f32 trig_t = DQN_CAST(Dqn_f32)input->timer_s * 2.f;
Dqn_V2 offset = Dqn_V2_InitNx2(DQN_COSF(trig_t), DQN_SINF(trig_t)) * 18.f;
portal_monkey->local_pos = Dqn_Rect_InterpolatedPoint(hit_box, Dqn_V2_InitNx2(0.5f, -0.75f)) + offset;
}
}
switch (*state) { switch (*state) {
case FP_EntityTerryState_Nil: { case FP_EntityTerryState_Nil: {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle);
@ -542,6 +595,19 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
if (we_are_clicked_entity) { if (we_are_clicked_entity) {
if (game->active_menu == FP_GameActiveMenu_Nil) { if (game->active_menu == FP_GameActiveMenu_Nil) {
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, 2.f))) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
entity->carried_monkey = closest_monkey.entity;
picked_up_monkey_this_frame = true;
}
}
}
if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
@ -549,8 +615,14 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack);
} }
} else {
if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey);
portal_monkey->local_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
entity->carried_monkey = {};
}
}
} }
if (action->next_state == action->state && (acceleration_meters_per_s->x || acceleration_meters_per_s->y)) { if (action->next_state == action->state && (acceleration_meters_per_s->x || acceleration_meters_per_s->y)) {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run);
@ -588,7 +660,20 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
} }
if (we_are_clicked_entity) { if (we_are_clicked_entity) {
bool picked_up_monkey_this_frame = false;
if (game->active_menu == FP_GameActiveMenu_Nil) { if (game->active_menu == FP_GameActiveMenu_Nil) {
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, 2.f))) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
entity->carried_monkey = closest_monkey.entity;
picked_up_monkey_this_frame = true;
}
}
}
if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack);
@ -597,11 +682,20 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack);
} }
} }
}
if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftControl) || if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftControl) ||
TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) { TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) {
FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash); FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash);
} }
} else {
if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey);
portal_monkey->local_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
entity->carried_monkey = {};
}
}
} }
if (!entity_has_velocity) { if (!entity_has_velocity) {
@ -645,6 +739,25 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
} }
} break; } break;
case FP_EntityType_PortalMonkey: {
FP_EntityPortalMonkeyState *state = DQN_CAST(FP_EntityPortalMonkeyState *) & action->state;
switch (*state) {
case FP_EntityPortalMonkeyState_Nil: {
FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Idle);
} break;
case FP_EntityPortalMonkeyState_Idle: {
} break;
case FP_EntityPortalMonkeyState_BeingCarried: {
} break;
case FP_EntityPortalMonkeyState_DisablingPortal: {
} break;
}
} break;
case FP_EntityType_Smoochie: { case FP_EntityType_Smoochie: {
FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->state; FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->state;
switch (*state) { switch (*state) {
@ -1184,6 +1297,35 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break;
case FP_EntityType_PhoneMessageProjectile: break; case FP_EntityType_PhoneMessageProjectile: break;
case FP_EntityType_MobSpawner: {
FP_EntityMobSpawnerState *state = DQN_CAST(FP_EntityMobSpawnerState *)&action->state;
switch (*state) {
case FP_EntityMobSpawnerState_Nil: {
FP_Game_EntityTransitionState(game, entity, FP_EntityMobSpawnerState_Idle);
} break;
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;
FP_Game_DeleteEntity(game, entity->carried_monkey);
}
} break;
}
} break;
} }
switch (entity->type) { switch (entity->type) {
@ -1284,6 +1426,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform
entity->attack_box_size = {}; entity->attack_box_size = {};
} }
} break; } break;
case FP_EntityType_AirportTerryPlane: break; case FP_EntityType_AirportTerryPlane: break;
} }
} }
@ -1318,6 +1461,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
dir_vector.y += input->left_stick[gamepad].y; dir_vector.y += input->left_stick[gamepad].y;
} }
// TODO(doyle): Some bug, diagonal is still faster
if (dir_vector.x && dir_vector.y) { if (dir_vector.x && dir_vector.y) {
dir_vector.x *= 0.7071067811865475244f; dir_vector.x *= 0.7071067811865475244f;
dir_vector.y *= 0.7071067811865475244f; dir_vector.y *= 0.7071067811865475244f;
@ -1421,9 +1565,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
// NOTE: Determine AI movement ============================================================= // NOTE: Determine AI movement =============================================================
if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) {
Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) {
if (entity->flags & FP_GameEntityFlag_Aggros && entity->faction != FP_GameEntityFaction_Nil) { if (entity->flags & FP_GameEntityFlag_Aggros && entity->faction != FP_GameEntityFaction_Nil) {
FP_GameFindClosestEntityResult closest_defender = {}; FP_GameFindClosestEntityResult closest_defender = {};
closest_defender.dist_squared = DQN_F32_MAX; closest_defender.dist_squared = DQN_F32_MAX;
@ -1815,6 +1958,18 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
} }
if (!FP_Game_IsNilEntityHandle(game, entity->carried_monkey) && entity->type != FP_EntityType_MobSpawner) {
FP_GameFindClosestEntityResult closest_portal = FP_Game_FindClosestEntityWithType(game, entity->carried_monkey, game->mob_spawners.data, game->mob_spawners.size);
if (closest_portal.dist_squared < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game, 1.f))) {
FP_GameEntity *portal = FP_Game_GetEntity(game, closest_portal.entity);
portal->carried_monkey = entity->carried_monkey;
entity->carried_monkey = {};
}
acceleration_meters_per_s *= 2.f; // TODO(doyle): Penalise the player
} else {
acceleration_meters_per_s *= 1.f; // TODO(doyle): Penalise the player
}
// NOTE: Tick the state machine // NOTE: Tick the state machine
// NOTE: This can delete the entity! Take caution // NOTE: This can delete the entity! Take caution
FP_GameEntityHandle entity_handle = entity->handle; FP_GameEntityHandle entity_handle = entity->handle;
@ -1877,8 +2032,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
// NOTE: Mob spawner ======================================================================= // NOTE: Mob spawner =======================================================================
if (entity->flags & FP_GameEntityFlag_MobSpawner) { if (entity->type == FP_EntityType_MobSpawner) {
// NOTE: Flush any spawn entities that are dead // NOTE: Flush any spawn entities that are dead
for (FP_SentinelListLink<FP_GameEntityHandle> *link = nullptr; FP_SentinelList_Iterate<FP_GameEntityHandle>(&entity->spawn_list, &link); ) { 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); FP_GameEntity *spawned_entity = FP_Game_GetEntity(game, link->data);
@ -1887,7 +2041,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
if (game->enemies_spawned_this_wave < game->enemies_per_wave && entity->spawn_list.size < entity->spawn_cap) { // NOTE: Spawn new entities if (game->enemies_spawned_this_wave < game->enemies_per_wave && entity->spawn_list.size < entity->spawn_cap) { // NOTE: Spawn new entities
if (input->timer_s >= entity->next_spawn_timestamp_s) { if (entity->action.state != FP_EntityMobSpawnerState_Shutdown && input->timer_s >= entity->next_spawn_timestamp_s) {
uint16_t hp_adjustment = DQN_CAST(uint16_t)game->current_wave; uint16_t hp_adjustment = DQN_CAST(uint16_t)game->current_wave;
entity->next_spawn_timestamp_s = DQN_CAST(uint64_t)(input->timer_s + 5.f); entity->next_spawn_timestamp_s = DQN_CAST(uint64_t)(input->timer_s + 5.f);
@ -2072,7 +2226,12 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
TELY_AssetAnimatedSprite const sprite = action->sprite; TELY_AssetAnimatedSprite const sprite = action->sprite;
uint64_t elapsed_ms = game->clock_ms - action->started_at_clock_ms; uint64_t elapsed_ms = game->clock_ms - action->started_at_clock_ms;
uint16_t anim_frame = DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame) % sprite.anim->count; uint16_t anim_frame = 0;
if (action->sprite_play_once) {
anim_frame = DQN_MIN(DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame), sprite.anim->count);
} else {
anim_frame = DQN_CAST(uint16_t)(elapsed_ms / sprite.anim->ms_per_frame) % sprite.anim->count;
}
Dqn_usize sprite_index = sprite.anim->index + anim_frame; Dqn_usize sprite_index = sprite.anim->index + anim_frame;
Dqn_Rect src_rect = {}; Dqn_Rect src_rect = {};
@ -2196,7 +2355,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
if (game->debug_ui) { if (game->debug_ui) {
// NOTE: Render waypoint entities ====================================================== // NOTE: Render waypoint entities ======================================================
if (entity->flags & FP_GameEntityFlag_MobSpawner) { if (entity->type == FP_EntityType_MobSpawner) {
Dqn_V2 start = world_pos; Dqn_V2 start = world_pos;
for (FP_GameEntity *waypoint_entity = entity->first_child; waypoint_entity; waypoint_entity = waypoint_entity->next) { for (FP_GameEntity *waypoint_entity = entity->first_child; waypoint_entity; waypoint_entity = waypoint_entity->next) {
if ((waypoint_entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) == 0) if ((waypoint_entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) == 0)
@ -2206,10 +2365,8 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
TELY_Render_LineColourV4(renderer, start, end, TELY_COLOUR_BLUE_CADET_V4, 2.f); TELY_Render_LineColourV4(renderer, start, end, TELY_COLOUR_BLUE_CADET_V4, 2.f);
start = end; start = end;
} }
}
if (entity->flags & FP_GameEntityFlag_MobSpawner)
TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4); TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4);
}
if (entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) if (entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint)
TELY_Render_CircleColourV4(renderer, world_pos, 16.f /*radius*/, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4); TELY_Render_CircleColourV4(renderer, world_pos, 16.f /*radius*/, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4);

View File

@ -77,6 +77,7 @@ struct FP_GlobalAnimations
Dqn_String8 particle_heart = DQN_STRING8("particle_heart"); Dqn_String8 particle_heart = DQN_STRING8("particle_heart");
Dqn_String8 particle_purchase = DQN_STRING8("particle_purchase"); Dqn_String8 particle_purchase = DQN_STRING8("particle_purchase");
Dqn_String8 portal = DQN_STRING8("portal"); Dqn_String8 portal = DQN_STRING8("portal");
Dqn_String8 portal_break = DQN_STRING8("portal_break");
Dqn_String8 portal_monk = DQN_STRING8("portal_monk"); Dqn_String8 portal_monk = DQN_STRING8("portal_monk");
Dqn_String8 shadow_long_circle = DQN_STRING8("shadow_long_circle"); Dqn_String8 shadow_long_circle = DQN_STRING8("shadow_long_circle");

View File

@ -19,6 +19,8 @@ enum FP_EntityType
FP_EntityType_MerchantGym, FP_EntityType_MerchantGym,
FP_EntityType_MerchantPhoneCompany, FP_EntityType_MerchantPhoneCompany,
FP_EntityType_MerchantTerry, FP_EntityType_MerchantTerry,
FP_EntityType_MobSpawner,
FP_EntityType_PortalMonkey,
FP_EntityType_Smoochie, FP_EntityType_Smoochie,
FP_EntityType_Terry, FP_EntityType_Terry,
FP_EntityType_PhoneMessageProjectile, FP_EntityType_PhoneMessageProjectile,
@ -35,6 +37,13 @@ enum FP_EntityTerryState
FP_EntityTerryState_Dash, FP_EntityTerryState_Dash,
}; };
enum FP_EntityMobSpawnerState
{
FP_EntityMobSpawnerState_Nil,
FP_EntityMobSpawnerState_Idle,
FP_EntityMobSpawnerState_Shutdown,
};
enum FP_EntitySmoochieState enum FP_EntitySmoochieState
{ {
FP_EntitySmoochieState_Nil, FP_EntitySmoochieState_Nil,
@ -133,6 +142,14 @@ enum FP_EntityHeartState
FP_EntityHeartState_Idle, FP_EntityHeartState_Idle,
}; };
enum FP_EntityPortalMonkeyState
{
FP_EntityPortalMonkeyState_Nil,
FP_EntityPortalMonkeyState_Idle,
FP_EntityPortalMonkeyState_BeingCarried,
FP_EntityPortalMonkeyState_DisablingPortal,
};
struct FP_EntityRenderData struct FP_EntityRenderData
{ {
FP_Meters height; FP_Meters height;

View File

@ -255,6 +255,21 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u
result.anim_name = g_anim_names.airport_terry_plane; result.anim_name = g_anim_names.airport_terry_plane;
} break; } break;
case FP_EntityType_MobSpawner: {
result.height.meters = 3.f;
FP_EntityMobSpawnerState state = DQN_CAST(FP_EntityMobSpawnerState)raw_state;
switch (state) {
case FP_EntityMobSpawnerState_Nil: break;
case FP_EntityMobSpawnerState_Idle: result.anim_name = g_anim_names.portal; break;
case FP_EntityMobSpawnerState_Shutdown: result.anim_name = g_anim_names.portal_break; break;
}
} break;
case FP_EntityType_PortalMonkey: {
result.height.meters = 1.f;
result.anim_name = g_anim_names.portal_monk; break;
} break;
case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break;
} }
@ -403,10 +418,11 @@ static FP_GameEntityHandle FP_Entity_CreateMobSpawner(FP_Game *game, Dqn_V2 pos,
FP_GameEntityHandle result = entity->handle; FP_GameEntityHandle result = entity->handle;
va_end(args); va_end(args);
entity->type = FP_EntityType_MobSpawner;
entity->local_pos = pos; entity->local_pos = pos;
entity->local_hit_box_size = Dqn_V2_InitNx1(32); entity->local_hit_box_size = Dqn_V2_InitNx1(32);
entity->sprite_height = FP_Entity_GetRenderData(game, entity->type, 0 /*state*/, FP_GameDirection_Down).height;
FP_Entity_AddDebugEditorFlags(game, result); FP_Entity_AddDebugEditorFlags(game, result);
entity->flags |= FP_GameEntityFlag_MobSpawner;
entity->spawn_cap = spawn_cap; entity->spawn_cap = spawn_cap;
entity->spawn_list = FP_SentinelList_Init<FP_GameEntityHandle>(game->chunk_pool); entity->spawn_list = FP_SentinelList_Init<FP_GameEntityHandle>(game->chunk_pool);
@ -478,7 +494,6 @@ static FP_GameEntityHandle FP_Entity_CreateMerchantGraveyard(FP_Game *game, Dqn_
FP_Entity_AddDebugEditorFlags(game, result); FP_Entity_AddDebugEditorFlags(game, result);
entity->flags |= FP_GameEntityFlag_NonTraversable; entity->flags |= FP_GameEntityFlag_NonTraversable;
TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.merchant_graveyard); TELY_AssetSpriteAnimation *sprite_anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.merchant_graveyard);
Dqn_Rect sprite_rect = game->atlas_sprite_sheet.rects.data[sprite_anim->index]; Dqn_Rect sprite_rect = game->atlas_sprite_sheet.rects.data[sprite_anim->index];
Dqn_f32 size_scale = FP_Entity_CalcSpriteScaleForDesiredHeight(game, entity->sprite_height, sprite_rect); Dqn_f32 size_scale = FP_Entity_CalcSpriteScaleForDesiredHeight(game, entity->sprite_height, sprite_rect);
@ -688,6 +703,30 @@ static FP_GameEntityHandle FP_Entity_CreatePhoneMessageProjectile(FP_Game *game,
return result; return result;
} }
static FP_GameEntityHandle FP_Entity_CreatePortalMonkey(FP_Game *game, Dqn_V2 pos, DQN_FMT_STRING_ANNOTATE char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
FP_GameEntity *entity = FP_Game_MakeEntityPointerFV(game, fmt, args);
FP_GameEntityHandle result = entity->handle;
va_end(args);
entity->type = FP_EntityType_PortalMonkey;
FP_EntityRenderData render_data = FP_Entity_GetRenderData(game, entity->type, 0, FP_GameDirection_Down);
entity->local_pos = pos;
entity->sprite_height = render_data.height;
FP_Entity_AddDebugEditorFlags(game, result);
entity->local_hit_box_offset = Dqn_V2_InitNx2(0, render_data.render_size.h * .1f);
entity->local_hit_box_size = Dqn_V2_InitNx2(render_data.render_size.w, render_data.render_size.h - (render_data.render_size.h * .4f));
uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite);
return result;
}
static FP_GameEntityHandle FP_Entity_CreateAirportTerryPlane(FP_Game *game, Dqn_V2 pos, DQN_FMT_STRING_ANNOTATE char const *fmt, ...) static FP_GameEntityHandle FP_Entity_CreateAirportTerryPlane(FP_Game *game, Dqn_V2 pos, DQN_FMT_STRING_ANNOTATE char const *fmt, ...)
{ {
va_list args; va_list args;

View File

@ -754,6 +754,31 @@ FP_GameFindClosestEntityResult FP_Game_FindClosestEntityWithType(FP_Game *game,
return result; return result;
} }
static FP_GameFindClosestEntityResult FP_Game_FindClosestEntityWithType(FP_Game *game, FP_GameEntityHandle src, FP_GameEntityHandle *entities, Dqn_usize size)
{
FP_GameFindClosestEntityResult result = {};
result.dist_squared = DQN_F32_MAX;
result.pos = Dqn_V2_InitNx1(DQN_F32_MAX);
Dqn_V2 src_pos = FP_Game_CalcEntityWorldPos(game, src);
DQN_FOR_UINDEX (index, size) {
FP_GameEntityHandle entity_handle = entities[index];
FP_GameEntity *entity = FP_Game_GetEntity(game, entity_handle);
if (FP_Game_IsNilEntity(entity))
continue;
Dqn_V2 pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
Dqn_f32 dist = Dqn_V2_LengthSq_V2x2(pos, src_pos);
if (dist < result.dist_squared) {
result.pos = pos;
result.dist_squared = dist;
result.entity = entity->handle;
}
}
return result;
}
static bool FP_Game_CanEntityAttack(FP_GameEntity *entity, uint64_t current_time_ms) static bool FP_Game_CanEntityAttack(FP_GameEntity *entity, uint64_t current_time_ms)
{ {
bool result = (current_time_ms - entity->last_attack_timestamp) >= entity->attack_cooldown_ms; bool result = (current_time_ms - entity->last_attack_timestamp) >= entity->attack_cooldown_ms;

View File

@ -12,19 +12,18 @@ enum FP_GameEntityFlag
FP_GameEntityFlag_DrawHitBox = 1 << 4, FP_GameEntityFlag_DrawHitBox = 1 << 4,
FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 5, FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 5,
FP_GameEntityFlag_NonTraversable = 1 << 6, FP_GameEntityFlag_NonTraversable = 1 << 6,
FP_GameEntityFlag_MobSpawner = 1 << 7, FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 7,
FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8, FP_GameEntityFlag_Aggros = 1 << 8,
FP_GameEntityFlag_Aggros = 1 << 9, FP_GameEntityFlag_Attackable = 1 << 9,
FP_GameEntityFlag_Attackable = 1 << 10, FP_GameEntityFlag_RespondsToBuildings = 1 << 10,
FP_GameEntityFlag_RespondsToBuildings = 1 << 11, FP_GameEntityFlag_OccupiedInBuilding = 1 << 11,
FP_GameEntityFlag_OccupiedInBuilding = 1 << 12, FP_GameEntityFlag_PointOfInterestHeart = 1 << 12,
FP_GameEntityFlag_PointOfInterestHeart = 1 << 13, FP_GameEntityFlag_CameraTracking = 1 << 13,
FP_GameEntityFlag_CameraTracking = 1 << 14, FP_GameEntityFlag_BuildZone = 1 << 14,
FP_GameEntityFlag_BuildZone = 1 << 15, FP_GameEntityFlag_TTL = 1 << 15,
FP_GameEntityFlag_TTL = 1 << 16, FP_GameEntityFlag_Friendly = 1 << 16,
FP_GameEntityFlag_Friendly = 1 << 17, FP_GameEntityFlag_Foe = 1 << 17,
FP_GameEntityFlag_Foe = 1 << 18, FP_GameEntityFlag_NoClip = 1 << 18,
FP_GameEntityFlag_NoClip = 1 << 19,
}; };
enum FP_GameShapeType enum FP_GameShapeType
@ -124,6 +123,7 @@ struct FP_GameEntityAction
uint32_t state; uint32_t state;
uint32_t next_state; uint32_t next_state;
TELY_AssetAnimatedSprite sprite; TELY_AssetAnimatedSprite sprite;
bool sprite_play_once;
Dqn_f32 sprite_alpha; Dqn_f32 sprite_alpha;
uint64_t started_at_clock_ms; uint64_t started_at_clock_ms;
uint64_t end_at_clock_ms; uint64_t end_at_clock_ms;
@ -232,6 +232,7 @@ struct FP_GameEntity
FP_GameEntityFaction faction; FP_GameEntityFaction faction;
uint32_t count_of_entities_targetting_sides[FP_GameDirection_Count]; uint32_t count_of_entities_targetting_sides[FP_GameDirection_Count];
FP_GameEntityHandle carried_monkey;
}; };
struct FP_GameEntityIterator struct FP_GameEntityIterator
@ -323,6 +324,7 @@ struct FP_Game
Dqn_usize build_mode_building_index; Dqn_usize build_mode_building_index;
Dqn_FArray<FP_GameEntityHandle, 4> mob_spawners; Dqn_FArray<FP_GameEntityHandle, 4> mob_spawners;
Dqn_FArray<FP_GameEntityHandle, 3> portal_monkeys;
uint32_t current_wave; uint32_t current_wave;
uint32_t enemies_per_wave; uint32_t enemies_per_wave;