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,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<FP_GameEntityHandle> *link = nullptr; FP_SentinelList_Iterate<FP_GameEntityHandle>(&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);

View File

@ -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");

View File

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

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;
} 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<FP_GameEntityHandle>(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;

View File

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

View File

@ -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<FP_GameEntityHandle, 4> mob_spawners;
Dqn_FArray<FP_GameEntityHandle, 3> portal_monkeys;
uint32_t current_wave;
uint32_t enemies_per_wave;