From 9bd3d1860919976581bc6fe4badd2b1bd6f5a253 Mon Sep 17 00:00:00 2001 From: Alejandro Asenjo Nitti <96613413+sonicdcer@users.noreply.github.com> Date: Sun, 23 Jul 2023 10:22:16 -0300 Subject: [PATCH] DRE EntitySuccubus (#388) Matching this one was tough, so many reused variables and strange array uses. ![image](https://github.com/Xeeynamo/sotn-decomp/assets/96613413/a1297919-50e3-4302-b43b-749bf0ecd5ad) Special thanks: Co-authored-by: @bismurphy (for tackling those messy loops) Co-authored-by: @MottZilla (for docs research) --- config/splat.us.stdre.yaml | 13 +- config/symbols.us.stdre.txt | 10 +- include/entity.h | 18 + include/sfx.h | 24 +- src/st/dre/11A64.c | 86 --- src/st/dre/13B3C.c | 367 ++++++++++++ src/st/dre/{14214.c => 14774.c} | 285 ++++------ src/st/dre/dre.h | 128 +++-- src/st/dre/succubus.c | 958 ++++++++++++++++++++++++++++++++ 9 files changed, 1574 insertions(+), 315 deletions(-) create mode 100644 src/st/dre/13B3C.c rename src/st/dre/{14214.c => 14774.c} (84%) create mode 100644 src/st/dre/succubus.c diff --git a/config/splat.us.stdre.yaml b/config/splat.us.stdre.yaml index abeae3b5b..1df8e556f 100644 --- a/config/splat.us.stdre.yaml +++ b/config/splat.us.stdre.yaml @@ -39,18 +39,17 @@ segments: - [0x14AC, rodata] - [0x16C0, rodata] - [0x1171C, .rodata, 11A64] - - [0x11728, rodata] - - [0x11770, rodata] - - [0x117A0, rodata] - - [0x117F0, .rodata, 13E18] - - [0x11808, .rodata, 14214] + - [0x11728, .rodata, succubus] # EntitySuccubus + - [0x117F0, .rodata, 13B3C] + - [0x11808, .rodata, 14774] - [0x1181C, rodata] - [0x118D0, rodata] - [0x119C4, .rodata, 1C7DC] - [0x119DC, rodata] - [0x11A64, c, 11A64] - - [0x13E18, c, 13E18] - - [0x14214, c, 14214] + - [0x11A64, c, succubus] + - [0x13B3C, c, 13B3C] + - [0x14774, c, 14774] - [0x1C7DC, c, 1C7DC] - [0x23260, data] - [0x23FCC] diff --git a/config/symbols.us.stdre.txt b/config/symbols.us.stdre.txt index bbf6c292f..95b11d965 100644 --- a/config/symbols.us.stdre.txt +++ b/config/symbols.us.stdre.txt @@ -6,6 +6,7 @@ g_eBreakableHitboxes = 0x80180630; g_eBreakableExplosionTypes = 0x80180638; g_eBreakableanimSets = 0x80180640; g_eBreakableBlendModes = 0x80180650; +g_CloneShootOrder = 0x801807D4; c_HeartPrizes = 0x801811AC; g_Rooms = 0x80181498; EntityUnkId11 = 0x80191A64; @@ -13,12 +14,12 @@ EntityBreakable = 0x80191D00; EntityBackgroundClouds = 0x80191E34; EntitySuccubus = 0x80192104; EntitySuccubusPetal = 0x80193B3C; -EntityUnkId1B = 0x80193D7C; -EntityUnkId1C = 0x80193E18; +EntitySuccubusWingOverlay = 0x80193D7C; +EntitySuccubusClone = 0x80193E18; EntityPinkBallProjectile = 0x80194214; EntitySuccubusWingSpike = 0x80194488; -EntityUnkId1F = 0x801946C4; -EntitySuccubusIntroCutscene = 0x801950F8; +EntitySuccubusWingSpikeTip = 0x801946C4; +EntitySuccubusCutscene = 0x801950F8; EntityUnkId21 = 0x80196238; EntityUnkId23 = 0x80196678; EntityFadeToWhite1 = 0x8019697C; @@ -44,6 +45,7 @@ MoveEntity = 0x8019A75C; FallEntity = 0x8019A78C; AllocEntity = 0x8019AC18; SetStep = 0x8019AFE8; +SetSubStep = 0x8019B008; InitializeEntity = 0x8019B0B8; CollectSubweapon = 0x8019BB94; CollectHeartVessel = 0x8019BCAC; diff --git a/include/entity.h b/include/entity.h index a14f5c820..96b07dedd 100644 --- a/include/entity.h +++ b/include/entity.h @@ -245,6 +245,23 @@ typedef struct { /* 0x8E */ s16 unk8E; } ET_StageTitleCard; +typedef struct ET_Succubus { + /* 0x7C */ char pad_7C[0x4]; + /* 0x80 */ s16 timer; + /* 0x82 */ char pad_82[0x2]; + /* 0x84 */ u8 facing; + /* 0x85 */ u8 unk85; + /* 0x86 */ u8 nextAttack; + /* 0x87 */ u8 unk87; + /* 0x88 */ u16 nextStep; + /* 0x8A */ char pad_8A[0x4]; + /* 0x8E */ s16 yOffset; + /* 0x90 */ char pad_90[0xC]; + /* 0x9C */ struct Entity* real; + /* 0xA0 */ s16 clonePosX; + /* 0xA2 */ s16 unkA2; +} ET_Succubus; + typedef union { /* 0x7C */ struct Primitive* prim; /* 0x7C */ ET_Generic generic; @@ -258,6 +275,7 @@ typedef union { /* 0x7C */ ET_801CF254 et_801CF254; /* 0x7C */ ET_GurkhaSword gurkhaSword; /* 0x7C */ ET_Dracula dracula; + /* 0x7C */ ET_Succubus succubus; /* 0x7C */ ET_StageTitleCard stageTitleCard; /* 0x7C */ char stub[0x40]; } Ext; \ No newline at end of file diff --git a/include/sfx.h b/include/sfx.h index 43d083c16..b326fcfd7 100644 --- a/include/sfx.h +++ b/include/sfx.h @@ -20,6 +20,7 @@ * SH = Shaft * ML = Master Librarian * FE = Ferryman + * SU = Succubus * AL = Alucard * MA = Maria * RI = Richter @@ -235,4 +236,25 @@ typedef enum { MONO_SOUND, STEREO_SOUND } soundMode; #define NA_SE_VO_DR_HURT_3 0x85C #define NA_SE_VO_DR_HURT_4 0x85D #define NA_SE_PL_TELEPORT 0x8BA -#define NA_SE_CS_BURNING_PHOTOGRAPH 0x8BE \ No newline at end of file +#define NA_SE_CS_BURNING_PHOTOGRAPH 0x8BE + +// STAGE DRE +#define NA_SE_SU_SHOOT_PINKBALLS 0x62C +#define NA_SE_SU_LANDING 0x646 +#define NA_VO_SU_CRYSTAL_2 0x6AF +#define NA_SE_SU_FLAPPING_WINGS 0x6C6 +#define NA_SE_SU_PETAL_ATTACK 0x6B0 +#define NA_SE_SU_CREATE_CLONES 0x6D5 +#define NA_SE_SU_CHARGE_PINKBALLS 0x6E2 +#define NA_VO_SU_LAUGH 0x86E +// Blank, may be a leftover from the Jap version. +#define NA_VO_SU_BLANK 0x86F +#define NA_VO_SU_GRUNT_1 0x870 +#define NA_VO_SU_GRUNT_2 0x872 +#define NA_VO_SU_GRUNT_3 0x874 +#define NA_VO_SU_HURT_1 0x879 +#define NA_VO_SU_HURT_2 0x87A +#define NA_VO_SU_CRYSTAL_1 0x87C +#define NA_VO_SU_SUCK_YOU_DRY 0x876 +#define NA_VO_SU_NO_SCREAM 0x87B +#define NA_VO_SU_DELICIOUS 0x8D1 \ No newline at end of file diff --git a/src/st/dre/11A64.c b/src/st/dre/11A64.c index c1a7f3e94..c3b61a4fe 100644 --- a/src/st/dre/11A64.c +++ b/src/st/dre/11A64.c @@ -201,89 +201,3 @@ void EntityBackgroundClouds(Entity* self) { g_GpuBuffers[1].draw.g0 = 24; g_GpuBuffers[1].draw.b0 = 24; } - -// ID 0x19 -INCLUDE_ASM("asm/us/st/dre/nonmatchings/11A64", EntitySuccubus); - -// Petal projectile shot by succubus ID 0x1A -void EntitySuccubusPetal(Entity* self) { - Entity* newEntity; - s32 temp_s2; - s16 angle; - - if (D_80180664 & 2) { - self->flags |= 0x100; - } - if (self->hitFlags & 0x80) { - D_80180668 = 1; - } - - if (self->flags & 0x100) { - newEntity = AllocEntity(&g_Entities[224], &g_Entities[256]); - if (newEntity != NULL) { - CreateEntityFromEntity(E_EXPLOSION, self, newEntity); - newEntity->params = 0; - } - DestroyEntity(self); - return; - } - - switch (self->step) { - case 0: - InitializeEntity(D_801804DC); - self->unk19 = 4; - self->rotAngle = rand() & 0xFFF; - temp_s2 = Random() & 3; - if (temp_s2 >= 3) { - temp_s2 = 0; - } - self->animCurFrame = temp_s2 + 64; - - angle = ((Random() & 0x1F) * 16) + 0xC0; - if (self->facing == 0) { - angle = 0x800 - angle; - } else { - angle = angle; - } - temp_s2 = ((rand() * 4) + 0x38000) >> 0xC; - self->velocityX = temp_s2 * rcos(angle); - self->velocityY = temp_s2 * rsin(angle); - self->ext.generic.unk80.modeS16.unk0 = (Random() & 0x1F) + 0x10; - - case 1: - self->velocityX = self->velocityX - (self->velocityX >> 6); - self->velocityY = self->velocityY - (self->velocityY >> 6); - MoveEntity(); - if (--self->ext.generic.unk80.modeS16.unk0 == 0) { - self->ext.generic.unk80.modeS16.unk0 = (Random() & 0x1F) + 0x20; - self->step++; - } - break; - - case 2: - MoveEntity(); - self->rotAngle += self->ext.generic.unk80.modeS16.unk0; - break; - } -} - -void EntityUnkId1B(Entity* entity) { - if (entity->step == 0) { - InitializeEntity(D_801804E8); - } - - entity->posX.i.hi = entity[-1].posX.i.hi; - entity->animCurFrame = 0; - entity->posY.i.hi = entity[-1].posY.i.hi; - entity->facing = entity[-1].facing; - - if (entity[-1].animCurFrame == 0x32) { - entity->animCurFrame = 0x3E; - } - - if (entity[-1].animCurFrame == 0x33) { - entity->animCurFrame = 0x3F; - } - - entity->zPriority = PLAYER.zPriority + 4; -} diff --git a/src/st/dre/13B3C.c b/src/st/dre/13B3C.c new file mode 100644 index 000000000..528c9fd7e --- /dev/null +++ b/src/st/dre/13B3C.c @@ -0,0 +1,367 @@ +#include "dre.h" + +// Petal projectile shot by succubus ID 0x1A +void EntitySuccubusPetal(Entity* self) { + Entity* newEntity; + s32 temp_s2; + s16 angle; + + if (D_80180664 & 2) { + self->flags |= 0x100; + } + if (self->hitFlags & 0x80) { + D_80180668 = 1; + } + + if (self->flags & 0x100) { + newEntity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (newEntity != NULL) { + CreateEntityFromEntity(E_EXPLOSION, self, newEntity); + newEntity->params = 0; + } + DestroyEntity(self); + return; + } + + switch (self->step) { + case 0: + InitializeEntity(D_801804DC); + self->unk19 = 4; + self->rotAngle = rand() & 0xFFF; + temp_s2 = Random() & 3; + if (temp_s2 >= 3) { + temp_s2 = 0; + } + self->animCurFrame = temp_s2 + 64; + + angle = ((Random() & 0x1F) * 16) + 0xC0; + if (self->facing == 0) { + angle = 0x800 - angle; + } else { + angle = angle; + } + temp_s2 = ((rand() * 4) + 0x38000) >> 0xC; + self->velocityX = temp_s2 * rcos(angle); + self->velocityY = temp_s2 * rsin(angle); + self->ext.succubus.timer = (Random() & 31) + 16; + + case 1: + self->velocityX = self->velocityX - (self->velocityX >> 6); + self->velocityY = self->velocityY - (self->velocityY >> 6); + MoveEntity(); + if (--self->ext.succubus.timer == 0) { + self->ext.succubus.timer = (Random() & 31) + 32; + self->step++; + } + break; + + case 2: + MoveEntity(); + self->rotAngle += self->ext.succubus.timer; + break; + } +} + +// Wings that appear over the player when the succubus does her charge attack +void EntitySuccubusWingOverlay(Entity* entity) { + if (entity->step == 0) { + InitializeEntity(D_801804E8); + } + + entity->posX.i.hi = entity[-1].posX.i.hi; + entity->animCurFrame = 0; + entity->posY.i.hi = entity[-1].posY.i.hi; + entity->facing = entity[-1].facing; + + if (entity[-1].animCurFrame == 50) { + entity->animCurFrame = 62; + } + + if (entity[-1].animCurFrame == 51) { + entity->animCurFrame = 63; + } + + entity->zPriority = PLAYER.zPriority + 4; +} + +extern s32 D_80180660; // clones counter + +void EntitySuccubusClone(Entity* self) { + Entity* newEntity; + s8* hitbox; + s32 velX; + s32 i; + + if (D_80180660 == 0) { + self->flags |= 0x100; + } + + if (self->flags & 0x100) { + if (self->step != 5) { + if (D_80180660 != 0) { + D_80180660--; + } + self->hitboxState = 0; + self->flags |= 0x100; + g_api.func_80134714(0x6D9, 0x54, 0); + SetStep(5); + } + } + + switch (self->step) { + case 0: + InitializeEntity(D_801804F4); + self->hitboxState = 0; + velX = self->ext.succubus.clonePosX - + (self->posX.i.hi + g_Camera.posX.i.hi) + << 0x10; + if (velX < 0) { + velX += 0x3F; + } + self->velocityX = velX >> 6; + self->ext.succubus.timer = 64; + + case 1: + MoveEntity(); + newEntity = self->ext.succubus.real; + self->animCurFrame = newEntity->animCurFrame; + self->facing = newEntity->facing; + if (--self->ext.succubus.timer == 0) { + self->hitboxState = 3; + SetStep(2); + } + break; + + case 2: + newEntity = self->ext.succubus.real; + self->animCurFrame = newEntity->animCurFrame; + self->facing = newEntity->facing; + if (newEntity->ext.succubus.unk85 != 0) { + self->ext.succubus.timer = (self->params * 48) + 1; + SetStep(3); + } + break; + + case 3: + self->animCurFrame = 26; + if (--self->ext.succubus.timer == 0) { + SetStep(4); + } + break; + + case 4: + if (self->step_s == 0) { + self->ext.succubus.unk85 = 0; + self->step_s++; + } + + if (AnimateEntity(D_80180780, self) == 0) { + self->ext.succubus.timer = 288; + SetStep(3); + } + + if (self->animFrameIdx == 4 && self->animFrameDuration == 0) { + func_801A046C(0x6E2); + for (i = 0; i < 2; i++) { + newEntity = AllocEntity(&g_Entities[160], &g_Entities[192]); + if (newEntity != NULL) { + CreateEntityFromEntity( + E_SUCCUBUS_PINK_BALL_PROJECTILE, self, newEntity); + newEntity->params = i; + if (i != 0) { + newEntity->posX.i.hi -= 2; + } else { + newEntity->posX.i.hi += 2; + } + newEntity->ext.succubus.real = self; + newEntity->posY.i.hi -= 10; + newEntity->zPriority = self->zPriority + 1; + } + } + } + if (self->animFrameIdx == 5 && self->animFrameDuration == 0) { + func_801A046C(NA_VO_SU_GRUNT_2); + func_801A046C(NA_VO_SU_CRYSTAL_1); + func_801A046C(NA_SE_SU_SHOOT_PINKBALLS); + self->ext.succubus.unk85 = 1; + } + break; + + case 5: + if (self->step_s == 0) { + self->ext.succubus.timer = 32; + self->step_s++; + } + if (self->ext.succubus.timer & 1) { + self->animSet = ANIMSET_DRA(0); + } else { + self->animSet = ANIMSET_OVL(1); + } + if (--self->ext.succubus.timer == 0) { + DestroyEntity(self); + return; + } + break; + } + hitbox = &D_80180830[self->animCurFrame][D_801807F8]; + hitbox--; + hitbox++; + self->hitboxOffX = *hitbox++; + self->hitboxOffY = *hitbox++; + self->hitboxWidth = hitbox[0]; + self->hitboxHeight = hitbox[1]; +} + +// Pink ball projectile shot by succubus duplicates ID 0x1D +void EntityPinkBallProjectile(Entity* self) { + Entity* entity; + s16 temp_s0; + s16 temp_v0; + + if (D_80180664 & 2) { + DestroyEntity(self); + return; + } + + switch (self->step) { + case 0: + InitializeEntity(D_80180500); + self->blendMode = 0x30; + self->unk19 = 3; + self->unk1C = 0; + self->unk1A = 0; + + case 1: + self->unk1A = self->unk1C += 4; + if (self->unk1A > 256) { + self->unk19 = 0; + } + AnimateEntity(D_80180794, self); + + entity = self->ext.succubus.real; + if (entity->ext.succubus.unk85 != 0) { + self->unk19 = 0; + self->step++; + } + if (entity->flags & 0x100) { + DestroyEntity(self); + } + break; + + case 2: + temp_s0 = (self->params << 10) + 0x200; + self->velocityX = rcos(temp_s0) * 0x38; + self->velocityY = rsin(temp_s0) * 0x38; + self->ext.succubus.unkA2 = temp_s0; + self->ext.succubus.timer = 128; + self->step++; + + case 3: + AnimateEntity(D_80180794, self); + MoveEntity(); + temp_v0 = func_8019AF08(self, g_Entities); + temp_s0 = func_8019AF88(0x10, self->ext.succubus.unkA2, temp_v0); + self->velocityX = rcos(temp_s0) * 0x38; + self->velocityY = rsin(temp_s0) * 0x38; + self->ext.succubus.unkA2 = temp_s0; + + if (self->hitFlags & 0x80) { + self->step = 4; + } + + if (--self->ext.succubus.timer == 0) { + self->step = 4; + } + break; + + case 4: + self->flags |= FLAG_DESTROY_IF_OUT_OF_CAMERA; + AnimateEntity(D_80180794, self); + MoveEntity(); + break; + } +} + +// Extending wing spike from succubus ID 0x1E +void EntitySuccubusWingSpike(Entity* self) { + s32 temp_s2; + s16 var_s0; + + if (D_80180664 & 2) { + DestroyEntity(self); + return; + } + + switch (self->step) { + case 0: + InitializeEntity(D_801804E8); + self->unk19 = 4; + self->animCurFrame = 0; + var_s0 = D_801807F0[self->params]; + self->rotAngle = var_s0; + self->unk19 |= 1; + self->unk1A = 0x100; + CreateEntityFromEntity(E_SUCCUBUS_WING_SPIKE_TIP, self, &self[1]); + self[1].facing = self->facing; + self[1].params = self->params; + self[1].rotAngle = self->rotAngle; + + case 1: + if (self->ext.succubus.real->ext.succubus.unk85 != 0) { + self->step++; + } + break; + + case 2: + self->animCurFrame = 85; + self->unk1A += 0x40; + if (self->unk1A > 0x600) { + self->unk1A = 0x600; + } + + if (self->ext.succubus.real->ext.succubus.unk85 == 0) { + self->step++; + } + break; + + case 3: + self->unk1A -= 0x40; + if (self->unk1A < 0x100) { + DestroyEntity(self); + return; + } + } + + var_s0 = self->rotAngle; + temp_s2 = (self->unk1A * 0xB) >> 6; + if (self->facing == 0) { + var_s0 = 0x800 - var_s0; + } + + self[1].posX.i.hi = self->posX.i.hi; + self[1].posY.i.hi = self->posY.i.hi; + self[1].posX.i.hi += temp_s2 * rcos(var_s0) >> 0xC; + self[1].posY.i.hi -= temp_s2 * rsin(var_s0) >> 0xC; +} + +void EntitySuccubusWingSpikeTip(Entity* self) { + switch (self->step) { + case 0: + InitializeEntity(D_8018050C); + self->animCurFrame = 0; + self->unk19 = 4; + self->hitboxState = 0; + + case 1: + if (self[-1].animCurFrame != 0) { + self->hitboxState = 1; + self->animCurFrame = 86; + } + if (self->hitFlags != 0) { + D_80180668 = 1; + } + if (self[-1].entityId != 0x1E) { + DestroyEntity(self); + } + } +} \ No newline at end of file diff --git a/src/st/dre/14214.c b/src/st/dre/14774.c similarity index 84% rename from src/st/dre/14214.c rename to src/st/dre/14774.c index d5daea090..824cd6915 100644 --- a/src/st/dre/14214.c +++ b/src/st/dre/14774.c @@ -1,158 +1,5 @@ #include "dre.h" -// Pink ball projectile shot by succubus duplicates ID 0x1D -void EntityPinkBallProjectile(Entity* self) { - Entity* entity; - s16 temp_s0; - s16 temp_v0; - - if (D_80180664 & 2) { - DestroyEntity(self); - return; - } - - switch (self->step) { - case 0: - InitializeEntity(D_80180500); - self->blendMode = 0x30; - self->unk19 = 3; - self->unk1C = 0; - self->unk1A = 0; - - case 1: - self->unk1A = self->unk1C += 4; - if (self->unk1A > 256) { - self->unk19 = 0; - } - AnimateEntity(D_80180794, self); - - entity = self->ext.generic.unk9C; - if (entity->ext.generic.unk84.U8.unk1 != 0) { - self->unk19 = 0; - self->step++; - } - if (entity->flags & 0x100) { - DestroyEntity(self); - } - break; - - case 2: - temp_s0 = (self->params << 0xA) + 0x200; - self->velocityX = rcos(temp_s0) * 0x38; - self->velocityY = rsin(temp_s0) * 0x38; - self->ext.generic.unkA2 = temp_s0; - self->ext.generic.unk80.modeS16.unk0 = 128; - self->step++; - - case 3: - AnimateEntity(D_80180794, self); - MoveEntity(); - temp_v0 = func_8019AF08(self, g_Entities); - temp_s0 = func_8019AF88(0x10, self->ext.generic.unkA2, temp_v0); - self->velocityX = rcos(temp_s0) * 0x38; - self->velocityY = rsin(temp_s0) * 0x38; - self->ext.generic.unkA2 = temp_s0; - - if (self->hitFlags & 0x80) { - self->step = 4; - } - - if (--self->ext.generic.unk80.modeS16.unk0 == 0) { - self->step = 4; - } - break; - - case 4: - self->flags |= FLAG_DESTROY_IF_OUT_OF_CAMERA; - AnimateEntity(D_80180794, self); - MoveEntity(); - break; - } -} - -// Extending wing spike from succubus ID 0x1E -void EntitySuccubusWingSpike(Entity* self) { - s32 temp_s2; - s16 var_s0; - - if (D_80180664 & 2) { - DestroyEntity(self); - return; - } - - switch (self->step) { - case 0: - InitializeEntity(D_801804E8); - self->unk19 = 4; - self->animCurFrame = 0; - var_s0 = D_801807F0[self->params]; - self->rotAngle = var_s0; - self->unk19 |= 1; - self->unk1A = 0x100; - CreateEntityFromEntity(0x1F, self, &self[1]); - self[1].facing = self->facing; - self[1].params = self->params; - self[1].rotAngle = self->rotAngle; - - case 1: - if (self->ext.generic.unk9C->ext.generic.unk84.U8.unk1 != 0) { - self->step++; - } - break; - - case 2: - self->animCurFrame = 85; - self->unk1A += 0x40; - if (self->unk1A > 0x600) { - self->unk1A = 0x600; - } - - if (self->ext.generic.unk9C->ext.generic.unk84.U8.unk1 == 0) { - self->step++; - } - break; - - case 3: - self->unk1A -= 0x40; - if (self->unk1A < 0x100) { - DestroyEntity(self); - return; - } - } - - var_s0 = self->rotAngle; - temp_s2 = (self->unk1A * 0xB) >> 6; - if (self->facing == 0) { - var_s0 = 0x800 - var_s0; - } - - self[1].posX.i.hi = self->posX.i.hi; - self[1].posY.i.hi = self->posY.i.hi; - self[1].posX.i.hi += temp_s2 * rcos(var_s0) >> 0xC; - self[1].posY.i.hi -= temp_s2 * rsin(var_s0) >> 0xC; -} - -void EntityUnkId1F(Entity* entity) { - switch (entity->step) { - case 0: - InitializeEntity(D_8018050C); - entity->animCurFrame = 0; - entity->unk19 = 4; - entity->hitboxState = 0; - case 1: - if (entity[-1].animCurFrame != 0) { - entity->hitboxState = 1; - entity->animCurFrame = 0x56; - } - if (entity->hitFlags != 0) { - D_80180668 = 1; - } - if (entity[-1].entityId != 0x1E) { - DestroyEntity(entity); - } - } -} - void func_80194774(void) { D_801A3EE4 = 2; D_801A3EE2 = 2; @@ -163,7 +10,65 @@ void func_80194774(void) { D_801A3EDE = D_801A3EE0 + 20; } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801947C8); +s32 func_801947C8(s32 arg0) { + Primitive* prim; + s16 firstPrimIndex; + + firstPrimIndex = g_api.AllocPrimitives(PRIM_SPRT, 7); + D_801A3F10[0] = firstPrimIndex; + if (firstPrimIndex == -1) { + D_801A3F10[0] = 0; + return 0; + } + D_801A3ED8 = arg0; + D_801A3F14 = 0; + D_801A3F0C = -1; + D_801A3F08 = -1; + func_80194774(); + + if (prim && prim) { // !FAKE + } + + prim = D_801A3EF0[0] = &g_PrimBuf[D_801A3F10[0]]; + + prim->blendMode = BLEND_VISIBLE; + prim = D_801A3EF0[1] = prim->next; + + prim->blendMode = BLEND_VISIBLE; + prim = D_801A3EF0[2] = prim->next; + + prim->blendMode = BLEND_VISIBLE; + prim = D_801A3EF0[3] = prim->next; + + prim->blendMode = BLEND_VISIBLE; + prim = D_801A3EF0[4] = prim->next; + + prim->blendMode = BLEND_VISIBLE; + prim = D_801A3EF0[5] = prim->next; + + prim->type = 4; + prim->blendMode = BLEND_VISIBLE; + + prim = prim->next; + prim->type = PRIM_G4; + prim->r0 = prim->r1 = prim->r2 = prim->r3 = 0xFF; + prim->b0 = prim->b1 = prim->b2 = prim->b3 = prim->g0 = prim->g1 = prim->g2 = + prim->g3 = 0; + prim->x0 = prim->x2 = 4; + prim->x1 = prim->x3 = 0xF8; + prim->priority = 0x1FD; + prim->blendMode = BLEND_VISIBLE; + + prim = prim->next; + prim->type = PRIM_TILE; + prim->x0 = 3; + prim->y0 = 0x2F; + prim->v0 = 0x4A; + prim->r0 = prim->g0 = prim->b0 = 0xFF; + prim->priority = 0x1FC; + prim->blendMode = BLEND_VISIBLE; + return 1; +} void func_8019498C(s16 yOffset) { RECT rect; @@ -175,9 +80,9 @@ void func_8019498C(s16 yOffset) { ClearImage(&rect, 0, 0, 0); } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801949E8); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_801949E8); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80194AA0); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_80194AA0); void func_80194C24(s32 arg0) { D_801A3F18 = arg0 + 0x100000; @@ -185,14 +90,40 @@ void func_80194C24(s32 arg0) { D_801A3F14 = 1; } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80194C50); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_80194C50); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80194F14); +void func_80194F14(Entity* self) { + /** TODO: !FAKE + * do while (0) fixed instruction reordering at + * entity->flags ^= FLAG_HAS_PRIMS; + * but intruduces a problem in PlaySfx, which is fixed + * by using gameApi pointer. + */ + GameApi* gameApi; -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80194FF4); + if (g_pads[0].tapped == PAD_START) { + D_801A3ED4 = 1; + g_api.FreePrimitives(self->primIndex); + do { + self->flags ^= FLAG_HAS_PRIMS; + } while (0); + if (D_801A3F0C != -1) { + g_api.FreePrimitives(D_801A3F0C); + } + if (D_801A3F08 != -1) { + g_api.FreePrimitives(D_801A3F08); + } + gameApi = &g_api; + (*gameApi).PlaySfx(SET_STOP_MUSIC); + self->step = 1; + self->step_s = 0; + } +} + +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_80194FF4); // dialogue with mother opens as alucard walks right ID 20 -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntitySuccubusIntroCutscene); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntitySuccubusCutscene); void func_801961DC(s16 arg0) { s16 temp_v0 = arg0 - *(s16*)D_8009740C; @@ -206,16 +137,16 @@ void func_801961DC(s16 arg0) { } } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityUnkId21); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityUnkId21); // appears to load from the CD and freeze the game -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityUnkId23); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityUnkId23); // Fades to white -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityFadeToWhite1); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityFadeToWhite1); // Fades to white ID 24 -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityFadeToWhite2); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityFadeToWhite2); s32 Random(void) { g_randomNext = (g_randomNext * 0x01010101) + 1; @@ -305,11 +236,11 @@ void Update(void) { } } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801972BC); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_801972BC); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801973C4); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_801973C4); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityNumericDamage); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityNumericDamage); void CreateEntityFromLayout(Entity* entity, LayoutEntity* initDesc) { DestroyEntity(entity); @@ -422,9 +353,9 @@ void func_80198EC0(s16 arg0) { } } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80198F18); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_80198F18); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_80199014); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_80199014); void func_80199128(s16 arg0) { while (1) { @@ -446,9 +377,9 @@ void func_80199174(s16 arg0) { } } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801991CC); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_801991CC); -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_801992C8); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_801992C8); void InitRoomEntities(s32 objLayoutId) { u16* pObjLayoutStart = D_80180220[objLayoutId]; @@ -546,7 +477,7 @@ s32 func_801996F8(Entity* e) { return diff; } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityRedDoor); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityRedDoor); void DestroyEntity(Entity* item) { s32 i; @@ -932,11 +863,11 @@ void SetStep(u8 step) { entity->animFrameDuration = 0; } -void func_8019B008(u8 arg0) { +void SetSubStep(u8 step_s) { Entity* entity; entity = g_CurrentEntity; - entity->step_s = arg0; + entity->step_s = step_s; entity->animFrameIdx = 0; entity->animFrameDuration = 0; } @@ -1062,7 +993,7 @@ void func_8019B304(u16* hitSensors, s16 sensorCount) { } } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_8019B45C); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_8019B45C); void ReplaceBreakableWithItemDrop(Entity* self) { u16 params; @@ -1169,7 +1100,7 @@ void func_8019BA38(u16 arg0) { DestroyEntity(g_CurrentEntity); } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", func_8019BAB8); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", func_8019BAB8); void CollectSubweapon(u16 subWeaponIdx) { Entity* player = &PLAYER; @@ -1227,7 +1158,7 @@ void CollectLifeVessel(void) { void DestroyCurrentEntity(void) { DestroyEntity(g_CurrentEntity); } -INCLUDE_ASM("asm/us/st/dre/nonmatchings/14214", EntityPrizeDrop); +INCLUDE_ASM("asm/us/st/dre/nonmatchings/14774", EntityPrizeDrop); void EntityExplosion(Entity* entity) { u32 temp_v0; diff --git a/src/st/dre/dre.h b/src/st/dre/dre.h index 351748833..348ac4f42 100644 --- a/src/st/dre/dre.h +++ b/src/st/dre/dre.h @@ -1,24 +1,33 @@ #include "stage.h" +#define STAGE_DRE_H typedef enum { - E_NONE, - E_BREAKABLE, - E_EXPLOSION, - E_PRIZE_DROP, - E_NUMERIC_DAMAGE, - E_RED_DOOR, - E_INTENSE_EXPLOSION, - E_SOUL_STEAL_ORB, - E_ROOM_FOREGROUND, - E_STAGE_NAME_POPUP, - E_EQUIP_ITEM_DROP, - E_RELIC_ORB, - E_HEART_DROP, - E_ENEMY_BLOOD, - E_SAVE_GAME_POPUP, - E_DUMMY_0F, - E_DUMMY_10, -} EntityIDs; + /* 0x00 */ E_NONE, + /* 0x01 */ E_BREAKABLE, + /* 0x02 */ E_EXPLOSION, + /* 0x03 */ E_PRIZE_DROP, + /* 0x04 */ E_NUMERIC_DAMAGE, + /* 0x05 */ E_RED_DOOR, + /* 0x06 */ E_INTENSE_EXPLOSION, + /* 0x07 */ E_SOUL_STEAL_ORB, + /* 0x08 */ E_ROOM_FOREGROUND, + /* 0x09 */ E_STAGE_NAME_POPUP, + /* 0x0A */ E_EQUIP_ITEM_DROP, + /* 0x0B */ E_RELIC_ORB, + /* 0x0C */ E_HEART_DROP, + /* 0x0D */ E_ENEMY_BLOOD, + /* 0x0E */ E_SAVE_GAME_POPUP, + /* 0x0F */ E_DUMMY_0F, + /* 0x10 */ E_DUMMY_10, + + /* 0x1A */ E_SUCCUBUS_PETAL = 0x1A, + /* 0x1B */ E_SUCCUBUS_WING_OVERLAY, + /* 0x1C */ E_SUCCUBUS_CLONE, + /* 0x1D */ E_SUCCUBUS_PINK_BALL_PROJECTILE, + /* 0x1E */ E_SUCCUBUS_WING_SPIKE, + /* 0x1F */ E_SUCCUBUS_WING_SPIKE_TIP, + /* 0x20 */ E_SUCCUBUS_CUTSCENE, +} DRE_EntityIDs; void ReplaceBreakableWithItemDrop(Entity* arg0); void DestroyEntity(Entity* entity); @@ -38,10 +47,13 @@ void func_8019E5E0(Entity* entity); LayoutEntity* D_80180220[]; LayoutEntity* D_801802F4[]; + +/* *** Initializers *** */ extern u16 D_80180464[]; -extern u16 D_80180488[]; // Init EntityBackgroundClouds +extern u16 D_80180488[]; // EntityBackgroundClouds extern u16 D_80180494[]; extern u16 D_801804A0[]; +extern u16 D_801804D0[]; // EntitySuccubus extern u16 D_801804DC[]; extern u16 D_80180500[]; extern u8 D_80180580[]; @@ -49,25 +61,43 @@ extern u8 D_80180588[]; extern u16 D_80180590[]; extern s32 D_80180664; + +/* *** EntitySuccubus animations START *** */ +extern u8 D_8018066C[]; +extern u8 D_80180674[]; +extern u8 D_80180680[]; +extern u8 D_80180694[]; +extern u8 D_801806A0[]; +extern u8 D_801806C4[]; +extern u8 D_801806D4[]; +extern u8 D_801806E8[]; +extern u8 D_801806F8[]; +extern u8 D_8018070C[]; +extern u8 D_8018071C[]; +extern u8 D_8018072C[]; +extern u8 D_80180734[]; +extern u8 D_80180748[]; +extern u8 D_80180760[]; +extern u8 D_80180768[]; +extern u8 D_80180770[]; +extern u8 D_80180778[]; +extern u8 D_80180780[]; +extern u8 D_8018079C[]; +extern u8 D_801807AC[]; +/* *** EntitySuccubus animations END *** */ + +extern s8 g_CloneShootOrder[4][7]; // 0x801807D4 + extern u8 D_80180780[]; // Animation extern const u8 D_80180794[]; extern s32 D_801807F8[]; extern u8 D_80180830[]; extern u16 D_8018097C[]; extern s16 D_80180D80[]; -extern LayoutEntity* D_801A32C4; -extern u16* D_801A32C8; -extern s8 D_801A32CC; -extern u8 D_801A32D0; extern u16 D_80180470[]; extern u16 D_801804AC[]; extern u16 D_801804F4[]; extern s8 c_HeartPrizes[]; -extern s32 D_801811B0[]; -extern u32 D_8018125C[]; -extern s16 D_801812E4[]; -extern u32 D_801812F4[]; -extern u8 D_80181338[]; extern PfnEntityUpdate PfnEntityUpdates[]; extern u16 D_801804E8[]; extern u16 D_8018050C[]; @@ -75,24 +105,18 @@ extern u16 D_80180528[]; extern s32 D_80180664; extern s32 D_80180668; extern s16 D_801807F0[]; +extern s32 D_801811B0[]; +extern u32 D_8018125C[]; +extern s16 D_801812E4[]; +extern u32 D_801812F4[]; +extern s8 D_801816C4; // Succubus facing assigned to it +extern u8 D_80181338[]; extern u16 D_801811A4[]; extern u32 D_8018130C[]; extern u8 D_80181324[]; extern u16 D_80181328[]; extern u16 D_801810B0[]; extern u16 D_801810E0[]; -extern s16 D_801A3EDE; -extern u16 D_801A3EE0; -extern s16 D_801A3EE2; -extern s16 D_801A3EE4; -extern s16 D_801A3EE6; -extern s16 D_801A3EEA; -extern s8 D_801A3EEE; -extern s8 D_801A3EEF; -extern s16 D_801A3F14; -extern s16 D_801A3F16; -extern s32 D_801A3F18; -extern u16 D_801A3F8C[]; // *** EntitySoulStealOrb properties START *** @@ -101,3 +125,27 @@ extern u16 D_8018139C[]; // NOTE(sestren): Animation frame properties? extern u8 D_801813FC; // *** EntitySoulStealOrb properties END *** + +extern LayoutEntity* D_801A32C4; +extern u16* D_801A32C8; +extern s8 D_801A32CC; +extern u8 D_801A32D0; +extern s32 D_801A3ED4; +extern s32 D_801A3ED8; +extern s16 D_801A3EDE; +extern u16 D_801A3EE0; +extern s16 D_801A3EE2; +extern s16 D_801A3EE4; +extern s16 D_801A3EE6; +extern s16 D_801A3EEA; +extern s8 D_801A3EEE; +extern s8 D_801A3EEF; +extern Primitive* D_801A3EF0[]; +extern s32 D_801A3F08; +extern s32 D_801A3F0C; +extern s32 D_801A3F10[]; +extern s32 D_801A3F84; +extern s16 D_801A3F14; +extern s16 D_801A3F16; +extern s32 D_801A3F18; +extern u16 D_801A3F8C[]; diff --git a/src/st/dre/succubus.c b/src/st/dre/succubus.c new file mode 100644 index 000000000..178f610f9 --- /dev/null +++ b/src/st/dre/succubus.c @@ -0,0 +1,958 @@ +/* + * Overlay: DRE + * Enemy: Succubus Boss + * ID: 0x19 + * BOSS ID: 9 + */ + +#include "dre.h" + +typedef enum { + /* 0 */ SUCCUBUS_INIT, + /* 1 */ SUCCUBUS_CS_1, + /* 2 */ SUCCUBUS_CS_2, + /* 3 */ SUCCUBUS_CS_3, + /* 4 */ SUCCUBUS_CS_4, + /* 5 */ SUCCUBUS_IDLE, + /* 7 */ SUCCUBUS_FLY = 7, + /* 8 */ SUCCUBUS_PETAL_ATTACK, + /* 9 */ SUCCUBUS_CHARGE, + /* 11 */ SUCCUBUS_CLONE_ATTACK = 11, + /* 12 */ SUCCUBUS_SPIKE_ATTACK, + /* 13 */ SUCCUBUS_TAUNT, + /* 14 */ SUCCUBUS_GET_HIT, + /* 15 */ SUCCUBUS_FACE_PLAYER, + /* 16 */ SUCCUBUS_NEXT_ACTION_CHECK, + /* 18 */ SUCCUBUS_DYING = 18, + /* 255 */ SUCCUBUS_DEBUG = 255, +} SuccubusSteps; + +typedef enum { + /* 0 */ SUCCUBUS_FLY_0, + /* 1 */ SUCCUBUS_FLY_1, + /* 2 */ SUCCUBUS_FLY_2, +} SuccubusFlySubSteps; + +typedef enum { + /* 0 */ SUCCUBUS_PETAL_ATTACK_SETUP, + /* 1 */ SUCCUBUS_PETAL_ATTACK_ANIM, + /* 2 */ SUCCUBUS_PETAL_ATTACK_CREATE_PETALS, +} SuccubusPetalAttackSubSteps; + +typedef enum { + /* 0 */ SUCCUBUS_CHARGE_SETUP, + /* 1 */ SUCCUBUS_CHARGE_FLY_TOWARDS_PLAYER, + /* 2 */ SUCCUBUS_CHARGE_AT_PLAYER_POSITION, + /* 3 */ SUCCUBUS_CHARGE_DEAL_DAMAGE, + /* 4 */ SUCCUBUS_CHARGE_FLY_AWAY, + /* 5 */ SUCCUBUS_CHARGE_DECELERATE, +} SuccubusChargeSubSteps; + +typedef enum { + /* 0 */ SUCCUBUS_CLONE_ATTACK_ANIM_1, + /* 1 */ SUCCUBUS_CLONE_ATTACK_CREATE_CLONES, + /* 2 */ SUCCUBUS_CLONE_ATTACK_WAIT, + /* 3 */ SUCCUBUS_CLONE_ATTACK_PLACE_REAL, + /* 4 */ SUCCUBUS_CLONE_ATTACK_ANIM_2, + /* 5 */ SUCCUBUS_CLONE_ATTACK_SET_SHOOTING, + /* 6 */ SUCCUBUS_CLONE_ATTACK_STOP_SHOOTING, + /* 7 */ SUCCUBUS_CLONE_ATTACK_SHOOT_PINKBALLS, // unused +} SuccubusCloneAttackSubSteps; + +typedef enum { + /* 0 */ SUCCUBUS_SPIKE_ATTACK_CREATE_SPIKES, + /* 1 */ SUCCUBUS_SPIKE_ATTACK_1, + /* 2 */ SUCCUBUS_SPIKE_ATTACK_2, + /* 3 */ SUCCUBUS_SPIKE_ATTACK_3, + /* 4 */ SUCCUBUS_SPIKE_ATTACK_4, +} SuccubusSpikeAttackSubSteps; + +typedef enum { + /* 0 */ SUCCUBUS_DYING_SETUP, + /* 1 */ SUCCUBUS_DYING_FALL, + /* 2 */ SUCCUBUS_DYING_LAND, + /* 3 */ SUCCUBUS_DYING_ANIM_1, + /* 4 */ SUCCUBUS_DYING_ANIM_2, +} SuccubusDyingSubSteps; + +// Original name: multiple_count +extern s32 D_80180660; // clones counter + +void EntitySuccubus(Entity* self) { + const int SeenCutscene = 212; + u8* clonesShootOrder; + s32 sideToPlayer; + Entity* entity; + s32 posX, posY; + s8* hitbox; + s32 facing; + s16 angle; + s32 temp; + s32 i; + + FntPrint("multiple_count %x\n", D_80180660); + + if ((self->hitFlags & 3) && (self->step & SUCCUBUS_CS_1)) { + SetStep(SUCCUBUS_GET_HIT); + } + + if (self->flags & 0x100) { + if (self->step != SUCCUBUS_DYING) { + self->hitboxState = 0; + SetStep(SUCCUBUS_DYING); + } + } + + switch (self->step) { + case SUCCUBUS_INIT: + InitializeEntity(D_801804D0); + self->animCurFrame = 82; + SetStep(SUCCUBUS_CS_1); + CreateEntityFromCurrentEntity(E_SUCCUBUS_WING_OVERLAY, &self[1]); + + case SUCCUBUS_CS_1: // Disguised as Lisa + if (D_8003BDEC[SeenCutscene] || (g_DemoMode != Demo_None)) { + self->facing = 0; + self->posX.i.hi = 416 - g_Camera.posX.i.hi; + self->posY.i.hi = 175 - g_Camera.posY.i.hi; + SetStep(SUCCUBUS_CS_4); + self->step_s = 3; + } + self->animCurFrame = 82; + if (D_801A3F84 & 4) { + SetStep(SUCCUBUS_CS_2); + } + break; + + case SUCCUBUS_CS_2: // Disguised as Lisa + if (D_801A3ED4 != 0) { + SetSubStep(4); + } + switch (self->step_s) { + case 0: + AnimateEntity(D_8018079C, self); + if (D_801A3F84 & 0x400) { + SetSubStep(1); + } + break; + + case 1: + AnimateEntity(D_801807AC, self); + if (D_801A3F84 & 0x800) { + self->animCurFrame = 84; + SetSubStep(2); + } + break; + + case 2: + if (D_801A3F84 & 0x1000) { + SetSubStep(3); + } + break; + + case 3: + self->animCurFrame = 83; + if (D_801A3F84 & 0x2000) { + SetSubStep(4); + } + break; + + case 4: + self->animCurFrame = 84; + if (D_801A3F84 & 0x20) { + SetStep(SUCCUBUS_CS_3); + } + break; + } + break; + + // Sets Succubus in position + case SUCCUBUS_CS_3: + if ((D_801A3ED4 == 0) || (self->step_s == 0)) { + switch (self->step_s) { + case 0: + self->facing = 0; + self->posX.i.hi = 416 - g_Camera.posX.i.hi; + self->posY.i.hi = 175 - g_Camera.posY.i.hi; + self->step_s++; + + case 1: + AnimateEntity(D_8018066C, self); + if (D_801A3F84 & 0x40) { + SetSubStep(2); + } + break; + + case 2: + self->animCurFrame = 4; + if (D_801A3F84 & 0x80) { + SetSubStep(3); + } + break; + + case 3: + AnimateEntity(D_80180674, self); + if (D_801A3F84 & 0x100) { + SetSubStep(4); + } + break; + + case 4: + if (AnimateEntity(D_80180680, self) == 0) { + SetStep(SUCCUBUS_CS_4); + } + break; + } + } else { + SetStep(SUCCUBUS_CS_4); + } + break; + + // Ascends Succubus into the air + case SUCCUBUS_CS_4: + switch (self->step_s) { + case 0: + if (AnimateEntity(D_80180694, self) == 0) { + SetSubStep(1); + } + break; + + case 1: + g_api.PlaySfx(MU_ENCHANTED_BANQUET); + g_api.func_800FD4C0(9, 2); + self->velocityX = 0; + self->velocityY = -0x40000; + self->step_s++; + + case 2: + MoveEntity(); + self->velocityY += 0x2000; + if (self->velocityY > 0) { + self->velocityY = 0; + } + if (AnimateEntity(D_801806A0, self) == 0) { + SetStep(SUCCUBUS_IDLE); + } + break; + + case 3: + AnimateEntity(D_80180694, self); + if (GetDistanceToPlayerX() < 96) { + SetSubStep(1); + } + break; + } + break; + + case SUCCUBUS_DYING: + switch (self->step_s) { + case SUCCUBUS_DYING_SETUP: + func_801A046C(NA_VO_SU_NO_SCREAM); + CreateEntityFromCurrentEntity( + E_SUCCUBUS_CUTSCENE, &g_Entities[200]); + g_Entities[200].params = 1; + D_80180660 = 0; + D_80180664 |= 2; + g_api.func_800FD4C0(9, 1); + self->velocityX = 0; + self->velocityY = 0; + posY = self->posY.i.hi + g_Camera.posY.i.hi; + if (posY > 160) { + self->velocityY = -0x20000; + self->step_s = 1; + } else { + self->step_s = 2; + } + break; + + case SUCCUBUS_DYING_FALL: + AnimateEntity(D_80180768, self); + MoveEntity(); + self->velocityY += 0x2000; + if (self->velocityY > 0) { + self->step_s = 2; + } + break; + + case SUCCUBUS_DYING_LAND: + AnimateEntity(D_80180768, self); + MoveEntity(); + self->velocityY += 0x2000; + posY = self->posY.i.hi + g_Camera.posY.i.hi; + if (posY >= 176) { + func_801A046C(NA_SE_SU_LANDING); + self->posY.i.hi = 175 - g_Camera.posY.i.hi; + SetSubStep(SUCCUBUS_DYING_ANIM_1); + posX = self->posX.i.hi + g_Camera.posX.i.hi; + if (posX < 80) { + D_801816C4 = self->facing = 1; + } else if (posX > 432) { + D_801816C4 = self->facing = 0; + } else { + D_801816C4 = self->facing = (GetSideToPlayer() & 1) ^ 1; + } + D_801A3F84 |= 2; + } + break; + + case SUCCUBUS_DYING_ANIM_1: + AnimateEntity(D_80180770, self); + if (D_801A3F84 & 0x10) { + SetSubStep(SUCCUBUS_DYING_ANIM_2); + } + break; + + case SUCCUBUS_DYING_ANIM_2: + AnimateEntity(D_80180778, self); + } + break; + + case SUCCUBUS_IDLE: + if (self->step_s == 0) { + self->ext.succubus.timer = 64; + self->step_s++; + } + + AnimateEntity(D_801806C4, self); + if ((self->animFrameIdx == 3) && (self->animFrameDuration == 0)) { + func_801A046C(NA_SE_SU_FLAPPING_WINGS); + } + + posY = self->posY.i.hi - self->ext.succubus.yOffset; + if (posY > 8) { + self->velocityY = -0xC000; + } + + if (--self->ext.succubus.timer == 0) { + self->ext.succubus.nextStep = SUCCUBUS_FLY; + SetStep(SUCCUBUS_FACE_PLAYER); + } + if ((self->ext.succubus.timer < 80) && (GetDistanceToPlayerX() < 80)) { + self->ext.succubus.nextStep = SUCCUBUS_FLY; + SetStep(SUCCUBUS_FACE_PLAYER); + } + break; + + case SUCCUBUS_FLY: + switch (self->step_s) { + case SUCCUBUS_FLY_0: + self->velocityY = 0; + self->ext.succubus.timer = (Random() & 31) + 32; + self->ext.succubus.nextAttack = SUCCUBUS_PETAL_ATTACK; + self->ext.succubus.yOffset = 88; + if (!(Random() % 4)) { + if (Random() % 2) { + self->ext.succubus.nextAttack = SUCCUBUS_SPIKE_ATTACK; + self->ext.succubus.yOffset = 120; + } else { + self->ext.succubus.nextAttack = SUCCUBUS_CLONE_ATTACK; + } + } + self->ext.succubus.facing = 0; + self->step_s++; + + case SUCCUBUS_FLY_1: + AnimateEntity(D_801806E8, self); + if ((self->animFrameIdx == 3) && (self->animFrameDuration == 0)) { + func_801A046C(NA_SE_SU_FLAPPING_WINGS); + } + + MoveEntity(); + posY = self->posY.i.hi - self->ext.succubus.yOffset; + if (posY > 8) { + self->velocityY = -0xC000; + } + if (posY < -8) { + self->velocityY = 0xC000; + } + + if (self->facing != self->ext.succubus.facing) { + self->velocityX += 0x1800; + if (self->velocityX >= 0x16000) { + self->velocityX = 0x16000; + } + } else { + self->velocityX -= 0x1800; + if (self->velocityX <= -0x16000) { + self->velocityX = -0x16000; + } + } + + if (self->ext.succubus.nextAttack == SUCCUBUS_CLONE_ATTACK) { + posX = 64; + } else { + posX = 96; + } + + temp = GetDistanceToPlayerX(); + if (self->ext.succubus.facing == 0) { + if (temp < posX) { + self->ext.succubus.facing ^= 1; + } + if (self->ext.succubus.facing != 0) { + if (posX < temp) { + self->ext.succubus.facing ^= 1; + } + } + } else if (posX < temp) { + self->ext.succubus.facing ^= 1; + } + + sideToPlayer = ((GetSideToPlayer() & 1) ^ 1); + if (self->facing != sideToPlayer) { + if (temp > 16) { + self->ext.succubus.nextStep = SUCCUBUS_CLONE_ATTACK; + SetStep(SUCCUBUS_NEXT_ACTION_CHECK); + } + } + + posX = self->posX.i.hi + g_Camera.posX.i.hi; + if (self->facing != 0) { + posX = 512 - posX; + } + if (posX > 416) { + self->ext.succubus.facing = 0; + self->ext.succubus.timer = 96; + self->step_s++; + } + + if (self->ext.succubus.timer == 0) { + if (GetDistanceToPlayerX() < 96) { + if (self->ext.succubus.nextAttack == + SUCCUBUS_PETAL_ATTACK) { + SetStep(SUCCUBUS_PETAL_ATTACK); + } else { + self->ext.succubus.nextStep = + self->ext.succubus.nextAttack; + SetStep(SUCCUBUS_NEXT_ACTION_CHECK); + } + } + } else { + self->ext.succubus.timer--; + } + break; + + case SUCCUBUS_FLY_2: + if (self->facing != self->ext.succubus.facing) { + self->velocityX += 0x1800; + if (self->velocityX >= 0x16000) { + self->velocityX = 0x16000; + } + } else { + self->velocityX -= 0x1800; + if (self->velocityX <= -0x16000) { + self->velocityX = -0x16000; + } + } + AnimateEntity(D_801806E8, self); + if ((self->animFrameIdx == 3) && (self->animFrameDuration == 0)) { + func_801A046C(NA_SE_SU_FLAPPING_WINGS); + } + MoveEntity(); + if (--self->ext.succubus.timer == 0) { + self->step_s = 0; + } + break; + } + break; + + case SUCCUBUS_FACE_PLAYER: + if (self->step_s == 0) { + self->facing = (GetSideToPlayer() & 1) ^ 1; + self->step_s++; + } + + if (AnimateEntity(D_801806D4, self) == 0) { + if (self->ext.succubus.nextStep != 0) { + SetStep(self->ext.succubus.nextStep); + self->ext.succubus.nextStep = 0; + } else { + SetStep(SUCCUBUS_FLY); + } + } + break; + + case SUCCUBUS_NEXT_ACTION_CHECK: + if (AnimateEntity(D_801806F8, self) == 0) { + if (self->ext.succubus.nextStep != 0) { + SetStep(self->ext.succubus.nextStep); + self->ext.succubus.nextStep = 0; + } else { + SetStep(SUCCUBUS_IDLE); + } + } + break; + + case SUCCUBUS_PETAL_ATTACK: + switch (self->step_s) { + case SUCCUBUS_PETAL_ATTACK_SETUP: + self->facing = (GetSideToPlayer() & 1) ^ 1; + if (g_Player.unk0C & PLAYER_STATUS_CURSE) { + self->ext.succubus.unk87 = false; + } else { + self->ext.succubus.unk87 = true; + } + D_80180668 = 0; + self->step_s++; + + case SUCCUBUS_PETAL_ATTACK_ANIM: + if (AnimateEntity(D_8018070C, self) == 0) { + self->ext.succubus.timer = 128; + func_801A046C(NA_VO_SU_LAUGH); + self->step_s++; + } + break; + + case SUCCUBUS_PETAL_ATTACK_CREATE_PETALS: + if (!(self->ext.succubus.timer & 1)) { + entity = AllocEntity(&g_Entities[160], &g_Entities[192]); + if (entity != NULL) { + CreateEntityFromEntity(E_SUCCUBUS_PETAL, self, entity); + entity->facing = self->facing; + entity->zPriority = self->zPriority - 1; + } + } + if ((self->ext.succubus.timer % 64) == 0) { + func_801A046C(NA_SE_SU_PETAL_ATTACK); + } + if (--self->ext.succubus.timer == 0) { + SetStep(SUCCUBUS_FLY); + } + if (self->ext.succubus.unk87) { + if (g_Player.unk0C & PLAYER_STATUS_CURSE) { + SetStep(SUCCUBUS_CHARGE); + } + } else if (D_80180668 != 0) { + if (GetDistanceToPlayerX() > 72) { + self->ext.succubus.nextStep = SUCCUBUS_TAUNT; + SetStep(SUCCUBUS_FACE_PLAYER); + } + } + break; + } + break; + + case SUCCUBUS_CHARGE: + switch (self->step_s) { + case SUCCUBUS_CHARGE_SETUP: + self->ext.succubus.timer = 112; + self->animCurFrame = 36; + func_801A046C(NA_VO_SU_BLANK); + self->step_s++; + + case SUCCUBUS_CHARGE_FLY_TOWARDS_PLAYER: + entity = &PLAYER; + posX = entity->posX.i.hi - self->posX.i.hi; + if (self->facing != 0) { + posX -= 16; + } else { + posX += 16; + } + posY = entity->posY.i.hi - self->posY.i.hi; + + angle = ratan2(posY, posX); + self->velocityX = (rcos(angle) << 0x11) >> 0xC; + self->velocityY = (rsin(angle) << 0x11) >> 0xC; + posX = self->velocityX; + + if (self->facing != 0) { + posX = -posX; + } + if (posX > 0) { + self->velocityX = 0; + } + + MoveEntity(); + + posX = PLAYER.posX.i.hi - self->posX.i.hi; + if (self->facing == 0) { + posX = -posX; + } + posX -= 4; + if (posX < 0) { + posX = 512; + } + posY = PLAYER.posY.i.hi - self->posY.i.hi; + if (posY < 0) { + posY = -posY; + } + + if (!(g_Player.unk0C & 0x010401A2)) { + if ((posY < 12) && (posX < 24)) { + g_Player.unk60 = 1; + g_Player.unk64 = 0; + g_Player.unk62 = 0; + self->hitboxState = 0; + self->step_s++; + } + } else { + FntPrint("ng status\n"); + } + + posY = self->posY.i.hi + g_Camera.posY.i.hi; + if (posY > 176) { + self->posY.i.hi = 176 - g_Camera.posY.i.hi; + } + + if (g_Player.unk60 == 0) { + if (--self->ext.succubus.timer == 0) { + self->ext.succubus.unk87 = false; + SetStep(SUCCUBUS_FLY); + } + } + break; + + case SUCCUBUS_CHARGE_AT_PLAYER_POSITION: + entity = &PLAYER; + posX = entity->posX.i.hi; + if (self->facing != 0) { + posX -= 16; + } else { + posX += 16; + } + self->posY.i.hi = posY = entity->posY.i.hi; + self->posX.i.hi = posX; + func_801A046C(NA_VO_SU_SUCK_YOU_DRY); + self->step_s++; + + case SUCCUBUS_CHARGE_DEAL_DAMAGE: + if (AnimateEntity(D_8018071C, self) == 0) { + g_Player.unk64 = g_api.enemyDefs[347].attack; + g_Player.unk60 = 3; + self->ext.succubus.timer = 48; + self->step_s++; + } + break; + + case SUCCUBUS_CHARGE_FLY_AWAY: + if (--self->ext.succubus.timer == 0) { + g_Player.unk60 = 0; + self->hitboxState = 3; + if (self->facing != 0) { + self->velocityX = -0x40000; + } else { + self->velocityX = 0x40000; + } + self->velocityY = -0x40000; + self->animCurFrame = 47; + self->ext.succubus.timer = 24; + self->step_s++; + } + break; + + case SUCCUBUS_CHARGE_DECELERATE: + MoveEntity(); + self->velocityX -= self->velocityX >> 5; + self->velocityY -= self->velocityY >> 5; + if (--self->ext.succubus.timer == 0) { + SetStep(SUCCUBUS_TAUNT); + } + break; + } + break; + + case SUCCUBUS_TAUNT: + if (self->step_s == 0) { + self->ext.succubus.timer = 80; + self->facing = (GetSideToPlayer() & 1) ^ 1; + self->step_s++; + if (self->ext.succubus.unk87) { + func_801A046C(NA_VO_SU_DELICIOUS); + } else { + func_801A046C(NA_VO_SU_LAUGH); + } + self->ext.succubus.unk87 = false; + } + AnimateEntity(D_8018072C, self); + if ((--self->ext.succubus.timer == 0) || + (GetDistanceToPlayerX() < 32)) { + SetStep(SUCCUBUS_FLY); + } + break; + + case SUCCUBUS_CLONE_ATTACK: + switch (self->step_s) { + case SUCCUBUS_CLONE_ATTACK_CREATE_CLONES: + posX = self->posX.i.hi + g_Camera.posX.i.hi; + posX -= 192; + + // left bound limit + if (posX < 32) { + posX = 96 - ((32 - posX) % 64); + } + + // right bound limit + temp = posX + 384; + if (temp > 480) { + posX = ((posX - 96) % 64) + 32; + } + + clonesShootOrder = *g_CloneShootOrder; + temp = Random() & 3; + clonesShootOrder += temp * 7; + + entity = &g_Entities[112]; + temp = self->posX.i.hi + g_Camera.posX.i.hi; + + for (i = 0; i < 6; i++, entity++, clonesShootOrder++, posX += 64) { + CreateEntityFromEntity(E_SUCCUBUS_CLONE, self, entity); + // Giving each clone a pointer to the real Succubus + entity->ext.succubus.real = self; + if (posX == temp) { + posX += 64; + } + entity->ext.succubus.clonePosX = posX; + entity->params = *clonesShootOrder; + } + self->params = *clonesShootOrder; + self->ext.succubus.timer = 64; + self->hitboxState = 0; + D_80180660 = 6; + func_801A046C(NA_VO_SU_GRUNT_1); + func_801A046C(NA_SE_SU_CREATE_CLONES); + self->step_s++; + + case SUCCUBUS_CLONE_ATTACK_WAIT: + if (--self->ext.succubus.timer == 0) { + self->step_s = 3; + } + + case SUCCUBUS_CLONE_ATTACK_ANIM_1: + self->ext.succubus.unk85 = false; + AnimateEntity(D_80180734, self); + if ((self->animFrameIdx == 4) && (self->animFrameDuration == 0)) { + self->step_s++; + } + break; + + case SUCCUBUS_CLONE_ATTACK_PLACE_REAL: + entity = &g_Entities[112]; + temp = Random() % 6; + entity += temp; + + posX = entity->posX.i.hi; + posY = entity->posY.i.hi; + entity->posX.i.hi = self->posX.i.hi; + entity->posY.i.hi = self->posY.i.hi; + self->posX.i.hi = posX; + self->posY.i.hi = posY; + self->hitboxState = 3; + self->step_s++; + + case SUCCUBUS_CLONE_ATTACK_ANIM_2: + if (AnimateEntity(D_80180734, self) == 0) { + SetSubStep(SUCCUBUS_CLONE_ATTACK_SET_SHOOTING); + } + break; + + case SUCCUBUS_CLONE_ATTACK_SET_SHOOTING: + if (AnimateEntity(D_80180760, self) == 0) { + self->ext.succubus.unk85 = true; + // PinkBall attack delay set + self->ext.succubus.timer = (self->params * 48) + 1; + SetSubStep(SUCCUBUS_CLONE_ATTACK_STOP_SHOOTING); + } + break; + + case SUCCUBUS_CLONE_ATTACK_STOP_SHOOTING: + self->animCurFrame = 26; + if (--self->ext.succubus.timer == 0) { + self->ext.succubus.unk85 = false; + /*! @bug: Likely a typo meant to be + * SetSubStep(SUCCUBUS_CLONE_ATTACK_SHOOT_PINKBALLS); + * case SUCCUBUS_CLONE_ATTACK_SHOOT_PINKBALLS is + * inaccessible causing the real Succubus to be + * unable to do the PinkBall attack. + */ + SetSubStep(SUCCUBUS_CLONE_ATTACK_STOP_SHOOTING); + } + if (D_80180660 == 0) { + SetStep(SUCCUBUS_IDLE); + } + break; + + /* Unused: see @bug in previous case. + * The real Succubus was supposed to shoot last, hinting + * the player her real position, but the end result is + * she doesn't shoot at all. + */ + case SUCCUBUS_CLONE_ATTACK_SHOOT_PINKBALLS: + if (AnimateEntity(D_80180780, self) == 0) { + self->ext.succubus.timer = 288; + SetSubStep(SUCCUBUS_CLONE_ATTACK_ANIM_2); + } + if ((self->animFrameIdx == 4) && (self->animFrameDuration == 0)) { + func_801A046C(NA_SE_SU_CHARGE_PINKBALLS); + + for (i = 0; i < 2; i++) { + entity = AllocEntity(&g_Entities[160], &g_Entities[192]); + if (entity != NULL) { + CreateEntityFromEntity( + E_SUCCUBUS_PINK_BALL_PROJECTILE, self, entity); + entity->params = i; + if (i != 0) { + entity->posX.i.hi -= 2; + } else { + entity->posX.i.hi += 2; + } + entity->ext.succubus.real = self; + entity->posY.i.hi -= 13; + entity->zPriority = self->zPriority + 1; + } + } + } + if ((self->animFrameIdx == 5) && (self->animFrameDuration == 0)) { + func_801A046C(NA_VO_SU_GRUNT_2); + func_801A046C(NA_VO_SU_CRYSTAL_1); + func_801A046C(NA_SE_SU_SHOOT_PINKBALLS); + self->ext.succubus.unk85 = true; + } + break; + } + break; + + case SUCCUBUS_SPIKE_ATTACK: + switch (self->step_s) { + case SUCCUBUS_SPIKE_ATTACK_CREATE_SPIKES: + entity = &g_Entities[96]; + self->facing = 0; + self->ext.succubus.unk85 = false; + D_80180668 = 0; + for (facing = 0; facing < 2; facing++) { + for (i = 0; i < 4; i++, entity += 2) { + CreateEntityFromEntity(E_SUCCUBUS_WING_SPIKE, self, entity); + entity->params = i; + entity->zPriority = self->zPriority; + entity->ext.succubus.real = self; + if (facing == 0) { + entity->posX.i.hi -= 5; + } else { + entity->posX.i.hi += 4; + } + entity->posY.i.hi -= 27; + entity->facing = facing; + } + } + self->step_s++; + + case SUCCUBUS_SPIKE_ATTACK_1: + if (AnimateEntity(D_80180748, self) == 0) { + self->ext.succubus.unk85 = true; + func_801A046C(NA_VO_SU_CRYSTAL_2); + func_801A046C(NA_VO_SU_GRUNT_3); + self->ext.succubus.timer = 64; + SetSubStep(2); + } + break; + + case SUCCUBUS_SPIKE_ATTACK_2: + if (--self->ext.succubus.timer == 0) { + self->ext.succubus.unk85 = false; + self->ext.succubus.timer = 64; + self->step_s++; + } + break; + + case SUCCUBUS_SPIKE_ATTACK_3: + if (--self->ext.succubus.timer == 0) { + self->step_s++; + } + break; + + case SUCCUBUS_SPIKE_ATTACK_4: + if (AnimateEntity(D_80180760, self) == 0) { + SetStep(SUCCUBUS_IDLE); + } + if (D_80180668 != 0) { + if (GetDistanceToPlayerX() > 64) { + self->ext.succubus.nextStep = SUCCUBUS_TAUNT; + SetStep(SUCCUBUS_FACE_PLAYER); + } + } + break; + } + break; + + case SUCCUBUS_GET_HIT: + if (self->step_s == 0) { + if (Random() % 2) { + func_801A046C(NA_VO_SU_HURT_1); + } else { + func_801A046C(NA_VO_SU_HURT_2); + } + + self->ext.succubus.timer = 32; + D_80180660 = 0; + if (GetSideToPlayer() & 1) { + self->velocityX = 0x20000; + } else { + self->velocityX = -0x20000; + } + self->velocityY = -0x20000; + self->step_s++; + } + AnimateEntity(D_80180768, self); + MoveEntity(); + self->velocityX -= self->velocityX >> 5; + self->velocityY -= self->velocityY >> 5; + self->velocityY += 0x1000; + if (--self->ext.succubus.timer == 0) { + if (Random() % 2) { + self->ext.succubus.nextStep = SUCCUBUS_SPIKE_ATTACK; + } else { + self->ext.succubus.nextStep = SUCCUBUS_CLONE_ATTACK; + } + SetStep(SUCCUBUS_NEXT_ACTION_CHECK); + } + break; + + case SUCCUBUS_DEBUG: + /** + * Debug: Press SQUARE / CIRCLE on the second controller + * to advance/rewind current animation frame + */ + FntPrint("charal %x\n", self->animCurFrame); + if (g_pads[1].pressed & PAD_SQUARE) { + if (self->params == 0) { + self->animCurFrame++; + self->params |= 1; + } else { + break; + } + } else { + self->params = 0; + } + if (g_pads[1].pressed & PAD_CIRCLE) { + if (self->step_s == 0) { + self->animCurFrame--; + self->step_s |= 1; + } + } else { + self->step_s = 0; + } + break; + } + posX = self->posX.i.hi + g_Camera.posX.i.hi; + posY = self->posY.i.hi + g_Camera.posY.i.hi; + + if (self->velocityX < 0) { + if (posX < 40) { + self->posX.i.hi = 40 - g_Camera.posX.i.hi; + } + } else if (posX > 480) { + self->posX.i.hi = 480 - g_Camera.posX.i.hi; + } + if ((self->velocityY < 0) && (posY < 48)) { + self->posY.i.hi = 48 - g_Camera.posY.i.hi; + } + // TODO: !FAKE + hitbox = (s8*)&D_80180830[self->animCurFrame][D_801807F8]; + hitbox--; + hitbox++; + self->hitboxOffX = *hitbox++; + self->hitboxOffY = *hitbox++; + self->hitboxWidth = hitbox[0]; + self->hitboxHeight = hitbox[1]; +} + +const u32 rodataPadding = 0;