diff --git a/assets/xml/objects/gameplay_keep.xml b/assets/xml/objects/gameplay_keep.xml
index cd93dfe3ab..3bfb60efde 100644
--- a/assets/xml/objects/gameplay_keep.xml
+++ b/assets/xml/objects/gameplay_keep.xml
@@ -834,10 +834,12 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/assets/xml/objects/object_rat.xml b/assets/xml/objects/object_rat.xml
index ce32e20ccd..af47a34373 100644
--- a/assets/xml/objects/object_rat.xml
+++ b/assets/xml/objects/object_rat.xml
@@ -1,28 +1,38 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spec b/spec
index 6811fb622f..c2e3e6e398 100644
--- a/spec
+++ b/spec
@@ -2864,8 +2864,7 @@ beginseg
name "ovl_En_Rat"
compress
include "build/src/overlays/actors/ovl_En_Rat/z_en_rat.o"
- include "build/data/ovl_En_Rat/ovl_En_Rat.data.o"
- include "build/data/ovl_En_Rat/ovl_En_Rat.reloc.o"
+ include "build/src/overlays/actors/ovl_En_Rat/ovl_En_Rat_reloc.o"
endseg
beginseg
diff --git a/src/overlays/actors/ovl_En_Bom/z_en_bom.c b/src/overlays/actors/ovl_En_Bom/z_en_bom.c
index 87f5cf1ba2..6b79d6021f 100644
--- a/src/overlays/actors/ovl_En_Bom/z_en_bom.c
+++ b/src/overlays/actors/ovl_En_Bom/z_en_bom.c
@@ -610,7 +610,7 @@ void EnBom_Draw(Actor* thisx, PlayState* play) {
Matrix_MultVec3f(&D_80872EE0, &this->actor.home.pos);
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
- gSPDisplayList(POLY_OPA_DISP++, gameplay_keep_DL_015FA0);
+ gSPDisplayList(POLY_OPA_DISP++, gBombCapDL);
Matrix_ReplaceRotation(&play->billboardMtxF);
Matrix_RotateXS(0x4000, MTXMODE_APPLY);
@@ -619,7 +619,7 @@ void EnBom_Draw(Actor* thisx, PlayState* play) {
gDPPipeSync(POLY_OPA_DISP++);
gDPSetEnvColor(POLY_OPA_DISP++, (s8)this->unk_1F4, 0, 40, 255);
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, (s8)this->unk_1F4, 0, 40, 255);
- gSPDisplayList(POLY_OPA_DISP++, gameplay_keep_DL_015DB0);
+ gSPDisplayList(POLY_OPA_DISP++, gBombBodyDL);
} else {
Vec3f sp58;
Vec3f sp4C;
diff --git a/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c b/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c
index b2b4ec362a..b1b9f8d14d 100644
--- a/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c
+++ b/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c
@@ -116,7 +116,6 @@ s32 EnBomChu_UpdateFloorPoly(EnBomChu* this, CollisionPoly* floorPoly, PlayState
}
normDotUp = DOTXYZ(normal, this->axisUp);
-
if (fabsf(normDotUp) >= 0.999f) {
return false;
}
@@ -129,7 +128,6 @@ s32 EnBomChu_UpdateFloorPoly(EnBomChu* this, CollisionPoly* floorPoly, PlayState
Math3D_CrossProduct(&this->axisUp, &normal, &vec);
magnitude = Math3D_Vec3fMagnitude(&vec);
-
if (magnitude < 0.001f) {
EnBomChu_Explode(this, play);
return false;
@@ -357,7 +355,8 @@ void EnBomChu_WaitForDeath(EnBomChu* this, PlayState* play) {
}
/**
- * Transform coordinates from actor coordinate space to world space, according to current orientation.
+ * Transform coordinates from actor coordinate space (origin at actor's world.pos, z-axis is facing
+ * angle, i.e. shape.rot.y) to world space, according to current orientation.
* `offset` is expected to already be at world scale.
*/
void EnBomChu_ActorCoordsToWorld(EnBomChu* this, Vec3f* offset, Vec3f* pos) {
@@ -497,7 +496,7 @@ void EnBomChu_Update(Actor* thisx, PlayState* play) {
if (this->isMoving) {
this->visualJitter =
- (5.0f + (Rand_ZeroOne() * 3.0f)) * Math_SinS((((s32)(Rand_ZeroOne() * 512.0f) + 0x3000) * this->timer));
+ (5.0f + (Rand_ZeroOne() * 3.0f)) * Math_SinS((((s32)(Rand_ZeroOne() * 0x200) + 0x3000) * this->timer));
EnBomChu_ActorCoordsToWorld(this, &sBlureP1Offset, &blureP1);
EnBomChu_ActorCoordsToWorld(this, &sBlureP2LeftOffset, &blureP2);
diff --git a/src/overlays/actors/ovl_En_Rat/z_en_rat.c b/src/overlays/actors/ovl_En_Rat/z_en_rat.c
index 207cf73233..1054876aab 100644
--- a/src/overlays/actors/ovl_En_Rat/z_en_rat.c
+++ b/src/overlays/actors/ovl_En_Rat/z_en_rat.c
@@ -5,6 +5,8 @@
*/
#include "z_en_rat.h"
+#include "objects/gameplay_keep/gameplay_keep.h"
+#include "overlays/actors/ovl_En_Bom/z_en_bom.h"
#define FLAGS (ACTOR_FLAG_1 | ACTOR_FLAG_4 | ACTOR_FLAG_200)
@@ -15,7 +17,24 @@ void EnRat_Destroy(Actor* thisx, PlayState* play);
void EnRat_Update(Actor* thisx, PlayState* play);
void EnRat_Draw(Actor* thisx, PlayState* play);
-#if 0
+void EnRat_InitializeAxes(EnRat* this);
+void EnRat_UpdateRotation(EnRat* this);
+void EnRat_Revive(EnRat* this, PlayState* play);
+void EnRat_SetupIdle(EnRat* this);
+void EnRat_Idle(EnRat* this, PlayState* play);
+void EnRat_SetupSpottedPlayer(EnRat* this);
+void EnRat_SpottedPlayer(EnRat* this, PlayState* play);
+void EnRat_SetupChasePlayer(EnRat* this);
+void EnRat_ChasePlayer(EnRat* this, PlayState* play);
+void EnRat_Bounced(EnRat* this, PlayState* play);
+void EnRat_Explode(EnRat* this, PlayState* play);
+void EnRat_PostDetonation(EnRat* this, PlayState* play);
+
+typedef enum {
+ /* -2 */ EN_RAT_HOOK_STARTED = -2,
+ /* -1 */ EN_RAT_HOOKED,
+} EnRatHookedState;
+
const ActorInit En_Rat_InitVars = {
ACTOR_EN_RAT,
ACTORCAT_ENEMY,
@@ -28,123 +47,920 @@ const ActorInit En_Rat_InitVars = {
(ActorFunc)EnRat_Draw,
};
-// static ColliderSphereInit sSphereInit = {
-static ColliderSphereInit D_80A58400 = {
- { COLTYPE_NONE, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_SPHERE, },
- { ELEMTYPE_UNK0, { 0xF7CFFFFF, 0x00, 0x08 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON | BUMP_HOOKABLE, OCELEM_ON, },
+static ColliderSphereInit sSphereInit = {
+ {
+ COLTYPE_NONE,
+ AT_ON | AT_TYPE_ENEMY,
+ AC_ON | AC_TYPE_PLAYER,
+ OC1_ON | OC1_TYPE_ALL,
+ OC2_TYPE_1,
+ COLSHAPE_SPHERE,
+ },
+ {
+ ELEMTYPE_UNK0,
+ { 0xF7CFFFFF, 0x00, 0x08 },
+ { 0xF7CFFFFF, 0x00, 0x00 },
+ TOUCH_ON | TOUCH_SFX_HARD,
+ BUMP_ON | BUMP_HOOKABLE,
+ OCELEM_ON,
+ },
{ 1, { { 0, 0, 0 }, 23 }, 100 },
};
-// static DamageTable sDamageTable = {
-static DamageTable D_80A5842C = {
- /* Deku Nut */ DMG_ENTRY(0, 0x1),
- /* Deku Stick */ DMG_ENTRY(1, 0x0),
- /* Horse trample */ DMG_ENTRY(1, 0x0),
- /* Explosives */ DMG_ENTRY(1, 0x0),
- /* Zora boomerang */ DMG_ENTRY(1, 0x0),
- /* Normal arrow */ DMG_ENTRY(1, 0x0),
- /* UNK_DMG_0x06 */ DMG_ENTRY(0, 0x0),
- /* Hookshot */ DMG_ENTRY(0, 0xF),
- /* Goron punch */ DMG_ENTRY(1, 0x0),
- /* Sword */ DMG_ENTRY(1, 0x0),
- /* Goron pound */ DMG_ENTRY(1, 0x0),
- /* Fire arrow */ DMG_ENTRY(2, 0x0),
- /* Ice arrow */ DMG_ENTRY(2, 0x0),
- /* Light arrow */ DMG_ENTRY(2, 0x0),
- /* Goron spikes */ DMG_ENTRY(1, 0x0),
- /* Deku spin */ DMG_ENTRY(1, 0x0),
- /* Deku bubble */ DMG_ENTRY(1, 0x0),
- /* Deku launch */ DMG_ENTRY(2, 0x0),
- /* UNK_DMG_0x12 */ DMG_ENTRY(0, 0x1),
- /* Zora barrier */ DMG_ENTRY(1, 0x0),
- /* Normal shield */ DMG_ENTRY(0, 0x0),
- /* Light ray */ DMG_ENTRY(0, 0x0),
- /* Thrown object */ DMG_ENTRY(1, 0x0),
- /* Zora punch */ DMG_ENTRY(1, 0x0),
- /* Spin attack */ DMG_ENTRY(1, 0x0),
- /* Sword beam */ DMG_ENTRY(0, 0x0),
- /* Normal Roll */ DMG_ENTRY(0, 0x0),
- /* UNK_DMG_0x1B */ DMG_ENTRY(0, 0x0),
- /* UNK_DMG_0x1C */ DMG_ENTRY(0, 0x0),
- /* Unblockable */ DMG_ENTRY(0, 0x0),
- /* UNK_DMG_0x1E */ DMG_ENTRY(0, 0x0),
- /* Powder Keg */ DMG_ENTRY(1, 0x0),
+typedef enum {
+ /* 0x0 */ EN_RAT_DMGEFF_NONE,
+ /* 0x1 */ EN_RAT_DMGEFF_STUN,
+ /* 0xF */ EN_RAT_DMGEFF_HOOKSHOT = 0xF, // Pulls the Real Bombchu towards the player
+} EnRatDamageEffect;
+
+static DamageTable sDamageTable = {
+ /* Deku Nut */ DMG_ENTRY(0, EN_RAT_DMGEFF_STUN),
+ /* Deku Stick */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Horse trample */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Explosives */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Zora boomerang */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Normal arrow */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* UNK_DMG_0x06 */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Hookshot */ DMG_ENTRY(0, EN_RAT_DMGEFF_HOOKSHOT),
+ /* Goron punch */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Sword */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Goron pound */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Fire arrow */ DMG_ENTRY(2, EN_RAT_DMGEFF_NONE),
+ /* Ice arrow */ DMG_ENTRY(2, EN_RAT_DMGEFF_NONE),
+ /* Light arrow */ DMG_ENTRY(2, EN_RAT_DMGEFF_NONE),
+ /* Goron spikes */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Deku spin */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Deku bubble */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Deku launch */ DMG_ENTRY(2, EN_RAT_DMGEFF_NONE),
+ /* UNK_DMG_0x12 */ DMG_ENTRY(0, EN_RAT_DMGEFF_STUN),
+ /* Zora barrier */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Normal shield */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Light ray */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Thrown object */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Zora punch */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Spin attack */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
+ /* Sword beam */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Normal Roll */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* UNK_DMG_0x1B */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* UNK_DMG_0x1C */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Unblockable */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* UNK_DMG_0x1E */ DMG_ENTRY(0, EN_RAT_DMGEFF_NONE),
+ /* Powder Keg */ DMG_ENTRY(1, EN_RAT_DMGEFF_NONE),
};
-// sColChkInfoInit
-static CollisionCheckInfoInit D_80A5844C = { 1, 30, 30, 50 };
+static CollisionCheckInfoInit sColChkInfoInit = { 1, 30, 30, 50 };
-// static InitChainEntry sInitChain[] = {
-static InitChainEntry D_80A58464[] = {
+static TexturePtr sSparkTextures[] = {
+ gElectricSpark1Tex,
+ gElectricSpark2Tex,
+ gElectricSpark3Tex,
+ gElectricSpark4Tex,
+};
+
+static InitChainEntry sInitChain[] = {
ICHAIN_S8(hintId, 97, ICHAIN_CONTINUE),
ICHAIN_VEC3F_DIV1000(scale, 15, ICHAIN_CONTINUE),
ICHAIN_F32(targetArrowOffset, 5000, ICHAIN_STOP),
};
-#endif
+static EffectBlureInit2 sBlureInit = {
+ 0, 0, 0, { 250, 0, 0, 250 }, { 200, 0, 0, 130 }, { 150, 0, 0, 100 }, { 100, 0, 0, 50 }, 16,
+ 0, 0, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
+};
-extern ColliderSphereInit D_80A58400;
-extern DamageTable D_80A5842C;
-extern CollisionCheckInfoInit D_80A5844C;
-extern InitChainEntry D_80A58464[];
+static s32 sTexturesDesegmented = false;
-extern UNK_TYPE D_06000174;
-extern UNK_TYPE D_0600026C;
+void EnRat_Init(Actor* thisx, PlayState* play) {
+ s32 pad;
+ EnRat* this = THIS;
+ s32 attackRange;
+ s32 i;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/EnRat_Init.s")
+ Actor_ProcessInitChain(&this->actor, sInitChain);
+ Collider_InitAndSetSphere(play, &this->collider, &this->actor, &sSphereInit);
+ this->collider.dim.worldSphere.radius = sSphereInit.dim.modelSphere.radius;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/EnRat_Destroy.s")
+ attackRange = EN_RAT_GET_ATTACK_RANGE(&this->actor);
+ if (EN_RAT_IS_OVERWORLD_TYPE(&this->actor)) {
+ this->actor.params = EN_RAT_TYPE_OVERWORLD;
+ } else {
+ this->actor.params = EN_RAT_TYPE_DUNGEON;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A563CC.s")
+ SkelAnime_InitFlex(play, &this->skelAnime, &gRealBombchuSkel, &gRealBombchuRunAnim, this->jointTable,
+ this->morphTable, REAL_BOMBCHU_LIMB_MAX);
+ ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 25.0f);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A56444.s")
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON) {
+ Effect_Add(play, &this->blure1Index, EFFECT_BLURE2, 0, 0, &sBlureInit);
+ Effect_Add(play, &this->blure2Index, EFFECT_BLURE2, 0, 0, &sBlureInit);
+ this->timer = 30;
+ } else {
+ this->timer = 150;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A5665C.s")
+ CollisionCheck_SetInfo(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit);
+ EnRat_InitializeAxes(this);
+ EnRat_UpdateRotation(this);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A566E0.s")
+ if ((attackRange == 255) || (attackRange == 0)) {
+ this->attackRange = 350.0f;
+ } else if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON) {
+ this->attackRange = attackRange * 0.1f * 40.0f;
+ } else {
+ this->attackRange = attackRange * 0.5f * 40.0f;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A56994.s")
+ if (!sTexturesDesegmented) {
+ for (i = 0; i < ARRAY_COUNT(sSparkTextures); i++) {
+ sSparkTextures[i] = Lib_SegmentedToVirtual(sSparkTextures[i]);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A56AFC.s")
+ sTexturesDesegmented = true;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A56EB8.s")
+ EnRat_SetupIdle(this);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A56F68.s")
+void EnRat_Destroy(Actor* thisx, PlayState* play) {
+ EnRat* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57010.s")
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON) {
+ Effect_Destroy(play, this->blure1Index);
+ Effect_Destroy(play, this->blure2Index);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57118.s")
+ Collider_DestroySphere(play, &this->collider);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57180.s")
+void EnRat_InitializeAxes(EnRat* this) {
+ Matrix_RotateYS(this->actor.shape.rot.y, MTXMODE_NEW);
+ Matrix_RotateXS(this->actor.shape.rot.x, MTXMODE_APPLY);
+ Matrix_RotateZS(this->actor.shape.rot.z, MTXMODE_APPLY);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A5723C.s")
+ Matrix_MultVecZ(1.0f, &this->axisForwards);
+ Matrix_MultVecY(1.0f, &this->axisUp);
+ Matrix_MultVecX(1.0f, &this->axisLeft);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57330.s")
+/**
+ * Returns true if floorPoly is valid for the Real Bombchu to move on, false otherwise.
+ */
+s32 EnRat_UpdateFloorPoly(EnRat* this, CollisionPoly* floorPoly, PlayState* play) {
+ Vec3f normal;
+ Vec3f vec;
+ f32 angle;
+ f32 magnitude;
+ f32 normDotUp;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57384.s")
+ this->actor.floorPoly = floorPoly;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57488.s")
+ if (floorPoly != NULL) {
+ normal.x = COLPOLY_GET_NORMAL(floorPoly->normal.x);
+ normal.y = COLPOLY_GET_NORMAL(floorPoly->normal.y);
+ normal.z = COLPOLY_GET_NORMAL(floorPoly->normal.z);
+ } else {
+ normal.x = 0.0f;
+ normal.z = 0.0f;
+ normal.y = 1.0f;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A574E8.s")
+ normDotUp = DOTXYZ(normal, this->axisUp);
+ if (fabsf(normDotUp) >= 0.999f) {
+ return false;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57570.s")
+ angle = func_80086C48(normDotUp);
+ if (angle < 0.001f) {
+ return false;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A575F4.s")
+ Math3D_CrossProduct(&this->axisUp, &normal, &vec);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A5764C.s")
+ magnitude = Math3D_Vec3fMagnitude(&vec);
+ if (magnitude < 0.001f) {
+ EnRat_Explode(this, play);
+ return false;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57918.s")
+ Math_Vec3f_Scale(&vec, 1.0f / magnitude);
+ Matrix_RotateAxisF(angle, &vec, MTXMODE_NEW);
+ Matrix_MultVec3f(&this->axisLeft, &vec);
+ Math_Vec3f_Copy(&this->axisLeft, &vec);
+ Math3D_CrossProduct(&this->axisLeft, &normal, &this->axisForwards);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57984.s")
+ magnitude = Math3D_Vec3fMagnitude(&this->axisForwards);
+ if (magnitude < 0.001f) {
+ EnRat_Explode(this, play);
+ return false;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57A08.s")
+ Math_Vec3f_Scale(&this->axisForwards, 1.0f / magnitude);
+ Math_Vec3f_Copy(&this->axisUp, &normal);
+ return true;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57A9C.s")
+void EnRat_UpdateRotation(EnRat* this) {
+ MtxF mf;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/EnRat_Update.s")
+ mf.xx = this->axisLeft.x;
+ mf.yx = this->axisLeft.y;
+ mf.zx = this->axisLeft.z;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57F10.s")
+ mf.xy = this->axisUp.x;
+ mf.yy = this->axisUp.y;
+ mf.zy = this->axisUp.z;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/func_80A57F4C.s")
+ mf.xz = this->axisForwards.x;
+ mf.yz = this->axisForwards.y;
+ mf.zz = this->axisForwards.z;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Rat/EnRat_Draw.s")
+ Matrix_MtxFToYXZRot(&mf, &this->actor.world.rot, false);
+ this->actor.world.rot.x = -this->actor.world.rot.x;
+}
+
+/**
+ * Chooses to either look at the player (if the Real Bombchu has spotted the player or
+ * is chasing after them) or at some other semi-random point (if the Bombchu is idle).
+ */
+void EnRat_ChooseDirection(EnRat* this) {
+ Vec3f newAxisForwards;
+ s16 angle;
+
+ if (this->actionFunc != EnRat_Idle) {
+ angle = this->actor.yawTowardsPlayer - this->actor.shape.rot.y;
+ if (this->axisUp.y < -0.25f) {
+ angle -= 0x8000;
+ }
+ } else {
+ if (Actor_DistanceToPoint(&this->actor, &this->actor.home.pos) > 50.0f) {
+ Vec3f homeInHome;
+ Vec3f worldInHome;
+ Vec3f worldPlusForwardInHome;
+ Vec3f upInHome;
+
+ // Set up matrix to unrotate, to make the "home rot" the new basis triad
+ Matrix_RotateZS(-this->actor.home.rot.z, MTXMODE_NEW);
+ Matrix_RotateXS(-this->actor.home.rot.x, MTXMODE_APPLY);
+ Matrix_RotateYS(-this->actor.home.rot.y, MTXMODE_APPLY);
+
+ // Unrotate axisUp into "home rot" triad and store in upfInHome
+ Matrix_MultVec3f(&this->axisUp, &upInHome);
+
+ // Move world.pos forward by axisForwards and store in homeInHome
+ Math_Vec3f_Sum(&this->actor.world.pos, &this->axisForwards, &homeInHome);
+
+ // Unrotate homeInHome into "home rot" triad and store in worldPlusForwardInHome
+ Matrix_MultVec3f(&homeInHome, &worldPlusForwardInHome);
+
+ // Unrotate home into "home rot" triad
+ Matrix_MultVec3f(&this->actor.home.pos, &homeInHome);
+
+ // Unrotate world into "home rot" triad
+ Matrix_MultVec3f(&this->actor.world.pos, &worldInHome);
+
+ angle = Math_Vec3f_Yaw(&worldInHome, &homeInHome) - Math_Vec3f_Yaw(&worldInHome, &worldPlusForwardInHome);
+ if (upInHome.y < -0.25f) {
+ angle -= 0x8000;
+ }
+
+ angle += (s16)randPlusMinusPoint5Scaled(0x800);
+ } else {
+ angle = (Rand_ZeroOne() < 0.1f) ? (s16)randPlusMinusPoint5Scaled(0x800) : 0;
+ }
+ }
+
+ angle = CLAMP(angle, -0x800, 0x800);
+ Matrix_RotateAxisF(angle * (M_PI / 0x8000), &this->axisUp, MTXMODE_NEW);
+ Matrix_MultVec3f(&this->axisForwards, &newAxisForwards);
+ Math_Vec3f_Copy(&this->axisForwards, &newAxisForwards);
+ Math3D_CrossProduct(&this->axisUp, &this->axisForwards, &this->axisLeft);
+ this->shouldRotateOntoSurfaces = true;
+}
+
+/**
+ * Returns true if the Real Bombchu is on a collision poly or is on the water's surface.
+ */
+s32 EnRat_IsOnCollisionPoly(PlayState* play, Vec3f* posA, Vec3f* posB, Vec3f* posResult, CollisionPoly** poly,
+ s32* bgId) {
+ WaterBox* waterBox;
+ s32 isOnWater;
+ f32 waterSurface;
+
+ if (WaterBox_GetSurface1(play, &play->colCtx, posB->x, posB->z, &waterSurface, &waterBox) &&
+ (waterSurface <= posA->y) && (posB->y <= waterSurface)) {
+ isOnWater = true;
+ } else {
+ isOnWater = false;
+ }
+
+ if (BgCheck_EntityLineTest1(&play->colCtx, posA, posB, posResult, poly, 1, 1, 1, 1, bgId)) {
+ if (!(func_800C9A4C(&play->colCtx, *poly, *bgId) & 0x30) && (!isOnWater || (waterSurface <= posResult->y))) {
+ return true;
+ }
+ }
+
+ if (isOnWater) {
+ posResult->x = posB->x;
+ posResult->y = waterSurface;
+ posResult->z = posB->z;
+ *poly = NULL;
+ *bgId = BGCHECK_SCENE;
+ return true;
+ }
+
+ return false;
+}
+
+s32 EnRat_IsTouchingSurface(EnRat* this, PlayState* play) {
+ CollisionPoly* polySide = NULL;
+ CollisionPoly* polyUpDown = NULL;
+ s32 bgIdSide;
+ s32 bgIdUpDown;
+ s32 i;
+ f32 lineLength;
+ Vec3f posA;
+ Vec3f posB;
+ Vec3f posSide;
+ Vec3f posUpDown;
+
+ bgIdUpDown = bgIdSide = BGCHECK_SCENE;
+
+ lineLength = 2.0f * this->actor.speedXZ;
+
+ posA.x = this->actor.world.pos.x + (this->axisUp.x * 5.0f);
+ posA.y = this->actor.world.pos.y + (this->axisUp.y * 5.0f);
+ posA.z = this->actor.world.pos.z + (this->axisUp.z * 5.0f);
+
+ posB.x = this->actor.world.pos.x - (this->axisUp.x * 4.0f);
+ posB.y = this->actor.world.pos.y - (this->axisUp.y * 4.0f);
+ posB.z = this->actor.world.pos.z - (this->axisUp.z * 4.0f);
+
+ if (EnRat_IsOnCollisionPoly(play, &posA, &posB, &posUpDown, &polyUpDown, &bgIdUpDown)) {
+ posB.x = (this->axisForwards.x * lineLength) + posA.x;
+ posB.y = (this->axisForwards.y * lineLength) + posA.y;
+ posB.z = (this->axisForwards.z * lineLength) + posA.z;
+
+ if (EnRat_IsOnCollisionPoly(play, &posA, &posB, &posSide, &polySide, &bgIdSide)) {
+ if ((polySide != NULL) && this->hasLostTrackOfPlayer) {
+ return false;
+ }
+
+ this->shouldRotateOntoSurfaces |= EnRat_UpdateFloorPoly(this, polySide, play);
+ Math_Vec3f_Copy(&this->actor.world.pos, &posSide);
+ this->actor.floorBgId = bgIdSide;
+ this->actor.speedXZ = 0.0f;
+ } else {
+ if (polyUpDown != this->actor.floorPoly) {
+ this->shouldRotateOntoSurfaces |= EnRat_UpdateFloorPoly(this, polyUpDown, play);
+ }
+
+ Math_Vec3f_Copy(&this->actor.world.pos, &posUpDown);
+ this->actor.floorBgId = bgIdUpDown;
+ }
+ } else {
+ this->actor.speedXZ = 0.0f;
+ lineLength *= 3.0f;
+ Math_Vec3f_Copy(&posA, &posB);
+
+ for (i = 0; i < 3; i++) {
+ if (i == 0) {
+ // backwards
+ posB.x = posA.x - (this->axisForwards.x * lineLength);
+ posB.y = posA.y - (this->axisForwards.y * lineLength);
+ posB.z = posA.z - (this->axisForwards.z * lineLength);
+ } else if (i == 1) {
+ // left
+ posB.x = posA.x + (this->axisLeft.x * lineLength);
+ posB.y = posA.y + (this->axisLeft.y * lineLength);
+ posB.z = posA.z + (this->axisLeft.z * lineLength);
+ } else {
+ // right
+ posB.x = posA.x - (this->axisLeft.x * lineLength);
+ posB.y = posA.y - (this->axisLeft.y * lineLength);
+ posB.z = posA.z - (this->axisLeft.z * lineLength);
+ }
+
+ if (EnRat_IsOnCollisionPoly(play, &posA, &posB, &posSide, &polySide, &bgIdSide)) {
+ this->shouldRotateOntoSurfaces |= EnRat_UpdateFloorPoly(this, polySide, play);
+ Math_Vec3f_Copy(&this->actor.world.pos, &posSide);
+ this->actor.floorBgId = bgIdSide;
+ break;
+ }
+ }
+
+ if (i == 3) {
+ // no collision nearby
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Transform coordinates from actor coordinate space (origin at actor's world.pos, z-axis is facing
+ * angle, i.e. shape.rot.y) to world space, according to current orientation.
+ * `offset` is expected to already be at world scale.
+ */
+void EnRat_ActorCoordsToWorld(EnRat* this, Vec3f* offset, Vec3f* pos) {
+ f32 x = offset->x + this->visualJitter;
+
+ pos->x = this->actor.world.pos.x + (this->axisLeft.x * x) + (this->axisUp.x * offset->y) +
+ (this->axisForwards.x * offset->z);
+ pos->y = this->actor.world.pos.y + (this->axisLeft.y * x) + (this->axisUp.y * offset->y) +
+ (this->axisForwards.y * offset->z);
+ pos->z = this->actor.world.pos.z + (this->axisLeft.z * x) + (this->axisUp.z * offset->y) +
+ (this->axisForwards.z * offset->z);
+}
+
+/**
+ * This function will spawn splashes and ripples as the Real Bombchu runs across the water's surface.
+ */
+void EnRat_SpawnWaterEffects(EnRat* this, PlayState* play) {
+ s32 pad;
+ Vec3f splashPos;
+
+ EffectSsGRipple_Spawn(play, &this->actor.world.pos, 70, 500, 0);
+ splashPos.x = this->actor.world.pos.x - (this->axisForwards.x * 10.0f);
+ splashPos.z = this->actor.world.pos.z - (this->axisForwards.z * 10.0f);
+ splashPos.y = this->actor.world.pos.y + 5.0f;
+ EffectSsGSplash_Spawn(play, &splashPos, NULL, NULL, 1, 450);
+}
+
+void EnRat_HandleNonSceneCollision(EnRat* this, PlayState* play) {
+ s16 yaw = this->actor.shape.rot.y;
+ f32 sin;
+ f32 cos;
+ f32 tempX;
+
+ BgCheck2_UpdateActorAttachedToMesh(&play->colCtx, this->actor.floorBgId, &this->actor);
+
+ if (yaw != this->actor.shape.rot.y) {
+ yaw = this->actor.shape.rot.y - yaw;
+
+ sin = Math_SinS(yaw);
+ cos = Math_CosS(yaw);
+
+ tempX = this->axisForwards.x;
+ this->axisForwards.x = (sin * this->axisForwards.z) + (cos * tempX);
+ this->axisForwards.z = (cos * this->axisForwards.z) - (sin * tempX);
+
+ tempX = this->axisUp.x;
+ this->axisUp.x = (sin * this->axisUp.z) + (cos * tempX);
+ this->axisUp.z = (cos * this->axisUp.z) - (sin * tempX);
+
+ tempX = this->axisLeft.x;
+ this->axisLeft.x = (sin * this->axisLeft.z) + (cos * tempX);
+ this->axisLeft.z = (cos * this->axisLeft.z) - (sin * tempX);
+ }
+}
+
+static Vec3f sSmokeAccel = { 0.0f, 0.6f, 0.0f };
+static Color_RGBA8 sSmokeColor = { 255, 255, 255, 255 };
+
+void EnRat_SpawnSmoke(EnRat* this, PlayState* play) {
+ func_800B0EB0(play, &this->smokePos, &gZeroVec3f, &sSmokeAccel, &sSmokeColor, &sSmokeColor, 75, 7, 8);
+}
+
+void EnRat_SetupRevive(EnRat* this) {
+ this->hasLostTrackOfPlayer = false;
+ this->timer = 200;
+ this->damageReaction.stunTimer = 0;
+ Math_Vec3f_Copy(&this->actor.world.pos, &this->actor.home.pos);
+ this->actor.shape.yOffset = 0.0f;
+ this->actor.shape.rot.x = this->actor.home.rot.x;
+ this->actor.shape.rot.y = this->actor.home.rot.y;
+ this->actor.shape.rot.z = this->actor.home.rot.z;
+ EnRat_InitializeAxes(this);
+ EnRat_UpdateRotation(this);
+ this->actor.flags &= ~ACTOR_FLAG_1;
+ this->actor.speedXZ = 0.0f;
+ Animation_PlayLoopSetSpeed(&this->skelAnime, &gRealBombchuSpotAnim, 0.0f);
+ this->revivePosY = 2666.6667f;
+ this->actor.draw = NULL;
+ this->actor.shape.shadowDraw = NULL;
+ this->actionFunc = EnRat_Revive;
+}
+
+/**
+ * Makes the Real Bombchu respawn after a certain amount of time by tunneling up from underground.
+ * Used only by Overworld-type Bombchu.
+ */
+void EnRat_Revive(EnRat* this, PlayState* play) {
+ if (this->timer > 0) {
+ this->timer--;
+ if (this->timer == 0) {
+ this->actor.flags |= ACTOR_FLAG_1;
+ this->actor.draw = EnRat_Draw;
+ this->skelAnime.playSpeed = 1.0f;
+ }
+ } else {
+ Math_StepToF(&this->revivePosY, 0.0f, 666.6667f);
+ if (Animation_OnFrame(&this->skelAnime, 2.0f)) {
+ func_800B1210(play, &this->actor.world.pos, &this->axisUp, &gZeroVec3f, 600, 100);
+ this->actor.shape.shadowDraw = ActorShadow_DrawCircle;
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ this->actor.flags &= ~ACTOR_FLAG_10;
+ this->timer = 150;
+ EnRat_SetupIdle(this);
+ }
+ }
+}
+
+void EnRat_SetupIdle(EnRat* this) {
+ Animation_PlayLoop(&this->skelAnime, &gRealBombchuRunAnim);
+ this->animLoopCounter = 5;
+ this->actor.speedXZ = 2.0f;
+ this->actionFunc = EnRat_Idle;
+}
+
+/**
+ * Move around randomly near the Bombchu's home until it spots the player.
+ */
+void EnRat_Idle(EnRat* this, PlayState* play) {
+ Player* player = GET_PLAYER(play);
+
+ this->actor.speedXZ = 2.0f;
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BOMCHU_WALK);
+ if (this->animLoopCounter != 0) {
+ this->animLoopCounter--;
+ }
+ }
+
+ if ((this->animLoopCounter == 0) && (Rand_ZeroOne() < 0.05f)) {
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BOMCHU_VOICE);
+ this->animLoopCounter = 5;
+ }
+
+ if (!(player->stateFlags3 & PLAYER_STATE3_100) && (this->actor.xzDistToPlayer < this->attackRange) &&
+ (Player_GetMask(play) != PLAYER_MASK_STONE) && Actor_IsFacingPlayer(&this->actor, 0x3800)) {
+ EnRat_SetupSpottedPlayer(this);
+ }
+}
+
+void EnRat_SetupSpottedPlayer(EnRat* this) {
+ this->actor.flags |= ACTOR_FLAG_10;
+ Animation_MorphToLoop(&this->skelAnime, &gRealBombchuSpotAnim, -5.0f);
+ this->animLoopCounter = 3;
+ this->actor.speedXZ = 0.0f;
+ this->actionFunc = EnRat_SpottedPlayer;
+}
+
+/**
+ * Play the "spotted" animation a few times, then start chasing the player.
+ */
+void EnRat_SpottedPlayer(EnRat* this, PlayState* play) {
+ if ((this->animLoopCounter == 3) && Animation_OnFrame(&this->skelAnime, 5.0f)) {
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BOMCHU_AIM);
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ this->animLoopCounter--;
+ if (this->animLoopCounter == 0) {
+ EnRat_SetupChasePlayer(this);
+ }
+ }
+}
+
+void EnRat_UpdateSparkOffsets(EnRat* this) {
+ s32 i;
+ Vec3f* ptr;
+
+ for (i = 0; i < ARRAY_COUNT(this->sparkOffsets); i++) {
+ ptr = &this->sparkOffsets[i];
+ ptr->x = randPlusMinusPoint5Scaled(6.0f);
+ ptr->y = randPlusMinusPoint5Scaled(6.0f);
+ ptr->z = randPlusMinusPoint5Scaled(6.0f);
+ }
+}
+
+void EnRat_SetupChasePlayer(EnRat* this) {
+ Animation_MorphToLoop(&this->skelAnime, &gRealBombchuRunAnim, -5.0f);
+ this->actor.speedXZ = 6.1f;
+ EnRat_UpdateSparkOffsets(this);
+ this->actionFunc = EnRat_ChasePlayer;
+}
+
+static Vec3f sBlureP1Offset = { 0.0f, 10.5f, -9.0f };
+static Vec3f sBlureP2LeftOffset = { 18.0f, 0.0f, -7.5f };
+static Vec3f sBlureP2RightOffset = { -18.0f, 0.0f, -7.5f };
+static Vec3f sDustVelocity = { 0.0f, 3.0f, 0.0f };
+
+/**
+ * Run towards the player. If the player puts on the Stone Mask or burrows into a Deku
+ * Flower, the Real Bombchu will lose track of the player and run in a straight line.
+ */
+void EnRat_ChasePlayer(EnRat* this, PlayState* play) {
+ Player* player = GET_PLAYER(play);
+ Vec3f blureP1;
+ Vec3f blureP2;
+
+ this->actor.speedXZ = 6.1f;
+ if (this->hasLostTrackOfPlayer) {
+ if (!(player->stateFlags3 & PLAYER_STATE3_100) && (Player_GetMask(play) != PLAYER_MASK_STONE) &&
+ Actor_IsFacingPlayer(&this->actor, 0x3000)) {
+ this->hasLostTrackOfPlayer = false;
+ }
+ } else if ((player->stateFlags3 & PLAYER_STATE3_100) || (Player_GetMask(play) == PLAYER_MASK_STONE)) {
+ this->hasLostTrackOfPlayer = true;
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ if (this->animLoopCounter != 0) {
+ this->animLoopCounter--;
+ }
+
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BOMCHU_WALK);
+ }
+
+ DECR(this->timer);
+
+ if ((this->timer == 0) && (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON)) {
+ this->timer = 30;
+ }
+
+ EnRat_SpawnSmoke(this, play);
+ this->visualJitter =
+ (5.0f + (Rand_ZeroOne() * 3.0f)) * Math_SinS(((Rand_ZeroOne() * 0x200) + 0x3000) * this->timer);
+
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON) {
+ EnRat_ActorCoordsToWorld(this, &sBlureP1Offset, &blureP1);
+
+ EnRat_ActorCoordsToWorld(this, &sBlureP2LeftOffset, &blureP2);
+ EffectBlure_AddVertex(Effect_GetByIndex(this->blure1Index), &blureP1, &blureP2);
+
+ EnRat_ActorCoordsToWorld(this, &sBlureP2RightOffset, &blureP2);
+ EffectBlure_AddVertex(Effect_GetByIndex(this->blure2Index), &blureP1, &blureP2);
+ } else if ((this->actor.floorPoly != NULL) && ((play->gameplayFrames % 4) == 0)) {
+ func_800B1210(play, &this->actor.world.pos, &sDustVelocity, &gZeroVec3f, 550, 50);
+ }
+
+ if ((this->actor.floorPoly == NULL) && (Animation_OnFrame(&this->skelAnime, 0.0f))) {
+ EnRat_SpawnWaterEffects(this, play);
+ }
+
+ if ((this->animLoopCounter == 0) && (Rand_ZeroOne() < 0.05f)) {
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BOMCHU_AIM);
+ this->animLoopCounter = 5;
+ }
+
+ func_800B9010(&this->actor, NA_SE_EN_BOMCHU_RUN - SFX_FLAG);
+ EnRat_UpdateSparkOffsets(this);
+}
+
+void EnRat_SetupBounced(EnRat* this) {
+ this->actor.speedXZ = 5.0f;
+ this->actor.velocity.y = 8.0f;
+ this->actor.world.rot.y = this->actor.yawTowardsPlayer + 0x8000;
+ this->actor.gravity = -1.0f;
+ EnRat_UpdateSparkOffsets(this);
+ this->actor.bgCheckFlags &= ~(0x10 | 0x8 | 0x1);
+ this->actionFunc = EnRat_Bounced;
+}
+
+/**
+ * Fly backwards until the Real Bombchu touches a wall, ceiling, or floor, then explode.
+ */
+void EnRat_Bounced(EnRat* this, PlayState* play) {
+ this->actor.shape.rot.x -= 0x700;
+ Math_StepToF(&this->actor.shape.yOffset, 1700.0f, 170.0f);
+ this->timer--;
+ if (this->timer == 0) {
+ this->timer = 30;
+ }
+
+ if (this->actor.bgCheckFlags & (0x10 | 0x8 | 0x1)) {
+ EnRat_Explode(this, play);
+ }
+}
+
+void EnRat_Explode(EnRat* this, PlayState* play) {
+ EnBom* bomb = (EnBom*)Actor_Spawn(&play->actorCtx, play, ACTOR_EN_BOM, this->actor.world.pos.x,
+ this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, ENBOM_0);
+
+ if (bomb != NULL) {
+ bomb->timer = 0;
+ }
+
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_OVERWORLD) {
+ Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x100);
+ }
+
+ this->actor.speedXZ = 0.0f;
+ this->actionFunc = EnRat_PostDetonation;
+}
+
+/**
+ * If the Real Bombchu is an Overworld-type Bombchu, this will set it up to revive.
+ * Otherwise, it will simply kill the Bombchu.
+ */
+void EnRat_PostDetonation(EnRat* this, PlayState* play) {
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_OVERWORLD) {
+ EnRat_SetupRevive(this);
+ } else {
+ Actor_MarkForDeath(&this->actor);
+ }
+}
+
+void EnRat_Update(Actor* thisx, PlayState* play) {
+ s32 pad;
+ EnRat* this = THIS;
+
+ this->shouldRotateOntoSurfaces = false;
+ if (this->damageReaction.stunTimer == 0) {
+ SkelAnime_Update(&this->skelAnime);
+ }
+
+ if (this->collider.base.atFlags & AT_HIT) {
+ this->collider.base.atFlags &= ~AT_HIT;
+ this->collider.base.acFlags &= ~AC_HIT;
+ this->collider.base.ocFlags1 &= ~OC1_HIT;
+ if (this->collider.base.atFlags & AT_BOUNCED) {
+ EnRat_SetupBounced(this);
+ } else {
+ EnRat_Explode(this, play);
+ return;
+ }
+ } else if (this->collider.base.acFlags & AC_HIT) {
+ this->collider.base.acFlags &= ~AC_HIT;
+ if (this->actor.colChkInfo.damageEffect == EN_RAT_DMGEFF_HOOKSHOT) {
+ this->damageReaction.hookedState = EN_RAT_HOOK_STARTED;
+ } else if (this->actor.colChkInfo.damageEffect == EN_RAT_DMGEFF_STUN) {
+ Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_COMMON_FREEZE);
+ Actor_SetColorFilter(&this->actor, 0, 120, 0, 40);
+ if (this->actionFunc == EnRat_Bounced) {
+ this->actor.speedXZ = 0.0f;
+ if (this->actor.velocity.y > 0.0f) {
+ this->actor.velocity.y = 0.0f;
+ }
+ } else {
+ this->damageReaction.stunTimer = 40;
+ }
+ } else {
+ EnRat_Explode(this, play);
+ return;
+ }
+ } else if (((this->collider.base.ocFlags1 & OC1_HIT) && (((this->collider.base.oc->category == ACTORCAT_ENEMY)) ||
+ (this->collider.base.oc->category == ACTORCAT_BOSS) ||
+ (this->collider.base.oc->category == ACTORCAT_PLAYER))) ||
+ ((this->actionFunc == EnRat_ChasePlayer) && (this->timer == 0))) {
+ this->collider.base.ocFlags1 &= ~OC1_HIT;
+ EnRat_Explode(this, play);
+ return;
+ }
+
+ this->actionFunc(this, play);
+
+ if ((this->actionFunc != EnRat_PostDetonation) && (this->actionFunc != EnRat_Revive)) {
+ if (this->damageReaction.stunTimer > 0) {
+ this->damageReaction.stunTimer--;
+ } else if (this->damageReaction.hookedState < 0) {
+ if (this->damageReaction.hookedState == EN_RAT_HOOK_STARTED) {
+ // The player just hit the Real Bombchu with the Hookshot.
+ this->damageReaction.hookedState = EN_RAT_HOOKED;
+ } else if ((this->actor.flags & ACTOR_FLAG_2000) != ACTOR_FLAG_2000) {
+ // The player has hooked the Real Bombchu for more than one frame, but
+ // the actor flag indicating that the Hookshot is attached is *not* set.
+ EnRat_Explode(this, play);
+ return;
+ }
+ } else if (this->actionFunc != EnRat_Bounced) {
+ if (this->actor.floorBgId != BGCHECK_SCENE) {
+ EnRat_HandleNonSceneCollision(this, play);
+ }
+
+ if (!this->hasLostTrackOfPlayer) {
+ EnRat_ChooseDirection(this);
+ }
+
+ if ((this->actionFunc != EnRat_SpottedPlayer) && !EnRat_IsTouchingSurface(this, play)) {
+ EnRat_Explode(this, play);
+ return;
+ }
+
+ if (this->shouldRotateOntoSurfaces) {
+ EnRat_UpdateRotation(this);
+ this->actor.shape.rot.x = -this->actor.world.rot.x;
+ this->actor.shape.rot.y = this->actor.world.rot.y;
+ this->actor.shape.rot.z = this->actor.world.rot.z;
+ }
+
+ Actor_MoveWithoutGravity(&this->actor);
+ this->actor.floorHeight = this->actor.world.pos.y;
+ } else {
+ Actor_MoveWithGravity(&this->actor);
+ Actor_UpdateBgCheckInfo(play, &this->actor, 20.0f, 30.0f, 60.0f, 7);
+ }
+
+ if (SurfaceType_IsWallDamage(&play->colCtx, this->actor.floorPoly, this->actor.floorBgId)) {
+ EnRat_Explode(this, play);
+ return;
+ }
+
+ this->collider.dim.worldSphere.center.x = this->actor.world.pos.x + (this->axisUp.x * 10.0f);
+ this->collider.dim.worldSphere.center.y = this->actor.world.pos.y + (this->axisUp.y * 10.0f);
+ this->collider.dim.worldSphere.center.z = this->actor.world.pos.z + (this->axisUp.z * 10.0f);
+
+ if (this->actionFunc != EnRat_Revive) {
+ if (this->damageReaction.stunTimer == 0) {
+ CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base);
+ }
+
+ CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base);
+ CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
+ }
+
+ Actor_SetFocus(&this->actor, this->actor.shape.yOffset * 0.015f);
+ }
+}
+
+s32 EnRat_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) {
+ EnRat* this = THIS;
+
+ if (limbIndex == REAL_BOMBCHU_LIMB_BODY) {
+ pos->y -= this->revivePosY;
+ }
+
+ if (limbIndex == REAL_BOMBCHU_LIMB_TAIL_END) {
+ *dList = NULL;
+ }
+
+ return false;
+}
+
+void EnRat_PostLimbDraw(PlayState* play2, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) {
+ PlayState* play = play2;
+ EnRat* this = THIS;
+ MtxF* currentMatrixState;
+ Vec3f* ptr;
+ f32 redModifier;
+
+ if (limbIndex == REAL_BOMBCHU_LIMB_TAIL_END) {
+ OPEN_DISPS(play->state.gfxCtx);
+
+ Matrix_ReplaceRotation(&play->billboardMtxF);
+ Matrix_MultZero(&this->smokePos);
+ this->smokePos.y += 15.0f;
+ currentMatrixState = Matrix_GetCurrent();
+
+ if (this->actionFunc == EnRat_ChasePlayer) {
+ s32 i;
+
+ gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 150, 255);
+ gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 0, 0);
+ Matrix_Scale(45.0f, 45.0f, 45.0f, MTXMODE_APPLY);
+
+ for (i = 0; i < ARRAY_COUNT(this->sparkOffsets); i++) {
+ ptr = &this->sparkOffsets[i];
+ currentMatrixState->mf[3][0] = this->smokePos.x + ptr->x;
+ currentMatrixState->mf[3][1] = this->smokePos.y + ptr->y;
+ currentMatrixState->mf[3][2] = this->smokePos.z + ptr->z;
+ gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx),
+ G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPSegment(POLY_XLU_DISP++, 0x08, sSparkTextures[(play->gameplayFrames + i) & 3]);
+ gSPDisplayList(POLY_XLU_DISP++, gEffSparkDL);
+ }
+
+ Matrix_Scale(1.0f / 45.0f, 1.0f / 45.0f, 1.0f / 45.0f, MTXMODE_APPLY);
+ currentMatrixState->mf[3][0] = this->smokePos.x;
+ currentMatrixState->mf[3][1] = this->smokePos.y - 15.0f;
+ currentMatrixState->mf[3][2] = this->smokePos.z;
+ }
+
+ gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_OPA_DISP++, gBombCapDL);
+ if (EN_RAT_GET_TYPE(&this->actor) == EN_RAT_TYPE_DUNGEON) {
+ redModifier = fabsf(cos_rad(this->timer * (M_PI / 30.f)));
+ } else {
+ if (this->timer >= 120) {
+ redModifier = fabsf(cos_rad((this->timer % 30) * (M_PI / 30.0f)));
+ } else if (this->timer >= 30) {
+ redModifier = fabsf(cos_rad((this->timer % 6) * (M_PI / 6.0f)));
+ } else {
+ redModifier = fabsf(cos_rad((this->timer % 3) * (M_PI / 3.0f)));
+ }
+ }
+
+ gDPSetEnvColor(POLY_OPA_DISP++, (s32)((1.0f - redModifier) * 255.0f), 0, 40, 255);
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, (s32)((1.0f - redModifier) * 255.0f), 0, 40, 255);
+ Matrix_RotateZYX(0x4000, 0, 0, MTXMODE_APPLY);
+ gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(POLY_OPA_DISP++, gBombBodyDL);
+
+ CLOSE_DISPS(play->state.gfxCtx);
+ }
+}
+
+void EnRat_Draw(Actor* thisx, PlayState* play) {
+ EnRat* this = THIS;
+
+ func_8012C28C(play->state.gfxCtx);
+ func_8012C974(play->state.gfxCtx);
+ func_800B8050(&this->actor, play, 0);
+ SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount,
+ EnRat_OverrideLimbDraw, EnRat_PostLimbDraw, &this->actor);
+}
diff --git a/src/overlays/actors/ovl_En_Rat/z_en_rat.h b/src/overlays/actors/ovl_En_Rat/z_en_rat.h
index c3ad7eedc3..6dd51d10a1 100644
--- a/src/overlays/actors/ovl_En_Rat/z_en_rat.h
+++ b/src/overlays/actors/ovl_En_Rat/z_en_rat.h
@@ -2,12 +2,57 @@
#define Z_EN_RAT_H
#include "global.h"
+#include "objects/object_rat/object_rat.h"
+
+#define EN_RAT_IS_OVERWORLD_TYPE(thisx) ((thisx)->params & 0x8000)
+#define EN_RAT_GET_TYPE(thisx) ((thisx)->params)
+#define EN_RAT_GET_ATTACK_RANGE(thisx) ((thisx)->params & 0xFF)
+
+/**
+ * There are many differences between the two types of Real Bombchu:
+ * - Dungeon-type don't respawn, while Overworld-type do.
+ * - Dungeon-type leave a blure trail behind them as they chase the player,
+ * while Overworld-type spawn dust around themselves instead.
+ * - Dungeon-type won't detonate automatically, while Overworld-type will
+ * detonate after 150 frames of chasing the player.
+ * - So long as EN_RAT_GET_ATTACK_RANGE returns something other than 0 or 255,
+ * Dungeon-type have 1/5th of the attack range compared to Overworld-type.
+ * - Dungeon-type do not drop items upon defeat, while Overworld-type do.
+ */
+typedef enum {
+ /* 0 */ EN_RAT_TYPE_DUNGEON,
+ /* 1 */ EN_RAT_TYPE_OVERWORLD,
+} EnRatType;
struct EnRat;
+typedef void (*EnRatActionFunc)(struct EnRat*, PlayState*);
+
typedef struct EnRat {
/* 0x000 */ Actor actor;
- /* 0x144 */ char unk_144[0x17C];
+ /* 0x144 */ SkelAnime skelAnime;
+ /* 0x188 */ EnRatActionFunc actionFunc;
+ /* 0x18C */ u8 hasLostTrackOfPlayer;
+ /* 0x18D */ u8 shouldRotateOntoSurfaces;
+ /* 0x18E */ s16 animLoopCounter;
+ /* 0x190 */ s16 timer; // Used for both exploding and reviving
+ /* 0x192 */ union {
+ s16 stunTimer;
+ s16 hookedState;
+ } damageReaction;
+ /* 0x194 */ Vec3s jointTable[REAL_BOMBCHU_LIMB_MAX];
+ /* 0x1D0 */ Vec3s morphTable[REAL_BOMBCHU_LIMB_MAX];
+ /* 0x20C */ Vec3f axisForwards;
+ /* 0x218 */ Vec3f axisUp;
+ /* 0x224 */ Vec3f axisLeft;
+ /* 0x230 */ Vec3f smokePos;
+ /* 0x23C */ Vec3f sparkOffsets[2];
+ /* 0x254 */ f32 visualJitter;
+ /* 0x258 */ f32 attackRange;
+ /* 0x25C */ f32 revivePosY;
+ /* 0x260 */ s32 blure1Index;
+ /* 0x264 */ s32 blure2Index;
+ /* 0x268 */ ColliderSphere collider;
} EnRat; // size = 0x2C0
extern const ActorInit En_Rat_InitVars;
diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt
index 0b79b37be2..87f081d152 100644
--- a/tools/disasm/functions.txt
+++ b/tools/disasm/functions.txt
@@ -10641,32 +10641,32 @@
0x80A560F0:("EnFirefly2_Draw",),
0x80A56150:("EnRat_Init",),
0x80A56370:("EnRat_Destroy",),
- 0x80A563CC:("func_80A563CC",),
- 0x80A56444:("func_80A56444",),
- 0x80A5665C:("func_80A5665C",),
- 0x80A566E0:("func_80A566E0",),
- 0x80A56994:("func_80A56994",),
- 0x80A56AFC:("func_80A56AFC",),
- 0x80A56EB8:("func_80A56EB8",),
- 0x80A56F68:("func_80A56F68",),
- 0x80A57010:("func_80A57010",),
- 0x80A57118:("func_80A57118",),
- 0x80A57180:("func_80A57180",),
- 0x80A5723C:("func_80A5723C",),
- 0x80A57330:("func_80A57330",),
- 0x80A57384:("func_80A57384",),
- 0x80A57488:("func_80A57488",),
- 0x80A574E8:("func_80A574E8",),
- 0x80A57570:("func_80A57570",),
- 0x80A575F4:("func_80A575F4",),
- 0x80A5764C:("func_80A5764C",),
- 0x80A57918:("func_80A57918",),
- 0x80A57984:("func_80A57984",),
- 0x80A57A08:("func_80A57A08",),
- 0x80A57A9C:("func_80A57A9C",),
+ 0x80A563CC:("EnRat_InitializeAxes",),
+ 0x80A56444:("EnRat_UpdateFloorPoly",),
+ 0x80A5665C:("EnRat_UpdateRotation",),
+ 0x80A566E0:("EnRat_ChooseDirection",),
+ 0x80A56994:("EnRat_IsOnCollisionPoly",),
+ 0x80A56AFC:("EnRat_IsTouchingSurface",),
+ 0x80A56EB8:("EnRat_ActorCoordsToWorld",),
+ 0x80A56F68:("EnRat_SpawnWaterEffects",),
+ 0x80A57010:("EnRat_HandleNonSceneCollision",),
+ 0x80A57118:("EnRat_SpawnSmoke",),
+ 0x80A57180:("EnRat_SetupRevive",),
+ 0x80A5723C:("EnRat_Revive",),
+ 0x80A57330:("EnRat_SetupIdle",),
+ 0x80A57384:("EnRat_Idle",),
+ 0x80A57488:("EnRat_SetupSpottedPlayer",),
+ 0x80A574E8:("EnRat_SpottedPlayer",),
+ 0x80A57570:("EnRat_UpdateSparkOffsets",),
+ 0x80A575F4:("EnRat_SetupChasePlayer",),
+ 0x80A5764C:("EnRat_ChasePlayer",),
+ 0x80A57918:("EnRat_SetupBounced",),
+ 0x80A57984:("EnRat_Bounced",),
+ 0x80A57A08:("EnRat_Explode",),
+ 0x80A57A9C:("EnRat_PostDetonation",),
0x80A57AE0:("EnRat_Update",),
- 0x80A57F10:("func_80A57F10",),
- 0x80A57F4C:("func_80A57F4C",),
+ 0x80A57F10:("EnRat_OverrideLimbDraw",),
+ 0x80A57F4C:("EnRat_PostLimbDraw",),
0x80A58354:("EnRat_Draw",),
0x80A587A0:("func_80A587A0",),
0x80A58908:("func_80A58908",),
diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt
index e04bc46f09..cfbf68df86 100644
--- a/tools/disasm/variables.txt
+++ b/tools/disasm/variables.txt
@@ -11739,20 +11739,19 @@
0x80A55E40:("D_80A55E40","f32","",0x4),
0x80A56100:("En_Firefly2_InitVars","UNK_TYPE1","",0x1),
0x80A583E0:("En_Rat_InitVars","UNK_TYPE1","",0x1),
- 0x80A58400:("D_80A58400","UNK_TYPE1","",0x1),
- 0x80A58428:("D_80A58428","UNK_TYPE2","",0x2),
- 0x80A5842C:("D_80A5842C","UNK_TYPE1","",0x1),
- 0x80A5844C:("D_80A5844C","UNK_TYPE1","",0x1),
- 0x80A58454:("D_80A58454","UNK_TYPE4","",0x4),
- 0x80A58464:("D_80A58464","UNK_TYPE1","",0x1),
- 0x80A58470:("D_80A58470","EffectBlureInit2","",0x24),
- 0x80A58494:("D_80A58494","UNK_TYPE4","",0x4),
- 0x80A58498:("D_80A58498","UNK_TYPE1","",0x1),
- 0x80A584A4:("D_80A584A4","UNK_TYPE1","",0x1),
- 0x80A584A8:("D_80A584A8","UNK_TYPE1","",0x1),
- 0x80A584B4:("D_80A584B4","UNK_TYPE1","",0x1),
- 0x80A584C0:("D_80A584C0","UNK_TYPE1","",0x1),
- 0x80A584CC:("D_80A584CC","UNK_TYPE1","",0x1),
+ 0x80A58400:("sSphereInit","UNK_TYPE1","",0x1),
+ 0x80A5842C:("sDamageTable","UNK_TYPE1","",0x1),
+ 0x80A5844C:("sColChkInfoInit","UNK_TYPE1","",0x1),
+ 0x80A58454:("sSparkTextures","UNK_TYPE4","",0x4),
+ 0x80A58464:("sInitChain","UNK_TYPE1","",0x1),
+ 0x80A58470:("sBlureInit","EffectBlureInit2","",0x24),
+ 0x80A58494:("sTexturesDesegmented","UNK_TYPE4","",0x4),
+ 0x80A58498:("sSmokeAccel","UNK_TYPE1","",0x1),
+ 0x80A584A4:("sSmokeColor","UNK_TYPE1","",0x1),
+ 0x80A584A8:("sBlureP1Offset","UNK_TYPE1","",0x1),
+ 0x80A584B4:("sBlureP2LeftOffset","UNK_TYPE1","",0x1),
+ 0x80A584C0:("sBlureP2RightOffset","UNK_TYPE1","",0x1),
+ 0x80A584CC:("sDustVelocity","UNK_TYPE1","",0x1),
0x80A584E0:("D_80A584E0","f32","",0x4),
0x80A584E4:("D_80A584E4","f32","",0x4),
0x80A584E8:("D_80A584E8","f32","",0x4),
diff --git a/undefined_syms.txt b/undefined_syms.txt
index b2410d3ce8..faf0aba385 100644
--- a/undefined_syms.txt
+++ b/undefined_syms.txt
@@ -1290,12 +1290,6 @@ D_06004340 = 0x06004340;
D_06001A80 = 0x06001A80;
-// ovl_En_Rat
-
-D_06000174 = 0x06000174;
-D_0600026C = 0x0600026C;
-D_06001858 = 0x06001858;
-
// ovl_En_Ru
D_0600C700 = 0x0600C700;