diff --git a/assets/xml/objects/object_jg.xml b/assets/xml/objects/object_jg.xml index 03d7c3ce59..17889995fd 100644 --- a/assets/xml/objects/object_jg.xml +++ b/assets/xml/objects/object_jg.xml @@ -1,100 +1,118 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec b/spec index 3302e9d415..a139b1b1c0 100644 --- a/spec +++ b/spec @@ -4250,8 +4250,7 @@ beginseg name "ovl_En_Jg" compress include "build/src/overlays/actors/ovl_En_Jg/z_en_jg.o" - include "build/data/ovl_En_Jg/ovl_En_Jg.data.o" - include "build/data/ovl_En_Jg/ovl_En_Jg.reloc.o" + include "build/src/overlays/actors/ovl_En_Jg/ovl_En_Jg_reloc.o" endseg beginseg 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 ce8651d3cd..0dc1d5df50 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 }, +}; -extern ColliderCylinderInit D_80B75820; -extern CollisionCheckInfoInit2 D_80B7584C; -extern DamageTable D_80B75858; +static Vec3f sSfxPos = { 0.0f, 0.0f, 0.0f }; -extern UNK_TYPE D_0601ADC0; +static Vec3f sFocusOffset = { 0.0f, 0.0f, 0.0f }; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73A90.s") +static Vec3f sBreathPosOffset = { 1000.0f, -500.0f, 0.0f }; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73AE4.s") +static Vec3f sBreathVelOffset = { 0.0f, 0.0f, 0.75f }; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73B98.s") +static Vec3f sBreathAccelOffset = { 0.0f, 0.0f, -0.070000000298f }; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73C58.s") +/** + * 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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73DF4.s") + while (actorIterator != NULL) { + if ((actorIterator->id == ACTOR_EN_S_GORO) && + (EN_S_GORO_GET_PARAM_F(actorIterator) == focusedShrineGoronParam)) { + return actorIterator; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73E3C.s") + actorIterator = actorIterator->next; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B73F1C.s") + return NULL; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7406C.s") +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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7408C.s") + 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); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74134.s") +s16 EnJg_GetWalkingYRotation(Path* path, s32 pointIndex, Vec3f* pos, f32* distSQ) { + Vec3s* points; + f32 diffX; + f32 diffZ; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B741F8.s") + 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; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B742F8.s") + *distSQ = SQ(diffX) + SQ(diffZ); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74440.s") + return RADF_TO_BINANG(Math_Acot2F(diffZ, diffX)); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74550.s") +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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B747C8.s") + 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; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74840.s") + func_8017B7F8(&point, RADF_TO_BINANG(func_80086B30(diffX, diffZ)), &px, &pz, &d); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B749D0.s") + if (((this->actor.world.pos.x * px) + (pz * this->actor.world.pos.z) + d) > 0.0f) { + reached = true; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74AD8.s") + return reached; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74B54.s") +s16 EnJg_GetCutsceneForTeachingLullabyIntro(EnJg* this) { + s16 temp = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74BC8.s") + if (temp > 0) { + return this->actor.cutscene; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B74E5C.s") + return ActorCutscene_GetAdditionalCutscene(this->actor.cutscene); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B750A0.s") +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); + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B7517C.s") + ActorCutscene_SetIntentToPlay(this->cutscene); + this->actionFunc = EnJg_GoronShrineCheer; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B751F8.s") + 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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Init.s") + default: + this->flags &= ~FLAG_SHRINE_GORON_ARMS_RAISED; + break; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Destroy.s") +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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Update.s") + case 0xDAD: // I must hurry! + this->animationIndex = EN_JG_ANIMATION_SURPRISE_START; + func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex); + this->actionFunc = EnJg_AlternateTalkOrWalkInPlace; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B75658.s") + case 0xDB7: // You're Darmani! + this->animationIndex = EN_JG_ANIMATION_SURPRISE_START; + func_8013BC6C(&this->skelAnime, sAnimations, this->animationIndex); + this->actionFunc = EnJg_Talk; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/func_80B75708.s") + 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; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jg/EnJg_Draw.s") + 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); + + 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; + } + } +} + +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; + + // 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); + } + } +} + +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; + } +} + +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); + } +} + +void EnJg_LullabyIntroCutsceneAction(EnJg* this, GlobalContext* globalCtx) { + s32 pad; + + if (func_800EE29C(globalCtx, 0x1D6)) { + u32 actionIndex = func_800EE200(globalCtx, 0x1D6); + + if (this->csAction != globalCtx->csCtx.npcActions[actionIndex]->unk0) { + this->csAction = globalCtx->csCtx.npcActions[actionIndex]->unk0; + + 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); + + if (Actor_ProcessTalkRequest(&this->actor, &globalCtx->state)) { + this->flags |= FLAG_LOOKING_AT_PLAYER; + this->actor.speedXZ = 0.0f; + + 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; + } + + 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); + } + } + + 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; + } + } +} + +void EnJg_Init(Actor* thisx, GlobalContext* globalCtx) { + s32 pad; + EnJg* this = THIS; + + 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); + + Collider_InitCylinder(globalCtx, &this->collider); + Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit); + CollisionCheck_SetInfo2(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit); + + Actor_SetScale(&this->actor, 0.01f); + + 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; + } +} + +void EnJg_Destroy(Actor* thisx, GlobalContext* globalCtx) { + EnJg* this = THIS; + + Collider_DestroyCylinder(globalCtx, &this->collider); +} + +void EnJg_Update(Actor* thisx, GlobalContext* globalCtx) { + EnJg* this = THIS; + + if ((this->actionFunc != EnJg_FrozenIdle) && (this->actionFunc != EnJg_EndFrozenInteraction)) { + EnJg_UpdateCollision(this, globalCtx); + Actor_MoveWithGravity(&this->actor); + SkelAnime_Update(&this->skelAnime); + + if ((this->action != EN_JG_ACTION_LULLABY_INTRO_CS) && (!EN_JG_IS_IN_GORON_SHRINE(&this->actor))) { + EnJg_SpawnBreath(this, globalCtx); + } + + func_800E9250(globalCtx, &this->actor, &this->unusedRotation1, &this->unusedRotation2, this->actor.focus.pos); + } + this->actionFunc(this, globalCtx); +} + +s32 EnJg_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) { + EnJg* this = THIS; + + 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); + } + } + + return false; +} + +void EnJg_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + EnJg* this = THIS; + + if (limbIndex == GORON_ELDER_LIMB_HEAD) { + Matrix_MultiplyVector3fByState(&sFocusOffset, &this->actor.focus.pos); + } + + 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); + } +} + +void EnJg_Draw(Actor* thisx, GlobalContext* globalCtx) { + EnJg* this = THIS; + + 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 3a860feb2b..67ba1d6389 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 07d0dc3f95..394ac5b056 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 489499b333..99c35127a3 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 a70d146009..b8a1406d75 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); } diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index bbd720e721..5d6fe37500 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -14407,35 +14407,35 @@ 0x80B72E88:("EnRailSkb_OverrideLimbDraw",), 0x80B7302C:("EnRailSkb_PostLimbDraw",), 0x80B731EC:("EnRailSkb_Draw",), - 0x80B73A90:("func_80B73A90",), - 0x80B73AE4:("func_80B73AE4",), - 0x80B73B98:("func_80B73B98",), - 0x80B73C58:("func_80B73C58",), - 0x80B73DF4:("func_80B73DF4",), - 0x80B73E3C:("func_80B73E3C",), - 0x80B73F1C:("func_80B73F1C",), - 0x80B7406C:("func_80B7406C",), - 0x80B7408C:("func_80B7408C",), - 0x80B74134:("func_80B74134",), - 0x80B741F8:("func_80B741F8",), - 0x80B742F8:("func_80B742F8",), - 0x80B74440:("func_80B74440",), - 0x80B74550:("func_80B74550",), - 0x80B747C8:("func_80B747C8",), - 0x80B74840:("func_80B74840",), - 0x80B749D0:("func_80B749D0",), - 0x80B74AD8:("func_80B74AD8",), - 0x80B74B54:("func_80B74B54",), - 0x80B74BC8:("func_80B74BC8",), - 0x80B74E5C:("func_80B74E5C",), - 0x80B750A0:("func_80B750A0",), - 0x80B7517C:("func_80B7517C",), - 0x80B751F8:("func_80B751F8",), + 0x80B73A90:("EnJg_GetShrineGoronToFocusOn",), + 0x80B73AE4:("EnJg_UpdateCollision",), + 0x80B73B98:("EnJg_GetWalkingYRotation",), + 0x80B73C58:("EnJg_ReachedPoint",), + 0x80B73DF4:("EnJg_GetCutsceneForTeachingLullabyIntro",), + 0x80B73E3C:("EnJg_SetupGoronShrineCheer",), + 0x80B73F1C:("EnJg_SetupTalk",), + 0x80B7406C:("EnJg_Idle",), + 0x80B7408C:("EnJg_GoronShrineIdle",), + 0x80B74134:("EnJg_GoronShrineTalk",), + 0x80B741F8:("EnJg_GoronShrineCheer",), + 0x80B742F8:("EnJg_AlternateTalkOrWalkInPlace",), + 0x80B74440:("EnJg_Walk",), + 0x80B74550:("EnJg_Talk",), + 0x80B747C8:("EnJg_SetupWalk",), + 0x80B74840:("EnJg_Freeze",), + 0x80B749D0:("EnJg_FrozenIdle",), + 0x80B74AD8:("EnJg_EndFrozenInteraction",), + 0x80B74B54:("EnJg_TeachLullabyIntro",), + 0x80B74BC8:("EnJg_LullabyIntroCutsceneAction",), + 0x80B74E5C:("EnJg_GetNextTextId",), + 0x80B750A0:("EnJg_GetStartingConversationTextId",), + 0x80B7517C:("EnJg_SpawnBreath",), + 0x80B751F8:("EnJg_CheckIfTalkingToPlayerAndHandleFreezeTimer",), 0x80B753A0:("EnJg_Init",), 0x80B75564:("EnJg_Destroy",), 0x80B75590:("EnJg_Update",), - 0x80B75658:("func_80B75658",), - 0x80B75708:("func_80B75708",), + 0x80B75658:("EnJg_OverrideLimbDraw",), + 0x80B75708:("EnJg_PostLimbDraw",), 0x80B757AC:("EnJg_Draw",), 0x80B76030:("func_80B76030",), 0x80B76110:("func_80B76110",), diff --git a/undefined_syms.txt b/undefined_syms.txt index 72d74dc400..c60f50de59 100644 --- a/undefined_syms.txt +++ b/undefined_syms.txt @@ -1541,11 +1541,6 @@ D_0600BA30 = 0x0600BA30; D_0600BCC8 = 0x0600BCC8; D_0600C240 = 0x0600C240; -// ovl_En_Jg - -D_0601ADC0 = 0x0601ADC0; -D_0601AFF0 = 0x0601AFF0; - // ovl_En_Jgame_Tsn D_06008AB8 = 0x06008AB8; @@ -2201,11 +2196,6 @@ D_06001888 = 0x06001888; D_060003A0 = 0x060003A0; -// ovl_Obj_Jg_Gakki - -D_0601B1E8 = 0x0601B1E8; -D_0601B210 = 0x0601B210; - // ovl_Obj_Kendo_Kanban D_06000180 = 0x06000180;