summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Overton <tom-overton@users.noreply.github.com>2022-02-13 17:07:26 -0800
committerGitHub <noreply@github.com>2022-02-13 20:07:26 -0500
commit3597e4809fa07f9f2fba445e56a1c8321afa2bfc (patch)
treed59fa4a4ec10ef7acd260a149e5786c6b12a0318 /src
parent45cee7d8af09395d8ab19461454b079748e2abae (diff)
En_Jg (Goron Elder) OK and documented. Also document object_jg (#594)
* Migrate data to C * Init / Destroy / Update / Draw * Functions called from Update * Better Init match * A few more functions * A few more functions * Drum-spawning function OK * EnJg OK * Name a few struct vars * Name some static vars * Name limbs * Name limb DLs * Name some textures * Name all remaining textures * First pass animation names + use object in actor file * Add drum to XML + clean it up * Better translations, thanks Emily! * Some more documentation * Improvements * Params accessor for s_goro * Proper object symbols for Jg_Gakki * Delete unnecessary undefined_syms * Text defines for my own sanity * Some more names * Document point/path stuff * Name every function, even if the names are bad * Params macro for if he's in goron shrine * Some misc improvements * Document EnJg_GetShrineGoronToFocusOn better * Final cleanup before PR * Change action name because this actor is dumb! * Run actorfixer * Rename some variables now that a function is named * ProcessTalkRequest takes GameState * Run actorfixer * Use new actor flags * Update text ID names to match my text ID PR * Define the limb enum in the object * Be consistent about where the EnumName is * Don't name limbs after the actor * Basic enum for drum limb * Fix stuff after merge * Do if/else if/else * Remove textId defines * Decimal weekEventReg accesses
Diffstat (limited to 'src')
-rw-r--r--src/overlays/actors/ovl_En_Jg/z_en_jg.c975
-rw-r--r--src/overlays/actors/ovl_En_Jg/z_en_jg.h34
-rw-r--r--src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.c2
-rw-r--r--src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.h2
-rw-r--r--src/overlays/actors/ovl_Obj_Jg_Gakki/z_obj_jg_gakki.c6
5 files changed, 966 insertions, 53 deletions
diff --git a/src/overlays/actors/ovl_En_Jg/z_en_jg.c b/src/overlays/actors/ovl_En_Jg/z_en_jg.c
index ce8651d3c..0dc1d5df5 100644
--- a/src/overlays/actors/ovl_En_Jg/z_en_jg.c
+++ b/src/overlays/actors/ovl_En_Jg/z_en_jg.c
@@ -5,17 +5,65 @@
*/
#include "z_en_jg.h"
+#include "overlays/actors/ovl_En_S_Goro/z_en_s_goro.h"
-#define FLAGS 0x00000019
+#define FLAGS (ACTOR_FLAG_1 | ACTOR_FLAG_8 | ACTOR_FLAG_10)
#define THIS ((EnJg*)thisx)
+#define FLAG_SHRINE_GORON_ARMS_RAISED (1 << 0)
+#define FLAG_LOOKING_AT_PLAYER (1 << 2)
+#define FLAG_DRUM_SPAWNED (1 << 3)
+
void EnJg_Init(Actor* thisx, GlobalContext* globalCtx);
void EnJg_Destroy(Actor* thisx, GlobalContext* globalCtx);
void EnJg_Update(Actor* thisx, GlobalContext* globalCtx);
void EnJg_Draw(Actor* thisx, GlobalContext* globalCtx);
-#if 0
+void EnJg_GoronShrineTalk(EnJg* this, GlobalContext* globalCtx);
+void EnJg_GoronShrineCheer(EnJg* this, GlobalContext* globalCtx);
+void EnJg_AlternateTalkOrWalkInPlace(EnJg* this, GlobalContext* globalCtx);
+void EnJg_Walk(EnJg* this, GlobalContext* globalCtx);
+void EnJg_Talk(EnJg* this, GlobalContext* globalCtx);
+void EnJg_SetupWalk(EnJg* this, GlobalContext* globalCtx);
+void EnJg_FrozenIdle(EnJg* this, GlobalContext* globalCtx);
+void EnJg_EndFrozenInteraction(EnJg* this, GlobalContext* globalCtx);
+void EnJg_TeachLullabyIntro(EnJg* this, GlobalContext* globalCtx);
+void EnJg_LullabyIntroCutsceneAction(EnJg* this, GlobalContext* globalCtx);
+s32 EnJg_GetNextTextId(EnJg* this);
+s32 EnJg_GetStartingConversationTextId(EnJg* this, GlobalContext* globalCtx);
+void EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer(EnJg* this, GlobalContext* globalCtx);
+
+typedef enum {
+ /* 0 */ EN_JG_ANIMATION_IDLE,
+ /* 1 */ EN_JG_ANIMATION_WALK,
+ /* 2 */ EN_JG_ANIMATION_WAVING,
+ /* 3 */ EN_JG_ANIMATION_SHAKING_HEAD,
+ /* 4 */ EN_JG_ANIMATION_SURPRISE_START,
+ /* 5 */ EN_JG_ANIMATION_SURPRISE_LOOP,
+ /* 6 */ EN_JG_ANIMATION_ANGRY,
+ /* 7 */ EN_JG_ANIMATION_FROZEN_START,
+ /* 8 */ EN_JG_ANIMATION_FROZEN_LOOP,
+ /* 9 */ EN_JG_ANIMATION_WALK_2,
+ /* 10 */ EN_JG_ANIMATION_TAKING_OUT_DRUM,
+ /* 11 */ EN_JG_ANIMATION_DRUM_IDLE,
+ /* 12 */ EN_JG_ANIMATION_PLAYING_DRUM,
+ /* 13 */ EN_JG_ANIMATION_THINKING,
+ /* 14 */ EN_JG_ANIMATION_REMEMBERING,
+ /* 15 */ EN_JG_ANIMATION_STRONG_REMEMBERING,
+ /* 16 */ EN_JG_ANIMATION_DEPRESSED,
+ /* 17 */ EN_JG_ANIMATION_CUTSCENE_IDLE,
+ /* 18 */ EN_JG_ANIMATION_CRADLE,
+ /* 19 */ EN_JG_ANIMATION_MAX,
+} EnJgAnimationIndex;
+
+typedef enum {
+ /* 0x0 */ EN_JG_ACTION_FIRST_THAW,
+ /* 0x1 */ EN_JG_ACTION_SPAWNING,
+ /* 0x2 */ EN_JG_ACTION_FROZEN_OR_NON_FIRST_THAW,
+ /* 0x3 */ EN_JG_ACTION_LULLABY_INTRO_CS,
+} EnJgAction;
+
const ActorInit En_Jg_InitVars = {
ACTOR_EN_JG,
ACTORCAT_NPC,
@@ -28,18 +76,29 @@ const ActorInit En_Jg_InitVars = {
(ActorFunc)EnJg_Draw,
};
-// static ColliderCylinderInit sCylinderInit = {
-static ColliderCylinderInit D_80B75820 = {
- { COLTYPE_NONE, AT_NONE, AC_NONE, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, },
- { ELEMTYPE_UNK0, { 0x00000000, 0x00, 0x00 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_NONE | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, },
+static ColliderCylinderInit sCylinderInit = {
+ {
+ COLTYPE_NONE,
+ AT_NONE,
+ AC_NONE,
+ OC1_ON | OC1_TYPE_ALL,
+ OC2_TYPE_1,
+ COLSHAPE_CYLINDER,
+ },
+ {
+ ELEMTYPE_UNK0,
+ { 0x00000000, 0x00, 0x00 },
+ { 0xF7CFFFFF, 0x00, 0x00 },
+ TOUCH_NONE | TOUCH_SFX_NORMAL,
+ BUMP_ON,
+ OCELEM_ON,
+ },
{ 60, 80, 0, { 0, 0, 0 } },
};
-// sColChkInfoInit
-static CollisionCheckInfoInit2 D_80B7584C = { 0, 50, 80, 0, MASS_IMMOVABLE };
+static CollisionCheckInfoInit2 sColChkInfoInit = { 0, 50, 80, 0, MASS_IMMOVABLE };
-// static DamageTable sDamageTable = {
-static DamageTable D_80B75858 = {
+static DamageTable sDamageTable = {
/* Deku Nut */ DMG_ENTRY(0, 0x0),
/* Deku Stick */ DMG_ENTRY(0, 0x0),
/* Horse trample */ DMG_ENTRY(0, 0x0),
@@ -74,70 +133,896 @@ static DamageTable D_80B75858 = {
/* Powder Keg */ DMG_ENTRY(0, 0x0),
};
-#endif
+static ActorAnimationEntryS sAnimations[] = {
+ { &gGoronElderIdleAnim, 1.0f, 0, -1, 0, -10 }, { &gGoronElderWalkAnim, 1.0f, 0, -1, 0, -10 },
+ { &gGoronElderWavingAnim, 1.0f, 0, -1, 0, -10 }, { &gGoronElderHeadShakeAnim, 1.0f, 0, -1, 0, -10 },
+ { &gGoronElderSurpriseStartAnim, 1.0f, 0, -1, 2, -10 }, { &gGoronElderSurpriseLoopAnim, 1.0f, 0, -1, 0, -10 },
+ { &gGoronElderAngryAnim, 1.0f, 0, -1, 0, -10 }, { &gGoronElderSurpriseStartAnim, 2.0f, 0, -1, 2, 0 },
+ { &gGoronElderSurpriseStartAnim, -2.0f, 0, -1, 2, 0 }, { &gGoronElderWalkAnim, -1.0f, 0, -1, 0, -10 },
+ { &gGoronElderTakeOutDrumAnim, 1.0f, 0, -1, 2, 0 }, { &gGoronElderDrumIdleAnim, 1.0f, 0, -1, 0, 0 },
+ { &gGoronElderPlayingDrumAnim, 1.0f, 1, 44, 2, 0 }, { &gGoronElderThinkingAnim, 1.0f, 0, -1, 0, 0 },
+ { &gGoronElderRememberingAnim, 1.0f, 0, -1, 2, 0 }, { &gGoronElderStrongRememberingAnim, 1.0f, 0, -1, 2, 0 },
+ { &gGoronElderDepressedAnim, 1.0f, 0, -1, 0, 0 }, { &gGoronElderIdleAnim, 1.0f, 0, -1, 0, 0 },
+ { &gGoronElderCradleAnim, 1.0f, 0, -1, 0, 0 },
+};
+
+static Vec3f sSfxPos = { 0.0f, 0.0f, 0.0f };
+
+static Vec3f sFocusOffset = { 0.0f, 0.0f, 0.0f };
+
+static Vec3f sBreathPosOffset = { 1000.0f, -500.0f, 0.0f };
+
+static Vec3f sBreathVelOffset = { 0.0f, 0.0f, 0.75f };
+
+static Vec3f sBreathAccelOffset = { 0.0f, 0.0f, -0.070000000298f };
+
+/**
+ * When the elder and the Gorons in the shrine are doing a cheer
+ * for Darmani, the camera will focus on specific Gorons for
+ * certain parts of the cheer. This function is responsible for
+ * returning a pointer to the specified Goron so the camera
+ * can focus on them.
+ */
+Actor* EnJg_GetShrineGoronToFocusOn(GlobalContext* globalCtx, u8 focusedShrineGoronParam) {
+ Actor* actorIterator = globalCtx->actorCtx.actorLists[ACTORCAT_NPC].first;
+
+ while (actorIterator != NULL) {
+ if ((actorIterator->id == ACTOR_EN_S_GORO) &&
+ (EN_S_GORO_GET_PARAM_F(actorIterator) == focusedShrineGoronParam)) {
+ return actorIterator;
+ }
+
+ actorIterator = actorIterator->next;
+ }
+
+ return NULL;
+}
+
+void EnJg_UpdateCollision(EnJg* this, GlobalContext* globalCtx) {
+ this->collider.dim.pos.x = this->actor.world.pos.x;
+ this->collider.dim.pos.y = this->actor.world.pos.y;
+ this->collider.dim.pos.z = this->actor.world.pos.z;
+
+ CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
+ CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
+ Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 0.0f, 30.0f, 30.0f, 7);
+}
+
+s16 EnJg_GetWalkingYRotation(Path* path, s32 pointIndex, Vec3f* pos, f32* distSQ) {
+ Vec3s* points;
+ f32 diffX;
+ f32 diffZ;
+
+ if (path != NULL) {
+ points = Lib_SegmentedToVirtual(path->points);
+ points = &points[pointIndex];
+ diffX = points[0].x - pos->x;
+ diffZ = points[0].z - pos->z;
+ } else {
+ diffX = 0.0f;
+ diffZ = 0.0f;
+ }
+
+ *distSQ = SQ(diffX) + SQ(diffZ);
+
+ return RADF_TO_BINANG(Math_Acot2F(diffZ, diffX));
+}
+
+s32 EnJg_ReachedPoint(EnJg* this, Path* path, s32 pointIndex) {
+ Vec3s* points = Lib_SegmentedToVirtual(path->points);
+ s32 pathCount = path->count;
+ s32 currentPoint = pointIndex;
+ s32 reached = false;
+ f32 diffX;
+ f32 diffZ;
+ f32 px;
+ f32 pz;
+ f32 d;
+ Vec3f point;
+
+ Math_Vec3s_ToVec3f(&point, &points[pointIndex]);
+ if (currentPoint == 0) {
+ diffX = points[1].x - points[0].x;
+ diffZ = points[1].z - points[0].z;
+ } else if (currentPoint == (pathCount - 1)) {
+ diffX = points[pathCount - 1].x - points[pathCount - 2].x;
+ diffZ = points[pathCount - 1].z - points[pathCount - 2].z;
+ } else {
+ diffX = points[currentPoint + 1].x - points[currentPoint - 1].x;
+ diffZ = points[currentPoint + 1].z - points[currentPoint - 1].z;
+ }
+
+ func_8017B7F8(&point, RADF_TO_BINANG(func_80086B30(diffX, diffZ)), &px, &pz, &d);
+
+ if (((this->actor.world.pos.x * px) + (pz * this->actor.world.pos.z) + d) > 0.0f) {
+ reached = true;
+ }
+
+ return reached;
+}
+
+s16 EnJg_GetCutsceneForTeachingLullabyIntro(EnJg* this) {
+ s16 temp = this->actor.yawTowardsPlayer - this->actor.shape.rot.y;
+
+ if (temp > 0) {
+ return this->actor.cutscene;
+ }
+
+ return ActorCutscene_GetAdditionalCutscene(this->actor.cutscene);
+}
+
+void EnJg_SetupGoronShrineCheer(EnJg* this) {
+ ActorCutscene_Stop(this->cutscene);
+ if (this->focusedShrineGoronParam == 10) {
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ this->actionFunc = EnJg_GoronShrineTalk;
+ } else {
+ this->cutscene = 0x7C;
+ }
+ } else {
+ this->cutscene = ActorCutscene_GetAdditionalCutscene(this->cutscene);
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ ActorCutscene_Stop(0x7C);
+ }
+ }
+
+ ActorCutscene_SetIntentToPlay(this->cutscene);
+ this->actionFunc = EnJg_GoronShrineCheer;
+
+ switch (this->textId) {
+ case 0xDD0: // The greatest Goron hero of all?
+ case 0xDD2: // The immortal Goron?
+ case 0xDD3: // The star we wish upon?
+ case 0xDD4: // "Darmani, greatest of Gorons!"
+ case 0xDD6: // Darmani, greatest in the world!
+ this->flags |= FLAG_SHRINE_GORON_ARMS_RAISED;
+ break;
+
+ default:
+ this->flags &= ~FLAG_SHRINE_GORON_ARMS_RAISED;
+ break;
+ }
+}
+
+void EnJg_SetupTalk(EnJg* this, GlobalContext* globalCtx) {
+ switch (this->textId) {
+ case 0xDAC: // What was I doing?
+ this->animationIndex = EN_JG_ANIMATION_SHAKING_HEAD;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Talk;
+ break;
+
+ case 0xDAD: // I must hurry!
+ this->animationIndex = EN_JG_ANIMATION_SURPRISE_START;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_AlternateTalkOrWalkInPlace;
+ break;
+
+ case 0xDB7: // You're Darmani!
+ this->animationIndex = EN_JG_ANIMATION_SURPRISE_START;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Talk;
+ break;
+
+ case 0xDAE: // Do you have business with the Elder?
+ case 0xDB3: // As Elder, I must do something
+ case 0xDB6: // "Hunh???"
+ case 0xDBA: // I've been made a fool of!
+ case 0xDBD: // "...What?"
+ case 0xDC4: // It's so cold I can't play
+ this->animationIndex = EN_JG_ANIMATION_IDLE;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Talk;
+ break;
+
+ case 0xDB0: // It's this cold snap
+ case 0xDBB: // If I can see past the illusion, you'll vanish
+ case 0xDBC: // Following me won't do you any good
+ case 0xDC6: // I am counting on you
+ this->animationIndex = EN_JG_ANIMATION_ANGRY;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Talk;
+ break;
+
+ case 0xDB4: // This is our problem (first)
+ case 0xDB5: // This is our problem (repeat)
+ this->animationIndex = EN_JG_ANIMATION_WAVING;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Talk;
+ break;
+ }
+}
+
+void EnJg_Idle(EnJg* this, GlobalContext* globalCtx) {
+ EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer(this, globalCtx);
+}
+
+void EnJg_GoronShrineIdle(EnJg* this, GlobalContext* globalCtx) {
+ if (Actor_ProcessTalkRequest(&this->actor, &globalCtx->state)) {
+ this->flags |= FLAG_LOOKING_AT_PLAYER;
+ func_801518B0(globalCtx, this->textId, &this->actor);
+ this->actionFunc = EnJg_GoronShrineTalk;
+ } else if ((this->actor.xzDistToPlayer < 100.0f) || (this->actor.isTargeted)) {
+ func_800B863C(&this->actor, globalCtx);
+ this->textId = EnJg_GetStartingConversationTextId(this, globalCtx);
+ }
+}
+
+void EnJg_GoronShrineTalk(EnJg* this, GlobalContext* globalCtx) {
+ if ((Message_GetState(&globalCtx->msgCtx) == 5) && (func_80147624(globalCtx))) {
+ if ((this->textId == 0xDCC) || (this->textId == 0xDDD) || (this->textId == 0xDE0)) {
+ // There is nothing more to say after these lines, so end the current conversation.
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->flags &= ~FLAG_LOOKING_AT_PLAYER;
+ this->actionFunc = EnJg_GoronShrineIdle;
+ } else {
+ this->textId = EnJg_GetNextTextId(this);
+ func_801518B0(globalCtx, this->textId, &this->actor);
+ }
+ }
+}
+
+void EnJg_GoronShrineCheer(EnJg* this, GlobalContext* globalCtx) {
+ if (ActorCutscene_GetCanPlayNext(this->cutscene)) {
+ switch (this->textId) {
+ case 0xDD0: // The greatest Goron hero of all?
+ case 0xDD2: // The immortal Goron?
+ case 0xDD3: // The star we wish upon?
+ case 0xDD4: // "Darmani, greatest of Gorons!"
+ case 0xDD6: // Darmani, greatest in the world!
+ // Focus on a specifc Goron for these lines
+ this->shrineGoron = EnJg_GetShrineGoronToFocusOn(globalCtx, this->focusedShrineGoronParam);
+ ActorCutscene_Start(this->cutscene, this->shrineGoron);
+ func_800E0308(globalCtx->cameraPtrs[0], this->shrineGoron);
+ break;
+
+ default:
+ // Focus on the whole group for these lines
+ ActorCutscene_Start(this->cutscene, &this->actor);
+ func_800E0308(globalCtx->cameraPtrs[0], this->shrineGoron);
+ break;
+ }
+ this->actionFunc = EnJg_GoronShrineTalk;
+ } else {
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ if (this->focusedShrineGoronParam == 10) {
+ this->actionFunc = EnJg_GoronShrineTalk;
+ } else {
+ ActorCutscene_Stop(0x7C);
+ }
+ }
+ ActorCutscene_SetIntentToPlay(this->cutscene);
+ }
+}
+
+/**
+ * You need to reach a certain point in the conversation when talking with the elder before
+ * he starts walking for the first time. This function specifically handles this scenario; the
+ * reason they used this instead of EnJg_Talk is seemingly to bypass EnJg_SetupWalk resetting
+ * the freeze timer.
+ *
+ * Additionally, when the elder has reached the end of his specified path, this function will
+ * set his speed to 0, causing him to walk in place.
+ */
+void EnJg_AlternateTalkOrWalkInPlace(EnJg* this, GlobalContext* globalCtx) {
+ u8 messageState = Message_GetState(&globalCtx->msgCtx);
+ s16 currentFrame = this->skelAnime.curFrame;
+ s16 lastFrame = Animation_GetLastFrame(sAnimations[this->animationIndex].animationSeg);
+
+ if (this->animationIndex == EN_JG_ANIMATION_SURPRISE_START) {
+ if (currentFrame == lastFrame) {
+ this->animationIndex = EN_JG_ANIMATION_SURPRISE_LOOP;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ }
+ } else if (this->animationIndex == EN_JG_ANIMATION_SURPRISE_LOOP) {
+ if ((messageState == 5) && (func_80147624(globalCtx))) {
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->flags &= ~FLAG_LOOKING_AT_PLAYER;
+ this->animationIndex = EN_JG_ANIMATION_WALK;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Walk;
+ }
+ } else if (this->animationIndex == EN_JG_ANIMATION_WALK) {
+ Math_ApproachF(&this->actor.speedXZ, 0.0f, 0.2f, 1.0f);
+ EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer(this, globalCtx);
+ }
+}
+
+void EnJg_Walk(EnJg* this, GlobalContext* globalCtx) {
+ s16 yRotation;
+ f32 distSQ;
+
+ if (this->path != NULL) {
+ yRotation = EnJg_GetWalkingYRotation(this->path, this->currentPoint, &this->actor.world.pos, &distSQ);
+ if (this->actor.bgCheckFlags & 8) {
+ yRotation = this->actor.wallYaw;
+ }
+
+ Math_SmoothStepToS(&this->actor.world.rot.y, yRotation, 4, 0x3E8, 1);
+ this->actor.shape.rot.y = this->actor.world.rot.y;
+
+ if (EnJg_ReachedPoint(this, this->path, this->currentPoint)) {
+ if (this->currentPoint >= (this->path->count - 1)) {
+ // Force the elder to walk in place
+ this->animationIndex = EN_JG_ANIMATION_WALK;
+ this->actionFunc = EnJg_AlternateTalkOrWalkInPlace;
+ } else {
+ this->currentPoint++;
+ Math_ApproachF(&this->actor.speedXZ, 0.5f, 0.2f, 1.0f);
+ }
+ } else {
+ Math_ApproachF(&this->actor.speedXZ, 0.5f, 0.2f, 1.0f);
+ }
+ }
+
+ EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer(this, globalCtx);
+}
+
+void EnJg_Talk(EnJg* this, GlobalContext* globalCtx) {
+ u8 messageState = Message_GetState(&globalCtx->msgCtx);
+ s16 currentFrame = this->skelAnime.curFrame;
+ s16 lastFrame = Animation_GetLastFrame(sAnimations[this->animationIndex].animationSeg);
+ u16 temp;
+
+ if ((this->animationIndex == EN_JG_ANIMATION_SURPRISE_START) && (currentFrame == lastFrame)) {
+ this->animationIndex = EN_JG_ANIMATION_SURPRISE_LOOP;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ }
+
+ if ((messageState == 5) && (func_80147624(globalCtx))) {
+ temp = this->textId;
+ if ((temp == 0xDB4) || (temp == 0xDB5) || (temp == 0xDC4) || (temp == 0xDC6)) {
+ // There is nothing more to say after these lines, so end the current conversation.
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->flags &= ~FLAG_LOOKING_AT_PLAYER;
+ this->actionFunc = EnJg_SetupWalk;
+ return;
+ }
+
+ temp = this->textId;
+ if ((temp == 0xDBB) || (temp == 0xDBC)) {
+ if (!(gSaveContext.weekEventReg[24] & 0x80)) {
+ // The player hasn't talked to the Goron Child at least once, so they can't learn
+ // the Lullaby Intro. End the current conversation with the player.
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->flags &= ~FLAG_LOOKING_AT_PLAYER;
+ this->actionFunc = EnJg_SetupWalk;
+ } else if (((gSaveContext.weekEventReg[24] & 0x40)) ||
+ (CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) || CHECK_QUEST_ITEM(QUEST_SONG_LULLABY_INTRO))) {
+ // The player already has the Lullaby or Lullaby Intro, so say "I'm counting on you"
+ this->textId = EnJg_GetNextTextId(this);
+ func_801518B0(globalCtx, this->textId, &this->actor);
+ this->actionFunc = EnJg_SetupTalk;
+ } else {
+ // To get to this point, the player *has* talked to the Goron Child, but doesn't
+ // already have the Lullaby or Lullaby Intro. End the current conversation and
+ // start the cutscene that teaches the Lullaby Intro.
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->flags &= ~FLAG_LOOKING_AT_PLAYER;
+ this->cutscene = EnJg_GetCutsceneForTeachingLullabyIntro(this);
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ ActorCutscene_Stop(0x7C);
+ }
+ ActorCutscene_SetIntentToPlay(this->cutscene);
+ this->actionFunc = EnJg_TeachLullabyIntro;
+ }
+ } else {
+ this->textId = EnJg_GetNextTextId(this);
+ func_801518B0(globalCtx, this->textId, &this->actor);
+ this->actionFunc = EnJg_SetupTalk;
+ }
+ }
+}
+
+void EnJg_SetupWalk(EnJg* this, GlobalContext* globalCtx) {
+ if (this->animationIndex != EN_JG_ANIMATION_WALK) {
+ this->animationIndex = EN_JG_ANIMATION_WALK;
+ this->freezeTimer = 1000;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Walk;
+ } else {
+ this->freezeTimer = 1000;
+ this->actionFunc = EnJg_Walk;
+ }
+}
+
+void EnJg_Freeze(EnJg* this, GlobalContext* globalCtx) {
+ s16 currentFrame = this->skelAnime.curFrame;
+ s16 lastFrame = Animation_GetLastFrame(sAnimations[this->animationIndex].animationSeg);
-extern ColliderCylinderInit D_80B75820;
-extern CollisionCheckInfoInit2 D_80B7584C;
-extern DamageTable D_80B75858;
+ if (this->action == EN_JG_ACTION_SPAWNING) {
+ this->action = EN_JG_ACTION_FROZEN_OR_NON_FIRST_THAW;
+ this->freezeTimer = 1000;
+ this->skelAnime.curFrame = lastFrame;
+ this->icePoly = Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_OBJ_ICE_POLY, this->actor.world.pos.x,
+ this->actor.world.pos.y, this->actor.world.pos.z, this->actor.world.rot.x,
+ this->actor.world.rot.y, this->actor.world.rot.z, 0xFF50);
+ this->animationIndex = EN_JG_ANIMATION_FROZEN_LOOP;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_FrozenIdle;
+ } else if (this->animationIndex == EN_JG_ANIMATION_FROZEN_START) {
+ this->action = EN_JG_ACTION_FROZEN_OR_NON_FIRST_THAW;
+ if (currentFrame == lastFrame) {
+ this->freezeTimer = 1000;
+ this->icePoly = Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_OBJ_ICE_POLY, this->actor.world.pos.x,
+ this->actor.world.pos.y, this->actor.world.pos.z, this->actor.world.rot.x,
+ this->actor.world.rot.y, this->actor.world.rot.z, 0xFF50);
+ this->animationIndex = EN_JG_ANIMATION_FROZEN_LOOP;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_FrozenIdle;
+ }
+ }
+}
-extern UNK_TYPE D_0601ADC0;
+void EnJg_FrozenIdle(EnJg* this, GlobalContext* globalCtx) {
+ if (this->icePoly->update == NULL) {
+ this->icePoly = NULL;
+ if (this->animationIndex == EN_JG_ANIMATION_FROZEN_LOOP) {
+ if (Animation_OnFrame(&this->skelAnime, 0.0f)) {
+ this->animationIndex = EN_JG_ANIMATION_IDLE;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73A90.s")
+ // If you've never talked to the elder before, he stands
+ // around idle until you've talked to him at least once.
+ // Otherwise, he immediately begins walking after being
+ // thawed out.
+ if (this->textId == 0xDAC) {
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Idle;
+ } else {
+ this->freezeTimer = 1000;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Walk;
+ }
+ }
+ }
+ } else {
+ if (Actor_ProcessTalkRequest(&this->actor, &globalCtx->state)) {
+ func_801518B0(globalCtx, 0x236, &this->actor); // The old Goron is frozen solid!
+ this->actionFunc = EnJg_EndFrozenInteraction;
+ } else if (this->actor.isTargeted) {
+ func_800B863C(&this->actor, globalCtx);
+ }
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73AE4.s")
+void EnJg_EndFrozenInteraction(EnJg* this, GlobalContext* globalCtx) {
+ if (Message_GetState(&globalCtx->msgCtx) == 6 && func_80147624(globalCtx) != 0) {
+ globalCtx->msgCtx.msgMode = 0x43;
+ globalCtx->msgCtx.unk12023 = 4;
+ this->actionFunc = EnJg_FrozenIdle;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73B98.s")
+void EnJg_TeachLullabyIntro(EnJg* this, GlobalContext* globalCtx) {
+ if (ActorCutscene_GetCanPlayNext(this->cutscene)) {
+ ActorCutscene_Start(this->cutscene, &this->actor);
+ this->actionFunc = EnJg_LullabyIntroCutsceneAction;
+ } else {
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ ActorCutscene_Stop(0x7C);
+ }
+ ActorCutscene_SetIntentToPlay(this->cutscene);
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73C58.s")
+void EnJg_LullabyIntroCutsceneAction(EnJg* this, GlobalContext* globalCtx) {
+ s32 pad;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73DF4.s")
+ if (func_800EE29C(globalCtx, 0x1D6)) {
+ u32 actionIndex = func_800EE200(globalCtx, 0x1D6);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73E3C.s")
+ if (this->csAction != globalCtx->csCtx.npcActions[actionIndex]->unk0) {
+ this->csAction = globalCtx->csCtx.npcActions[actionIndex]->unk0;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73F1C.s")
+ switch (globalCtx->csCtx.npcActions[actionIndex]->unk0) {
+ case 1:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_CUTSCENE_IDLE;
+ if (this->drum != NULL) {
+ Actor_MarkForDeath(this->drum);
+ this->drum = NULL;
+ }
+ break;
+
+ case 2:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_TAKING_OUT_DRUM;
+ break;
+
+ case 3:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_DRUM_IDLE;
+ break;
+
+ case 4:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_PLAYING_DRUM;
+ break;
+
+ case 5:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_THINKING;
+ break;
+
+ case 6:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_REMEMBERING;
+ break;
+
+ case 7:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_STRONG_REMEMBERING;
+ break;
+
+ case 8:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_DEPRESSED;
+ break;
+
+ case 9:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_CRADLE;
+ break;
+
+ default:
+ this->cutsceneAnimationIndex = EN_JG_ANIMATION_IDLE;
+ break;
+ }
+
+ func_8013BC6C(&this->skelAnime, sAnimations, this->cutsceneAnimationIndex);
+ }
+
+ if ((!(this->flags & FLAG_DRUM_SPAWNED)) &&
+ (((this->cutsceneAnimationIndex == EN_JG_ANIMATION_TAKING_OUT_DRUM) &&
+ (Animation_OnFrame(&this->skelAnime, 14.0f)) && (this->action != EN_JG_ACTION_LULLABY_INTRO_CS)) ||
+ (((this->cutsceneAnimationIndex == EN_JG_ANIMATION_DRUM_IDLE) ||
+ (this->cutsceneAnimationIndex == EN_JG_ANIMATION_PLAYING_DRUM)) &&
+ (this->action == EN_JG_ACTION_LULLABY_INTRO_CS)))) {
+ this->flags |= FLAG_DRUM_SPAWNED;
+ this->drum = Actor_SpawnAsChildAndCutscene(
+ &globalCtx->actorCtx, globalCtx, ACTOR_OBJ_JG_GAKKI, this->actor.world.pos.x, this->actor.world.pos.y,
+ this->actor.world.pos.z, this->actor.shape.rot.x, this->actor.shape.rot.y, this->actor.shape.rot.z,
+ this->actor.params, this->actor.cutscene, this->actor.unk20, NULL);
+ }
+
+ if (this->cutsceneAnimationIndex == EN_JG_ANIMATION_TAKING_OUT_DRUM) {
+ if (Animation_OnFrame(&this->skelAnime, 23.0f)) {
+ Audio_PlaySfxAtPos(&sSfxPos, NA_SE_EV_WOOD_BOUND_S);
+ } else if (Animation_OnFrame(&this->skelAnime, 38.0f)) {
+ Audio_PlaySfxAtPos(&sSfxPos, NA_SE_EV_OBJECT_SLIDE);
+ }
+ }
+ } else {
+ this->csAction = 99;
+ this->freezeTimer = 1000;
+ gSaveContext.weekEventReg[24] |= 0x40;
+ this->actionFunc = EnJg_Idle;
+ }
+}
+
+/**
+ * Returns the "next" text ID that the elder should use based on his current text ID.
+ * Note that the results here can be counterintuitive, because his current text ID can
+ * cause multiple text boxes to display in sequence; this function will return the text
+ * ID that starts the *next* sequence.
+ *
+ * Sequences generally seem to be tied to text IDs that should all share the same animation.
+ */
+s32 EnJg_GetNextTextId(EnJg* this) {
+ switch (this->textId) {
+ case 0xDAC: // What was I doing?
+ return 0xDAD; // I must hurry!
+
+ case 0xDAE: // Do you have business with the Elder?
+ return 0xDB0; // It's this cold snap
+
+ case 0xDB0: // It's this cold snap
+ return 0xDB3; // As Elder, I must do something
+
+ case 0xDB3: // As Elder, I must do something
+ return 0xDB4; // This is our problem (first)
+
+ case 0xDB6: // "Hunh???"
+ return 0xDB7; // You're Darmani!
+
+ case 0xDB7: // You're Darmani!
+ return 0xDBA; // I've been made a fool of!
+
+ case 0xDBA: // I've been made a fool of!
+ return 0xDBB; // If I can see past the illusion, you'll vanish
+
+ case 0xDBB: // If I can see past the illusion, you'll vanish
+ case 0xDBC: // Following me won't do you any good
+ return 0xDC6; // I am counting on you
+
+ case 0xDCB: // Welcome to spring
+ return 0xDCC; // We're holding the Goron Races
+
+ case 0xDDE: // Come back after entering the race
+ return 0xDDF; // My son is waiting for you
+
+ case 0xDDF: // My son is waiting for you
+ return 0xDE0; // Go beyond Twin Islands Cave
+
+ case 0xDCD: // I've been waiting impatiently for you
+ return 0xDCE; // I heard you sealed the blizzard
+
+ case 0xDCE: // I heard you sealed the blizzard
+ return 0xDCF; // Spring has come thanks to you
+
+ case 0xDCF: // Spring has come thanks to you
+ this->focusedShrineGoronParam = 3;
+ if (ActorCutscene_GetCurrentIndex() == 0x7C) {
+ ActorCutscene_Stop(0x7C);
+ }
+ ActorCutscene_SetIntentToPlay(this->cutscene);
+ this->actionFunc = EnJg_GoronShrineCheer;
+ return 0xDD0; // The greatest Goron hero of all?
+
+ case 0xDD0: // The greatest Goron hero of all?
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD1; // "Darmani!"
+
+ case 0xDD1: // "Darmani!"
+ switch (this->focusedShrineGoronParam) {
+ case 3:
+ this->focusedShrineGoronParam = 4;
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD2; // The immortal Goron?
+
+ case 4:
+ this->focusedShrineGoronParam = 5;
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD3; // The star we wish upon?
+
+ case 5:
+ this->focusedShrineGoronParam = 6;
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD4; // "Darmani, greatest of Gorons!"
+
+ default:
+ return 0xDD4; // "Darmani, greatest of Gorons!"
+ }
+ break;
+
+ case 0xDD2: // The immortal Goron?
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD1; // "Darmani!"
+
+ case 0xDD3: // The star we wish upon?
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD1; // "Darmani!"
+
+ case 0xDD4: // "Darmani, greatest of Gorons!"
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD5; // "Greatest of Gorons!"
+
+ case 0xDD5: // "Greatest of Gorons!"
+ this->focusedShrineGoronParam = 7;
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD6; // Darmani, greatest in the world!
+
+ case 0xDD6: // Darmani, greatest in the world!
+ EnJg_SetupGoronShrineCheer(this);
+ return 0xDD7; // "Greatest in the world!"
+
+ case 0xDD7: // "Greatest in the world!"
+ this->focusedShrineGoronParam = 10;
+ EnJg_SetupGoronShrineCheer(this);
+ this->flags &= ~FLAG_SHRINE_GORON_ARMS_RAISED;
+ return 0xDD8; // My son went to see the races
+
+ case 0xDD8: // My son went to see the races
+ return 0xDD9; // I yield to a younger one
+
+ case 0xDD9: // I yield to a younger one
+ return 0xDDA; // A Goron who takes care of all
+
+ case 0xDDA: // A Goron who takes care of all
+ return 0xDDB; // The chosen one, Darmani!
+
+ case 0xDDB: // The chosen one, Darmani!
+ return 0xDDC; // Everyone would accept you
+
+ case 0xDDC: // Everyone would accept you
+ gSaveContext.weekEventReg[77] |= 0x80;
+ return 0xDDD; // Think it over slowly
+
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Returns the text ID of the first thing the elder should say when you talk to him
+ * under most circumstances. Some circumstances (like the very first time the player
+ * talks to him after he thaws) bypass this function.
+ */
+s32 EnJg_GetStartingConversationTextId(EnJg* this, GlobalContext* globalCtx) {
+ Player* player = GET_PLAYER(globalCtx);
+
+ if (!EN_JG_IS_IN_GORON_SHRINE(&this->actor)) {
+ if (player->transformation == PLAYER_FORM_GORON) {
+ if ((gSaveContext.weekEventReg[24] & 0x10) || CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) ||
+ CHECK_QUEST_ITEM(QUEST_SONG_LULLABY_INTRO)) {
+ // The player has already talked as a Goron at least once.
+ return 0xDBC; // Following me won't do you any good
+ }
+
+ // The player has never talked as a Goron.
+ return 0xDB6; // "Hunh???"
+ }
+
+ if (gSaveContext.weekEventReg[24] & 0x20) {
+ // The player has already talked as a non-Goron at least once.
+ return 0xDB5; // This is our problem (repeat)
+ }
+
+ // The player has never talked as a non-Goron.
+ return 0xDAE; // Do you have business with the Elder?
+ }
+
+ if (player->transformation == PLAYER_FORM_GORON) {
+ if (gSaveContext.weekEventReg[77] & 0x80) {
+ // The player has heard the Goron Shrine cheer as a Goron at least once.
+ return 0xDDE; // Come back after entering the race
+ }
+
+ // The player has never heard the Goron Shrine cheer as a Goron.
+ return 0xDCD; // I've been waiting impatiently for you
+ }
+
+ // The player is talking to the elder in Goron Shrine as a non-Goron.
+ return 0xDCB; // Welcome to spring
+}
+
+void EnJg_SpawnBreath(EnJg* this, GlobalContext* globalCtx) {
+ s16 scale = (Rand_ZeroOne() * 20.0f) + 30.0f;
+
+ if (globalCtx->state.frames % 8 == 0) {
+ EffectSsIceSmoke_Spawn(globalCtx, &this->breathPos, &this->breathVelocity, &this->breathAccel, scale);
+ }
+}
+
+/**
+ * This function has two purposes:
+ * - If the player starts talking to the elder, this stops him from moving, makes him face
+ * towards the player, updates certain weekEventRegs, and calls EnJg_SetupTalk.
+ * - If the player is *not* talking to the elder, this decrements his freeze timer. If his
+ * freeze timer is 0 or negative, this function starts the process of freezing him before
+ * handing it off to EnJg_Freeze.
+ */
+void EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer(EnJg* this, GlobalContext* globalCtx) {
+ s16 currentFrame = this->skelAnime.curFrame;
+ s16 lastFrame = Animation_GetLastFrame(sAnimations[this->animationIndex].animationSeg);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7406C.s")
+ if (Actor_ProcessTalkRequest(&this->actor, &globalCtx->state)) {
+ this->flags |= FLAG_LOOKING_AT_PLAYER;
+ this->actor.speedXZ = 0.0f;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7408C.s")
+ if (this->textId == 0xDAC) {
+ this->action = EN_JG_ACTION_FIRST_THAW;
+ } else if (this->textId == 0xDAE) {
+ gSaveContext.weekEventReg[24] |= 0x20;
+ } else if (this->textId == 0xDB6) {
+ gSaveContext.weekEventReg[24] |= 0x10;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74134.s")
+ func_801518B0(globalCtx, this->textId, &this->actor);
+ this->actionFunc = EnJg_SetupTalk;
+ } else {
+ if ((this->actor.xzDistToPlayer < 100.0f) || (this->actor.isTargeted)) {
+ func_800B863C(&this->actor, globalCtx);
+ if (this->action == EN_JG_ACTION_FIRST_THAW) {
+ this->textId = EnJg_GetStartingConversationTextId(this, globalCtx);
+ }
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B741F8.s")
+ this->freezeTimer--;
+ if ((this->freezeTimer <= 0) && (currentFrame == lastFrame)) {
+ this->animationIndex = EN_JG_ANIMATION_FROZEN_START;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ Audio_PlaySfxAtPos(&sSfxPos, NA_SE_EV_FREEZE_S);
+ this->actionFunc = EnJg_Freeze;
+ }
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B742F8.s")
+void EnJg_Init(Actor* thisx, GlobalContext* globalCtx) {
+ s32 pad;
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74440.s")
+ ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 20.0f);
+ SkelAnime_InitFlex(globalCtx, &this->skelAnime, &gGoronElderSkel, &gGoronElderIdleAnim, this->jointTable,
+ this->morphTable, GORON_ELDER_LIMB_MAX);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74550.s")
+ Collider_InitCylinder(globalCtx, &this->collider);
+ Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit);
+ CollisionCheck_SetInfo2(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B747C8.s")
+ Actor_SetScale(&this->actor, 0.01f);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74840.s")
+ if (!EN_JG_IS_IN_GORON_SHRINE(thisx)) {
+ if ((globalCtx->sceneNum == SCENE_SPOT00) && (gSaveContext.sceneSetupIndex == 7) &&
+ (globalCtx->csCtx.unk_12 == 0)) {
+ // This is the elder that appears in the cutscene for learning the full Goron Lullaby.
+ this->animationIndex = EN_JG_ANIMATION_IDLE;
+ this->action = EN_JG_ACTION_LULLABY_INTRO_CS;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_LullabyIntroCutsceneAction;
+ } else {
+ // This is the elder that appears in Mountain Village or the Path to Goron Village in winter.
+ this->path = func_8013D648(globalCtx, EN_JG_GET_PATH(thisx), 0x3F);
+ this->animationIndex = EN_JG_ANIMATION_SURPRISE_START;
+ this->action = EN_JG_ACTION_SPAWNING;
+ this->freezeTimer = 1000;
+ this->textId = 0xDAC; // What was I doing?
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_Freeze;
+ }
+ } else {
+ // This is the elder that appears in Goron Shrine in spring.
+ this->animationIndex = EN_JG_ANIMATION_IDLE;
+ this->cutscene = this->actor.cutscene;
+ func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex);
+ this->actionFunc = EnJg_GoronShrineIdle;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B749D0.s")
+void EnJg_Destroy(Actor* thisx, GlobalContext* globalCtx) {
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74AD8.s")
+ Collider_DestroyCylinder(globalCtx, &this->collider);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74B54.s")
+void EnJg_Update(Actor* thisx, GlobalContext* globalCtx) {
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74BC8.s")
+ if ((this->actionFunc != EnJg_FrozenIdle) && (this->actionFunc != EnJg_EndFrozenInteraction)) {
+ EnJg_UpdateCollision(this, globalCtx);
+ Actor_MoveWithGravity(&this->actor);
+ SkelAnime_Update(&this->skelAnime);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74E5C.s")
+ if ((this->action != EN_JG_ACTION_LULLABY_INTRO_CS) && (!EN_JG_IS_IN_GORON_SHRINE(&this->actor))) {
+ EnJg_SpawnBreath(this, globalCtx);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B750A0.s")
+ func_800E9250(globalCtx, &this->actor, &this->unusedRotation1, &this->unusedRotation2, this->actor.focus.pos);
+ }
+ this->actionFunc(this, globalCtx);
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7517C.s")
+s32 EnJg_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) {
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B751F8.s")
+ if (limbIndex == GORON_ELDER_LIMB_ROOT) {
+ if (this->flags & FLAG_LOOKING_AT_PLAYER) {
+ Math_SmoothStepToS(&this->rootRotationWhenTalking, this->actor.yawTowardsPlayer - this->actor.shape.rot.y,
+ 5, 0x1000, 0x100);
+ Matrix_RotateY(this->rootRotationWhenTalking, MTXMODE_APPLY);
+ } else {
+ Math_SmoothStepToS(&this->rootRotationWhenTalking, 0, 5, 0x1000, 0x100);
+ Matrix_RotateY(this->rootRotationWhenTalking, MTXMODE_APPLY);
+ }
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Init.s")
+ return false;
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Destroy.s")
+void EnJg_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) {
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Update.s")
+ if (limbIndex == GORON_ELDER_LIMB_HEAD) {
+ Matrix_MultiplyVector3fByState(&sFocusOffset, &this->actor.focus.pos);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B75658.s")
+ if (limbIndex == GORON_ELDER_LIMB_LOWER_LIP) {
+ Matrix_MultiplyVector3fByState(&sBreathPosOffset, &this->breathPos);
+ Matrix_RotateY(this->actor.shape.rot.y, MTXMODE_NEW);
+ Matrix_MultiplyVector3fByState(&sBreathVelOffset, &this->breathVelocity);
+ Matrix_MultiplyVector3fByState(&sBreathAccelOffset, &this->breathAccel);
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B75708.s")
+void EnJg_Draw(Actor* thisx, GlobalContext* globalCtx) {
+ EnJg* this = THIS;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Draw.s")
+ SkelAnime_DrawFlexOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount,
+ EnJg_OverrideLimbDraw, EnJg_PostLimbDraw, &this->actor);
+}
diff --git a/src/overlays/actors/ovl_En_Jg/z_en_jg.h b/src/overlays/actors/ovl_En_Jg/z_en_jg.h
index 3a860feb2..67ba1d638 100644
--- a/src/overlays/actors/ovl_En_Jg/z_en_jg.h
+++ b/src/overlays/actors/ovl_En_Jg/z_en_jg.h
@@ -2,16 +2,42 @@
#define Z_EN_JG_H
#include "global.h"
+#include "objects/object_jg/object_jg.h"
+
+#define EN_JG_IS_IN_GORON_SHRINE(thisx) ((thisx)->params & 0x1)
+#define EN_JG_GET_PATH(thisx) (((thisx)->params & 0xFC00) >> 10)
struct EnJg;
typedef void (*EnJgActionFunc)(struct EnJg*, GlobalContext*);
typedef struct EnJg {
- /* 0x0000 */ Actor actor;
- /* 0x0144 */ char unk_144[0x98];
- /* 0x01DC */ EnJgActionFunc actionFunc;
- /* 0x01E0 */ char unk_1E0[0x1F4];
+ /* 0x000 */ Actor actor;
+ /* 0x144 */ Actor* shrineGoron;
+ /* 0x148 */ Actor* icePoly;
+ /* 0x14C */ ColliderCylinder collider;
+ /* 0x198 */ SkelAnime skelAnime;
+ /* 0x1DC */ EnJgActionFunc actionFunc;
+ /* 0x1E0 */ Path* path;
+ /* 0x1E4 */ s32 currentPoint;
+ /* 0x1E8 */ Actor* drum;
+ /* 0x1EC */ Vec3s unusedRotation1; // probably meant to be a head rotation to look at the player
+ /* 0x1F2 */ Vec3s unusedRotation2; // probably meant to be a body rotation to look at the player
+ /* 0x1F8 */ Vec3s jointTable[GORON_ELDER_LIMB_MAX];
+ /* 0x2CA */ Vec3s morphTable[GORON_ELDER_LIMB_MAX];
+ /* 0x39C */ s16 rootRotationWhenTalking;
+ /* 0x39E */ s16 animationIndex;
+ /* 0x3A0 */ s16 action;
+ /* 0x3A2 */ s16 freezeTimer;
+ /* 0x3A4 */ Vec3f breathPos;
+ /* 0x3B0 */ Vec3f breathVelocity;
+ /* 0x3BC */ Vec3f breathAccel;
+ /* 0x3C8 */ s16 cutscene;
+ /* 0x3CA */ u8 cutsceneAnimationIndex;
+ /* 0x3CB */ u8 csAction;
+ /* 0x3CC */ u16 flags;
+ /* 0x3CE */ u16 textId;
+ /* 0x3D0 */ u8 focusedShrineGoronParam;
} EnJg; // size = 0x3D4
extern const ActorInit En_Jg_InitVars;
diff --git a/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.c b/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.c
index 07d0dc3f9..394ac5b05 100644
--- a/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.c
+++ b/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.c
@@ -1,7 +1,7 @@
/*
* File: z_en_s_goro.c
* Overlay: ovl_En_S_Goro
- * Description: Goron Complaining About Crying / Bomb Shop Goron
+ * Description: Goron in Goron Shrine / Bomb Shop Goron
*/
#include "z_en_s_goro.h"
diff --git a/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.h b/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.h
index 489499b33..99c35127a 100644
--- a/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.h
+++ b/src/overlays/actors/ovl_En_S_Goro/z_en_s_goro.h
@@ -3,6 +3,8 @@
#include "global.h"
+#define EN_S_GORO_GET_PARAM_F(thisx) ((thisx)->params & 0xF)
+
struct EnSGoro;
typedef void (*EnSGoroActionFunc)(struct EnSGoro*, GlobalContext*);
diff --git a/src/overlays/actors/ovl_Obj_Jg_Gakki/z_obj_jg_gakki.c b/src/overlays/actors/ovl_Obj_Jg_Gakki/z_obj_jg_gakki.c
index a70d14600..b8a1406d7 100644
--- a/src/overlays/actors/ovl_Obj_Jg_Gakki/z_obj_jg_gakki.c
+++ b/src/overlays/actors/ovl_Obj_Jg_Gakki/z_obj_jg_gakki.c
@@ -31,16 +31,16 @@ const ActorInit Obj_Jg_Gakki_InitVars = {
void ObjJgGakki_Init(Actor* thisx, GlobalContext* globalCtx2) {
GlobalContext* globalCtx = globalCtx2;
ObjJgGakki* this = THIS;
- f32 frameCount = Animation_GetLastFrame(&gGoronElderDrumAnim);
+ f32 frameCount = Animation_GetLastFrame(&gGoronElderDrumTakeOutAnim);
ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 24.0f);
SkelAnime_Init(globalCtx, &this->skelAnime, &gGoronElderDrumSkel, NULL, NULL, NULL, 0);
if (((globalCtx->sceneNum == SCENE_SPOT00) && (gSaveContext.sceneSetupIndex == 7)) &&
(globalCtx->csCtx.unk_12 == 0)) {
- Animation_Change(&this->skelAnime, &gGoronElderDrumAnim, 1.0f, frameCount, frameCount, 2, 0.0f);
+ Animation_Change(&this->skelAnime, &gGoronElderDrumTakeOutAnim, 1.0f, frameCount, frameCount, 2, 0.0f);
} else if ((globalCtx->sceneNum == SCENE_17SETUGEN) || (globalCtx->sceneNum == SCENE_10YUKIYAMANOMURA)) {
- Animation_Change(&this->skelAnime, &gGoronElderDrumAnim, 1.0f, 0.0f, frameCount, 2, 0.0f);
+ Animation_Change(&this->skelAnime, &gGoronElderDrumTakeOutAnim, 1.0f, 0.0f, frameCount, 2, 0.0f);
} else {
Actor_MarkForDeath(&this->actor);
}