From dfea3429ae3a95696b2427a9c5e27131e054a141 Mon Sep 17 00:00:00 2001 From: Josh Schreuder <681706+JoshSchreuder@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:57:00 +1100 Subject: [PATCH] Decompile `no0` Skelerang entities (#1900) ~~Planning on giving this a tidy up when I import data and do some analysis in debugger so I'll fix up some of the naming then unless there's something glaring that sticks out here.~~ `func_us_801D191C` / `EntitySkelerangUnknown` was previous decompiled but slightly updated here for PSP match: https://decomp.me/scratch/8lEH0 `func_us_801D20A4` / `EntitySkelerangBoomerang` matching both PSX: https://decomp.me/scratch/SMgwb PSP: https://decomp.me/scratch/WFD0w `func_us_801D191C` / `EntitySkelerang` matching both PSX: https://decomp.me/scratch/BUkq2 PSP: https://decomp.me/scratch/OYg4D Thanks to Discord gang for helping with some PSP stuff here ^ --- config/symbols.us.stno0.txt | 3 + include/common.h | 1 + include/entity.h | 13 +- src/st/no0/e_init.c | 16 +- src/st/no0/e_skelerang.c | 346 +++++++++++++++++++++++++++++++++++- src/st/no0/no0.h | 8 + 6 files changed, 369 insertions(+), 18 deletions(-) diff --git a/config/symbols.us.stno0.txt b/config/symbols.us.stno0.txt index 5d29c7343..50c592b82 100644 --- a/config/symbols.us.stno0.txt +++ b/config/symbols.us.stno0.txt @@ -185,6 +185,9 @@ EntityStoneDoor = 0x801CE0F8; EntityClockRoomUnused = 0x801CE2D8; StageNamePopupHelper = 0x801CE654; EntityStageNamePopup = 0x801CE824; +EntitySkelerang = 0x801D191C; +EntitySkelerangBoomerang = 0x801D20A4; +EntitySkelerangUnknown = 0x801D2318; func_801CD78C = 0x801D2374; EntityGhostEnemy = 0x801D5E4C; EntityGhostEnemySpawner = 0x801D606C; diff --git a/include/common.h b/include/common.h index 9b02a9c1e..b21e01f0d 100644 --- a/include/common.h +++ b/include/common.h @@ -84,6 +84,7 @@ #define LOHU(x) (*(u16*)&(x)) #define LOW(x) (*(s32*)&(x)) #define LOWU(x) (*(u32*)&(x)) +#define F(x) (*(f32*)&(x)) #if defined(HACKS) && !defined(PERMUTER) #define ALIGNED4 __attribute__((aligned(4))) diff --git a/include/entity.h b/include/entity.h index 18a9d7758..d0c5f33cb 100644 --- a/include/entity.h +++ b/include/entity.h @@ -2055,10 +2055,6 @@ typedef struct { /* 0x84 */ u32 speed; } ET_GhostEnemy; -// ====== RIC ENTITIES ====== - -// ========================== - typedef struct { /* 0x7C */ struct Entity* next; /* 0x80 */ s16 timer; @@ -2081,6 +2077,14 @@ typedef struct { /* 0x94 */ s16 unk94; } ET_StoneRose; +typedef struct { + /* 0x7C */ s32 : 32; + /* 0x80 */ u8 angle; + /* 0x81 */ u8 : 8; + /* 0x82 */ u16 : 16; + /* 0x84 */ s16 unk84; +} ET_Skelerang; + typedef union { // offset=0x7C struct Primitive* prim; ET_Placeholder ILLEGAL; @@ -2257,6 +2261,7 @@ typedef union { // offset=0x7C ET_StoneRose stoneRose; ET_GhostEnemy ghostEnemy; ET_GhostEnemySpawner ghostEnemySpawner; + ET_Skelerang skelerang; } Ext; #define SYNC_FIELD(struct1, struct2, field) \ diff --git a/src/st/no0/e_init.c b/src/st/no0/e_init.c index a03f9ab07..2960d1891 100644 --- a/src/st/no0/e_init.c +++ b/src/st/no0/e_init.c @@ -46,9 +46,9 @@ void func_us_801C27A4(Entity*); void func_us_801C2A34(Entity*); void func_us_801C2CD8(Entity*); void func_us_801C2E7C(Entity*); -void func_us_801D191C(Entity*); -void func_us_801D20A4(Entity*); -void func_us_801D2318(Entity*); +void EntitySkelerang(Entity*); +void EntitySkelerangBoomerang(Entity*); +void EntitySkelerangUnknown(Entity*); void func_us_801D2A64(Entity*); void func_us_801D4324(Entity*); void func_us_801D44A0(Entity*); @@ -129,9 +129,9 @@ PfnEntityUpdate OVL_EXPORT(EntityUpdates)[] = { func_us_801C2A34, func_us_801C2CD8, func_us_801C2E7C, - func_us_801D191C, - func_us_801D20A4, - func_us_801D2318, + EntitySkelerang, + EntitySkelerangBoomerang, + EntitySkelerangUnknown, func_us_801D2A64, func_us_801D4324, func_us_801D44A0, @@ -189,8 +189,8 @@ EInit D_us_80180B3C = {ANIMSET_OVL(0x09), 0x08, 0x4B, 0x20E, 0x005}; EInit D_us_80180B48 = {ANIMSET_OVL(0x09), 0x16, 0x4B, 0x20E, 0x012}; EInit D_us_80180B54 = {ANIMSET_OVL(0x0D), 0x00, 0x4E, 0x2C0, 0x013}; EInit g_EInitElevator = {ANIMSET_OVL(0x0B), 0x01, 0x48, 0x223, 0x005}; -EInit D_us_80180B6C = {ANIMSET_OVL(0x04), 0x01, 0x48, 0x228, 0x00B}; -EInit D_us_80180B78 = {ANIMSET_OVL(0x04), 0x2B, 0x48, 0x228, 0x00C}; +EInit g_EInitSkelerang = {ANIMSET_OVL(0x04), 0x01, 0x48, 0x228, 0x00B}; +EInit g_EInitSkelerangBoomerang = {ANIMSET_OVL(0x04), 0x2B, 0x48, 0x228, 0x00C}; EInit D_us_80180B84 = {ANIMSET_OVL(0x05), 0x01, 0x4C, 0x22B, 0x061}; EInit D_us_80180B90 = {ANIMSET_OVL(0x05), 0x16, 0x4C, 0x22B, 0x062}; EInit g_EInitGhostEnemy = {ANIMSET_OVL(0x06), 0x01, 0x4A, 0x200, 0x09C}; diff --git a/src/st/no0/e_skelerang.c b/src/st/no0/e_skelerang.c index 23ac97d1b..3f91c2daf 100644 --- a/src/st/no0/e_skelerang.c +++ b/src/st/no0/e_skelerang.c @@ -2,17 +2,351 @@ #include "common.h" #include "no0.h" -INCLUDE_ASM("st/no0/nonmatchings/e_skelerang", func_us_801D191C); +typedef enum { + SKELERANG_INIT, + SKELERANG_GROUND_INIT, + SKELERANG_IDLE = 3, + SKELERANG_ATTACK_INIT, + SKELERANG_WAKE_ANIM, + SKELERANG_BOOMERANG_CHECK, + SKELERANG_ATTACK_PREAMBLE, + SKELERANG_COWER, + SKELERANG_POST_ATTACK, + SKELERANG_DEATH_COWER, + SKELERANG_DEATH = 12, + SKELERANG_DEATH_FLY = 14, + SKELERANG_DEBUG = 16 +} SkelerangSteps; -INCLUDE_ASM("st/no0/nonmatchings/e_skelerang", func_us_801D20A4); +typedef enum { + BOOMERANG_INIT, + BOOMERANG_PRETHROW, + BOOMERANG_FLY, + BOOMERANG_IN_HAND, + BOOMERANG_DESTROY +} SkelerangBoomerangSteps; -extern u16 g_EInitInteractable[]; +extern u16 D_us_80181E94[]; // sensors_ground +extern Point16 D_us_80181EA4[]; // positions +extern u8 D_us_80181EC8[]; // anim +extern u8 D_us_80181EDC[]; // anim +extern u8 D_us_80181F00[]; // anim +extern u8 D_us_80181F34[]; // anim +extern u8 D_us_80181F38[]; // anim +extern u8 D_us_80181F4C[]; // anim +extern u8 D_us_80181F60[]; // anim -void func_us_801D2318(Entity* entity) { - if (entity->step == 0) { +void EntitySkelerang(Entity* self) { + Entity* entity; + Entity* entityTwo; + s32 i; + u8 index; + + if (self->step % 2 && GetDistanceToPlayerY() < 32 && + GetDistanceToPlayerX() < 80) { + SetStep(SKELERANG_COWER); + } + + if ((self->flags & FLAG_DEAD) && self->step < 10) { + self->hitboxState = 0; + PlaySfxPositional(SFX_SKELETON_DEATH_C); + DestroyEntity(self + 1); + (self + 2)->step = BOOMERANG_DESTROY; + (self + 3)->step = BOOMERANG_DESTROY; + if (self->animCurFrame > 39) { + SetStep(SKELERANG_DEATH_COWER); + } else { + SetStep(SKELERANG_DEATH); + } + } + + switch (self->step) { + case SKELERANG_INIT: + InitializeEntity(g_EInitSkelerang); + CreateEntityFromEntity(E_SKELERANG_UNK, self, self + 1); + + entity = self + 2; + CreateEntityFromEntity(E_SKELERANG_BOOMERANG, self, entity); + entity->params = 0; + entity->facingLeft = self->params; + + entity = self + 3; + CreateEntityFromEntity(E_SKELERANG_BOOMERANG, self, entity); + entity->params = 1; + entity->facingLeft = self->params; + + self->facingLeft = self->params; + break; + case SKELERANG_GROUND_INIT: + if (UnkCollisionFunc3(D_us_80181E94) & 1) { + SetStep(SKELERANG_IDLE); + } + break; + case SKELERANG_IDLE: + self->animCurFrame = 1; + // When player gets close enough shift into wake up animation + if (!(self->posY.i.hi & 256) && GetDistanceToPlayerX() < 128) { + if (((GetSideToPlayer() & 1) ^ 1) == self->facingLeft) { + SetStep(SKELERANG_WAKE_ANIM); + } + } + break; + case SKELERANG_WAKE_ANIM: + if (!AnimateEntity(D_us_80181EC8, self)) { + SetStep(SKELERANG_ATTACK_INIT); + } + break; + case SKELERANG_POST_ATTACK: + if (!self->step_s) { + self->ext.skelerang.unk84 = 0; + self->step_s++; + } + if (!AnimateEntity(D_us_80181EDC, self)) { + self->ext.skelerang.unk84++; + } + if (self->ext.skelerang.unk84 == 3) { + self->ext.skelerang.unk84 = 0; + // If player is still in range after throwing 3 boomerangs, keep + // attacking, else return to idle + if (GetDistanceToPlayerX() < 144 && GetDistanceToPlayerY() < 144 && + ((GetSideToPlayer() & 1) ^ 1) == self->facingLeft) { + SetStep(SKELERANG_ATTACK_INIT); + } else { + SetStep(SKELERANG_IDLE); + } + } + break; + case SKELERANG_ATTACK_INIT: + // Play catch animation + if (!AnimateEntity(D_us_80181F00, self)) { + SetStep(SKELERANG_BOOMERANG_CHECK); + } + if (self->animCurFrame == 30) { + // Spawn thrown boomerangs + PlaySfxPositional(SFX_THROW_WEAPON_SWISHES); + entityTwo = &PLAYER; + entity = self + 1; + entity->posX.i.hi = entityTwo->posX.i.hi; + entity->posY.i.hi = entityTwo->posY.i.hi; + + entity = self + 2; + entity->posX.i.hi = self->posX.i.hi; + entity->posY.i.hi = self->posY.i.hi; + entity->animCurFrame = 43; + entity->step++; + + entity = self + 3; + entity->posX.i.hi = self->posX.i.hi; + entity->posY.i.hi = self->posY.i.hi; + entity->animCurFrame = 43; + entity->step++; + } + break; + case SKELERANG_BOOMERANG_CHECK: + entity = self + 2; + entityTwo = self + 3; + // If boomerangs are back in hand, prep for another attack + if (entity->step == BOOMERANG_IN_HAND && + entityTwo->step == BOOMERANG_IN_HAND) { + entity->step = BOOMERANG_PRETHROW; + entityTwo->step = BOOMERANG_PRETHROW; + SetStep(SKELERANG_ATTACK_PREAMBLE); + } + break; + case SKELERANG_ATTACK_PREAMBLE: + if (!AnimateEntity(D_us_80181F34, self)) { + self->ext.skelerang.unk84++; + // Throw boomerangs 3 times, then return to attack init + if (self->ext.skelerang.unk84 > 2) { + SetStep(SKELERANG_POST_ATTACK); + } else { + SetStep(SKELERANG_ATTACK_INIT); + } + } + break; + case SKELERANG_COWER: + switch (self->step_s) { + case 0: + if (!AnimateEntity(D_us_80181F38, self)) { + self->animFrameIdx = 0; + self->animFrameDuration = 0; + self->step_s++; + } + break; + case 1: + // Play cower animation until player gets far enough away + AnimateEntity(D_us_80181F60, self); + if ((GetDistanceToPlayerX() > 80) || + (GetDistanceToPlayerY() > 48)) { + self->animFrameIdx = 0; + self->animFrameDuration = 0; + self->step_s++; + } + break; + case 2: + // Once player is far enough away move out of cower and back into + // potential attack state + if (!AnimateEntity(D_us_80181F4C, self)) { + SetStep(SKELERANG_POST_ATTACK); + } + break; + } + break; + case SKELERANG_DEATH_COWER: + entity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (entity != NULL) { + CreateEntityFromEntity(2, self, entity); + entity->params = 2; + entity->posY.i.hi += 24; + } + DestroyEntity(self); + break; + case SKELERANG_DEATH: + for (i = 0; i < 8; i++) { + entity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (entity != NULL) { + MakeEntityFromId(E_SKELERANG, self, entity); + entity->flags = FLAG_DESTROY_IF_OUT_OF_CAMERA | + FLAG_POS_CAMERA_LOCKED | FLAG_UNK_2000; + entity->hitboxState = 0; + entity->animCurFrame = i + 44; + entity->params = i; + entity->facingLeft = self->facingLeft; + if ((GetSideToPlayer() & 1) ^ 1) { + // Sus store to Entity 0xA + F(entity->velocityX).i.hi = -(Random() & 3); + } else { + // Sus store to Entity 0xA + // This + 1 - 1 is an oddity that is required to align PSP + F(entity->velocityX).i.hi = (Random() & 3) + 1 - 1; + } + // Sus store to Entity 0xE + F(entity->velocityY).i.hi = -2 - (Random() & 3); + entity->ext.skelerang.unk84 = 16; + entity->step = SKELERANG_DEATH_FLY; + } + } + + entity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (entity != NULL) { + CreateEntityFromEntity(2, self, entity); + entity->params = 2; + } + DestroyEntity(self); + break; + case SKELERANG_DEATH_FLY: + // When dying body goes flying back in flames + MoveEntity(); + self->velocityY += FIX(0.1875); + if (!--self->ext.skelerang.unk84) { + entity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (entity != NULL) { + CreateEntityFromEntity(2, self, entity); + index = self->params; + if (self->facingLeft) { + entity->posX.i.hi -= D_us_80181EA4[index].x; + } else { + entity->posX.i.hi += D_us_80181EA4[index].x; + } + entity->posY.i.hi += D_us_80181EA4[index].y; + } + DestroyEntity(self); + } + break; + case SKELERANG_DEBUG: +#include "../pad2_anim_debug.h" + } + + if (self->animCurFrame > 39) { + self->hitboxOffX = -1; + self->hitboxOffY = 16; + self->hitboxWidth = 8; + self->hitboxHeight = 8; + } else { + self->hitboxOffX = 0; + self->hitboxOffY = 2; + self->hitboxWidth = 5; + self->hitboxHeight = 21; + } +} + +void EntitySkelerangBoomerang(Entity* self) { + Entity* entity; + u8 step; + s16 angle; + s16 posX; + s16 posY; + + switch (self->step) { + case BOOMERANG_INIT: + InitializeEntity(g_EInitSkelerangBoomerang); + self->drawFlags |= FLAG_DRAW_ROTZ; + self->animCurFrame = 0; + self->hitboxState = 0; + break; + case BOOMERANG_PRETHROW: + entity = (self - 2) - self->params; + self->posX.i.hi = entity->posX.i.hi; + self->posY.i.hi = entity->posY.i.hi; + self->hitboxState = 0; + self->animCurFrame = 0; + self->step_s = 0; + self->ext.skelerang.unk84 = 48; + if (self->params) { + self->ext.skelerang.angle = 0; + return; + } + self->ext.skelerang.angle = 128; + break; + case BOOMERANG_FLY: + self->hitboxState = 1; + MoveEntity(); + self->rotZ += 256; + if (!self->ext.skelerang.unk84) { + self->step_s = 1; + } else { + self->ext.skelerang.unk84--; + } + entity = (self - 1) - self->params - self->step_s; + angle = GetAngleBetweenEntitiesShifted(self, entity); + step = self->step_s + 3; + self->ext.skelerang.angle = + AdjustValueWithinThreshold(step, self->ext.skelerang.angle, angle); + SetEntityVelocityFromAngle(self->ext.skelerang.angle, 48); + posX = entity->posX.i.hi - self->posX.i.hi; + posY = entity->posY.i.hi - self->posY.i.hi; + posX = abs(posX); + posY = abs(posY); + if (posX < 6 && posY < 6) { + PlaySfxPositional(SFX_MULTI_CLOCK_TICK); + self->step_s++; + } + if (self->step_s == 2) { + self->step++; + } + break; + case BOOMERANG_IN_HAND: + self->hitboxState = 0; + self->rotZ = 512; + break; + case BOOMERANG_DESTROY: + entity = AllocEntity(&g_Entities[224], &g_Entities[256]); + if (entity != NULL) { + CreateEntityFromEntity(2, self, entity); + entity->params = 1; + } + DestroyEntity(self); + break; + } +} + +void EntitySkelerangUnknown(Entity* entity) { + Entity* parent; + if (!entity->step) { InitializeEntity(g_EInitInteractable); } - if ((entity - 1)->entityId != 0x2E) { + parent = entity - 1; + if (parent->entityId != E_SKELERANG) { DestroyEntity(entity); } } diff --git a/src/st/no0/no0.h b/src/st/no0/no0.h index 7ab4f5a12..53f8b3fb1 100644 --- a/src/st/no0/no0.h +++ b/src/st/no0/no0.h @@ -30,6 +30,9 @@ typedef enum EntityIDs { /* 0x14 */ E_ID_14 = 0x14, /* 0x15 */ E_GREY_PUFF, /* 0x1D */ E_CLOCK_ROOM_SHADOW = 0x20, + /* 0x2E */ E_SKELERANG = 0x2E, + /* 0x2F */ E_SKELERANG_BOOMERANG, + /* 0x30 */ E_SKELERANG_UNK, /* 0x37 */ E_GHOST_ENEMY = 0x37, /* 0x3B */ E_SLINGER_THROWN_BONE = 0x3B, /* 0x3C */ E_SLINGER_PIECES, @@ -52,6 +55,7 @@ typedef enum EntityIDs { extern s16 g_SineTable[]; extern u16 g_EInitCommon[]; extern u16 g_EInitParticle[]; +extern u16 g_EInitInteractable[]; // Axe knight extern EInit g_EInitAxeKnightAxe; @@ -76,6 +80,10 @@ extern EInit g_EInitSlingerRib; // Ghost (enemy) extern EInit g_EInitGhostEnemy; +// Skelerang +extern EInit g_EInitSkelerang; +extern EInit g_EInitSkelerangBoomerang; + // Clock room extern EInit g_Statues;