summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Overton <tom-overton@users.noreply.github.com>2024-06-23 18:09:22 -0700
committerGitHub <noreply@github.com>2024-06-23 18:09:22 -0700
commitbb221b4a0fb588389a0847d38ba010c0e46f34d6 (patch)
tree4751ddef84b86377939ccdf27c4371ffc79225af
parent3682a13ec55f84eecf8b16aa7e32bda7bcf9bdfc (diff)
EnOkuta (Octorok and its projectiles) OK and documented (#1641)
* Match EnOkuta * Match data * Use the generated reloc * Delete extern stuff and format * Extract the DL * Fix lots of stuff * Copy a lot of names over from EnSyatekiOkuta * SFX enums * Lots more stuff * Constants and other stuff * Some more stuff I missed * Name more functions using OoT/SyatekiOkuta as reference * An easy function I missed * Some floats * Getter macros * Types enum * Name the cylinder inits a little clearer * Create bodyparts enum and name all data * Name all functions * Name `timer` struct var and all temps * Name the `jumpHeight` struct var * Create damage effect enum * Name the extracted DL * Name `numConsecutiveProjectiles` * Finish docs * Document XML * Clarify that this blue Octorok isn't the shooting gallery one * Respond to reviews * Stuff --------- Co-authored-by: Synray <31429825+Synray@users.noreply.github.com> Co-authored-by: Derek Hensley <hensley.derek58@gmail.com>
-rw-r--r--assets/xml/overlays/ovl_En_Okuta.xml7
-rw-r--r--linker_scripts/undefined_syms.ld11
-rw-r--r--spec3
-rw-r--r--src/overlays/actors/ovl_En_Bb/z_en_bb.c48
-rw-r--r--src/overlays/actors/ovl_En_Bb/z_en_bb.h8
-rw-r--r--src/overlays/actors/ovl_En_Bbfall/z_en_bbfall.c48
-rw-r--r--src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c2
-rw-r--r--src/overlays/actors/ovl_En_Okuta/z_en_okuta.c1169
-rw-r--r--src/overlays/actors/ovl_En_Okuta/z_en_okuta.h41
-rw-r--r--src/overlays/actors/ovl_En_Syateki_Okuta/z_en_syateki_okuta.c15
-rw-r--r--tools/disasm/functions.txt70
-rw-r--r--tools/disasm/variables.txt35
12 files changed, 1212 insertions, 245 deletions
diff --git a/assets/xml/overlays/ovl_En_Okuta.xml b/assets/xml/overlays/ovl_En_Okuta.xml
new file mode 100644
index 000000000..dbf1247e8
--- /dev/null
+++ b/assets/xml/overlays/ovl_En_Okuta.xml
@@ -0,0 +1,7 @@
+<Root>
+ <!-- Octorok assets -->
+ <File Name="ovl_En_Okuta" BaseAddress="0x8086DE20" RangeStart="0x2A50" RangeEnd="0x2A60">
+ <!-- DisplayList that turns the Octorok blue. In the final game, the blue Octorok variant is unused. -->
+ <DList Name="gOctorokBlueMaterialDL" Offset="0x2A50"/>
+ </File>
+</Root>
diff --git a/linker_scripts/undefined_syms.ld b/linker_scripts/undefined_syms.ld
index d92b391d1..e02eac8a1 100644
--- a/linker_scripts/undefined_syms.ld
+++ b/linker_scripts/undefined_syms.ld
@@ -301,17 +301,6 @@ D_06021E34 = 0x06021E34;
D_06022728 = 0x06022728;
D_06022CAC = 0x06022CAC;
-// ovl_En_Okuta
-
-D_0600044C = 0x0600044C;
-D_06003250 = 0x06003250;
-D_060033D0 = 0x060033D0;
-D_06003958 = 0x06003958;
-D_06003B24 = 0x06003B24;
-D_06003EE4 = 0x06003EE4;
-D_06004204 = 0x06004204;
-D_0600466C = 0x0600466C;
-
// ovl_En_Zl4
D_06013328 = 0x06013328;
diff --git a/spec b/spec
index c9df4a8e1..b4fd803de 100644
--- a/spec
+++ b/spec
@@ -758,8 +758,7 @@ beginseg
name "ovl_En_Okuta"
compress
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Okuta/z_en_okuta.o"
- include "$(BUILD_DIR)/data/ovl_En_Okuta/ovl_En_Okuta.data.o"
- include "$(BUILD_DIR)/data/ovl_En_Okuta/ovl_En_Okuta.reloc.o"
+ include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Okuta/ovl_En_Okuta_reloc.o"
endseg
beginseg
diff --git a/src/overlays/actors/ovl_En_Bb/z_en_bb.c b/src/overlays/actors/ovl_En_Bb/z_en_bb.c
index 2fba5f75f..9b8624892 100644
--- a/src/overlays/actors/ovl_En_Bb/z_en_bb.c
+++ b/src/overlays/actors/ovl_En_Bb/z_en_bb.c
@@ -616,32 +616,30 @@ s32 EnBb_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* po
}
/**
- * This maps a given limb based on its limbIndex to its appropriate index
- * in the bodyPartsPos/Velocity arrays.
+ * This maps a given limb based on its limbIndex to its appropriate index in the `bodyPartsPos/Velocity` arrays.
*/
static s8 sLimbToBodyParts[BUBBLE_LIMB_MAX] = {
- BODYPART_NONE, // BUBBLE_LIMB_NONE
- BODYPART_NONE, // BUBBLE_LIMB_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_CRANIUM_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_JAW_ROOT
- BUBBLE_BODYPART_0, // BUBBLE_LIMB_JAW
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WRAPPER
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WEBBING_ROOT
- BUBBLE_BODYPART_1, // BUBBLE_LIMB_LEFT_WING_WEBBING
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_BONE
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WRAPPER
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WEBBING_ROOT
- BUBBLE_BODYPART_2, // BUBBLE_LIMB_RIGHT_WING_WEBBING
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_BONE
- BUBBLE_BODYPART_3, // BUBBLE_LIMB_CRANIUM
+ BODYPART_NONE, // BUBBLE_LIMB_NONE
+ BODYPART_NONE, // BUBBLE_LIMB_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_CRANIUM_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_JAW_ROOT
+ BUBBLE_BODYPART_JAW, // BUBBLE_LIMB_JAW
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WRAPPER
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WEBBING_ROOT
+ BUBBLE_BODYPART_LEFT_WING_WEBBING, // BUBBLE_LIMB_LEFT_WING_WEBBING
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_BONE
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WRAPPER
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WEBBING_ROOT
+ BUBBLE_BODYPART_RIGHT_WING_WEBBING, // BUBBLE_LIMB_RIGHT_WING_WEBBING
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_BONE
+ BUBBLE_BODYPART_CRANIUM, // BUBBLE_LIMB_CRANIUM
};
/**
- * The last element of the bodyParts arrays is a duplicate of the cranium
- * limb, which is then offset by a certain amount. There is no display list
- * associated with this, so it is only used for effects.
+ * The last element of the `bodyParts` arrays is not tied to any particular limb and is instead used to control the
+ * placement of effects. The positions of these effects are offset by a certain amount from the Bubble's cranium limb.
*/
static Vec3f sEffectsBodyPartOffset = { 1000.0f, -700.0f, 0.0f };
@@ -652,10 +650,10 @@ void EnBb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
if (this->bodyPartDrawStatus == BB_BODY_PART_DRAW_STATUS_ALIVE) {
if (sLimbToBodyParts[limbIndex] != BODYPART_NONE) {
- if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_0) {
- Matrix_MultVecX(1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_0]);
- } else if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_3) {
- Matrix_MultVecX(-1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_3]);
+ if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_JAW) {
+ Matrix_MultVecX(1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_JAW]);
+ } else if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_CRANIUM) {
+ Matrix_MultVecX(-1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_CRANIUM]);
Matrix_MultVec3f(&sEffectsBodyPartOffset, &this->bodyPartsPos[BUBBLE_BODYPART_EFFECTS]);
} else {
Matrix_MultZero(&this->bodyPartsPos[sLimbToBodyParts[limbIndex]]);
diff --git a/src/overlays/actors/ovl_En_Bb/z_en_bb.h b/src/overlays/actors/ovl_En_Bb/z_en_bb.h
index 7c94d4b37..468ca1e38 100644
--- a/src/overlays/actors/ovl_En_Bb/z_en_bb.h
+++ b/src/overlays/actors/ovl_En_Bb/z_en_bb.h
@@ -11,10 +11,10 @@ struct EnBb;
typedef void (*EnBbActionFunc)(struct EnBb*, PlayState*);
typedef enum BubbleBodyPart {
- /* 0 */ BUBBLE_BODYPART_0,
- /* 1 */ BUBBLE_BODYPART_1,
- /* 2 */ BUBBLE_BODYPART_2,
- /* 3 */ BUBBLE_BODYPART_3,
+ /* 0 */ BUBBLE_BODYPART_JAW,
+ /* 1 */ BUBBLE_BODYPART_LEFT_WING_WEBBING,
+ /* 2 */ BUBBLE_BODYPART_RIGHT_WING_WEBBING,
+ /* 3 */ BUBBLE_BODYPART_CRANIUM,
/* 4 */ BUBBLE_BODYPART_EFFECTS,
/* 5 */ BUBBLE_BODYPART_MAX
} BubbleBodyPart;
diff --git a/src/overlays/actors/ovl_En_Bbfall/z_en_bbfall.c b/src/overlays/actors/ovl_En_Bbfall/z_en_bbfall.c
index a224b3744..db7a05738 100644
--- a/src/overlays/actors/ovl_En_Bbfall/z_en_bbfall.c
+++ b/src/overlays/actors/ovl_En_Bbfall/z_en_bbfall.c
@@ -652,32 +652,30 @@ s32 EnBbfall_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f
}
/**
- * This maps a given limb based on its limbIndex to its appropriate index
- * in the bodyPartsPos/Velocity arrays.
+ * This maps a given limb based on its limbIndex to its appropriate index in the `bodyPartsPos/Velocity` arrays.
*/
static s8 sLimbToBodyParts[BUBBLE_LIMB_MAX] = {
- BODYPART_NONE, // BUBBLE_LIMB_NONE
- BODYPART_NONE, // BUBBLE_LIMB_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_CRANIUM_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_JAW_ROOT
- BUBBLE_BODYPART_0, // BUBBLE_LIMB_JAW
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WRAPPER
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WEBBING_ROOT
- BUBBLE_BODYPART_1, // BUBBLE_LIMB_LEFT_WING_WEBBING
- BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_BONE
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_ROOT
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WRAPPER
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WEBBING_ROOT
- BUBBLE_BODYPART_2, // BUBBLE_LIMB_RIGHT_WING_WEBBING
- BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_BONE
- BUBBLE_BODYPART_3, // BUBBLE_LIMB_CRANIUM
+ BODYPART_NONE, // BUBBLE_LIMB_NONE
+ BODYPART_NONE, // BUBBLE_LIMB_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_CRANIUM_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_JAW_ROOT
+ BUBBLE_BODYPART_JAW, // BUBBLE_LIMB_JAW
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WRAPPER
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_WEBBING_ROOT
+ BUBBLE_BODYPART_LEFT_WING_WEBBING, // BUBBLE_LIMB_LEFT_WING_WEBBING
+ BODYPART_NONE, // BUBBLE_LIMB_LEFT_WING_BONE
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_ROOT
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WRAPPER
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_WEBBING_ROOT
+ BUBBLE_BODYPART_RIGHT_WING_WEBBING, // BUBBLE_LIMB_RIGHT_WING_WEBBING
+ BODYPART_NONE, // BUBBLE_LIMB_RIGHT_WING_BONE
+ BUBBLE_BODYPART_CRANIUM, // BUBBLE_LIMB_CRANIUM
};
/**
- * The last element of the bodyParts arrays is a duplicate of the cranium
- * limb, which is then offset by a certain amount. There is no display list
- * associated with this, so it is only used for effects.
+ * The last element of the `bodyParts` arrays is not tied to any particular limb and is instead used to control the
+ * placement of effects. The positions of these effects are offset by a certain amount from the Bubble's cranium limb.
*/
static Vec3f sEffectsBodyPartOffset = { 1000.0f, -700.0f, 0.0f };
@@ -688,10 +686,10 @@ void EnBbfall_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* r
if (this->bodyPartDrawStatus == BBFALL_BODY_PART_DRAW_STATUS_ALIVE) {
if (sLimbToBodyParts[limbIndex] != BODYPART_NONE) {
- if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_0) {
- Matrix_MultVecX(1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_0]);
- } else if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_3) {
- Matrix_MultVecX(-1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_3]);
+ if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_JAW) {
+ Matrix_MultVecX(1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_JAW]);
+ } else if (sLimbToBodyParts[limbIndex] == BUBBLE_BODYPART_CRANIUM) {
+ Matrix_MultVecX(-1000.0f, &this->bodyPartsPos[BUBBLE_BODYPART_CRANIUM]);
Matrix_MultVec3f(&sEffectsBodyPartOffset, &this->bodyPartsPos[BUBBLE_BODYPART_EFFECTS]);
} else {
Matrix_MultZero(&this->bodyPartsPos[sLimbToBodyParts[limbIndex]]);
diff --git a/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c b/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c
index c130d5c6a..0152b12ec 100644
--- a/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c
+++ b/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c
@@ -551,7 +551,7 @@ void EnBigokuta_Update(Actor* thisx, PlayState* play) {
Math_StepToF(&this->drawDmgEffAlpha, 0.0f, 0.05f);
this->drawDmgEffScale = (this->drawDmgEffAlpha + 1.0f) * 0.6f;
this->drawDmgEffScale = CLAMP_MAX(this->drawDmgEffScale, 1.2f);
- } else if (!Math_StepToF(&this->drawDmgEffFrozenSteamScale, 1.2f, 0.030000001f)) {
+ } else if (!Math_StepToF(&this->drawDmgEffFrozenSteamScale, 1.2f, 30.0f * 0.001f)) {
Actor_PlaySfx_Flagged(&this->picto.actor, NA_SE_EV_ICE_FREEZE - SFX_FLAG);
}
}
diff --git a/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c b/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c
index 8c8e3ce41..2c0c1d6e4 100644
--- a/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c
+++ b/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c
@@ -1,33 +1,48 @@
/*
* File: z_en_okuta.c
* Overlay: ovl_En_Okuta
- * Description: Octorok
+ * Description: Octorok and its projectiles
+ *
+ * In addition to the standard red Octorok that appears in the final game, this actor is also responsible for an unused
+ * blue Octorok variant. This blue Octorok is invulnerable to every attack (it simply spins whenever the player hits it
+ * with something), cannot be frozen with Ice Arrows, and can follow the player underwater if the player dives below the
+ * surface. Note that this blue Octorok is separate from the one that is used in the Town Shooting Gallery; that Octorok
+ * is handled by EnSyatekiOkuta. This actor also handles the projectiles that the Octoroks shoot.
*/
#include "z_en_okuta.h"
+#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY)
#define THIS ((EnOkuta*)thisx)
-void EnOkuta_Init(Actor* thisx, PlayState* play);
+void EnOkuta_Init(Actor* thisx, PlayState* play2);
void EnOkuta_Destroy(Actor* thisx, PlayState* play);
-void EnOkuta_Update(Actor* thisx, PlayState* play);
+void EnOkuta_Update(Actor* thisx, PlayState* play2);
void EnOkuta_Draw(Actor* thisx, PlayState* play);
-void func_8086E52C(EnOkuta* this, PlayState* play);
-void func_8086E658(EnOkuta* this, PlayState* play);
-void func_8086E7E8(EnOkuta* this, PlayState* play);
-void func_8086E948(EnOkuta* this, PlayState* play);
-void func_8086EC00(EnOkuta* this, PlayState* play);
-void func_8086EF14(EnOkuta* this, PlayState* play);
-void func_8086EFE8(EnOkuta* this, PlayState* play);
-void func_8086F434(EnOkuta* this, PlayState* play);
-void func_8086F4B0(EnOkuta* this, PlayState* play);
-void func_8086F57C(EnOkuta* this, PlayState* play);
-void func_8086F694(EnOkuta* this, PlayState* play);
-
-#if 0
+void EnOkuta_SetupWaitToAppear(EnOkuta* this);
+void EnOkuta_WaitToAppear(EnOkuta* this, PlayState* play);
+void EnOkuta_SetupAppear(EnOkuta* this, PlayState* play);
+void EnOkuta_Appear(EnOkuta* this, PlayState* play);
+void EnOkuta_SetupHide(EnOkuta* this);
+void EnOkuta_Hide(EnOkuta* this, PlayState* play);
+void EnOkuta_SetupFloat(EnOkuta* this);
+void EnOkuta_Float(EnOkuta* this, PlayState* play);
+void EnOkuta_SetupShoot(EnOkuta* this, PlayState* play);
+void EnOkuta_Shoot(EnOkuta* this, PlayState* play);
+void EnOkuta_Damaged(EnOkuta* this, PlayState* play);
+void EnOkuta_SetupDie(EnOkuta* this);
+void EnOkuta_Die(EnOkuta* this, PlayState* play);
+void EnOkuta_FrozenInIceBlock(EnOkuta* this, PlayState* play);
+void EnOkuta_Frozen(EnOkuta* this, PlayState* play);
+void EnOkuta_Spin(EnOkuta* this, PlayState* play);
+void EnOkuta_Projectile_Fly(EnOkuta* this, PlayState* play);
+void EnOkuta_Projectile_Update(Actor* thisx, PlayState* play);
+
+#include "assets/overlays/ovl_En_Okuta/ovl_En_Okuta.c"
+
ActorInit En_Okuta_InitVars = {
/**/ ACTOR_EN_OKUTA,
/**/ ACTORCAT_ENEMY,
@@ -40,153 +55,1079 @@ ActorInit En_Okuta_InitVars = {
/**/ EnOkuta_Draw,
};
-// static ColliderCylinderInit sCylinderInit = {
-static ColliderCylinderInit D_808708A0 = {
- { COLTYPE_NONE, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_2, COLSHAPE_CYLINDER, },
- { ELEMTYPE_UNK4, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON, OCELEM_ON, },
+static ColliderCylinderInit sProjectileCylinderInit = {
+ {
+ COLTYPE_NONE,
+ AT_ON | AT_TYPE_ENEMY,
+ AC_ON | AC_TYPE_PLAYER,
+ OC1_ON | OC1_TYPE_ALL,
+ OC2_TYPE_2,
+ COLSHAPE_CYLINDER,
+ },
+ {
+ ELEMTYPE_UNK4,
+ { 0xF7CFFFFF, 0x00, 0x04 },
+ { 0xF7CFFFFF, 0x00, 0x00 },
+ TOUCH_ON | TOUCH_SFX_HARD,
+ BUMP_ON,
+ OCELEM_ON,
+ },
{ 13, 20, 0, { 0, 0, 0 } },
};
-// static ColliderCylinderInit sCylinderInit = {
-static ColliderCylinderInit D_808708CC = {
- { COLTYPE_HIT0, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, },
- { ELEMTYPE_UNK1, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON, OCELEM_ON, },
+static ColliderCylinderInit sOctorokCylinderInit = {
+ {
+ COLTYPE_HIT0,
+ AT_ON | AT_TYPE_ENEMY,
+ AC_ON | AC_TYPE_PLAYER,
+ OC1_ON | OC1_TYPE_ALL,
+ OC2_TYPE_1,
+ COLSHAPE_CYLINDER,
+ },
+ {
+ ELEMTYPE_UNK1,
+ { 0xF7CFFFFF, 0x00, 0x04 },
+ { 0xF7CFFFFF, 0x00, 0x00 },
+ TOUCH_ON | TOUCH_SFX_HARD,
+ BUMP_ON,
+ OCELEM_ON,
+ },
{ 20, 40, -30, { 0, 0, 0 } },
};
-// sColChkInfoInit
-static CollisionCheckInfoInit D_808708F8 = { 4, 15, 60, 100 };
-
-// static DamageTable sDamageTable = {
-static DamageTable D_80870900 = {
- /* Deku Nut */ DMG_ENTRY(0, 0x0),
- /* Deku Stick */ DMG_ENTRY(1, 0x0),
- /* Horse trample */ DMG_ENTRY(0, 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(1, 0x0),
- /* Goron punch */ DMG_ENTRY(1, 0x0),
- /* Sword */ DMG_ENTRY(1, 0x0),
- /* Goron pound */ DMG_ENTRY(0, 0x0),
- /* Fire arrow */ DMG_ENTRY(1, 0x0),
- /* Ice arrow */ DMG_ENTRY(2, 0x3),
- /* Light arrow */ DMG_ENTRY(2, 0x4),
- /* 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, 0x0),
- /* Zora barrier */ DMG_ENTRY(0, 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),
+static CollisionCheckInfoInit sColChkInfoInit = { 4, 15, 60, 100 };
+
+typedef enum EnOkutaDamageEffect {
+ /* 0x0 */ EN_OKUTA_DMGEFF_NONE,
+ /* 0x3 */ EN_OKUTA_DMGEFF_FREEZE = 0x3,
+ /* 0x4 */ EN_OKUTA_DMGEFF_LIGHT_ORB
+} EnOkutaDamageEffect;
+
+static DamageTable sDamageTable = {
+ /* Deku Nut */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Deku Stick */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Horse trample */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Explosives */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Zora boomerang */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Normal arrow */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* UNK_DMG_0x06 */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Hookshot */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Goron punch */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Sword */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Goron pound */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Fire arrow */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Ice arrow */ DMG_ENTRY(2, EN_OKUTA_DMGEFF_FREEZE),
+ /* Light arrow */ DMG_ENTRY(2, EN_OKUTA_DMGEFF_LIGHT_ORB),
+ /* Goron spikes */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Deku spin */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Deku bubble */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Deku launch */ DMG_ENTRY(2, EN_OKUTA_DMGEFF_NONE),
+ /* UNK_DMG_0x12 */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Zora barrier */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Normal shield */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Light ray */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Thrown object */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Zora punch */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Spin attack */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
+ /* Sword beam */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Normal Roll */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* UNK_DMG_0x1B */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* UNK_DMG_0x1C */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Unblockable */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* UNK_DMG_0x1E */ DMG_ENTRY(0, EN_OKUTA_DMGEFF_NONE),
+ /* Powder Keg */ DMG_ENTRY(1, EN_OKUTA_DMGEFF_NONE),
};
-// static InitChainEntry sInitChain[] = {
-static InitChainEntry D_80870920[] = {
+static InitChainEntry sInitChain[] = {
ICHAIN_S8(hintId, TATL_HINT_ID_OCTOROK, ICHAIN_CONTINUE),
ICHAIN_F32(targetArrowOffset, 6500, ICHAIN_STOP),
};
-#endif
+void EnOkuta_Init(Actor* thisx, PlayState* play2) {
+ PlayState* play = play2;
+ EnOkuta* this = THIS;
+ WaterBox* waterBox;
+ f32 waterSurface;
+ s32 bgId;
+
+ Actor_ProcessInitChain(thisx, sInitChain);
+ this->numConsecutiveProjectiles = EN_OKUTA_GET_NUM_CONSECUTIVE_PROJECTILES(thisx);
+ thisx->params &= 0xFF;
+
+ if ((EN_OKUTA_GET_TYPE(thisx) == EN_OKUTA_TYPE_RED_OCTOROK) ||
+ (EN_OKUTA_GET_TYPE(thisx) == EN_OKUTA_TYPE_BLUE_OCTOROK)) {
+ SkelAnime_Init(play, &this->skelAnime, &gOctorokSkel, &gOctorokAppearAnim, this->jointTable, this->morphTable,
+ OCTOROK_LIMB_MAX);
+ Collider_InitAndSetCylinder(play, &this->collider, thisx, &sOctorokCylinderInit);
+ CollisionCheck_SetInfo(&thisx->colChkInfo, &sDamageTable, &sColChkInfoInit);
+
+ if (this->numConsecutiveProjectiles == 0xFF || this->numConsecutiveProjectiles == 0) {
+ this->numConsecutiveProjectiles = 1;
+ }
+
+ thisx->floorHeight =
+ BgCheck_EntityRaycastFloor5(&play->colCtx, &thisx->floorPoly, &bgId, thisx, &thisx->world.pos);
+
+ if (!WaterBox_GetSurface1_2(play, &play->colCtx, thisx->world.pos.x, thisx->world.pos.z, &waterSurface,
+ &waterBox) ||
+ waterSurface <= thisx->floorHeight) {
+ Actor_Kill(thisx);
+ } else {
+ thisx->home.pos.y = waterSurface;
+ }
+
+ if (EN_OKUTA_GET_TYPE(thisx) == EN_OKUTA_TYPE_BLUE_OCTOROK) {
+ this->collider.base.colType = COLTYPE_HARD;
+ this->collider.base.acFlags |= AC_HARD;
+ }
+
+ thisx->targetMode = TARGET_MODE_5;
+ EnOkuta_SetupWaitToAppear(this);
+ } else {
+ ActorShape_Init(&thisx->shape, 1100.0f, ActorShadow_DrawCircle, 18.0f);
+ thisx->flags &= ~ACTOR_FLAG_TARGETABLE;
+ thisx->flags |= ACTOR_FLAG_10;
+ Collider_InitAndSetCylinder(play, &this->collider, thisx, &sProjectileCylinderInit);
+ Actor_ChangeCategory(play, &play->actorCtx, thisx, ACTORCAT_PROP);
+ this->timer = 22;
+ thisx->shape.rot.y = 0;
+ thisx->world.rot.x = -thisx->shape.rot.x;
+ thisx->shape.rot.x = 0;
+ this->actionFunc = EnOkuta_Projectile_Fly;
+ thisx->update = EnOkuta_Projectile_Update;
+ thisx->speed = 10.0f;
+ }
+}
+
+void EnOkuta_Destroy(Actor* thisx, PlayState* play) {
+ EnOkuta* this = THIS;
+ Collider_DestroyCylinder(play, &this->collider);
+}
+
+/**
+ * Covers the Octorok's body parts with ice and turns the Octorok red. Note that this is different from being frozen
+ * inside a block of ice; see `EnOkuta_FrozenInIceBlock` for how that is handled.
+ */
+void EnOkuta_Freeze(EnOkuta* this) {
+ this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX;
+ this->drawDmgEffScale = 0.6f;
+ this->drawDmgEffFrozenSteamScale = 9.0f * 0.1f;
+ this->drawDmgEffAlpha = 1.0f;
+ this->timer = 80;
+ this->collider.base.colType = COLTYPE_HIT3;
+ Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 80);
+}
+
+/**
+ * Spawns ice shards on all of the Octorok's body parts that fly off in random directions. Note that this only occurs
+ * when the Octorok is frozen *without* encasing it in a block of ice.
+ */
+void EnOkuta_Thaw(EnOkuta* this, PlayState* play) {
+ if (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX) {
+ this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE;
+ this->drawDmgEffAlpha = 0.0f;
+ Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, EN_OKUTA_BODYPART_MAX, 2, 0.3f, 0.2f);
+ this->collider.base.colType = COLTYPE_HIT0;
+ }
+}
+
+void EnOkuta_SpawnBubbles(EnOkuta* this, PlayState* play) {
+ s32 i;
+
+ for (i = 0; i < 10; i++) {
+ EffectSsBubble_Spawn(play, &this->actor.world.pos, -10.0f, 10.0f, 30.0f, 0.25f);
+ }
+}
+
+/**
+ * Spawns the puff of smoke that appears at the Octorok's snout.
+ */
+void EnOkuta_SpawnSmoke(Vec3f* pos, Vec3f* velocity, s16 scaleStep, PlayState* play) {
+ static Color_RGBA8 sSmokePrimColor = { 255, 255, 255, 255 };
+ static Color_RGBA8 sSmokeEnvColor = { 150, 150, 150, 255 };
+
+ func_800B0DE0(play, pos, velocity, &gZeroVec3f, &sSmokePrimColor, &sSmokeEnvColor, 400, scaleStep);
+}
+
+/**
+ * Spawns the splash that appears when the Octorok appears from underwater, hides underwater, shoots a rock, or dies.
+ */
+void EnOkuta_SpawnSplash(EnOkuta* this, PlayState* play) {
+ EffectSsGSplash_Spawn(play, &this->actor.home.pos, NULL, NULL, 0, 1300);
+}
+
+void EnOkuta_SpawnRipple(EnOkuta* this, PlayState* play) {
+ f32 diffY = this->actor.world.pos.y - this->actor.home.pos.y;
+ Vec3f pos;
+
+ if (((play->gameplayFrames % 7) == 0) && (diffY < 50.0f) && (diffY >= -20.0f)) {
+ pos.x = this->actor.world.pos.x;
+ pos.y = this->actor.home.pos.y;
+ pos.z = this->actor.world.pos.z;
+ EffectSsGRipple_Spawn(play, &pos, 250, 650, 0);
+ }
+}
+
+/**
+ * Returns the height at which the unused blue Octorok variant should float at within the water. This allows for the
+ * Octorok to follow the player underwater, but it does not take collision into account.
+ */
+f32 EnOkuta_GetFloatHeight(EnOkuta* this) {
+ f32 height = this->actor.world.pos.y + this->actor.playerHeightRel + 60.0f;
+
+ // `EnOkuta_Init` sets the Octorok's home y-position to be the water's surface, so this ensures that the Octorok
+ // will always be either at the water's surface or below it.
+ if (this->actor.home.pos.y < height) {
+ return this->actor.home.pos.y;
+ }
+
+ return height;
+}
+
+void EnOkuta_SpawnProjectile(EnOkuta* this, PlayState* play) {
+ Vec3f pos;
+ Vec3f velocity;
+ f32 sin = Math_SinS(this->actor.shape.rot.y);
+ f32 cos = Math_CosS(this->actor.shape.rot.y);
+
+ pos.x = this->actor.world.pos.x + 25.0f * sin;
+ pos.y = this->actor.world.pos.y - 6.0f;
+ pos.z = this->actor.world.pos.z + 25.0f * cos;
+
+ if (Actor_Spawn(&play->actorCtx, play, ACTOR_EN_OKUTA, pos.x, pos.y, pos.z, this->actor.shape.rot.x,
+ this->actor.shape.rot.y, this->actor.shape.rot.z,
+ EN_OKUTA_GET_TYPE(&this->actor) + EN_OKUTA_TYPE_PROJECTILE_BASE) != NULL) {
+ pos.x = this->actor.world.pos.x + (40.0f * sin);
+ pos.z = this->actor.world.pos.z + (40.0f * cos);
+ pos.y = this->actor.world.pos.y;
+
+ velocity.x = 1.5f * sin;
+ velocity.y = 0.0f;
+ velocity.z = 1.5f * cos;
+
+ EnOkuta_SpawnSmoke(&pos, &velocity, 20, play);
+ }
+
+ Actor_PlaySfx(&this->actor, NA_SE_EN_NUTS_THROW);
+}
+
+void EnOkuta_SetupWaitToAppear(EnOkuta* this) {
+ this->actor.draw = NULL;
+ this->actor.world.pos.y = this->actor.home.pos.y;
+ this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
+ this->actionFunc = EnOkuta_WaitToAppear;
+}
+
+/**
+ * Waits until the player gets close, but not *too* close, then makes the Octorok appear at the water's surface.
+ */
+void EnOkuta_WaitToAppear(EnOkuta* this, PlayState* play) {
+ this->actor.world.pos.y = this->actor.home.pos.y;
+
+ if (this->actor.xzDistToPlayer < 480.0f && this->actor.xzDistToPlayer > 200.0f) {
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ EnOkuta_SetupAppear(this, play);
+ } else if (ABS_ALT((s16)(this->actor.yawTowardsPlayer - this->actor.world.rot.y)) < 0x4000 &&
+ play->unk_1887C == 0) {
+ EnOkuta_SetupAppear(this, play);
+ }
+ }
+}
+
+void EnOkuta_SetupAppear(EnOkuta* this, PlayState* play) {
+ this->actor.draw = EnOkuta_Draw;
+ this->actor.shape.rot.y = this->actor.yawTowardsPlayer;
+ this->actor.flags |= ACTOR_FLAG_TARGETABLE;
+ Animation_PlayOnce(&this->skelAnime, &gOctorokAppearAnim);
+ EnOkuta_SpawnBubbles(this, play);
+ this->actionFunc = EnOkuta_Appear;
+}
+
+/**
+ * Plays the appear animation to completion while scaling the Octorok to its full size, then transitions to the idle
+ * floating state. If the Octorok is a red Octorok, and the player is too close to the Octorok when its appear animation
+ * is complete, then the Octorok will instead hide underwater.
+ */
+void EnOkuta_Appear(EnOkuta* this, PlayState* play) {
+ if (SkelAnime_Update(&this->skelAnime)) {
+ if ((this->actor.xzDistToPlayer < 160.0f) && (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK)) {
+ EnOkuta_SetupHide(this);
+ } else {
+ EnOkuta_SetupFloat(this);
+ }
+ } else {
+ f32 curFrame = this->skelAnime.curFrame;
+
+ if (curFrame <= 4.0f) {
+ Actor_SetScale(&this->actor, curFrame * 0.25f * 0.01f);
+ } else if (Animation_OnFrame(&this->skelAnime, 5.0f)) {
+ Actor_SetScale(&this->actor, 0.01f);
+ }
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 2.0f)) {
+ Actor_PlaySfx(&this->actor, NA_SE_EN_OCTAROCK_JUMP);
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 12.0f)) {
+ Actor_PlaySfx(&this->actor, NA_SE_EN_DAIOCTA_LAND);
+ }
+
+ if ((Animation_OnFrame(&this->skelAnime, 3.0f)) || (Animation_OnFrame(&this->skelAnime, 15.0f))) {
+ EnOkuta_SpawnSplash(this, play);
+ }
+}
+
+void EnOkuta_SetupHide(EnOkuta* this) {
+ Animation_PlayOnce(&this->skelAnime, &gOctorokHideAnim);
+ this->actionFunc = EnOkuta_Hide;
+}
+
+/**
+ * Retreats underwater while scaling the Octorok down, then makes the Octorok wait to appear again.
+ */
+void EnOkuta_Hide(EnOkuta* this, PlayState* play) {
+ Math_ApproachF(&this->actor.world.pos.y, this->actor.home.pos.y, 0.5f, 30.0f);
+
+ if (SkelAnime_Update(&this->skelAnime)) {
+ Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_WATER_MID);
+ EnOkuta_SpawnBubbles(this, play);
+ EnOkuta_SetupWaitToAppear(this);
+ } else {
+ f32 curFrame = this->skelAnime.curFrame;
+
+ if (curFrame >= 4.0f) {
+ Actor_SetScale(&this->actor, (6.0f - curFrame) * 0.5f * 0.01f);
+ }
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 2.0f)) {
+ Actor_PlaySfx(&this->actor, NA_SE_EN_DAIOCTA_SINK);
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 4.0f)) {
+ EnOkuta_SpawnSplash(this, play);
+ }
+}
+
+void EnOkuta_SetupFloat(EnOkuta* this) {
+ Animation_PlayLoop(&this->skelAnime, &gOctorokFloatAnim);
+
+ // `EnOkuta_Float` uses `timer` to track how many times the float animation loops before shooting at the player.
+ if (this->actionFunc == EnOkuta_Shoot) {
+ this->timer = 8;
+ } else {
+ this->timer = 0;
+ }
+
+ this->actionFunc = EnOkuta_Float;
+}
+
+/**
+ * Floats in place while slowly turning to face the player. If the player is too far from the Octorok, it will hide
+ * underwater (the red Octoroks will also do this if the player is too close). Otherwise, it will start shooting
+ * projectiles at the player after a short time, so long as the Octorok is roughly facing the player.
+ */
+void EnOkuta_Float(EnOkuta* this, PlayState* play) {
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ this->actor.world.pos.y = this->actor.home.pos.y;
+ } else {
+ this->actor.world.pos.y = EnOkuta_GetFloatHeight(this);
+ }
+
+ SkelAnime_Update(&this->skelAnime);
+
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ DECR(this->timer);
+ }
+
+ if (Animation_OnFrame(&this->skelAnime, 0.5f)) {
+ Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_WATER_SLW);
+ }
+
+ if ((this->actor.xzDistToPlayer > 560.0f) ||
+ ((this->actor.xzDistToPlayer < 160.0f) && (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK))) {
+ EnOkuta_SetupHide(this);
+ } else {
+ s16 yawDiff = Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 3, 0xE38, 0x38E);
+
+ if ((ABS_ALT(yawDiff) < 0x38E) && ((((EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK)) &&
+ (this->timer == 0) && (this->actor.playerHeightRel < 120.0f)) ||
+ ((EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_BLUE_OCTOROK) &&
+ ((this->timer == 0) || (this->actor.xzDistToPlayer < 150.0f))))) {
+ EnOkuta_SetupShoot(this, play);
+ }
+ }
+}
+
+void EnOkuta_SetupShoot(EnOkuta* this, PlayState* play) {
+ Animation_PlayOnce(&this->skelAnime, &gOctorokShootAnim);
+
+ if (this->actionFunc != EnOkuta_Shoot) {
+ // `EnOkuta_Shoot` uses `timer` to track how many projectiles are remaining in the current "volley". The Octorok
+ // will shoot at the player repeatedly, decrementing the `timer` variable after each shot, until it reaches 0.
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ this->timer = this->numConsecutiveProjectiles;
+ } else {
+ this->timer = (560.0f - this->actor.xzDistToPlayer) * 0.005f + 1.0f;
+ }
+ }
+
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ this->jumpHeight = this->actor.playerHeightRel + 20.0f;
+ this->jumpHeight = CLAMP_MIN(this->jumpHeight, 10.0f);
+
+ if (this->jumpHeight > 50.0f) {
+ EnOkuta_SpawnSplash(this, play);
+ Actor_PlaySfx(&this->actor, NA_SE_EN_OCTAROCK_JUMP);
+ }
+ }
+
+ this->actionFunc = EnOkuta_Shoot;
+}
+
+/**
+ * Repeatedly fires projectiles at the player based on the value of the `numConsecutiveProjectiles` variable (for the
+ * red Octorok) or based on the distance between the Octorok and the player (for the blue Octorok). Once it's done
+ * shooting projectiles, the Octorok will go back to floating. If it's a red Octorok, then it will stop shooting and
+ * hide underwater if the player gets too close.
+ */
+void EnOkuta_Shoot(EnOkuta* this, PlayState* play) {
+ f32 curFrame;
-extern ColliderCylinderInit D_808708A0;
-extern ColliderCylinderInit D_808708CC;
-extern CollisionCheckInfoInit D_808708F8;
-extern DamageTable D_80870900;
-extern InitChainEntry D_80870920[];
+ Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 3, 0x71C);
-extern UNK_TYPE D_0600044C;
-extern UNK_TYPE D_06003250;
-extern UNK_TYPE D_06003958;
-extern UNK_TYPE D_06003B24;
-extern UNK_TYPE D_06003EE4;
-extern UNK_TYPE D_06004204;
-extern UNK_TYPE D_0600466C;
+ if (SkelAnime_Update(&this->skelAnime)) {
+ DECR(this->timer);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/EnOkuta_Init.s")
+ if (this->timer == 0) {
+ if ((EN_OKUTA_GET_TYPE(&this->actor) != EN_OKUTA_TYPE_BLUE_OCTOROK) ||
+ (this->actor.xzDistToPlayer > 150.0f)) {
+ EnOkuta_SetupFloat(this);
+ } else {
+ EnOkuta_SetupShoot(this, play);
+ }
+ } else {
+ EnOkuta_SetupShoot(this, play);
+ }
+ } else {
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ curFrame = this->skelAnime.curFrame;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/EnOkuta_Destroy.s")
+ if (curFrame < 13.0f) {
+ this->actor.world.pos.y = Math_SinF(0.2617889f * curFrame) * this->jumpHeight + this->actor.home.pos.y;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E084.s")
+ if ((this->jumpHeight > 50.0f) && Animation_OnFrame(&this->skelAnime, 13.0f)) {
+ EnOkuta_SpawnSplash(this, play);
+ Actor_PlaySfx(&this->actor, NA_SE_EN_DAIOCTA_LAND);
+ }
+ } else {
+ this->actor.world.pos.y = EnOkuta_GetFloatHeight(this);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E0F0.s")
+ if ((curFrame = this->skelAnime.curFrame) < 13.0f) {
+ Player* player = GET_PLAYER(play);
+ Vec3f targetPos;
+ s16 pitch;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E168.s")
+ targetPos.x = player->actor.world.pos.x;
+ targetPos.y = player->actor.world.pos.y + 20.0f;
+ targetPos.z = player->actor.world.pos.z;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E214.s")
+ pitch = Actor_WorldPitchTowardPoint(&this->actor, &targetPos);
+ pitch = CLAMP(pitch, -0x2AAA, 0);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E27C.s")
+ this->actor.shape.rot.x = Math_SinF(0.2617889f * curFrame) * pitch;
+ }
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E2C0.s")
+ if (Animation_OnFrame(&this->skelAnime, 6.0f)) {
+ EnOkuta_SpawnProjectile(this, play);
+ }
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E378.s")
+ if ((EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) && (this->actor.xzDistToPlayer < 160.0f)) {
+ EnOkuta_SetupHide(this);
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E3B8.s")
+void EnOkuta_SetupDamaged(EnOkuta* this) {
+ Animation_MorphToPlayOnce(&this->skelAnime, &gOctorokHitAnim, -5.0f);
+ Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 11);
+ this->collider.base.acFlags &= ~AC_ON;
+ Actor_SetScale(&this->actor, 0.01f);
+ Actor_PlaySfx(&this->actor, NA_SE_EN_OCTAROCK_DEAD1);
+ this->actionFunc = EnOkuta_Damaged;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E4FC.s")
+void EnOkuta_Damaged(EnOkuta* this, PlayState* play) {
+ if (SkelAnime_Update(&this->skelAnime)) {
+ if (this->actor.colChkInfo.health == 0) {
+ EnOkuta_SetupDie(this);
+ } else {
+ this->collider.base.acFlags |= AC_ON;
+ EnOkuta_SetupFloat(this);
+ }
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E52C.s")
+ Math_ApproachF(&this->actor.world.pos.y, this->actor.home.pos.y, 0.5f, 5.0f);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E5E8.s")
+void EnOkuta_SetupDie(EnOkuta* this) {
+ Animation_MorphToPlayOnce(&this->skelAnime, &gOctorokDieAnim, -3.0f);
+ this->timer = 0;
+ this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
+ this->actionFunc = EnOkuta_Die;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E658.s")
+void EnOkuta_Die(EnOkuta* this, PlayState* play) {
+ static Vec3f sBubbleAccel = { 0.0f, -0.5f, 0.0f };
+ static Color_RGBA8 sBubblePrimColor = { 255, 255, 255, 255 };
+ static Color_RGBA8 sBubbleEnvColor = { 150, 150, 150, 0 };
+ Vec3f velocity;
+ Vec3f pos;
+ s32 i;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E7A8.s")
+ if (SkelAnime_Update(&this->skelAnime)) {
+ this->timer++;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E7E8.s")
+ Math_ApproachF(&this->actor.world.pos.y, this->actor.home.pos.y, 0.5f, 5.0f);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E8E8.s")
+ if (this->timer == 5) {
+ pos.x = this->actor.world.pos.x;
+ pos.y = this->actor.world.pos.y + 40.0f;
+ pos.z = this->actor.world.pos.z;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086E948.s")
+ velocity.x = 0.0f;
+ velocity.y = -0.5f;
+ velocity.z = 0.0f;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EAE0.s")
+ EnOkuta_SpawnSmoke(&pos, &velocity, -20, play);
+ Actor_PlaySfx(&this->actor, NA_SE_EN_OCTAROCK_DEAD2);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EC00.s")
+ if (Animation_OnFrame(&this->skelAnime, 15.0f)) {
+ EnOkuta_SpawnSplash(this, play);
+ Actor_PlaySfx(&this->actor, NA_SE_EN_DAIOCTA_LAND);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EE8C.s")
+ if (this->timer < 3) {
+ Actor_SetScale(&this->actor, ((this->timer * 0.25f) + 1.0f) * 0.01f);
+ return;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EF14.s")
+ if (this->timer < 6) {
+ Actor_SetScale(&this->actor, (1.5f - ((this->timer - 2) * 0.2333f)) * 0.01f);
+ return;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EF90.s")
+ if (this->timer < 11) {
+ Actor_SetScale(&this->actor, (((this->timer - 5) * 0.04f) + 0.8f) * 0.01f);
+ return;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086EFE8.s")
+ if (Math_StepToF(&this->actor.scale.x, 0.0f, 0.0005f)) {
+ SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 30, NA_SE_EN_COMMON_WATER_MID);
+ Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0xA0);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F2FC.s")
+ for (i = 0; i < 20; i++) {
+ velocity.x = (Rand_ZeroOne() - 0.5f) * 7.0f;
+ velocity.y = Rand_ZeroOne() * 7.0f;
+ velocity.z = (Rand_ZeroOne() - 0.5f) * 7.0f;
+ EffectSsDtBubble_SpawnCustomColor(play, &this->actor.world.pos, &velocity, &sBubbleAccel, &sBubblePrimColor,
+ &sBubbleEnvColor, Rand_S16Offset(100, 50), 25, false);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F434.s")
+ Actor_Kill(&this->actor);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F4B0.s")
+ this->actor.scale.z = this->actor.scale.x;
+ this->actor.scale.y = this->actor.scale.x;
+}
+
+void EnOkuta_SetupFrozen(EnOkuta* this, PlayState* play) {
+ this->timer = 10;
+ Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_GRAY, COLORFILTER_INTENSITY_FLAG | 255,
+ COLORFILTER_BUFFLAG_OPA, 10);
+ this->actor.child = Actor_SpawnAsChild(
+ &play->actorCtx, &this->actor, play, ACTOR_OBJ_ICEBLOCK, this->actor.world.pos.x,
+ this->actor.world.pos.y + this->skelAnime.jointTable->y * this->actor.scale.y + 25.0f * this->headScale.y,
+ this->actor.world.pos.z, 0, this->actor.home.rot.y, 0, 3);
+
+ if (this->actor.child != NULL) {
+ this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
+ this->actor.flags |= ACTOR_FLAG_10;
+ this->actor.child->csId = this->actor.csId;
+ this->actionFunc = EnOkuta_FrozenInIceBlock;
+ } else {
+ EnOkuta_Freeze(this);
+
+ if (Actor_ApplyDamage(&this->actor) == 0) {
+ Enemy_StartFinishingBlow(play, &this->actor);
+ this->collider.base.acFlags &= ~AC_ON;
+ this->timer = 3;
+ }
+
+ this->actionFunc = EnOkuta_Frozen;
+ }
+}
+
+/**
+ * Stops all movement and animation for the Octorok and waits until the ice block actor is killed. Afterwards, the
+ * Octorok will sink back to the water's surface and then start floating again.
+ */
+void EnOkuta_FrozenInIceBlock(EnOkuta* this, PlayState* play) {
+ this->actor.colorFilterTimer = 10;
+
+ if ((this->actor.child == NULL) || (this->actor.child->update == NULL)) {
+ this->actor.flags |= ACTOR_FLAG_TARGETABLE;
+
+ if (Math_StepToF(&this->actor.world.pos.y, this->actor.home.pos.y, 10.0f)) {
+ this->actor.flags &= ~ACTOR_FLAG_10;
+ EnOkuta_SetupFloat(this);
+ }
+ }
+}
+
+/**
+ * Stops all movement and animation for the Octorok and waits 80 frames (or 3 frames if the attack that froze the
+ * Octorok also killed it). Afterwards, ice pieces will fly off the Octorok, and it will play its damaged animation.
+ * Note that this function is *not* responsible for handling the Octorok being encased in an ice block; that's handled
+ * by `EnOkuta_FrozenInIceBlock`. This behavior is incredibly difficult to trigger in-game and is unlikely to be seen by
+ * most players; see `EnOkuta_SetupFrozen` for more information.
+ */
+void EnOkuta_Frozen(EnOkuta* this, PlayState* play) {
+ DECR(this->timer);
+
+ if (this->timer == 0) {
+ EnOkuta_Thaw(this, play);
+ EnOkuta_SetupDamaged(this);
+ }
+}
+
+void EnOkuta_SetupSpin(EnOkuta* this) {
+ Animation_Change(&this->skelAnime, &gOctorokHitAnim, 1.0f, 0.0f, Animation_GetLastFrame(&gOctorokHitAnim) - 3.0f,
+ ANIMMODE_ONCE, -3.0f);
+ this->timer = 20;
+ this->actionFunc = EnOkuta_Spin;
+}
+
+/**
+ * Spins in place for 20 frames while playing its hit animation, then makes the Octorok go back to floating. This
+ * function is only ever used by the unused blue Octorok variant; this is what the blue Octorok does instead of taking
+ * damage from the player's attacks.
+ */
+void EnOkuta_Spin(EnOkuta* this, PlayState* play) {
+ this->timer--;
+ Math_ScaledStepToS(&this->actor.shape.rot.x, 0, 0x400);
+
+ if (SkelAnime_Update(&this->skelAnime)) {
+ Animation_Change(&this->skelAnime, &gOctorokHitAnim, 1.0f, 0.0f,
+ Animation_GetLastFrame(&gOctorokHitAnim) - 3.0f, ANIMMODE_ONCE, -3.0f);
+ }
+
+ if (this->timer < 10) {
+ this->actor.shape.rot.y += (s16)(0x2000 * Math_SinF(this->timer * (M_PIf / 20.0f)));
+ } else {
+ this->actor.shape.rot.y += 0x2000;
+ }
+
+ if (this->timer == 0) {
+ EnOkuta_SetupFloat(this);
+ }
+}
+
+/**
+ * Handles all the behavior of the Octorok's projectile, including spinning the projectile around constantly and
+ * reflecting the projectile when it bounces off the player's shield .
+ */
+void EnOkuta_Projectile_Fly(EnOkuta* this, PlayState* play) {
+ Vec3f fragmentPos;
+ Player* player = GET_PLAYER(play);
+ Vec3s shieldRot;
+
+ this->timer--;
+
+ if (this->timer < 0) {
+ this->actor.velocity.y -= 0.5f;
+ this->actor.world.rot.x =
+ Math_Atan2S_XY(sqrtf(SQ(this->actor.velocity.x) + SQ(this->actor.velocity.z)), this->actor.velocity.y);
+ }
+
+ this->actor.home.rot.z += 0x1554;
+
+ if ((this->actor.bgCheckFlags & BGCHECKFLAG_WALL) || (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) ||
+ (this->actor.bgCheckFlags & BGCHECKFLAG_CEILING) || (this->collider.base.atFlags & AT_HIT) ||
+ (this->collider.base.acFlags & AC_HIT) || (this->collider.base.ocFlags1 & OC1_HIT) ||
+ (this->actor.floorHeight == BGCHECK_Y_MIN)) {
+ if (player->currentShield == PLAYER_SHIELD_HEROS_SHIELD) {
+ if ((this->collider.base.atFlags & AT_HIT) && (this->collider.base.atFlags & AT_TYPE_ENEMY)) {
+ if (this->collider.base.atFlags & AT_BOUNCED) {
+ this->collider.base.atFlags &= ~(AT_HIT | AT_BOUNCED | AT_TYPE_ENEMY);
+ this->collider.base.atFlags |= AT_TYPE_PLAYER;
+ this->collider.info.toucher.dmgFlags = DMG_THROWN_OBJECT;
+ this->collider.info.toucher.damage = 2;
+ Matrix_MtxFToYXZRot(&player->shieldMf, &shieldRot, false);
+ this->actor.world.rot.y = shieldRot.y + 0x8000;
+ this->timer = 22;
+ return;
+ }
+ }
+ }
+
+ fragmentPos.x = this->actor.world.pos.x;
+ fragmentPos.y = this->actor.world.pos.y + 11.0f;
+ fragmentPos.z = this->actor.world.pos.z;
+ EffectSsHahen_SpawnBurst(play, &fragmentPos, 6.0f, 0, 1, 2, 15, OBJECT_OKUTA, 10, gOctorokProjectileDL);
+ SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EN_OCTAROCK_ROCK);
+
+ if ((this->collider.base.atFlags & AT_HIT) && (this->collider.base.atFlags & AT_TYPE_ENEMY) &&
+ !(this->collider.base.atFlags & AT_BOUNCED) &&
+ (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_BLUE_PROJECTILE)) {
+ func_800B8D98(play, &this->actor, 8.0f, this->actor.world.rot.y, 6.0f);
+ }
+
+ Actor_Kill(&this->actor);
+ return;
+ }
+
+ if (this->timer == -300) {
+ Actor_Kill(&this->actor);
+ }
+}
+
+/**
+ * Adjusts the scale of the Octorok's head based on their current action and their current animation frame.
+ */
+void EnOkuta_UpdateHeadScale(EnOkuta* this) {
+ f32 curFrame = this->skelAnime.curFrame;
+
+ if (this->actionFunc == EnOkuta_Appear) {
+ if (curFrame < 8.0f) {
+ this->headScale.x = this->headScale.y = this->headScale.z = 1.0f;
+ } else if (curFrame < 10.0f) {
+ this->headScale.x = this->headScale.z = 1.0f;
+ this->headScale.y = ((curFrame - 7.0f) * 0.4f) + 1.0f;
+ } else if (curFrame < 14.0f) {
+ this->headScale.x = this->headScale.z = ((curFrame - 9.0f) * 0.075f) + 1.0f;
+ this->headScale.y = 1.8f - ((curFrame - 9.0f) * 0.25f);
+ } else {
+ this->headScale.x = this->headScale.z = 1.3f - ((curFrame - 13.0f) * 0.05f);
+ this->headScale.y = ((curFrame - 13.0f) * 0.0333f) + 0.8f;
+ }
+ } else if (this->actionFunc == EnOkuta_Hide) {
+ if (curFrame < 3.0f) {
+ this->headScale.y = 1.0f;
+ } else {
+ if (curFrame < 4.0f) {
+ this->headScale.y = (curFrame - 2.0f) + 1.0f;
+ } else {
+ this->headScale.y = 2.0f - ((curFrame - 3.0f) * 0.333f);
+ }
+ }
+ this->headScale.x = this->headScale.z = 1.0f;
+ } else if (this->actionFunc == EnOkuta_Shoot) {
+ if (curFrame < 5.0f) {
+ this->headScale.x = this->headScale.y = this->headScale.z = (curFrame * 0.125f) + 1.0f;
+ } else if (curFrame < 7.0f) {
+ this->headScale.x = this->headScale.y = this->headScale.z = 1.5f - ((curFrame - 4.0f) * 0.35f);
+ } else if (curFrame < 17.0f) {
+ this->headScale.x = this->headScale.z = ((curFrame - 6.0f) * 0.05f) + 0.8f;
+ this->headScale.y = 0.8f;
+ } else {
+ this->headScale.x = this->headScale.z = 1.3f - ((curFrame - 16.0f) * 0.1f);
+ this->headScale.y = ((curFrame - 16.0f) * 0.0666f) + 0.8f;
+ }
+ } else if (this->actionFunc == EnOkuta_Float) {
+ this->headScale.x = this->headScale.z = 1.0f;
+ this->headScale.y = Math_SinF((M_PIf / 16) * curFrame) * 0.2f + 1.0f;
+ } else {
+ this->headScale.x = this->headScale.y = this->headScale.z = 1.0f;
+ }
+}
+
+void EnOkuta_UpdateDamage(EnOkuta* this, PlayState* play) {
+ if (!(this->collider.base.acFlags & AC_HIT)) {
+ return;
+ }
+
+ this->collider.base.acFlags &= ~AC_HIT;
+
+ if (this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX ||
+ !(this->collider.info.acHitInfo->toucher.dmgFlags & 0xDB0B3)) {
+ Actor_SetDropFlag(&this->actor, &this->collider.info);
+ EnOkuta_Thaw(this, play);
+
+ if (this->actor.colChkInfo.damageEffect == EN_OKUTA_DMGEFF_FREEZE) {
+ EnOkuta_SetupFrozen(this, play);
+ return;
+ }
+
+ if (this->actor.colChkInfo.damageEffect == EN_OKUTA_DMGEFF_LIGHT_ORB) {
+ this->drawDmgEffAlpha = 4.0f;
+ this->drawDmgEffScale = 0.6f;
+ this->drawDmgEffType = ACTOR_DRAW_DMGEFF_LIGHT_ORBS;
+ Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CLEAR_TAG, this->collider.info.bumper.hitPos.x,
+ this->collider.info.bumper.hitPos.y, this->collider.info.bumper.hitPos.z, 0, 0, 0,
+ CLEAR_TAG_PARAMS(CLEAR_TAG_LARGE_LIGHT_RAYS));
+ }
+
+ if (Actor_ApplyDamage(&this->actor) == 0) {
+ Enemy_StartFinishingBlow(play, &this->actor);
+ }
+
+ EnOkuta_SetupDamaged(this);
+ }
+}
+
+void EnOkuta_Update(Actor* thisx, PlayState* play2) {
+ PlayState* play = play2;
+ EnOkuta* this = THIS;
+ s32 pad[2];
+
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ EnOkuta_UpdateDamage(this, play);
+ } else if ((this->collider.base.atFlags & AT_HIT) || (this->collider.base.acFlags & AC_HIT)) {
+ if (this->collider.base.atFlags & AT_HIT) {
+ func_800B8D98(play, &this->actor, 8.0f, this->actor.world.rot.y, 6.0f);
+ }
+
+ EnOkuta_SetupSpin(this);
+ }
+
+ this->actionFunc(this, play);
+
+ if (this->actionFunc != EnOkuta_FrozenInIceBlock) {
+ EnOkuta_UpdateHeadScale(this);
+ this->collider.dim.height = (sOctorokCylinderInit.dim.height * this->headScale.y - this->collider.dim.yShift) *
+ this->actor.scale.y * 100.0f;
+ Collider_UpdateCylinder(&this->actor, &this->collider);
+
+ if (this->actionFunc == EnOkuta_Appear || this->actionFunc == EnOkuta_Hide) {
+ this->collider.dim.pos.y = this->actor.world.pos.y + this->skelAnime.jointTable->y * this->actor.scale.y;
+ this->collider.dim.radius = sOctorokCylinderInit.dim.radius * this->actor.scale.x * 100.0f;
+ }
+
+ if (this->actor.draw != NULL) {
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_BLUE_OCTOROK) {
+ CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base);
+ }
+
+ if (this->collider.base.acFlags & AC_ON) {
+ CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base);
+ }
+
+ CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
+ EnOkuta_SpawnRipple(this, play);
+ }
+
+ Actor_SetFocus(&this->actor, 15.0f);
+
+ if (this->drawDmgEffAlpha > 0.0f) {
+ if (this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX) {
+ Math_StepToF(&this->drawDmgEffAlpha, 0.0f, 0.05f);
+ this->drawDmgEffScale = (this->drawDmgEffAlpha + 1.0f) * 0.3f;
+ this->drawDmgEffScale = CLAMP_MAX(this->drawDmgEffScale, 0.6f);
+ return;
+ }
+
+ if (!Math_StepToF(&this->drawDmgEffFrozenSteamScale, 0.6f, 15.0f * 0.001f)) {
+ Actor_PlaySfx_Flagged(&this->actor, NA_SE_EV_ICE_FREEZE - SFX_FLAG);
+ }
+ }
+ }
+}
+
+void EnOkuta_Projectile_Update(Actor* thisx, PlayState* play) {
+ EnOkuta* this = THIS;
+ Player* player = GET_PLAYER(play);
+ s32 pad;
+ Vec3f prevPos;
+ s32 canRestorePrevPos = false;
+
+ if (!(player->stateFlags1 & (PLAYER_STATE1_2 | PLAYER_STATE1_40 | PLAYER_STATE1_80 | PLAYER_STATE1_200 |
+ PLAYER_STATE1_10000000 | PLAYER_STATE1_20000000))) {
+ this->actionFunc(this, play);
+ Actor_MoveWithoutGravity(&this->actor);
+ Math_Vec3f_Copy(&prevPos, &this->actor.world.pos);
+ Actor_UpdateBgCheckInfo(play, &this->actor, 10.0f, 15.0f, 30.0f,
+ UPDBGCHECKINFO_FLAG_1 | UPDBGCHECKINFO_FLAG_2 | UPDBGCHECKINFO_FLAG_4);
+
+ if ((this->actor.bgCheckFlags & BGCHECKFLAG_WALL) &&
+ (SurfaceType_IsIgnoredByProjectiles(&play->colCtx, this->actor.wallPoly, this->actor.wallBgId))) {
+ canRestorePrevPos = true;
+ this->actor.bgCheckFlags &= ~BGCHECKFLAG_WALL;
+ }
+
+ if ((this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) &&
+ (SurfaceType_IsIgnoredByProjectiles(&play->colCtx, this->actor.floorPoly, this->actor.floorBgId))) {
+ canRestorePrevPos = true;
+ this->actor.bgCheckFlags &= ~BGCHECKFLAG_GROUND;
+ }
+
+ if (canRestorePrevPos && !(this->actor.bgCheckFlags & (BGCHECKFLAG_GROUND | BGCHECKFLAG_WALL))) {
+ Math_Vec3f_Copy(&this->actor.world.pos, &prevPos);
+ }
+
+ Collider_UpdateCylinder(&this->actor, &this->collider);
+ this->actor.flags |= ACTOR_FLAG_1000000;
+ CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base);
+ CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base);
+ CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
+ }
+}
+
+/**
+ * Gets the scaling factor for animating the snout limb. If the limb is not being transformed, no scale value is
+ * returned. Returns true if the snout scale should be updated, false otherwise. The snout scale is returned via the
+ * `scale` parameter.
+ */
+s32 EnOkuta_GetSnoutScale(EnOkuta* this, f32 curFrame, Vec3f* scale) {
+ if (this->actionFunc == EnOkuta_Float) {
+ scale->z = scale->y = 1.0f;
+ scale->x = Math_SinF((M_PIf / 16) * curFrame) * 0.4f + 1.0f;
+ } else if (this->actionFunc == EnOkuta_Shoot) {
+ if (curFrame < 5.0f) {
+ scale->z = 1.0f;
+ scale->x = scale->y = (curFrame * 0.25f) + 1.0f;
+ } else if (curFrame < 7.0f) {
+ scale->z = (curFrame - 4.0f) * 0.5f + 1.0f;
+ scale->x = scale->y = 2.0f - (curFrame - 4.0f) * 0.5f;
+ } else {
+ scale->z = 2.0f - ((curFrame - 6.0f) * 0.0769f);
+ scale->x = scale->y = 1.0f;
+ }
+ } else if (this->actionFunc == EnOkuta_Die) {
+ if (curFrame >= 35.0f || curFrame < 25.0f) {
+ return false;
+ }
+
+ if (curFrame < 27.0f) {
+ scale->z = 1.0f;
+ scale->x = scale->y = (curFrame - 24.0f) * 0.5f + 1.0f;
+ } else if (curFrame < 30.0f) {
+ scale->z = (curFrame - 26.0f) * 0.333f + 1.0f;
+ scale->x = scale->y = 2.0f - (curFrame - 26.0f) * 0.333f;
+ } else {
+ scale->z = 2.0f - ((curFrame - 29.0f) * 0.2f);
+ scale->x = scale->y = 1.0f;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+s32 EnOkuta_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) {
+ EnOkuta* this = THIS;
+ s32 shouldScaleLimb = false;
+ Vec3f scale;
+ f32 curFrame = this->skelAnime.curFrame;
+
+ if (this->actionFunc == EnOkuta_Die) {
+ curFrame += this->timer;
+ }
+
+ if (limbIndex == OCTOROK_LIMB_HEAD) {
+ if (this->headScale.x != 1.0f || this->headScale.y != 1.0f || this->headScale.z != 1.0f) {
+ Math_Vec3f_Copy(&scale, &this->headScale);
+ shouldScaleLimb = true;
+ }
+ } else if (limbIndex == OCTOROK_LIMB_SNOUT) {
+ shouldScaleLimb = EnOkuta_GetSnoutScale(this, curFrame, &scale);
+ }
+
+ if (shouldScaleLimb) {
+ Matrix_Scale(scale.x, scale.y, scale.z, MTXMODE_APPLY);
+ }
+
+ return false;
+}
+
+static s8 sLimbToBodyParts[OCTOROK_LIMB_MAX] = {
+ BODYPART_NONE, // OCTOROK_LIMB_NONE
+ EN_OKUTA_BODYPART_BODY, // OCTOROK_LIMB_BODY
+ BODYPART_NONE, // OCTOROK_LIMB_FRONT_LEFT_ARM_BASE
+ BODYPART_NONE, // OCTOROK_LIMB_FRONT_LEFT_ARM_MIDDLE
+ EN_OKUTA_BODYPART_FRONT_LEFT_ARM_END, // OCTOROK_LIMB_FRONT_LEFT_ARM_END
+ BODYPART_NONE, // OCTOROK_LIMB_FRONT_RIGHT_ARM_BASE
+ BODYPART_NONE, // OCTOROK_LIMB_FRONT_RIGHT_ARM_MIDDLE
+ EN_OKUTA_BODYPART_FRONT_RIGHT_ARM_END, // OCTOROK_LIMB_FRONT_RIGHT_ARM_END
+ BODYPART_NONE, // OCTOROK_LIMB_BACK_LEFT_ARM_BASE
+ BODYPART_NONE, // OCTOROK_LIMB_BACK_LEFT_ARM_MIDDLE
+ EN_OKUTA_BODYPART_BACK_LEFT_ARM_END, // OCTOROK_LIMB_BACK_LEFT_ARM_END
+ BODYPART_NONE, // OCTOROK_LIMB_BACK_RIGHT_ARM_BASE
+ BODYPART_NONE, // OCTOROK_LIMB_BACK_RIGHT_ARM_MIDDLE
+ EN_OKUTA_BODYPART_BACK_RIGHT_ARM_END, // OCTOROK_LIMB_BACK_RIGHT_ARM_END
+ EN_OKUTA_BODYPART_HEAD, // OCTOROK_LIMB_HEAD
+ EN_OKUTA_BODYPART_SNOUT, // OCTOROK_LIMB_SNOUT
+};
+
+/**
+ * The last three elements of `bodyPartsPos` are not tied to any particular limb and are instead used to control the
+ * placement of effects. The positions of these effects are offset by a certain amount from the Octorok's head limb.
+ */
+static Vec3f sEffectsBodyPartOffsets[3] = {
+ { 1500.0f, 1000.0f, 0.0f },
+ { -1500.0f, 1000.0f, 0.0f },
+ { 0.0f, 1500.0f, -2000.0f },
+};
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F4F4.s")
+void EnOkuta_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) {
+ EnOkuta* this = THIS;
+ s32 bodyPartIndex = sLimbToBodyParts[limbIndex];
+ s32 i;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F57C.s")
+ if (bodyPartIndex != BODYPART_NONE) {
+ if (bodyPartIndex == EN_OKUTA_BODYPART_SNOUT) {
+ Matrix_MultVecX(1500.0f, &this->bodyPartsPos[bodyPartIndex]);
+ } else if (bodyPartIndex == EN_OKUTA_BODYPART_HEAD) {
+ Matrix_MultVecY(2800.0f, &this->bodyPartsPos[bodyPartIndex]);
+ bodyPartIndex++;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F694.s")
+ for (i = 0; i < ARRAY_COUNT(sEffectsBodyPartOffsets); i++) {
+ Matrix_MultVec3f(&sEffectsBodyPartOffsets[i], &this->bodyPartsPos[bodyPartIndex + i]);
+ }
+ } else {
+ Matrix_MultZero(&this->bodyPartsPos[bodyPartIndex]);
+ }
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086F8FC.s")
+void EnOkuta_Draw(Actor* thisx, PlayState* play) {
+ EnOkuta* this = THIS;
+ s32 pad;
+ Gfx* gfx;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_8086FCA4.s")
+ OPEN_DISPS(play->state.gfxCtx);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/EnOkuta_Update.s")
+ gfx = POLY_OPA_DISP;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_808700C0.s")
+ gSPDisplayList(&gfx[0], gSetupDLs[SETUPDL_25]);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_80870254.s")
+ if (EN_OKUTA_GET_TYPE(&this->actor) < EN_OKUTA_TYPE_PROJECTILE_BASE) {
+ if (EN_OKUTA_GET_TYPE(&this->actor) == EN_OKUTA_TYPE_RED_OCTOROK) {
+ gSPSegment(&gfx[1], 0x08, D_801AEFA0);
+ } else {
+ gSPSegment(&gfx[1], 0x08, gOctorokBlueMaterialDL);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_808704DC.s")
+ POLY_OPA_DISP = &gfx[2];
+ SkelAnime_DrawOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, EnOkuta_OverrideLimbDraw,
+ EnOkuta_PostLimbDraw, &this->actor);
+ } else {
+ Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY);
+ Matrix_RotateZS(this->actor.home.rot.z, MTXMODE_APPLY);
+ gSPMatrix(&gfx[1], Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+ gSPDisplayList(&gfx[2], gOctorokProjectileDL);
+ POLY_OPA_DISP = &gfx[3];
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/func_808705C8.s")
+ CLOSE_DISPS(play->state.gfxCtx);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Okuta/EnOkuta_Draw.s")
+ Actor_DrawDamageEffects(play, &this->actor, this->bodyPartsPos, EN_OKUTA_BODYPART_MAX,
+ this->drawDmgEffScale * this->actor.scale.y * 100.0f, this->drawDmgEffFrozenSteamScale,
+ this->drawDmgEffAlpha, this->drawDmgEffType);
+}
diff --git a/src/overlays/actors/ovl_En_Okuta/z_en_okuta.h b/src/overlays/actors/ovl_En_Okuta/z_en_okuta.h
index 93a68ae38..3f6b66cd7 100644
--- a/src/overlays/actors/ovl_En_Okuta/z_en_okuta.h
+++ b/src/overlays/actors/ovl_En_Okuta/z_en_okuta.h
@@ -2,16 +2,53 @@
#define Z_EN_OKUTA_H
#include "global.h"
+#include "objects/object_okuta/object_okuta.h"
struct EnOkuta;
typedef void (*EnOkutaActionFunc)(struct EnOkuta*, PlayState*);
+#define EN_OKUTA_GET_NUM_CONSECUTIVE_PROJECTILES(thisx) (((thisx)->params >> 8) & 0xFF)
+#define EN_OKUTA_GET_TYPE(thisx) ((thisx)->params)
+
+typedef enum EnOkutaType {
+ /* 0 */ EN_OKUTA_TYPE_RED_OCTOROK,
+ /* 1 */ EN_OKUTA_TYPE_BLUE_OCTOROK, // Unused in the final game. This variant is invincible to all attacks and can chase the player underwater if they dive.
+ /* 16 */ EN_OKUTA_TYPE_PROJECTILE_BASE = 16,
+ /* 16 */ EN_OKUTA_TYPE_RED_PROJECTILE = EN_OKUTA_TYPE_RED_OCTOROK + EN_OKUTA_TYPE_PROJECTILE_BASE,
+ /* 17 */ EN_OKUTA_TYPE_BLUE_PROJECTILE = EN_OKUTA_TYPE_BLUE_OCTOROK + EN_OKUTA_TYPE_PROJECTILE_BASE
+} EnOkutaType;
+
+typedef enum EnOkutaBodyPart {
+ /* 0 */ EN_OKUTA_BODYPART_BODY,
+ /* 1 */ EN_OKUTA_BODYPART_FRONT_LEFT_ARM_END,
+ /* 2 */ EN_OKUTA_BODYPART_FRONT_RIGHT_ARM_END,
+ /* 3 */ EN_OKUTA_BODYPART_BACK_LEFT_ARM_END,
+ /* 4 */ EN_OKUTA_BODYPART_BACK_RIGHT_ARM_END,
+ /* 5 */ EN_OKUTA_BODYPART_SNOUT,
+ /* 6 */ EN_OKUTA_BODYPART_HEAD,
+ /* 7 */ EN_OKUTA_BODYPART_EFFECTS_1,
+ /* 8 */ EN_OKUTA_BODYPART_EFFECTS_2,
+ /* 9 */ EN_OKUTA_BODYPART_EFFECTS_3,
+ /* 10 */ EN_OKUTA_BODYPART_MAX
+} EnOkutaBodyPart;
+
typedef struct EnOkuta {
/* 0x000 */ Actor actor;
- /* 0x144 */ char unk_144[0x44];
+ /* 0x144 */ SkelAnime skelAnime;
/* 0x188 */ EnOkutaActionFunc actionFunc;
- /* 0x18C */ char unk_18C[0x1A8];
+ /* 0x18C */ u8 drawDmgEffType;
+ /* 0x18E */ s16 timer;
+ /* 0x190 */ s16 numConsecutiveProjectiles; // when the Octorok starts shooting projectiles, it will shoot this many projectiles in a row before stopping
+ /* 0x192 */ Vec3s jointTable[OCTOROK_LIMB_MAX];
+ /* 0x1F2 */ Vec3s morphTable[OCTOROK_LIMB_MAX];
+ /* 0x254 */ f32 drawDmgEffAlpha;
+ /* 0x258 */ f32 drawDmgEffScale;
+ /* 0x25C */ f32 drawDmgEffFrozenSteamScale;
+ /* 0x260 */ f32 jumpHeight;
+ /* 0x264 */ Vec3f headScale;
+ /* 0x270 */ Vec3f bodyPartsPos[EN_OKUTA_BODYPART_MAX];
+ /* 0x2E8 */ ColliderCylinder collider;
} EnOkuta; // size = 0x334
#endif // Z_EN_OKUTA_H
diff --git a/src/overlays/actors/ovl_En_Syateki_Okuta/z_en_syateki_okuta.c b/src/overlays/actors/ovl_En_Syateki_Okuta/z_en_syateki_okuta.c
index f4f8f3ffa..7f650a6ad 100644
--- a/src/overlays/actors/ovl_En_Syateki_Okuta/z_en_syateki_okuta.c
+++ b/src/overlays/actors/ovl_En_Syateki_Okuta/z_en_syateki_okuta.c
@@ -124,11 +124,11 @@ void EnSyatekiOkuta_Destroy(Actor* thisx, PlayState* play) {
/**
* Spawns the puff of smoke that appears when the Octorok disappears when it dies.
*/
-void EnSyatekiOkuta_SpawnDust(Vec3f* pos, Vec3f* velocity, s16 scaleStep, PlayState* play) {
- static Color_RGBA8 sDustPrimColor = { 255, 255, 255, 255 };
- static Color_RGBA8 sDustEnvColor = { 150, 150, 150, 255 };
+void EnSyatekiOkuta_SpawnSmoke(Vec3f* pos, Vec3f* velocity, s16 scaleStep, PlayState* play) {
+ static Color_RGBA8 sSmokePrimColor = { 255, 255, 255, 255 };
+ static Color_RGBA8 sSmokeEnvColor = { 150, 150, 150, 255 };
- func_800B0DE0(play, pos, velocity, &gZeroVec3f, &sDustPrimColor, &sDustEnvColor, 400, scaleStep);
+ func_800B0DE0(play, pos, velocity, &gZeroVec3f, &sSmokePrimColor, &sSmokeEnvColor, 400, scaleStep);
}
/**
@@ -308,7 +308,7 @@ void EnSyatekiOkuta_Die(EnSyatekiOkuta* this, PlayState* play) {
velocity.x = 0.0f;
velocity.y = -0.5f;
velocity.z = 0.0f;
- EnSyatekiOkuta_SpawnDust(&pos, &velocity, -20, play);
+ EnSyatekiOkuta_SpawnSmoke(&pos, &velocity, -20, play);
Actor_PlaySfx(&this->actor, NA_SE_EN_OCTAROCK_DEAD2);
}
@@ -502,8 +502,9 @@ void EnSyatekiOkuta_UpdateHeadScale(EnSyatekiOkuta* this) {
}
/**
- * Returns true if the snout scale should be updated, false otherwise. The snout scale is returned via the scale
- * parameter.
+ * Gets the scaling factor for animating the snout limb. If the limb is not being transformed, no scale value is
+ * returned. Returns true if the snout scale should be updated, false otherwise. The snout scale is returned via the
+ * `scale` parameter.
*/
s32 EnSyatekiOkuta_GetSnoutScale(EnSyatekiOkuta* this, f32 curFrame, Vec3f* scale) {
if (this->actionFunc == EnSyatekiOkuta_Appear) {
diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt
index 3b259bea7..fdc06d2f2 100644
--- a/tools/disasm/functions.txt
+++ b/tools/disasm/functions.txt
@@ -5060,41 +5060,41 @@
0x8086D898:("EnPametfrog_Draw",),
0x8086DE20:("EnOkuta_Init",),
0x8086E058:("EnOkuta_Destroy",),
- 0x8086E084:("func_8086E084",),
- 0x8086E0F0:("func_8086E0F0",),
- 0x8086E168:("func_8086E168",),
- 0x8086E214:("func_8086E214",),
- 0x8086E27C:("func_8086E27C",),
- 0x8086E2C0:("func_8086E2C0",),
- 0x8086E378:("func_8086E378",),
- 0x8086E3B8:("func_8086E3B8",),
- 0x8086E4FC:("func_8086E4FC",),
- 0x8086E52C:("func_8086E52C",),
- 0x8086E5E8:("func_8086E5E8",),
- 0x8086E658:("func_8086E658",),
- 0x8086E7A8:("func_8086E7A8",),
- 0x8086E7E8:("func_8086E7E8",),
- 0x8086E8E8:("func_8086E8E8",),
- 0x8086E948:("func_8086E948",),
- 0x8086EAE0:("func_8086EAE0",),
- 0x8086EC00:("func_8086EC00",),
- 0x8086EE8C:("func_8086EE8C",),
- 0x8086EF14:("func_8086EF14",),
- 0x8086EF90:("func_8086EF90",),
- 0x8086EFE8:("func_8086EFE8",),
- 0x8086F2FC:("func_8086F2FC",),
- 0x8086F434:("func_8086F434",),
- 0x8086F4B0:("func_8086F4B0",),
- 0x8086F4F4:("func_8086F4F4",),
- 0x8086F57C:("func_8086F57C",),
- 0x8086F694:("func_8086F694",),
- 0x8086F8FC:("func_8086F8FC",),
- 0x8086FCA4:("func_8086FCA4",),
+ 0x8086E084:("EnOkuta_Freeze",),
+ 0x8086E0F0:("EnOkuta_Thaw",),
+ 0x8086E168:("EnOkuta_SpawnBubbles",),
+ 0x8086E214:("EnOkuta_SpawnSmoke",),
+ 0x8086E27C:("EnOkuta_SpawnSplash",),
+ 0x8086E2C0:("EnOkuta_SpawnRipple",),
+ 0x8086E378:("EnOkuta_GetFloatHeight",),
+ 0x8086E3B8:("EnOkuta_SpawnProjectile",),
+ 0x8086E4FC:("EnOkuta_SetupWaitToAppear",),
+ 0x8086E52C:("EnOkuta_WaitToAppear",),
+ 0x8086E5E8:("EnOkuta_SetupAppear",),
+ 0x8086E658:("EnOkuta_Appear",),
+ 0x8086E7A8:("EnOkuta_SetupHide",),
+ 0x8086E7E8:("EnOkuta_Hide",),
+ 0x8086E8E8:("EnOkuta_SetupFloat",),
+ 0x8086E948:("EnOkuta_Float",),
+ 0x8086EAE0:("EnOkuta_SetupShoot",),
+ 0x8086EC00:("EnOkuta_Shoot",),
+ 0x8086EE8C:("EnOkuta_SetupDamaged",),
+ 0x8086EF14:("EnOkuta_Damaged",),
+ 0x8086EF90:("EnOkuta_SetupDie",),
+ 0x8086EFE8:("EnOkuta_Die",),
+ 0x8086F2FC:("EnOkuta_SetupFrozen",),
+ 0x8086F434:("EnOkuta_FrozenInIceBlock",),
+ 0x8086F4B0:("EnOkuta_Frozen",),
+ 0x8086F4F4:("EnOkuta_SetupSpin",),
+ 0x8086F57C:("EnOkuta_Spin",),
+ 0x8086F694:("EnOkuta_Projectile_Fly",),
+ 0x8086F8FC:("EnOkuta_UpdateHeadScale",),
+ 0x8086FCA4:("EnOkuta_UpdateDamage",),
0x8086FDE0:("EnOkuta_Update",),
- 0x808700C0:("func_808700C0",),
- 0x80870254:("func_80870254",),
- 0x808704DC:("func_808704DC",),
- 0x808705C8:("func_808705C8",),
+ 0x808700C0:("EnOkuta_Projectile_Update",),
+ 0x80870254:("EnOkuta_GetSnoutScale",),
+ 0x808704DC:("EnOkuta_OverrideLimbDraw",),
+ 0x808705C8:("EnOkuta_PostLimbDraw",),
0x808706E0:("EnOkuta_Draw",),
0x80870DB0:("EnBom_Init",),
0x80870FF8:("EnBom_Destroy",),
@@ -10217,7 +10217,7 @@
0x80A35DDC:("ObjBell_Draw",),
0x80A35FF0:("EnSyatekiOkuta_Init",),
0x80A3611C:("EnSyatekiOkuta_Destroy",),
- 0x80A36148:("EnSyatekiOkuta_SpawnDust",),
+ 0x80A36148:("EnSyatekiOkuta_SpawnSmoke",),
0x80A361B0:("EnSyatekiOkuta_SpawnSplash",),
0x80A361F4:("EnSyatekiOkuta_IsHiddenByAnotherOctorok",),
0x80A36260:("EnSyatekiOkuta_SetupAttachToShootingGalleryMan",),
diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt
index 1055a990a..dce154a10 100644
--- a/tools/disasm/variables.txt
+++ b/tools/disasm/variables.txt
@@ -5389,23 +5389,20 @@
0x8086DA94:("D_8086DA94","f32","",0x4),
0x8086DA98:("D_8086DA98","f32","",0x4),
0x8086DA9C:("D_8086DA9C","f32","",0x4),
- 0x80870870:("D_80870870","UNK_TYPE1","",0x1),
- 0x80870880:("En_Okuta_InitVars","UNK_TYPE1","",0x1),
- 0x808708A0:("D_808708A0","UNK_TYPE1","",0x1),
- 0x808708CC:("D_808708CC","UNK_TYPE1","",0x1),
- 0x808708EC:("D_808708EC","UNK_TYPE2","",0x2),
- 0x808708EE:("D_808708EE","UNK_TYPE2","",0x2),
- 0x808708F8:("D_808708F8","UNK_TYPE1","",0x1),
- 0x80870900:("D_80870900","UNK_TYPE1","",0x1),
- 0x80870920:("D_80870920","UNK_TYPE1","",0x1),
- 0x80870928:("D_80870928","UNK_TYPE1","",0x1),
- 0x8087092C:("D_8087092C","UNK_TYPE1","",0x1),
- 0x80870930:("D_80870930","UNK_TYPE1","",0x1),
- 0x8087093C:("D_8087093C","UNK_TYPE1","",0x1),
- 0x80870940:("D_80870940","UNK_TYPE1","",0x1),
- 0x80870944:("D_80870944","UNK_TYPE1","",0x1),
- 0x80870954:("D_80870954","UNK_TYPE1","",0x1),
- 0x80870978:("D_80870978","UNK_TYPE1","",0x1),
+ 0x80870870:("gOctorokBlueMaterialDL","Gfx","[2]",0x10),
+ 0x80870880:("En_Okuta_InitVars","ActorInit","",0x20),
+ 0x808708A0:("sProjectileCylinderInit","ColliderCylinderInit","",0x2C),
+ 0x808708CC:("sOctorokCylinderInit","ColliderCylinderInit","",0x2C),
+ 0x808708F8:("sColChkInfoInit","CollisionCheckInfoInit","",0x4),
+ 0x80870900:("sDamageTable","DamageTable","",0x20),
+ 0x80870920:("sInitChain","InitChainEntry","[2]",0x8),
+ 0x80870928:("sSmokePrimColor","Color_RGBA8","",0x4),
+ 0x8087092C:("sSmokeEnvColor","Color_RGBA8","",0x4),
+ 0x80870930:("sBubbleAccel","Vec3f","",0xC),
+ 0x8087093C:("sBubblePrimColor","Color_RGBA8","",0x4),
+ 0x80870940:("sBubbleEnvColor","Color_RGBA8","",0x4),
+ 0x80870944:("sLimbToBodyParts","s8","[16]",0x10),
+ 0x80870954:("sEffectsBodyPartOffsets","Vec3f","[3]",0x24),
0x80870980:("D_80870980","f32","",0x4),
0x80870984:("D_80870984","f32","",0x4),
0x80870988:("D_80870988","f32","",0x4),
@@ -11058,8 +11055,8 @@
0x80A37A88:("D_80A37A88","UNK_TYPE1","",0x1),
0x80A37B08:("D_80A37B08","UNK_TYPE1","",0x1),
0x80A37B88:("D_80A37B88","UNK_TYPE1","",0x1),
- 0x80A37B90:("sDustPrimColor","UNK_TYPE1","",0x1),
- 0x80A37B94:("sDustEnvColor","UNK_TYPE1","",0x1),
+ 0x80A37B90:("sSmokePrimColor","UNK_TYPE1","",0x1),
+ 0x80A37B94:("sSmokeEnvColor","UNK_TYPE1","",0x1),
0x80A37B98:("sBubbleAccel","UNK_TYPE1","",0x1),
0x80A37BA4:("sBubblePrimColor","UNK_TYPE1","",0x1),
0x80A37BA8:("sBubbleEnvColor","UNK_TYPE1","",0x1),