diff --git a/Data/Textures/atlas.png b/Data/Textures/atlas.png index 85cd8c6..4f09e1e 100644 --- a/Data/Textures/atlas.png +++ b/Data/Textures/atlas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46f9f01f3ce4a608cd33105c4b1e284b741370337ff1558ebdfd3cba69d40678 -size 7662811 +oid sha256:bc1c98690a7b2f0811e85194a418518bc57dd61f998cc17b1e4c11e3633a5e1c +size 8374839 diff --git a/Data/Textures/atlas.txt b/Data/Textures/atlas.txt index 2ad1573..94c0f15 100644 --- a/Data/Textures/atlas.txt +++ b/Data/Textures/atlas.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da02e47120656cf357b33343dc09c619eea2e2d207a5f93dd0e090209e53c418 -size 5331 +oid sha256:4513308644f4da73dde6ea59d1b3ccf1f6d3e109ad04ce0f9b2b0cc9215d8ec9 +size 5915 diff --git a/Data/Textures/atlas/particle_church_cross.png b/Data/Textures/atlas/particle_church_cross.png new file mode 100644 index 0000000..818fe9c --- /dev/null +++ b/Data/Textures/atlas/particle_church_cross.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fe22ce903b54ad13602942a029b4e5d4d03b5cc5ef70ae0e29c7c46f4301ffd +size 2838 diff --git a/Data/Textures/atlas/particle_church_halo.png b/Data/Textures/atlas/particle_church_halo.png new file mode 100644 index 0000000..bde34c1 --- /dev/null +++ b/Data/Textures/atlas/particle_church_halo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:573055871e498dabfd394faea12ea829524415a66f837272f26624f09e247a76 +size 4531 diff --git a/Data/Textures/atlas/particle_drunk_bottle.png b/Data/Textures/atlas/particle_drunk_bottle.png new file mode 100644 index 0000000..7ee2d5e --- /dev/null +++ b/Data/Textures/atlas/particle_drunk_bottle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eda07a3b0dbd99be773737554d24b95c6f8ffa934a8b8976bebb131498222d0e +size 2939 diff --git a/Data/Textures/atlas/particle_drunk_martini.png b/Data/Textures/atlas/particle_drunk_martini.png new file mode 100644 index 0000000..fe2572a --- /dev/null +++ b/Data/Textures/atlas/particle_drunk_martini.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:172bad8157d9bd0fb7e8b358bc65dac29b706814d05a10e813610d5cd2ead71d +size 2477 diff --git a/Data/Textures/atlas/particle_hit_1_1.png b/Data/Textures/atlas/particle_hit_1_1.png new file mode 100644 index 0000000..e09a77c --- /dev/null +++ b/Data/Textures/atlas/particle_hit_1_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bccde6e33c61125a03a421e5391ab86e57c9d55be9c5f0ba53cc69b0acc179da +size 2953 diff --git a/Data/Textures/atlas/particle_hit_2_1.png b/Data/Textures/atlas/particle_hit_2_1.png new file mode 100644 index 0000000..86ced85 --- /dev/null +++ b/Data/Textures/atlas/particle_hit_2_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8511323506a0be6df2d73982358eb0a98e155ec118a336d9032a79f1d46eb0d +size 2574 diff --git a/Data/Textures/atlas/particle_hit_3_1.png b/Data/Textures/atlas/particle_hit_3_1.png new file mode 100644 index 0000000..93f2320 --- /dev/null +++ b/Data/Textures/atlas/particle_hit_3_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6c0c90bcb4bcf0556100a3a30890afac1743d4cd64202ee249dc081d0189626 +size 3060 diff --git a/Data/Textures/atlas/portal_break_1.png b/Data/Textures/atlas/portal_break_1.png new file mode 100644 index 0000000..9dd7b6c --- /dev/null +++ b/Data/Textures/atlas/portal_break_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d1687c45791a0d5a41cf6c838b44f048b09e41db535255a61649549ec089e33 +size 69099 diff --git a/Data/Textures/atlas/portal_break_10.png b/Data/Textures/atlas/portal_break_10.png new file mode 100644 index 0000000..0370801 --- /dev/null +++ b/Data/Textures/atlas/portal_break_10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcca964636860e6ac43d4680e3e52b0aa519cc0b7862cf7c8aa908d630adfd0a +size 29874 diff --git a/Data/Textures/atlas/portal_break_11.png b/Data/Textures/atlas/portal_break_11.png new file mode 100644 index 0000000..5e11245 --- /dev/null +++ b/Data/Textures/atlas/portal_break_11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8834b84f2ee89972f28826a4c9adcc99d05a77419de1fecee97e301ed7cfed4 +size 30389 diff --git a/Data/Textures/atlas/portal_break_12.png b/Data/Textures/atlas/portal_break_12.png new file mode 100644 index 0000000..1cde6db --- /dev/null +++ b/Data/Textures/atlas/portal_break_12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adc6d2e9c580877ea4dc3e94325f27560821044e2aac6661dedaa675920e750c +size 27302 diff --git a/Data/Textures/atlas/portal_break_13.png b/Data/Textures/atlas/portal_break_13.png new file mode 100644 index 0000000..4f8d22c --- /dev/null +++ b/Data/Textures/atlas/portal_break_13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1018809f0bf2e00192737b31906306d81a83dbdf1c51c420f7a53275e0e1cd4e +size 25187 diff --git a/Data/Textures/atlas/portal_break_14.png b/Data/Textures/atlas/portal_break_14.png new file mode 100644 index 0000000..b38c375 --- /dev/null +++ b/Data/Textures/atlas/portal_break_14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:459a2499f1517f8849b668614ba7969f9689a646e8fbc8c8880534bff3289a03 +size 24090 diff --git a/Data/Textures/atlas/portal_break_2.png b/Data/Textures/atlas/portal_break_2.png new file mode 100644 index 0000000..6ecb379 --- /dev/null +++ b/Data/Textures/atlas/portal_break_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fe8e8a12693acfc00375c3aa93b147eb90b2cb979fbacea40dd9f95c82465af +size 57586 diff --git a/Data/Textures/atlas/portal_break_3.png b/Data/Textures/atlas/portal_break_3.png new file mode 100644 index 0000000..73107a7 --- /dev/null +++ b/Data/Textures/atlas/portal_break_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf8ca976d31b9eeb3285f0a8bf2261038cb73b98889b2a745519c5e4b813325 +size 59276 diff --git a/Data/Textures/atlas/portal_break_4.png b/Data/Textures/atlas/portal_break_4.png new file mode 100644 index 0000000..a2af286 --- /dev/null +++ b/Data/Textures/atlas/portal_break_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1fd60e1af8da15ce61213ae29a369d275f222da583e1096f352b61c3143d6cb +size 58451 diff --git a/Data/Textures/atlas/portal_break_5.png b/Data/Textures/atlas/portal_break_5.png new file mode 100644 index 0000000..d779d4c --- /dev/null +++ b/Data/Textures/atlas/portal_break_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:487ed0007734601e75dbb82dde199bc77f8fe201f96ca85946f38d4e55ed1562 +size 81568 diff --git a/Data/Textures/atlas/portal_break_6.png b/Data/Textures/atlas/portal_break_6.png new file mode 100644 index 0000000..3cdf6d5 --- /dev/null +++ b/Data/Textures/atlas/portal_break_6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8680906e09ca7751851897f3c4aabe1e20271ab09b9969f4f8bad2fb134291c8 +size 87775 diff --git a/Data/Textures/atlas/portal_break_7.png b/Data/Textures/atlas/portal_break_7.png new file mode 100644 index 0000000..f9b3e56 --- /dev/null +++ b/Data/Textures/atlas/portal_break_7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95042676fe44ccb0badf8e72a62a51de8c6835eeaf83ad1b5bf22e70e16fc0a4 +size 71778 diff --git a/Data/Textures/atlas/portal_break_8.png b/Data/Textures/atlas/portal_break_8.png new file mode 100644 index 0000000..9ac7ce2 --- /dev/null +++ b/Data/Textures/atlas/portal_break_8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:096dca98c3f78501559c434939a91e21fd1416ca70bee7392efc82f450790f50 +size 71381 diff --git a/Data/Textures/atlas/portal_break_9.png b/Data/Textures/atlas/portal_break_9.png new file mode 100644 index 0000000..08050d7 --- /dev/null +++ b/Data/Textures/atlas/portal_break_9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dcf2c6f2a739f9dbfc82d975646d4925f0a8490462c9f8f03cf5fd01904756d +size 48548 diff --git a/Data/Textures/atlas/portal_monk_1.png b/Data/Textures/atlas/portal_monk_1.png index 6b98363..8132f53 100644 --- a/Data/Textures/atlas/portal_monk_1.png +++ b/Data/Textures/atlas/portal_monk_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeb970558a3f577ad132816a3e1a14d4162fbbd2aa1a6da57d346d788aca738c -size 14046 +oid sha256:9b84ab70c94f1745c20a35431c959bf2417ded4b2227843f2a8d1d1f6067c52d +size 11564 diff --git a/Data/Textures/atlas/portal_monk_2.png b/Data/Textures/atlas/portal_monk_2.png index 54fd4c8..45a4179 100644 --- a/Data/Textures/atlas/portal_monk_2.png +++ b/Data/Textures/atlas/portal_monk_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11f564e8da387754506eb14c73c53c7d7927e8d951593644090e1ba37cd63c1a -size 14931 +oid sha256:474e877df77a2fafc2f141c21d7b8c97f792588512839484e9f0d96cdbd61798 +size 11599 diff --git a/Data/Textures/sprite_spec.txt b/Data/Textures/sprite_spec.txt index 76791b6..dcc2b0f 100644 --- a/Data/Textures/sprite_spec.txt +++ b/Data/Textures/sprite_spec.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8dd10473420755b7eaa02fc5c1a1219adba9ad7ce123a8dab6758b33c1f7815e -size 1692 +oid sha256:b98df29cf4142477a772668565e5d588d45daef17342d3793b236eabf2280ec5 +size 1707 diff --git a/feely_pona.cpp b/feely_pona.cpp index 5b697b8..ae4a44f 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -424,6 +424,56 @@ void TELY_DLL_Init(void *user_data) } } + Dqn_V2 base_mid_p = Dqn_V2_InitNx2(1580, 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; + { + FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, mid_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); + FP_Game_PushParentEntity(game, mob_spawner); + FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-mid_lane_mob_spawner_pos.x + base_mid_p.x, base_mid_p.y), "Waypoint"); + FP_Game_PopParentEntity(game); + Dqn_FArray_Add(&game->mob_spawners, mob_spawner); + } + + // NOTE: Bottom lane spawner =================================================================== + #if 1 + Dqn_V2 bottom_lane_mob_spawner_pos = Dqn_V2_InitNx2(mid_lane_mob_spawner_pos.x, mid_lane_mob_spawner_pos.y + 932.f); + { + FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, bottom_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); + FP_Game_PushParentEntity(game, mob_spawner); + FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-bottom_lane_mob_spawner_pos.x + base_mid_p.x, 0.f), "Waypoint"); + FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-bottom_lane_mob_spawner_pos.x + base_mid_p.x, -932.f), "Waypoint"); + FP_Game_PopParentEntity(game); + Dqn_FArray_Add(&game->mob_spawners, mob_spawner); + } + + // NOTE: Top lane spawner =================================================================== + Dqn_V2 top_lane_mob_spawner_pos = Dqn_V2_InitNx2(mid_lane_mob_spawner_pos.x, mid_lane_mob_spawner_pos.y - 915.f); + { + FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, top_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); + FP_Game_PushParentEntity(game, mob_spawner); + FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-top_lane_mob_spawner_pos.x + base_mid_p.x, 0.f), "Waypoint"); + FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-top_lane_mob_spawner_pos.x + base_mid_p.x, +915.f), "Waypoint"); + FP_Game_PopParentEntity(game); + Dqn_FArray_Add(&game->mob_spawners, mob_spawner); + } + #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"); @@ -454,46 +504,9 @@ void TELY_DLL_Init(void *user_data) 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 mid_lane_mob_spawner_pos = Dqn_V2_InitNx2(game->map->local_hit_box_size.w * -0.5f, 0.f); - Dqn_usize spawn_cap = 16; - { - FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, mid_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); - FP_Game_PushParentEntity(game, mob_spawner); - FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-mid_lane_mob_spawner_pos.x + base_mid_p.x, base_mid_p.y), "Waypoint"); - FP_Game_PopParentEntity(game); - Dqn_FArray_Add(&game->mob_spawners, mob_spawner); - } - - // NOTE: Bottom lane spawner =================================================================== - #if 1 - Dqn_V2 bottom_lane_mob_spawner_pos = Dqn_V2_InitNx2(mid_lane_mob_spawner_pos.x, mid_lane_mob_spawner_pos.y + 932.f); - { - FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, bottom_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); - FP_Game_PushParentEntity(game, mob_spawner); - FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-bottom_lane_mob_spawner_pos.x + base_mid_p.x, 0.f), "Waypoint"); - FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-bottom_lane_mob_spawner_pos.x + base_mid_p.x, -932.f), "Waypoint"); - FP_Game_PopParentEntity(game); - Dqn_FArray_Add(&game->mob_spawners, mob_spawner); - } - - // NOTE: Top lane spawner =================================================================== - Dqn_V2 top_lane_mob_spawner_pos = Dqn_V2_InitNx2(mid_lane_mob_spawner_pos.x, mid_lane_mob_spawner_pos.y - 915.f); - { - FP_GameEntityHandle mob_spawner = FP_Entity_CreateMobSpawner(game, top_lane_mob_spawner_pos, spawn_cap, "Mob spawner"); - FP_Game_PushParentEntity(game, mob_spawner); - FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-top_lane_mob_spawner_pos.x + base_mid_p.x, 0.f), "Waypoint"); - FP_Entity_CreateWaypointF(game, Dqn_V2_InitNx2(-top_lane_mob_spawner_pos.x + base_mid_p.x, +915.f), "Waypoint"); - FP_Game_PopParentEntity(game); - Dqn_FArray_Add(&game->mob_spawners, mob_spawner); - } - #endif - + // NOTE: Heart FP_Entity_CreateHeart(game, base_mid_p, "Heart"); - uint16_t font_size = 18; 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); @@ -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")); } +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) { 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: { 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) { case FP_EntityTerryState_Nil: { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Idle); @@ -542,16 +595,35 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform if (we_are_clicked_entity) { if (game->active_menu == FP_GameActiveMenu_Nil) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); + 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) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { + FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); + } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { + 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)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Run); } @@ -588,19 +660,41 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { + bool picked_up_monkey_this_frame = false; if (game->active_menu == FP_GameActiveMenu_Nil) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); + 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) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_X)) { + FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); + } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_Y)) { + FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); + } } } - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftControl) || - TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) { - FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash); + if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { + if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_LeftControl) || + TELY_Platform_InputGamepadButtonCodeIsPressed(input, 0, TELY_PlatformInputGamepadButtonCode_A)) { + 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 = {}; + } } } @@ -645,6 +739,25 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } } 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: { FP_EntitySmoochieState *state = DQN_CAST(FP_EntitySmoochieState *) & action->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_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) { @@ -1284,6 +1426,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform entity->attack_box_size = {}; } } 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; } + // TODO(doyle): Some bug, diagonal is still faster if (dir_vector.x && dir_vector.y) { dir_vector.x *= 0.7071067811865475244f; dir_vector.y *= 0.7071067811865475244f; @@ -1421,9 +1565,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input } // NOTE: Determine AI movement ============================================================= + Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); if (acceleration_meters_per_s.x == 0 && acceleration_meters_per_s.y == 0) { - Dqn_V2 entity_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); - if (entity->flags & FP_GameEntityFlag_Aggros && entity->faction != FP_GameEntityFaction_Nil) { FP_GameFindClosestEntityResult closest_defender = {}; 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: This can delete the entity! Take caution 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 ======================================================================= - if (entity->flags & FP_GameEntityFlag_MobSpawner) { - + if (entity->type == FP_EntityType_MobSpawner) { // NOTE: Flush any spawn entities that are dead for (FP_SentinelListLink *link = nullptr; FP_SentinelList_Iterate(&entity->spawn_list, &link); ) { 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 (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; 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; 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_Rect src_rect = {}; @@ -2196,7 +2355,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) if (game->debug_ui) { // NOTE: Render waypoint entities ====================================================== - if (entity->flags & FP_GameEntityFlag_MobSpawner) { + if (entity->type == FP_EntityType_MobSpawner) { Dqn_V2 start = world_pos; for (FP_GameEntity *waypoint_entity = entity->first_child; waypoint_entity; waypoint_entity = waypoint_entity->next) { if ((waypoint_entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) == 0) @@ -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); start = end; } - } - - if (entity->flags & FP_GameEntityFlag_MobSpawner) TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4); + } if (entity->flags & FP_GameEntityFlag_MobSpawnerWaypoint) TELY_Render_CircleColourV4(renderer, world_pos, 16.f /*radius*/, TELY_RenderShapeMode_Line, TELY_COLOUR_BLUE_CADET_V4); diff --git a/feely_pona.h b/feely_pona.h index 459b07c..a6ac0d8 100644 --- a/feely_pona.h +++ b/feely_pona.h @@ -77,6 +77,7 @@ struct FP_GlobalAnimations Dqn_String8 particle_heart = DQN_STRING8("particle_heart"); Dqn_String8 particle_purchase = DQN_STRING8("particle_purchase"); Dqn_String8 portal = DQN_STRING8("portal"); + Dqn_String8 portal_break = DQN_STRING8("portal_break"); Dqn_String8 portal_monk = DQN_STRING8("portal_monk"); Dqn_String8 shadow_long_circle = DQN_STRING8("shadow_long_circle"); diff --git a/feely_pona_entity.h b/feely_pona_entity.h index da35812..03ee98b 100644 --- a/feely_pona_entity.h +++ b/feely_pona_entity.h @@ -19,6 +19,8 @@ enum FP_EntityType FP_EntityType_MerchantGym, FP_EntityType_MerchantPhoneCompany, FP_EntityType_MerchantTerry, + FP_EntityType_MobSpawner, + FP_EntityType_PortalMonkey, FP_EntityType_Smoochie, FP_EntityType_Terry, FP_EntityType_PhoneMessageProjectile, @@ -35,6 +37,13 @@ enum FP_EntityTerryState FP_EntityTerryState_Dash, }; +enum FP_EntityMobSpawnerState +{ + FP_EntityMobSpawnerState_Nil, + FP_EntityMobSpawnerState_Idle, + FP_EntityMobSpawnerState_Shutdown, +}; + enum FP_EntitySmoochieState { FP_EntitySmoochieState_Nil, @@ -133,6 +142,14 @@ enum FP_EntityHeartState FP_EntityHeartState_Idle, }; +enum FP_EntityPortalMonkeyState +{ + FP_EntityPortalMonkeyState_Nil, + FP_EntityPortalMonkeyState_Idle, + FP_EntityPortalMonkeyState_BeingCarried, + FP_EntityPortalMonkeyState_DisablingPortal, +}; + struct FP_EntityRenderData { FP_Meters height; diff --git a/feely_pona_entity_create.cpp b/feely_pona_entity_create.cpp index e62190f..bef87ae 100644 --- a/feely_pona_entity_create.cpp +++ b/feely_pona_entity_create.cpp @@ -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; } 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; } @@ -403,10 +418,11 @@ static FP_GameEntityHandle FP_Entity_CreateMobSpawner(FP_Game *game, Dqn_V2 pos, FP_GameEntityHandle result = entity->handle; va_end(args); + entity->type = FP_EntityType_MobSpawner; entity->local_pos = pos; 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); - entity->flags |= FP_GameEntityFlag_MobSpawner; entity->spawn_cap = spawn_cap; entity->spawn_list = FP_SentinelList_Init(game->chunk_pool); @@ -478,7 +494,6 @@ static FP_GameEntityHandle FP_Entity_CreateMerchantGraveyard(FP_Game *game, Dqn_ FP_Entity_AddDebugEditorFlags(game, result); entity->flags |= FP_GameEntityFlag_NonTraversable; - 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_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; } +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, ...) { va_list args; diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 7d46387..7733d8e 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -754,6 +754,31 @@ FP_GameFindClosestEntityResult FP_Game_FindClosestEntityWithType(FP_Game *game, 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) { bool result = (current_time_ms - entity->last_attack_timestamp) >= entity->attack_cooldown_ms; diff --git a/feely_pona_game.h b/feely_pona_game.h index f2fb506..856c32c 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -12,19 +12,18 @@ enum FP_GameEntityFlag FP_GameEntityFlag_DrawHitBox = 1 << 4, FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 5, FP_GameEntityFlag_NonTraversable = 1 << 6, - FP_GameEntityFlag_MobSpawner = 1 << 7, - FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 8, - FP_GameEntityFlag_Aggros = 1 << 9, - FP_GameEntityFlag_Attackable = 1 << 10, - FP_GameEntityFlag_RespondsToBuildings = 1 << 11, - FP_GameEntityFlag_OccupiedInBuilding = 1 << 12, - FP_GameEntityFlag_PointOfInterestHeart = 1 << 13, - FP_GameEntityFlag_CameraTracking = 1 << 14, - FP_GameEntityFlag_BuildZone = 1 << 15, - FP_GameEntityFlag_TTL = 1 << 16, - FP_GameEntityFlag_Friendly = 1 << 17, - FP_GameEntityFlag_Foe = 1 << 18, - FP_GameEntityFlag_NoClip = 1 << 19, + FP_GameEntityFlag_MobSpawnerWaypoint = 1 << 7, + FP_GameEntityFlag_Aggros = 1 << 8, + FP_GameEntityFlag_Attackable = 1 << 9, + FP_GameEntityFlag_RespondsToBuildings = 1 << 10, + FP_GameEntityFlag_OccupiedInBuilding = 1 << 11, + FP_GameEntityFlag_PointOfInterestHeart = 1 << 12, + FP_GameEntityFlag_CameraTracking = 1 << 13, + FP_GameEntityFlag_BuildZone = 1 << 14, + FP_GameEntityFlag_TTL = 1 << 15, + FP_GameEntityFlag_Friendly = 1 << 16, + FP_GameEntityFlag_Foe = 1 << 17, + FP_GameEntityFlag_NoClip = 1 << 18, }; enum FP_GameShapeType @@ -124,6 +123,7 @@ struct FP_GameEntityAction uint32_t state; uint32_t next_state; TELY_AssetAnimatedSprite sprite; + bool sprite_play_once; Dqn_f32 sprite_alpha; uint64_t started_at_clock_ms; uint64_t end_at_clock_ms; @@ -232,6 +232,7 @@ struct FP_GameEntity FP_GameEntityFaction faction; uint32_t count_of_entities_targetting_sides[FP_GameDirection_Count]; + FP_GameEntityHandle carried_monkey; }; struct FP_GameEntityIterator @@ -323,6 +324,7 @@ struct FP_Game Dqn_usize build_mode_building_index; Dqn_FArray mob_spawners; + Dqn_FArray portal_monkeys; uint32_t current_wave; uint32_t enemies_per_wave;