diff --git a/spec b/spec index 6cd76563ca..a375a6692d 100644 --- a/spec +++ b/spec @@ -2162,8 +2162,7 @@ beginseg name "ovl_En_Jso" compress include "build/src/overlays/actors/ovl_En_Jso/z_en_jso.o" - include "build/data/ovl_En_Jso/ovl_En_Jso.data.o" - include "build/data/ovl_En_Jso/ovl_En_Jso.reloc.o" + include "build/src/overlays/actors/ovl_En_Jso/ovl_En_Jso_reloc.o" endseg beginseg diff --git a/src/overlays/actors/ovl_En_Hint_Skb/z_en_hint_skb.c b/src/overlays/actors/ovl_En_Hint_Skb/z_en_hint_skb.c index 86cb7235d1..e62be00956 100644 --- a/src/overlays/actors/ovl_En_Hint_Skb/z_en_hint_skb.c +++ b/src/overlays/actors/ovl_En_Hint_Skb/z_en_hint_skb.c @@ -5,6 +5,7 @@ */ #include "z_en_hint_skb.h" +#include "overlays/actors/ovl_En_Part/z_en_part.h" #include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_10) @@ -903,12 +904,12 @@ void EnHintSkb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* Collider_UpdateSpheres(limbIndex, &this->collider); if ((limbIndex == STALCHILD_LIMB_HEAD) && (this->unk_3E8 & 1) && !(this->unk_3E8 & 2)) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); this->unk_3E8 |= 2; } else if ((this->unk_3E8 & 4) && !(this->unk_3E8 & 8) && ((limbIndex != STALCHILD_LIMB_HEAD) || !(this->unk_3E8 & 1)) && (limbIndex != STALCHILD_LIMB_LOWER_JAW)) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); } if (this->drawDmgEffTimer != 0) { diff --git a/src/overlays/actors/ovl_En_Jso/z_en_jso.c b/src/overlays/actors/ovl_En_Jso/z_en_jso.c index 43a27759fe..5d083246ad 100644 --- a/src/overlays/actors/ovl_En_Jso/z_en_jso.c +++ b/src/overlays/actors/ovl_En_Jso/z_en_jso.c @@ -5,6 +5,9 @@ */ #include "z_en_jso.h" +#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h" +#include "overlays/actors/ovl_En_Encount3/z_en_encount3.h" +#include "overlays/actors/ovl_En_Part/z_en_part.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY | ACTOR_FLAG_10) @@ -13,60 +16,135 @@ void EnJso_Init(Actor* thisx, PlayState* play); void EnJso_Destroy(Actor* thisx, PlayState* play); void EnJso_Update(Actor* thisx, PlayState* play); +void EnJso_Draw(Actor* thisx, PlayState* play); -void func_809ADCB8(EnJso* this, PlayState* play); -void func_809AE87C(EnJso* this, PlayState* play); -void func_809AEA08(EnJso* this, PlayState* play); -void func_809AED00(EnJso* this, PlayState* play); -void func_809AEDAC(EnJso* this, PlayState* play); -void func_809AEEC0(EnJso* this, PlayState* play); -void func_809AF110(EnJso* this, PlayState* play); -void func_809AF2F8(EnJso* this, PlayState* play); -void func_809AF3C0(EnJso* this, PlayState* play); -void func_809AF440(EnJso* this, PlayState* play); -void func_809AF53C(EnJso* this, PlayState* play); -void func_809AF714(EnJso* this, PlayState* play); -void func_809AF7F4(EnJso* this, PlayState* play); -void func_809AF99C(EnJso* this, PlayState* play); -void func_809AFAF4(EnJso* this, PlayState* play); -void func_809AFC10(EnJso* this, PlayState* play); -void func_809AFE38(EnJso* this, PlayState* play); +void EnJso_SetupHandleIntroCutscene(EnJso* this); +void EnJso_HandleIntroCutscene(EnJso* this, PlayState* play); +void EnJso_Reappear(EnJso* this, PlayState* play); +void EnJso_SetupCirclePlayer(EnJso* this, PlayState* play); +void EnJso_CirclePlayer(EnJso* this, PlayState* play); +void EnJso_Guard(EnJso* this, PlayState* play); +void EnJso_SetupSpinBeforeAttack(EnJso* this); +void EnJso_SpinBeforeAttack(EnJso* this, PlayState* play); +void EnJso_SetupDashAttack(EnJso* this); +void EnJso_DashAttack(EnJso* this, PlayState* play); +void EnJso_SetupSlash(EnJso* this, PlayState* play); +void EnJso_Slash(EnJso* this, PlayState* play); +void EnJso_SetupWaitAfterSlash(EnJso* this); +void EnJso_WaitAfterSlash(EnJso* this, PlayState* play); +void EnJso_SetupKnockedBack(EnJso* this); +void EnJso_KnockedBack(EnJso* this, PlayState* play); +void EnJso_SetupCower(EnJso* this); +void EnJso_Cower(EnJso* this, PlayState* play); +void EnJso_Stunned(EnJso* this, PlayState* play); +void EnJso_Damaged(EnJso* this, PlayState* play); +void EnJso_SetupJumpBack(EnJso* this); +void EnJso_JumpBack(EnJso* this, PlayState* play); +void EnJso_Dead(EnJso* this, PlayState* play); +void EnJso_SetupFallDownAndTalk(EnJso* this, PlayState* play); +void EnJso_FallDownAndTalk(EnJso* this, PlayState* play); +void EnJso_TellHint(EnJso* this, PlayState* play); +void EnJso_BurstIntoFlames(EnJso* this, PlayState* play); -#if 0 -// static DamageTable sDamageTable = { -static DamageTable D_809B0F48 = { - /* Deku Nut */ DMG_ENTRY(0, 0x1), - /* Deku Stick */ DMG_ENTRY(1, 0xF), - /* Horse trample */ DMG_ENTRY(0, 0x0), - /* Explosives */ DMG_ENTRY(1, 0xF), - /* Zora boomerang */ DMG_ENTRY(1, 0xF), - /* Normal arrow */ DMG_ENTRY(1, 0xF), - /* UNK_DMG_0x06 */ DMG_ENTRY(0, 0x0), - /* Hookshot */ DMG_ENTRY(0, 0x1), - /* Goron punch */ DMG_ENTRY(1, 0xF), - /* Sword */ DMG_ENTRY(1, 0xF), - /* Goron pound */ DMG_ENTRY(1, 0xF), - /* Fire arrow */ DMG_ENTRY(2, 0x2), - /* Ice arrow */ DMG_ENTRY(2, 0x3), - /* Light arrow */ DMG_ENTRY(2, 0x4), - /* Goron spikes */ DMG_ENTRY(1, 0xF), - /* Deku spin */ DMG_ENTRY(0, 0x1), - /* Deku bubble */ DMG_ENTRY(1, 0xF), - /* Deku launch */ DMG_ENTRY(2, 0xF), - /* UNK_DMG_0x12 */ DMG_ENTRY(0, 0x1), - /* Zora barrier */ DMG_ENTRY(0, 0x5), - /* Normal shield */ DMG_ENTRY(0, 0x0), - /* Light ray */ DMG_ENTRY(0, 0x0), - /* Thrown object */ DMG_ENTRY(1, 0xF), - /* Zora punch */ DMG_ENTRY(1, 0xF), - /* Spin attack */ DMG_ENTRY(1, 0xF), - /* 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, 0xF), +typedef enum { + /* 0 */ EN_JSO_ACTION_HANDLE_INTRO_CUTSCENE, + /* 1 */ EN_JSO_ACTION_REAPPEAR, + /* 2 */ EN_JSO_ACTION_CIRCLE_PLAYER, + /* 3 */ EN_JSO_ACTION_GUARD, + /* 4 */ EN_JSO_ACTION_SPIN_BEFORE_ATTACK, + /* 5 */ EN_JSO_ACTION_DASH_ATTACK, + /* 6 */ EN_JSO_ACTION_SLASH, + /* 7 */ EN_JSO_ACTION_WAIT_AFTER_SLASH, + /* 8 */ EN_JSO_ACTION_STUNNED, + /* 9 */ EN_JSO_ACTION_KNOCKED_BACK, + /* 10 */ EN_JSO_ACTION_COWER, + /* 11 */ EN_JSO_ACTION_DAMAGED, + /* 12 */ EN_JSO_ACTION_JUMP_BACK, + /* 13 */ EN_JSO_ACTION_DEAD, + /* 14 */ EN_JSO_ACTION_FALL_DOWN_AND_TALK, + /* 15 */ EN_JSO_ACTION_UNK_15 // Checked in EnJso_Update, but never actually used +} EnJsoAction; + +typedef enum { + /* 0 */ EN_JSO_INTRO_SPIN_UP_FROM_GROUND, + /* 1 */ EN_JSO_INTRO_JUMP_OUT_FROM_GROUND, + /* 2 */ EN_JSO_INTRO_LAND_FROM_ABOVE, + /* 3 */ EN_JSO_INTRO_SCALE_UP +} EnJsoIntroType; + +typedef enum { + // Either the cutscene started (in which case, we'll transition to the next state on the next frame) or it's done. + /* 0 */ EN_JSO_INTRO_CS_STATE_DONE_OR_STARTED, + // Waits for the Garo to finish their intro animation and open the first textbox. + /* 1 */ EN_JSO_INTRO_CS_STATE_WAITING_FOR_TEXTBOX_TO_APPEAR, + // Waits for the player to press a button to continue on from the Garo's first textbox. + /* 2 */ EN_JSO_INTRO_CS_STATE_WAITING_FOR_TEXTBOX_TO_CONTINUE, + // Lifts the Garo's right arm such that, when they draw their sword, it's pointed at the player. Waits until the + // player has finished reading all of the Garo's dialogue before continuing. + /* 3 */ EN_JSO_INTRO_CS_STATE_RAISE_ARM_AND_DRAW_RIGHT_SWORD, + // Waits 10 frames, then the Garo draws its left sword and continues to the next state. + /* 4 */ EN_JSO_INTRO_CS_STATE_DRAW_LEFT_SWORD, + // Waits 10 frames, then the Garo jumps back and the cutscene ends. + /* 5 */ EN_JSO_INTRO_CS_STATE_ENDING +} EnJsoIntroCsState; + +typedef enum { + /* 0 */ EN_JSO_SWORD_STATE_BOTH_DRAWN, + /* 1 */ EN_JSO_SWORD_STATE_KNOCKED_OUT_OF_HANDS, + /* 2 */ EN_JSO_SWORD_STATE_RIGHT_DRAWN, + /* 3 */ EN_JSO_SWORD_STATE_LEFT_DRAWN, + /* 5 */ EN_JSO_SWORD_STATE_NONE_DRAWN = 5 +} EnJsoSwordState; + +// Seemingly a duplicate of the isAttacking instance variable. Its purpose is unknown. +static s32 sIsAttacking = false; + +// Seemingly a duplicate of the isPlayerLockedOn instance variable. Its purpose is unknown. +static s32 sIsPlayerLockedOn = false; + +typedef enum { + /* 0x0 */ EN_JSO_DMGEFF_IMMUNE, // Deals no damage and has no special effect + /* 0x1 */ EN_JSO_DMGEFF_STUN, // Deals no damage but stuns the Garo + /* 0x2 */ EN_JSO_DMGEFF_FIRE, // Damages and sets the Garo on fire + /* 0x3 */ EN_JSO_DMGEFF_FREEZE, // Damages and surrounds the Garo with ice + /* 0x4 */ EN_JSO_DMGEFF_LIGHT_ORB, // Damages and surrounds the Garo with light orbs + /* 0x5 */ EN_JSO_DMGEFF_ELECTRIC_STUN, // Deals no damage but stuns the Garo and surrounds them with electric sparks + /* 0xF */ EN_JSO_DMGEFF_NONE = 0xF // Damages the Garo and has no special effect +} EnJsoDamageEffect; + +static DamageTable sDamageTable = { + /* Deku Nut */ DMG_ENTRY(0, EN_JSO_DMGEFF_STUN), + /* Deku Stick */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Horse trample */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Explosives */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Zora boomerang */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Normal arrow */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* UNK_DMG_0x06 */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Hookshot */ DMG_ENTRY(0, EN_JSO_DMGEFF_STUN), + /* Goron punch */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Sword */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Goron pound */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Fire arrow */ DMG_ENTRY(2, EN_JSO_DMGEFF_FIRE), + /* Ice arrow */ DMG_ENTRY(2, EN_JSO_DMGEFF_FREEZE), + /* Light arrow */ DMG_ENTRY(2, EN_JSO_DMGEFF_LIGHT_ORB), + /* Goron spikes */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Deku spin */ DMG_ENTRY(0, EN_JSO_DMGEFF_STUN), + /* Deku bubble */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Deku launch */ DMG_ENTRY(2, EN_JSO_DMGEFF_NONE), + /* UNK_DMG_0x12 */ DMG_ENTRY(0, EN_JSO_DMGEFF_STUN), + /* Zora barrier */ DMG_ENTRY(0, EN_JSO_DMGEFF_ELECTRIC_STUN), + /* Normal shield */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Light ray */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Thrown object */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Zora punch */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Spin attack */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), + /* Sword beam */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Normal Roll */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* UNK_DMG_0x1B */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* UNK_DMG_0x1C */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Unblockable */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* UNK_DMG_0x1E */ DMG_ENTRY(0, EN_JSO_DMGEFF_IMMUNE), + /* Powder Keg */ DMG_ENTRY(1, EN_JSO_DMGEFF_NONE), }; ActorInit En_Jso_InitVars = { @@ -81,106 +159,1560 @@ ActorInit En_Jso_InitVars = { (ActorFunc)NULL, }; -// static ColliderCylinderInit sCylinderInit = { -static ColliderCylinderInit D_809B0F88 = { - { COLTYPE_NONE, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_HARD | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, }, - { ELEMTYPE_UNK0, { 0xF7CFFFFF, 0x08, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_NONE, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_HARD | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_1, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK0, + { 0xF7CFFFFF, 0x08, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, { 22, 55, 0, { 0, 0, 0 } }, }; -// static ColliderQuadInit sQuadInit = { -static ColliderQuadInit D_809B0FB4 = { - { COLTYPE_NONE, AT_ON | AT_TYPE_ENEMY, AC_NONE, OC1_NONE, OC2_NONE, COLSHAPE_QUAD, }, - { ELEMTYPE_UNK0, { 0xF7CFFFFF, 0x04, 0x08 }, { 0x00000000, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL | TOUCH_UNK7, BUMP_NONE, OCELEM_NONE, }, +static ColliderQuadInit sQuadInit = { + { + COLTYPE_NONE, + AT_ON | AT_TYPE_ENEMY, + AC_NONE, + OC1_NONE, + OC2_NONE, + COLSHAPE_QUAD, + }, + { + ELEMTYPE_UNK0, + { 0xF7CFFFFF, 0x04, 0x08 }, + { 0x00000000, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL | TOUCH_UNK7, + BUMP_NONE, + OCELEM_NONE, + }, { { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } } }, }; -#endif +// These text IDs are the ones common to all Garos, regardless of which hint they provide. This includes their dialogue +// during their intro cutscene, as well as most of their dialogue when they are defeated. +static u16 sTextIds[] = { + 0x1396, + 0x1397, + 0x1398, + 0x1399, +}; -extern DamageTable D_809B0F48; -extern ColliderCylinderInit D_809B0F88; -extern ColliderQuadInit D_809B0FB4; +typedef enum { + /* 0 */ EN_JSO_ANIM_APPEAR, + /* 1 */ EN_JSO_ANIM_IDLE, + /* 2 */ EN_JSO_ANIM_BOUNCE, + /* 3 */ EN_JSO_ANIM_GUARD, + /* 4 */ EN_JSO_ANIM_DASH_ATTACK, + /* 5 */ EN_JSO_ANIM_SLASH_START, + /* 6 */ EN_JSO_ANIM_SLASH_LOOP, + /* 7 */ EN_JSO_ANIM_KNOCKED_BACK, + /* 8 */ EN_JSO_ANIM_COWER, + /* 9 */ EN_JSO_ANIM_DAMAGED, + /* 10 */ EN_JSO_ANIM_JUMP_BACK, + /* 11 */ EN_JSO_ANIM_FALL_DOWN, + /* 12 */ EN_JSO_ANIM_MAX +} EnJsoAnimation; -extern UNK_TYPE D_060081F4; +static AnimationHeader* sAnimations[EN_JSO_ANIM_MAX] = { + &gGaroAppearAnim, // EN_JSO_ANIM_APPEAR + &gGaroIdleAnim, // EN_JSO_ANIM_IDLE + &gGaroBounceAnim, // EN_JSO_ANIM_BOUNCE + &gGaroGuardAnim, // EN_JSO_ANIM_GUARD + &gGaroDashAttackAnim, // EN_JSO_ANIM_DASH_ATTACK + &gGaroSlashStartAnim, // EN_JSO_ANIM_SLASH_START + &gGaroSlashLoopAnim, // EN_JSO_ANIM_SLASH_LOOP + &gGaroKnockedBackAnim, // EN_JSO_ANIM_KNOCKED_BACK + &gGaroCowerAnim, // EN_JSO_ANIM_COWER + &gGaroDamagedAnim, // EN_JSO_ANIM_DAMAGED + &gGaroJumpBackAnim, // EN_JSO_ANIM_JUMP_BACK + &gGaroFallDownAnim, // EN_JSO_ANIM_FALL_DOWN +}; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/EnJso_Init.s") +static u8 sAnimationModes[EN_JSO_ANIM_MAX] = { + ANIMMODE_ONCE, // EN_JSO_ANIM_APPEAR + ANIMMODE_LOOP, // EN_JSO_ANIM_IDLE + ANIMMODE_LOOP, // EN_JSO_ANIM_BOUNCE + ANIMMODE_ONCE, // EN_JSO_ANIM_GUARD + ANIMMODE_ONCE, // EN_JSO_ANIM_DASH_ATTACK + ANIMMODE_ONCE, // EN_JSO_ANIM_SLASH_START + ANIMMODE_LOOP, // EN_JSO_ANIM_SLASH_LOOP + ANIMMODE_ONCE, // EN_JSO_ANIM_KNOCKED_BACK + ANIMMODE_LOOP, // EN_JSO_ANIM_COWER + ANIMMODE_LOOP, // EN_JSO_ANIM_DAMAGED + ANIMMODE_ONCE, // EN_JSO_ANIM_JUMP_BACK + ANIMMODE_ONCE, // EN_JSO_ANIM_FALL_DOWN +}; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/EnJso_Destroy.s") +void EnJso_Init(Actor* thisx, PlayState* play) { + EnJso* this = THIS; + EffectBlureInit1 rightSwordBlureInit; + EffectBlureInit1 leftSwordBlureInit; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809ADBC8.s") + this->actor.hintId = TATL_HINT_ID_GARO; + this->actor.targetMode = TARGET_MODE_5; + this->actor.colChkInfo.mass = 80; + this->actor.colChkInfo.health = 3; + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 0.0f); + this->actor.colChkInfo.damageTable = &sDamageTable; + this->actor.shape.shadowScale = 0.0f; + SkelAnime_InitFlex(play, &this->skelAnime, &gGaroSkel, &gGaroAppearAnim, this->jointTable, this->morphTable, + GARO_LIMB_MAX); + this->actor.focus.pos = this->actor.world.pos; + Collider_InitAndSetCylinder(play, &this->bodyCollider, &this->actor, &sCylinderInit); + Collider_InitAndSetQuad(play, &this->rightSwordCollider, &this->actor, &sQuadInit); + Collider_InitAndSetQuad(play, &this->leftSwordCollider, &this->actor, &sQuadInit); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809ADC7C.s") + rightSwordBlureInit.p1StartColor[0] = leftSwordBlureInit.p1StartColor[0] = rightSwordBlureInit.p1StartColor[1] = + leftSwordBlureInit.p1StartColor[1] = rightSwordBlureInit.p1StartColor[2] = leftSwordBlureInit.p1StartColor[2] = + rightSwordBlureInit.p1StartColor[3] = leftSwordBlureInit.p1StartColor[3] = + rightSwordBlureInit.p2StartColor[0] = leftSwordBlureInit.p2StartColor[0] = + rightSwordBlureInit.p2StartColor[1] = leftSwordBlureInit.p2StartColor[1] = + rightSwordBlureInit.p2StartColor[2] = leftSwordBlureInit.p2StartColor[2] = + rightSwordBlureInit.p1EndColor[0] = leftSwordBlureInit.p1EndColor[0] = + rightSwordBlureInit.p1EndColor[1] = leftSwordBlureInit.p1EndColor[1] = + rightSwordBlureInit.p1EndColor[2] = leftSwordBlureInit.p1EndColor[2] = + rightSwordBlureInit.p2EndColor[0] = leftSwordBlureInit.p2EndColor[0] = + rightSwordBlureInit.p2EndColor[1] = leftSwordBlureInit.p2EndColor[1] = + rightSwordBlureInit.p2EndColor[2] = leftSwordBlureInit.p2EndColor[2] = + 255; + leftSwordBlureInit.p2StartColor[3] = rightSwordBlureInit.p2StartColor[3] = 64; + rightSwordBlureInit.p1EndColor[3] = leftSwordBlureInit.p1EndColor[3] = rightSwordBlureInit.p2EndColor[3] = + leftSwordBlureInit.p2EndColor[3] = 0; + rightSwordBlureInit.elemDuration = leftSwordBlureInit.elemDuration = 8; + rightSwordBlureInit.unkFlag = leftSwordBlureInit.unkFlag = 0; + rightSwordBlureInit.calcMode = leftSwordBlureInit.calcMode = 2; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809ADCB8.s") + Effect_Add(play, &this->rightSwordBlureIndex, EFFECT_BLURE1, 0, 0, &rightSwordBlureInit); + Effect_Add(play, &this->leftSwordBlureIndex, EFFECT_BLURE1, 0, 0, &leftSwordBlureInit); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AE754.s") + this->actor.gravity = -3.0f; + this->scale = 0.035f; + this->actor.flags |= ACTOR_FLAG_CANT_LOCK_ON; + this->hintType = EN_JSO_GET_HINT_TYPE(&this->actor); + this->introCsType = this->hintType & EN_JSO_INTRO_SCALE_UP; + EnJso_SetupHandleIntroCutscene(this); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AE87C.s") +void EnJso_Destroy(Actor* thisx, PlayState* play) { + EnJso* this = THIS; + EnEncount3* parent; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AE9B0.s") + Collider_DestroyCylinder(play, &this->bodyCollider); + Collider_DestroyQuad(play, &this->rightSwordCollider); + Collider_DestroyQuad(play, &this->leftSwordCollider); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AEA08.s") + if ((this->actor.parent != NULL) && (this->actor.parent->update != NULL)) { + parent = (EnEncount3*)this->actor.parent; + if (parent->actor.update != NULL) { + parent->child = NULL; + if (parent->unk14E > 0) { + parent->unk14E--; + } + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AECA8.s") + Effect_Destroy(play, this->rightSwordBlureIndex); + Effect_Destroy(play, this->leftSwordBlureIndex); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AED00.s") +void EnJso_ChangeAnim(EnJso* this, s32 animIndex) { + f32 morphFrames = -4.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AED54.s") + this->animEndFrame = Animation_GetLastFrame(sAnimations[animIndex]); + if ((animIndex == EN_JSO_ANIM_GUARD) || (animIndex == EN_JSO_ANIM_DAMAGED)) { + morphFrames = 0.0f; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AEDAC.s") + Animation_Change(&this->skelAnime, sAnimations[animIndex], 1.0f, 0.0f, this->animEndFrame, + sAnimationModes[animIndex], morphFrames); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AEE44.s") +void EnJso_SetupHandleIntroCutscene(EnJso* this) { + EnEncount3* parent = (EnEncount3*)this->actor.parent; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AEEC0.s") + this->csId = parent->csId; + this->swordState = EN_JSO_SWORD_STATE_NONE_DRAWN; + this->action = EN_JSO_ACTION_HANDLE_INTRO_CUTSCENE; + this->actor.flags |= ACTOR_FLAG_100000; + this->actionFunc = EnJso_HandleIntroCutscene; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF064.s") +/** + * Responsible for handling all aspects of the intro cutscene, including manipulating the sub-camera, making the Garo + * appear in one of four different ways, activating the ring of fire, etc. When the cutscene is over, the Garo will + * jump back to start the fight. + */ +void EnJso_HandleIntroCutscene(EnJso* this, PlayState* play) { + Player* player = GET_PLAYER(play); + f32 curFrame = this->skelAnime.curFrame; + s16 showTextbox; + s16 diffToTargetRobeRightRot; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF110.s") + switch (this->introCsState) { + case EN_JSO_INTRO_CS_STATE_DONE_OR_STARTED: + if (!CutsceneManager_IsNext(this->csId)) { + CutsceneManager_Queue(this->csId); + return; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF28C.s") + CutsceneManager_StartWithPlayerCs(this->csId, &this->actor); + func_800B7298(play, &this->actor, PLAYER_CSMODE_21); + this->subCamId = CutsceneManager_GetCurrentSubCamId(this->actor.csId); + player->actor.world.pos.x = this->actor.home.pos.x + 30.0f; + player->actor.world.pos.z = this->actor.home.pos.z + 30.0f; + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + this->subCamEyeNext.x = player->actor.world.pos.x; + this->subCamEyeNext.y = player->actor.world.pos.y; + this->subCamEyeNext.z = player->actor.world.pos.z; + this->subCamAtNext.x = player->actor.world.pos.x; + this->subCamAtNext.y = player->actor.world.pos.y; + this->subCamAtNext.z = player->actor.world.pos.z; + player->actor.shape.rot.y = player->actor.world.rot.y = + Math_Vec3f_Yaw(&player->actor.world.pos, &this->actor.world.pos); + Math_Vec3f_Copy(&this->subCamEye, &this->subCamEyeNext); + Math_Vec3f_Copy(&this->subCamAt, &this->subCamAtNext); + EnJso_ChangeAnim(this, EN_JSO_ANIM_IDLE); + this->actor.draw = EnJso_Draw; + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_ENTRY); + this->actor.shape.yOffset = 970.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF2F8.s") + switch (this->introCsType) { + case EN_JSO_INTRO_SPIN_UP_FROM_GROUND: + this->actor.shape.yOffset = -300.0f; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF368.s") + case EN_JSO_INTRO_JUMP_OUT_FROM_GROUND: + EnJso_ChangeAnim(this, EN_JSO_ANIM_APPEAR); + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, this->actor.shape.shadowScale, + 1, 8.0f, 500, 10, true); + this->actor.shape.rot.y = this->actor.world.rot.y = this->actor.yawTowardsPlayer; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF3C0.s") + case EN_JSO_INTRO_LAND_FROM_ABOVE: + this->actor.shape.rot.y = this->actor.world.rot.y = this->actor.yawTowardsPlayer; + this->actor.world.pos.y = this->actor.home.pos.y + 200.0f; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF3FC.s") + case EN_JSO_INTRO_SCALE_UP: + this->scale = 0.0f; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF440.s") + default: + break; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF470.s") + this->introCsTimer = 0; + this->introCsState++; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF53C.s") + case EN_JSO_INTRO_CS_STATE_WAITING_FOR_TEXTBOX_TO_APPEAR: + player->actor.world.pos.x = this->actor.home.pos.x + 30.0f; + player->actor.world.pos.z = this->actor.home.pos.z + 90.0f; + this->subCamEyeNext.x = player->actor.world.pos.x - 170.0f; + this->subCamEyeNext.y = player->actor.world.pos.y + 30.0f; + this->subCamEyeNext.z = player->actor.world.pos.z - 80.0f; + this->subCamAtNext.x = player->actor.world.pos.x + 200.0f; + this->subCamAtNext.y = player->actor.world.pos.y + 10.0f; + this->subCamAtNext.z = player->actor.world.pos.z; + Math_ApproachF(&this->actor.shape.shadowScale, 16.0f, 0.4f, 4.0f); + showTextbox = false; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF5F8.s") + switch (this->introCsType) { + case EN_JSO_INTRO_SPIN_UP_FROM_GROUND: + Math_ApproachF(&this->actor.shape.yOffset, 970.0f, 0.5f, 100.0f); + this->actor.shape.rot.y += 0x1770; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF714.s") + if (this->actor.shape.yOffset >= 969.0f) { + showTextbox = true; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF76C.s") + if ((play->gameplayFrames % 4) == 0) { + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, + this->actor.shape.shadowScale, 1, 8.0f, 500, 10, true); + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF7F4.s") + case EN_JSO_INTRO_JUMP_OUT_FROM_GROUND: + if (curFrame >= this->animEndFrame) { + showTextbox = true; + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, + this->actor.shape.shadowScale, 1, 8.0f, 500, 10, true); + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF8D0.s") + case EN_JSO_INTRO_LAND_FROM_ABOVE: + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + showTextbox = true; + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, + this->actor.shape.shadowScale, 1, 8.0f, 500, 10, true); + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AF99C.s") + case EN_JSO_INTRO_SCALE_UP: + Math_ApproachF(&this->scale, 0.035f, 0.2f, 0.03f); + if (this->scale >= 0.034f) { + this->scale = 0.035f; + showTextbox = true; + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AFA58.s") + default: + break; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AFAF4.s") + if (showTextbox) { + func_800B7298(play, &this->actor, PLAYER_CSMODE_4); + Message_StartTextbox(play, sTextIds[this->textIndex], &this->actor); + this->textIndex++; + this->actor.shape.yOffset = 970.0f; + this->introCsTimer = 0; + this->introCsState++; + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AFC10.s") + case EN_JSO_INTRO_CS_STATE_WAITING_FOR_TEXTBOX_TO_CONTINUE: + this->subCamEyeNext.x = player->actor.world.pos.x - 30.0f; + this->subCamEyeNext.y = player->actor.world.pos.y + 30.0f; + this->subCamEyeNext.z = player->actor.world.pos.z + 70.0f; + this->subCamAtNext.x = player->actor.world.pos.x - 20.0f; + this->subCamAtNext.y = player->actor.world.pos.y + 20.0f; + this->subCamAtNext.z = player->actor.world.pos.z; + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 1, 0xBB8, 0); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809AFE38.s") + if ((Message_GetState(&play->msgCtx) == TEXT_STATE_5) && Message_ShouldAdvance(play)) { + Message_CloseTextbox(play); + Message_ContinueTextbox(play, sTextIds[this->textIndex]); + this->introCsTimer = 0; + this->textIndex++; + this->introCsState++; + } + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809B0034.s") + case EN_JSO_INTRO_CS_STATE_RAISE_ARM_AND_DRAW_RIGHT_SWORD: + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 1, 0xBB8, 0); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/EnJso_Update.s") + if (Message_GetState(&play->msgCtx) == TEXT_STATE_10) { + this->targetRobeRightRot.x = -0x2710; + this->targetRobeRightRot.z = -0x2710; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809B0734.s") + diffToTargetRobeRightRot = this->targetRobeRightRot.x - this->robeRightRot.x; + diffToTargetRobeRightRot = ABS_ALT(diffToTargetRobeRightRot); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809B0820.s") + if (this->targetRobeRightRot.x != 0) { + if ((ABS_ALT(diffToTargetRobeRightRot) < 0x64) && (this->swordState == EN_JSO_SWORD_STATE_NONE_DRAWN)) { + this->swordState = EN_JSO_SWORD_STATE_RIGHT_DRAWN; + this->targetRightArmRot.x = -0x32C8; + this->targetRightArmRot.y = -0xBB8; + this->rightArmRot.z = this->targetRightArmRot.z = -0x32C8; + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_SWORD); + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809B0B70.s") + this->subCamEyeNext.x = player->actor.world.pos.x + 100.0f; + this->subCamEyeNext.y = player->actor.world.pos.y + 60.0f; + this->subCamEyeNext.z = player->actor.world.pos.z - 30.0f; + this->subCamAtNext.x = player->actor.world.pos.x - 35.0f; + this->subCamAtNext.y = player->actor.world.pos.y + 30.0f; + this->subCamAtNext.z = player->actor.world.pos.z - 40.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Jso/func_809B0BB0.s") + if ((Message_GetState(&play->msgCtx) == TEXT_STATE_5) && Message_ShouldAdvance(play)) { + Message_CloseTextbox(play); + this->targetRightArmRot.x = this->targetRightArmRot.y = this->targetRightArmRot.z = + this->targetRobeRightRot.x = this->targetRobeRightRot.y = this->targetRobeRightRot.z = 0; + this->introCsTimer = 0; + this->introCsState++; + } + break; + + case EN_JSO_INTRO_CS_STATE_DRAW_LEFT_SWORD: + this->subCamEyeNext.x = player->actor.world.pos.x - 40.0f; + this->subCamEyeNext.y = player->actor.world.pos.y + 20.0f; + this->subCamEyeNext.z = player->actor.world.pos.z; + this->subCamAtNext.x = player->actor.world.pos.x - 30.0f; + this->subCamAtNext.y = player->actor.world.pos.y + 40.0f; + this->subCamAtNext.z = player->actor.world.pos.z - 130.0f; + this->introCsTimer++; + + if (this->introCsTimer >= 10) { + this->swordState = EN_JSO_SWORD_STATE_BOTH_DRAWN; + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_SWORD); + if ((this->actor.parent != NULL) && (this->actor.parent->update != NULL)) { + EnEncount3* parent = (EnEncount3*)this->actor.parent; + + if (parent->actor.update != NULL) { + parent->unk148 = 1; + play->unk_18880 = 1; + } + } + + EnJso_ChangeAnim(this, EN_JSO_ANIM_IDLE); + this->introCsTimer = 0; + this->introCsState++; + } + break; + + case EN_JSO_INTRO_CS_STATE_ENDING: + this->subCamEyeNext.x = player->actor.world.pos.x - 40.0f; + this->subCamEyeNext.y = player->actor.world.pos.y + 20.0f; + this->subCamEyeNext.z = player->actor.world.pos.z; + this->subCamAtNext.x = player->actor.world.pos.x - 30.0f; + this->subCamAtNext.y = player->actor.world.pos.y + 40.0f; + this->subCamAtNext.z = player->actor.world.pos.z - 130.0f; + this->introCsTimer++; + + if (this->introCsTimer >= 10) { + func_800B7298(play, &this->actor, PLAYER_CSMODE_END); + CutsceneManager_Stop(this->csId); + this->rightArmRot.x = this->rightArmRot.y = this->rightArmRot.z = this->robeRightRot.x = + this->robeRightRot.y = this->robeRightRot.z = 0; + this->introCsState = EN_JSO_INTRO_CS_STATE_DONE_OR_STARTED; + this->subCamId = SUB_CAM_ID_DONE; + this->actor.flags &= ~ACTOR_FLAG_100000; + this->actor.flags &= ~ACTOR_FLAG_CANT_LOCK_ON; + this->actor.flags |= ACTOR_FLAG_TARGETABLE; + this->actor.world.rot.y = this->actor.yawTowardsPlayer; + EnJso_SetupJumpBack(this); + } + break; + } + + Math_SmoothStepToS(&this->rightArmRot.x, this->targetRightArmRot.x, 1, 0xBB8, 0); + Math_SmoothStepToS(&this->rightArmRot.y, this->targetRightArmRot.y, 1, 0xBB8, 0); + Math_SmoothStepToS(&this->rightArmRot.z, this->targetRightArmRot.z, 1, 0xBB8, 0); + Math_SmoothStepToS(&this->robeRightRot.x, this->targetRobeRightRot.x, 1, 0xBB8, 0); + Math_SmoothStepToS(&this->robeRightRot.y, this->targetRobeRightRot.y, 1, 0xBB8, 0); + Math_SmoothStepToS(&this->robeRightRot.z, this->targetRobeRightRot.z, 1, 0xBB8, 0); + Math_Vec3f_Copy(&this->subCamEye, &this->subCamEyeNext); + Math_Vec3f_Copy(&this->subCamAt, &this->subCamAtNext); + + if (this->subCamId != SUB_CAM_ID_DONE) { + this->subCamUp.x = 0.0f; + this->subCamUp.y = 1.0f; + this->subCamUp.z = 0.0f; + Play_SetCameraAtEyeUp(play, this->subCamId, &this->subCamAt, &this->subCamEye, &this->subCamUp); + } +} + +void EnJso_SetupReappear(EnJso* this, PlayState* play) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_APPEAR); + this->actor.speed = 0.0f; + this->actor.velocity.y = 0.0f; + + if (this->isPlayerLockedOn) { + this->isPlayerLockedOn = false; + sIsPlayerLockedOn = false; + } + + if (this->isAttacking) { + this->isAttacking = false; + sIsAttacking = false; + } + + if (this->swordState != EN_JSO_SWORD_STATE_BOTH_DRAWN) { + this->swordState = EN_JSO_SWORD_STATE_BOTH_DRAWN; + } + + this->actor.shape.rot.x = this->actor.world.rot.x = 0; + this->actor.shape.rot.y = this->actor.world.rot.y = this->actor.yawTowardsPlayer; + Math_Vec3f_Copy(&this->actor.world.pos, &this->actor.home.pos); + this->actor.floorHeight = this->actor.home.pos.y; + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, this->actor.shape.shadowScale, 1, 8.0f, 500, + 10, true); + this->actor.flags |= ACTOR_FLAG_CANT_LOCK_ON; + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_ENTRY); + this->afterimageCount = 0; + this->action = EN_JSO_ACTION_REAPPEAR; + this->actor.gravity = 0.0f; + this->actionFunc = EnJso_Reappear; +} + +/** + * This function makes the Garo jump out of the ground from its home position. It's intended to be used if the Garo gets + * too far away from the player (for example, if the Garo's dash attack makes it fall off a ledge); this will ensure the + * Garo is back inside the ring of fire, so the player can interact with them again. + */ +void EnJso_Reappear(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + if (this->actor.colChkInfo.health <= 0) { + Player* player = GET_PLAYER(play); + + player->actor.freezeTimer = 3; + } + + Math_ApproachF(&this->actor.shape.shadowScale, 16.0f, 0.4f, 4.0f); + Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xBB8, 0x14); + + if ((play->gameplayFrames % 8) == 0) { + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, this->actor.shape.shadowScale, 1, 8.0f, + 500, 10, true); + } + + if (curFrame >= this->animEndFrame) { + this->actor.gravity = -3.0f; + if (this->actor.colChkInfo.health > 0) { + this->actor.flags &= ~ACTOR_FLAG_CANT_LOCK_ON; + EnJso_SetupCirclePlayer(this, play); + } else { + EnJso_SetupFallDownAndTalk(this, play); + } + } +} + +void EnJso_SetupCirclePlayer(EnJso* this, PlayState* play) { + Player* player = GET_PLAYER(play); + + EnJso_ChangeAnim(this, EN_JSO_ANIM_BOUNCE); + this->circlingAngularVelocity = 0x258; + this->action = EN_JSO_ACTION_CIRCLE_PLAYER; + this->circlingAngle = player->actor.shape.rot.y; + this->actionFunc = EnJso_CirclePlayer; +} + +/** + * Makes the Garo bounce in a circle around the player, sometimes randomly switching the direction it's traveling, until + * the attack wait timer reaches 0. Once it does, then the Garo will prepare to attack. + */ +void EnJso_CirclePlayer(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + Player* player = GET_PLAYER(play); + Vec3f targetPos; + s32 pad; + + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_MOVING - SFX_FLAG); + if ((this->action != EN_JSO_ACTION_REAPPEAR) && (this->action != EN_JSO_ACTION_HANDLE_INTRO_CUTSCENE) && + (fabsf(player->actor.world.pos.y - this->actor.world.pos.y) > 60.0f)) { + EnJso_SetupReappear(this, play); + return; + } + + if (curFrame < this->animEndFrame) { + SkelAnime_Update(&this->skelAnime); + } else if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + SkelAnime_Update(&this->skelAnime); + } + + if (Animation_OnFrame(&this->skelAnime, 6.0f)) { + this->actor.velocity.y = 10.0f; + if ((play->gameplayFrames % 2) == 0) { + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_CRYING); + } + } + + if (Animation_OnFrame(&this->skelAnime, 12.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_SKIP); + this->actor.speed = 0.0f; + if (Rand_ZeroFloat(1.0f) < 0.5f) { + this->circlingAngularVelocity = -this->circlingAngularVelocity; + } + } + + //! @note: Since sIsPlayerLockedOn always mirrors the value of this->isPlayerLockedOn, the last part of this + //! if-statement is always true and has no impact on the logic. In other words, the Garo doesn't care whether or not + //! the player is locked on; it will attack regardless as long as the other conditions are met. + if ((this->attackWaitTimer == 0) && !sIsAttacking && (!sIsPlayerLockedOn || this->isPlayerLockedOn)) { + sIsAttacking = true; + this->isAttacking = true; + this->action = EN_JSO_ACTION_SPIN_BEFORE_ATTACK; + this->actor.speed = 0.0f; + EnJso_SetupSpinBeforeAttack(this); + return; + } + + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xFA0, 0x14); + Math_ApproachF(&this->actor.speed, 5.0f, 0.3f, 2.0f); + this->circlingAngle += this->circlingAngularVelocity; + targetPos.x = (Math_SinS(this->circlingAngle) * 200.0f) + player->actor.world.pos.x; + targetPos.y = this->actor.world.pos.y; + targetPos.z = (Math_CosS(this->circlingAngle) * 200.0f) + player->actor.world.pos.z; + Math_SmoothStepToS(&this->actor.world.rot.y, Math_Vec3f_Yaw(&this->actor.world.pos, &targetPos), 0xA, 0xFA0, 0x14); +} + +void EnJso_SetupGuard(EnJso* this) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_GUARD); + this->action = EN_JSO_ACTION_GUARD; + this->actor.world.rot.y = this->actor.yawTowardsPlayer; + Actor_PlaySfx(&this->actor, NA_SE_IT_SHIELD_BOUND); + this->bodyCollider.base.acFlags |= AC_HARD; + this->actionFunc = EnJso_Guard; +} + +/** + * Plays the guard animation to completion, then goes back to circling the player. + */ +void EnJso_Guard(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + if (curFrame >= this->animEndFrame) { + this->isGuarding = false; + + if (this->isAttacking) { + this->isAttacking = false; + sIsAttacking = false; + } + + EnJso_SetupCirclePlayer(this, play); + } +} + +void EnJso_SetupSpinBeforeAttack(EnJso* this) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_DASH_ATTACK); + this->actor.world.rot.y = -this->actor.yawTowardsPlayer; + this->actor.speed = 10.0f; + this->actor.velocity.y = 20.0f; + this->actionFunc = EnJso_SpinBeforeAttack; +} + +/** + * Leaps into the air while spinning forward. Once the Garo touches the ground, it will begin a dash attack. + */ +void EnJso_SpinBeforeAttack(EnJso* this, PlayState* play) { + this->actor.world.rot.x += 0x1770; + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xFA0, 0x14); + + if (this->actor.velocity.y > 0.0f) { + return; + } + + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + this->actor.world.rot.x = 0; + this->actor.velocity.y = 0.0f; + this->actor.speed = 0.0f; + this->actor.world.rot.y = this->actor.shape.rot.y = this->actor.yawTowardsPlayer; + EnJso_SetupDashAttack(this); + } +} + +void EnJso_SetupDashAttack(EnJso* this) { + this->action = EN_JSO_ACTION_DASH_ATTACK; + this->dashAttackTimer = 40; + this->bodyCollider.base.colType = COLTYPE_HIT2; + this->bodyCollider.base.acFlags &= ~AC_HARD; + this->actor.speed = 15.0f; + this->actor.velocity.y = 13.0f; + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_ENTRY); + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_DASH_2); + this->actionFunc = EnJso_DashAttack; +} + +/** + * Dashes toward the player with its swords out. If the player shields the attack, the Garo will be knocked back and + * drop its swords. The Garo will stop dashing and perform a slash if it's close enough to the player, if the difference + * between its y-rotation and the yaw towards the player is large enough (usually indicating that it has dashed past the + * player), or if 40 frames have passed, whichever comes first. + */ +void EnJso_DashAttack(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + s16 yawDiff; + s16 absYawDiff; + Vec3f knockbackVelocity; + + if ((this->rightSwordCollider.base.atFlags & AT_BOUNCED) || (this->leftSwordCollider.base.atFlags & AT_BOUNCED)) { + this->rightSwordCollider.base.atFlags &= ~(AT_HIT | AT_BOUNCED); + this->leftSwordCollider.base.atFlags &= ~(AT_HIT | AT_BOUNCED); + if (this->swordState == EN_JSO_SWORD_STATE_BOTH_DRAWN) { + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + knockbackVelocity.x = 0.0f; + knockbackVelocity.y = 0.0f; + knockbackVelocity.z = -10.0f; + Matrix_MultVec3f(&knockbackVelocity, &this->knockbackVelocity); + this->swordState = EN_JSO_SWORD_STATE_KNOCKED_OUT_OF_HANDS; + this->dashAttackTimer = 0; + this->disableBlure = true; + AudioSfx_SetChannelIO(&this->actor.projectedPos, NA_SE_EN_ANSATSUSYA_DASH_2, 0); + EnJso_SetupKnockedBack(this); + return; + } + } + + if ((this->actor.velocity.y < 0.0f) && (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND)) { + this->actor.velocity.y = 13.0f; + } + + if (curFrame < this->animEndFrame) { + return; + } + + yawDiff = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; + absYawDiff = ABS_ALT(yawDiff); + + if ((this->dashAttackTimer == 0) || (this->actor.xzDistToPlayer < 100.0f) || (absYawDiff > 0x4300)) { + AudioSfx_SetChannelIO(&this->actor.projectedPos, NA_SE_EN_ANSATSUSYA_DASH_2, 0); + Math_ApproachZeroF(&this->actor.speed, 0.3f, 3.0f); + EnJso_SetupSlash(this, play); + } +} + +void EnJso_SetupSlash(EnJso* this, PlayState* play) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_SLASH_START); + this->action = EN_JSO_ACTION_SLASH; + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, this->actor.shape.shadowScale, 1, 8.0f, 500, + 10, true); + Math_ApproachZeroF(&this->actor.speed, 0.3f, 3.0f); + this->swordHitSomething = false; + Actor_PlaySfx(&this->actor, NA_SE_IT_SWORD_SWING_HARD); + this->actionFunc = EnJso_Slash; +} + +/** + * Slash in place with both swords. If the player shields the attack, the Garo will be knocked back and drop its swords. + * Once the slash animation ends, this function will transition the Garo to a waiting state. + */ +void EnJso_Slash(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + Math_ApproachZeroF(&this->actor.speed, 0.5f, 5.0f); + + if ((play->gameplayFrames % 8) == 0) { + Actor_SpawnFloorDustRing(play, &this->actor, &this->actor.world.pos, this->actor.shape.shadowScale, 1, 8.0f, + 500, 10, true); + } + + if ((this->rightSwordCollider.base.atFlags & AT_HIT) || (this->leftSwordCollider.base.atFlags & AT_HIT)) { + this->swordHitSomething = true; + this->rightSwordCollider.base.atFlags &= ~AT_HIT; + this->leftSwordCollider.base.atFlags &= ~AT_HIT; + } + + if ((this->rightSwordCollider.base.atFlags & AT_BOUNCED) || (this->leftSwordCollider.base.atFlags & AT_BOUNCED)) { + this->rightSwordCollider.base.atFlags &= ~(AT_HIT | AT_BOUNCED); + this->leftSwordCollider.base.atFlags &= ~(AT_HIT | AT_BOUNCED); + + if (this->swordState == EN_JSO_SWORD_STATE_BOTH_DRAWN) { + Vec3f knockbackVelocity; + + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + knockbackVelocity.x = 0.0f; + knockbackVelocity.y = 0.0f; + knockbackVelocity.z = -10.0f; + Matrix_MultVec3f(&knockbackVelocity, &this->knockbackVelocity); + this->swordState = EN_JSO_SWORD_STATE_KNOCKED_OUT_OF_HANDS; + this->disableBlure = true; + EnJso_SetupKnockedBack(this); + return; + } + } + + if (curFrame >= this->animEndFrame) { + this->actor.speed = 0.0f; + this->disableBlure = true; + EnJso_SetupWaitAfterSlash(this); + } +} + +void EnJso_SetupWaitAfterSlash(EnJso* this) { + if (this->swordHitSomething) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_SLASH_LOOP); + this->timer = 20; + } else { + EnJso_ChangeAnim(this, EN_JSO_ANIM_SLASH_LOOP); + this->timer = 40; + } + + this->action = EN_JSO_ACTION_WAIT_AFTER_SLASH; + this->actionFunc = EnJso_WaitAfterSlash; +} + +/** + * Waits either 20 or 40 frames, depending on whether or not the Garo hit the player with its slash attack. Once this + * waiting period ends, the Garo starts circling the player again. + */ +void EnJso_WaitAfterSlash(EnJso* this, PlayState* play) { + if (this->timer == 0) { + this->attackWaitTimer = Rand_S16Offset(30, 30); + this->bodyCollider.base.colType = COLTYPE_NONE; + this->bodyCollider.base.acFlags |= AC_HARD; + this->swordHitSomething = false; + sIsAttacking = false; + this->isAttacking = false; + EnJso_SetupCirclePlayer(this, play); + } +} + +void EnJso_SetupKnockedBack(EnJso* this) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_KNOCKED_BACK); + this->bodyCollider.base.acFlags &= ~AC_HARD; + this->timer = 30; + this->action = EN_JSO_ACTION_KNOCKED_BACK; + this->actor.speed = 0.0f; + this->actionFunc = EnJso_KnockedBack; +} + +/** + * Plays the knocked back animation to completion, then transitions to cowering on the ground. + */ +void EnJso_KnockedBack(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + if (curFrame >= this->animEndFrame) { + EnJso_SetupCower(this); + } +} + +void EnJso_SetupCower(EnJso* this) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_COWER); + this->action = EN_JSO_ACTION_COWER; + this->timer = 30; + this->actionFunc = EnJso_Cower; +} + +/** + * Cower on the ground for 30 frames, then jump back. + */ +void EnJso_Cower(EnJso* this, PlayState* play) { + if (this->timer == 0) { + EnJso_SetupJumpBack(this); + } +} + +/** + * Unlike most enemies, this will make the Garo play its damaged animation rather than stopping all animations. + */ +void EnJso_SetupStunned(EnJso* this) { + Vec3f knockbackVelocity; + + AudioSfx_SetChannelIO(&this->actor.projectedPos, NA_SE_EN_ANSATSUSYA_DASH_2, 0); + EnJso_ChangeAnim(this, EN_JSO_ANIM_DAMAGED); + + // This assignment is immediately overriden below. + this->timer = 30; + this->actor.speed = 0.0f; + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffAlpha == 0)) { + this->drawDmgEffAlpha = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + this->timer = 40; + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + Matrix_MultVecZ(-10.0f, &knockbackVelocity); + Math_Vec3f_Copy(&this->knockbackVelocity, &knockbackVelocity); + this->action = EN_JSO_ACTION_STUNNED; + this->bodyCollider.base.acFlags &= ~AC_HARD; + this->actionFunc = EnJso_Stunned; +} + +/** + * Makes the Garo play its damaged animation and stop moving until 40 frames have passed, then it jumps back. + */ +void EnJso_Stunned(EnJso* this, PlayState* play) { + if (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) { + if ((this->drawDmgEffAlpha != 0) && (this->drawDmgEffAlpha < 60)) { + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX; + } + } + + if ((this->timer == 0) && (this->drawDmgEffAlpha == 0)) { + if ((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, EN_JSO_BODYPART_MAX, 2, 0.3f, 0.2f); + this->drawDmgEffAlpha = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + EnJso_SetupJumpBack(this); + } +} + +void EnJso_SetupDamaged(EnJso* this, PlayState* play) { + Vec3f knockbackVelocity; + + AudioSfx_SetChannelIO(&this->actor.projectedPos, NA_SE_EN_ANSATSUSYA_DASH_2, 0); + EnJso_ChangeAnim(this, EN_JSO_ANIM_DAMAGED); + this->swordHitSomething = false; + this->actor.velocity.y = 10.0f; + this->actor.speed = 0.0f; + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + Matrix_MultVecZ(-20.0f, &knockbackVelocity); + Math_Vec3f_Copy(&this->knockbackVelocity, &knockbackVelocity); + + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffAlpha != 0)) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, EN_JSO_BODYPART_MAX, 2, 0.3f, 0.2f); + this->drawDmgEffAlpha = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 8); + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_DAMAGE); + this->action = EN_JSO_ACTION_DAMAGED; + this->actionFunc = EnJso_Damaged; +} + +/** + * Plays the damaged animation until the Garo is touching the ground and no longer has its red color filter, at which + * point it will jump back. + */ +void EnJso_Damaged(EnJso* this, PlayState* play) { + if (this->actor.velocity.y > 0.0f) { + return; + } + + if ((this->actor.colorFilterTimer == 0) && (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND)) { + EnJso_SetupJumpBack(this); + } +} + +void EnJso_SetupJumpBack(EnJso* this) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_JUMP_BACK); + this->action = EN_JSO_ACTION_JUMP_BACK; + this->timer = 30; + this->actor.speed = 7.0f; + this->actor.velocity.y = 20.0f; + + if (this->swordState != EN_JSO_SWORD_STATE_BOTH_DRAWN) { + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_SWORD); + this->swordState = EN_JSO_SWORD_STATE_BOTH_DRAWN; + } + + this->actor.world.rot.y += 0x8000; + this->actionFunc = EnJso_JumpBack; +} + +/** + * Jump backwards away from the player. After the Garo touches the ground, the jump back animation completes, and 30 + * frames have passed, the Garo will transition to circling the player. + */ +void EnJso_JumpBack(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xBB8, 0x14); + + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + this->actor.speed = 0.0f; + } + + if ((this->timer == 0) || ((curFrame >= this->animEndFrame) && (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND))) { + this->actor.world.rot.x = 0; + this->actor.velocity.y = 0.0f; + this->actor.speed = 0.0f; + this->timer = 0; + sIsAttacking = false; + this->isAttacking = false; + this->actor.world.rot.y = this->actor.shape.rot.y; + this->attackWaitTimer = Rand_S16Offset(10, 10); + EnJso_SetupCirclePlayer(this, play); + } +} + +void EnJso_SetupDead(EnJso* this, PlayState* play) { + AudioSfx_SetChannelIO(&this->actor.projectedPos, NA_SE_EN_ANSATSUSYA_DASH_2, 0); + EnJso_ChangeAnim(this, EN_JSO_ANIM_DAMAGED); + + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffAlpha == 0)) { + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + this->actor.flags &= ~(ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY); + this->actor.flags |= ACTOR_FLAG_CANT_LOCK_ON; + this->actor.speed = 0.0f; + this->disableBlure = true; + this->timer = 30; + Enemy_StartFinishingBlow(play, &this->actor); + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_DEAD); + this->action = EN_JSO_ACTION_DEAD; + this->actionFunc = EnJso_Dead; +} + +/** + * Plays the damaged animation for 30 frames, then the Garo falls down and starts talking to the player. + */ +void EnJso_Dead(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + if ((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) { + if (this->drawDmgEffAlpha != 0) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, EN_JSO_BODYPART_MAX, 2, 0.3f, 0.2f); + this->drawDmgEffAlpha = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } else { + return; + } + } + + if ((curFrame >= this->animEndFrame) && (this->timer == 0)) { + EnJso_SetupFallDownAndTalk(this, play); + } +} + +void EnJso_SetupFallDownAndTalk(EnJso* this, PlayState* play) { + EnJso_ChangeAnim(this, EN_JSO_ANIM_FALL_DOWN); + this->textIndex = 2; + this->actor.colChkInfo.mass = MASS_IMMOVABLE; + this->actor.flags |= (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY); + func_800BC154(play, &play->actorCtx, &this->actor, ACTORCAT_NPC); + this->actor.flags &= ~ACTOR_FLAG_CANT_LOCK_ON; + this->actor.flags &= ~ACTOR_FLAG_100000; + this->action = EN_JSO_ACTION_FALL_DOWN_AND_TALK; + this->actionFunc = EnJso_FallDownAndTalk; +} + +/** + * Handles the first part of the dialogue the Garo gives when defeated. + */ +void EnJso_FallDownAndTalk(EnJso* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + Player* player = GET_PLAYER(play); + + if (curFrame < this->animEndFrame) { + return; + } + + if (!Play_InCsMode(play) && (play->msgCtx.msgLength == 0)) { + if (fabsf(player->actor.world.pos.y - this->actor.world.pos.y) < 60.0f) { + Player* player2 = GET_PLAYER(play); + + this->actor.textId = sTextIds[this->textIndex]; + Message_StartTextbox(play, this->actor.textId, &this->actor); + player2->stateFlags1 |= PLAYER_STATE1_10000000; + player2->actor.freezeTimer = 3; + this->actor.flags |= ACTOR_FLAG_CANT_LOCK_ON; + this->actionFunc = EnJso_TellHint; + } else { + EnJso_SetupReappear(this, play); + } + } +} + +/** + * Handles the second part of the dialogue the Garo gives when defeated involving its specific hint. If the player has + * the Elegy of Emptiness, then the Garo ignore its specific hint and will instead always tell them that the Elegy can + * be used to climb Stone Tower. Once the Garo has given its hint, this function will have it say one final piece of + * dialogue and prepare for it to burst into flames. + */ +void EnJso_TellHint(EnJso* this, PlayState* play) { + Player* player = GET_PLAYER(play); + + player->actor.freezeTimer = 3; + + // Checking for Dawn of the Second Day, Night of the First Day, etc. messages + if ((play->msgCtx.currentTextId >= 0x1BB2) && (play->msgCtx.currentTextId < 0x1BB7)) { + this->actor.textId = sTextIds[this->textIndex]; + Message_StartTextbox(play, this->actor.textId, &this->actor); + return; + } + + if ((Message_GetState(&play->msgCtx) == TEXT_STATE_5) && Message_ShouldAdvance(play)) { + Message_CloseTextbox(play); + if (this->textIndex == 2) { + u16 textId = 0x139C; // Hint about using Elegy to climb Stone Tower + + switch (this->hintType) { + case EN_JSO_HINT_FREEZE_OCTOROKS: + textId = 0x139A; // Hint about freezing the Octoroks + break; + + case EN_JSO_HINT_FLATS_LOCATION: + textId = 0x139B; // Hint about Flat being in the Graveyard + break; + + case EN_JSO_HINT_VARIABLE_1: + textId = 0x139D; // Hint about Pamela's family being in the house with the waterwheel + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_14_04)) { + textId = 0x13A1; // Hint about using the Song of Healing on Pamela's father + } + break; + + case EN_JSO_HINT_VARIABLE_2: + textId = 0x139E; // Hint about Pamela leaving her house to check something + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_14_04)) { + textId = 0x13A2; // Hint about the well being connected to Ikana Castle + } + break; + + case EN_JSO_HINT_VARIABLE_3: + textId = 0x139F; // Hint about restoring the river by going into Sharp's cave + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_14_04)) { + textId = 0x13A3; // Hint about the Gibdos in the well wanting items + } + break; + + case EN_JSO_HINT_VARIABLE_4: + textId = 0x13A0; // Hint about needing the Song of Storms to pacify Sharp + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_14_04)) { + textId = 0x13A4; // Hint about the items the Gibdos want being in the well + } + break; + + case EN_JSO_HINT_DANCING_REDEADS: + textId = 0x13A5; // Hint about the Redeads being able to dance + break; + + case EN_JSO_HINT_DESTRUCTIBLE_CEILING_IN_CASTLE: + textId = 0x13A6; // Hint about the destructible ceiling in Ikana Castle + break; + + case EN_JSO_HINT_SECOND_ENTRANCE_TO_CASTLE: + textId = 0x13A7; // Hint about the second entrance to Ikana Castle + break; + + case EN_JSO_HINT_KING_WEAK_TO_MIRROR_SHIELD: + textId = 0x13A8; // Hint about using the Mirror Shield on Igos du Ikana + break; + + default: + break; + } + + if (CHECK_QUEST_ITEM(QUEST_SONG_ELEGY)) { + textId = 0x139C; // Hint about using Elegy to climb Stone Tower + } + + Message_ContinueTextbox(play, textId); + this->textIndex++; + } else if (this->textIndex == 3) { + Message_ContinueTextbox(play, sTextIds[this->textIndex]); + this->actionFunc = EnJso_BurstIntoFlames; + } + } +} + +/** + * Once the player closes the Garo's final textbox, this function will spawn nine green flames to obscure the Garo and + * kill the actor. + */ +void EnJso_BurstIntoFlames(EnJso* this, PlayState* play) { + Vec3f firePos; + Vec3f fireVelocityAndAccel[] = { + { 1.0f, 0.0f, 0.5f }, { 1.0f, 0.0f, -0.5f }, { -1.0f, 0.0f, 0.5f }, + { -1.0f, 0.0f, -0.5f }, { 0.5f, 0.0f, 1.0f }, { -0.5f, 0.0f, 1.0f }, + { 0.5f, 0.0f, -1.0f }, { -0.5f, 0.0f, -1.0f }, { 0.0f, 0.0f, 0.0f }, + }; + Player* player = GET_PLAYER(play); + f32 scale; + f32 scaleStep; + s32 i; + + player->actor.freezeTimer = 3; + if ((Message_GetState(&play->msgCtx) == TEXT_STATE_5) && Message_ShouldAdvance(play)) { + Player* player2 = GET_PLAYER(play); + + Message_CloseTextbox(play); + for (i = 0; i < ARRAY_COUNT(fireVelocityAndAccel); i++) { + Math_Vec3f_Copy(&firePos, &this->actor.world.pos); + firePos.x += Rand_CenteredFloat(30.0f); + firePos.y = this->actor.floorHeight; + firePos.z += Rand_CenteredFloat(30.0f); + scale = (Rand_ZeroFloat(1.0f) * 100.0f) + 100.0f; + scaleStep = 20.0f; + func_800B3030(play, &firePos, &fireVelocityAndAccel[i], &fireVelocityAndAccel[i], scale, scaleStep, 1); + } + + SoundSource_PlaySfxEachFrameAtFixedWorldPos(play, &this->actor.world.pos, 10, + NA_SE_EN_COMMON_EXTINCT_LEV - SFX_FLAG); + + sIsAttacking = false; + this->isAttacking = false; + if (this->isPlayerLockedOn) { + this->isPlayerLockedOn = false; + sIsPlayerLockedOn = false; + } + + player2->stateFlags1 &= ~PLAYER_STATE1_10000000; + Actor_Kill(&this->actor); + } +} + +void EnJso_UpdateDamage(EnJso* this, PlayState* play) { + s32 attackDealsDamage = false; + + if (this->bodyCollider.base.acFlags & AC_HIT) { + this->bodyCollider.base.acFlags &= ~AC_HIT; + if ((this->actor.colChkInfo.damageEffect == EN_JSO_DMGEFF_STUN) || + (this->actor.colChkInfo.damageEffect == EN_JSO_DMGEFF_ELECTRIC_STUN)) { + if (((this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_SFX) && + (this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) || + (this->drawDmgEffAlpha == 0)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_FREEZE); + Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_BLUE, 255, COLORFILTER_BUFFLAG_OPA, 40); + if (this->actor.colChkInfo.damageEffect == EN_JSO_DMGEFF_ELECTRIC_STUN) { + this->drawDmgEffAlpha = 40; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_ELECTRIC_SPARKS_SMALL; + } + + EnJso_SetupStunned(this); + } + } else { + switch (this->action) { + case EN_JSO_ACTION_CIRCLE_PLAYER: + case EN_JSO_ACTION_GUARD: + this->actor.speed = 0.0f; + EnJso_SetupGuard(this); + attackDealsDamage = false; + break; + + case EN_JSO_ACTION_DASH_ATTACK: + case EN_JSO_ACTION_SLASH: + case EN_JSO_ACTION_WAIT_AFTER_SLASH: + case EN_JSO_ACTION_STUNNED: + case EN_JSO_ACTION_COWER: + switch (this->actor.colChkInfo.damageEffect) { + case EN_JSO_DMGEFF_NONE: + attackDealsDamage = true; + break; + + case EN_JSO_DMGEFF_FIRE: + this->drawDmgEffAlpha = 40; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + attackDealsDamage = true; + break; + + case EN_JSO_DMGEFF_LIGHT_ORB: + if (((this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_SFX) && + (this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) || + (this->drawDmgEffAlpha == 0)) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CLEAR_TAG, this->actor.focus.pos.x, + this->actor.focus.pos.y, this->actor.focus.pos.z, 0, 0, 0, + CLEAR_TAG_PARAMS(CLEAR_TAG_LARGE_LIGHT_RAYS)); + this->drawDmgEffAlpha = 20; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_LIGHT_ORBS; + attackDealsDamage = true; + } + break; + + case EN_JSO_DMGEFF_FREEZE: + if (((this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_SFX) && + (this->drawDmgEffType != ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) || + (this->drawDmgEffAlpha == 0)) { + attackDealsDamage = false; + Actor_ApplyDamage(&this->actor); + this->drawDmgEffAlpha = 80; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_SFX; + this->drawDmgEffScale = 0.0f; + this->drawDmgEffFrozenSteamScale = 1.5f; + } + + if (this->actor.colChkInfo.health <= 0) { + EnJso_SetupDead(this, play); + attackDealsDamage = false; + } else { + EnJso_SetupStunned(this); + attackDealsDamage = false; + } + break; + + default: + break; + } + break; + + default: + break; + } + + if (attackDealsDamage) { + Actor_ApplyDamage(&this->actor); + this->bodyCollider.base.colType = COLTYPE_NONE; + this->bodyCollider.base.acFlags |= AC_HARD; + if (this->actor.colChkInfo.health > 0) { + EnJso_SetupDamaged(this, play); + } else { + EnJso_SetupDead(this, play); + } + } + } + } +} + +void EnJso_Update(Actor* thisx, PlayState* play) { + EnJso* this = THIS; + s32 pad; + + if ((this->action != EN_JSO_ACTION_CIRCLE_PLAYER) && !this->disableAnimations) { + SkelAnime_Update(&this->skelAnime); + } + + EnJso_UpdateDamage(this, play); + if (this->actor.isLockedOn) { + sIsPlayerLockedOn = true; + this->isPlayerLockedOn = true; + } else if (this->isPlayerLockedOn) { + this->isPlayerLockedOn = false; + sIsPlayerLockedOn = false; + } + + this->actionFunc(this, play); + DECR(this->dashAttackTimer); + DECR(this->attackWaitTimer); + DECR(this->timer); + DECR(this->drawDmgEffAlpha); + + if (this->action != EN_JSO_ACTION_FALL_DOWN_AND_TALK) { + Actor_SetFocus(&this->actor, 50.0f); + } else { + Actor_SetFocus(&this->actor, 30.0f); + } + + Actor_SetScale(&this->actor, this->scale); + Actor_MoveWithGravity(&this->actor); + this->actor.world.pos.x += this->knockbackVelocity.x; + this->actor.world.pos.z += this->knockbackVelocity.z; + + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + Math_ApproachZeroF(&this->knockbackVelocity.x, 1.0f, 2.0f); + Math_ApproachZeroF(&this->knockbackVelocity.z, 1.0f, 2.0f); + } + + if (this->action != EN_JSO_ACTION_REAPPEAR) { + Actor_UpdateBgCheckInfo(play, &this->actor, 35.0f, 40.0f, 40.0f, + UPDBGCHECKINFO_FLAG_1 | UPDBGCHECKINFO_FLAG_2 | UPDBGCHECKINFO_FLAG_4 | + UPDBGCHECKINFO_FLAG_8 | UPDBGCHECKINFO_FLAG_10); + } + + if ((this->action == EN_JSO_ACTION_SPIN_BEFORE_ATTACK) || (this->action == EN_JSO_ACTION_DASH_ATTACK)) { + this->afterimageIndex++; + if (this->afterimageIndex >= EN_JSO_AFTERIMAGE_COUNT) { + this->afterimageIndex = 0; + } + + if (this->afterimageCount < EN_JSO_AFTERIMAGE_COUNT - 1) { + this->afterimageCount++; + } + + this->afterimagePos[this->afterimageIndex] = this->actor.world.pos; + this->afterimagePos[this->afterimageIndex].y += 40.0f; + this->afterimageRot[this->afterimageIndex] = this->actor.world.rot; + + { + s32 i; + + for (i = 0; i < EN_JSO_AFTERIMAGE_COUNT; i++) { + this->afterimageJointTable[this->afterimageIndex][i] = this->jointTable[i]; + } + } + } else { + this->afterimageCount = 0; + } + + if ((this->action != EN_JSO_ACTION_HANDLE_INTRO_CUTSCENE) && (this->action != EN_JSO_ACTION_CIRCLE_PLAYER) && + (this->action != EN_JSO_ACTION_SPIN_BEFORE_ATTACK) && (this->action != EN_JSO_ACTION_DAMAGED) && + (this->action != EN_JSO_ACTION_JUMP_BACK)) { + this->actor.shape.rot.y = this->actor.world.rot.y; + } + + this->actor.shape.rot.x = this->actor.world.rot.x; + Collider_UpdateCylinder(&this->actor, &this->bodyCollider); + + if ((this->action != EN_JSO_ACTION_HANDLE_INTRO_CUTSCENE) && (this->action != EN_JSO_ACTION_REAPPEAR) && + (this->action != EN_JSO_ACTION_DAMAGED) && (this->action != EN_JSO_ACTION_SPIN_BEFORE_ATTACK) && + (this->action != EN_JSO_ACTION_DEAD) && (this->action != EN_JSO_ACTION_FALL_DOWN_AND_TALK) && + (this->action != EN_JSO_ACTION_UNK_15)) { + CollisionCheck_SetOC(play, &play->colChkCtx, &this->bodyCollider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->bodyCollider.base); + + if ((this->action == EN_JSO_ACTION_SLASH) && !this->swordHitSomething && + (this->swordState == EN_JSO_SWORD_STATE_BOTH_DRAWN)) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->rightSwordCollider.base); + CollisionCheck_SetAT(play, &play->colChkCtx, &this->leftSwordCollider.base); + } + } +} + +s32 EnJso_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) { + EnJso* this = THIS; + + if (limbIndex == GARO_LIMB_RIGHT_ARM) { + rot->x += this->rightArmRot.x; + rot->y += this->rightArmRot.y; + rot->z += this->rightArmRot.z; + } + + if (limbIndex == GARO_LIMB_ROBE_RIGHT) { + rot->x += this->robeRightRot.x; + rot->y += this->robeRightRot.y; + rot->z += this->robeRightRot.z; + } + + if ((limbIndex == GARO_LIMB_LEFT_SWORD) || (limbIndex == GARO_LIMB_RIGHT_SWORD)) { + if (this->swordState == EN_JSO_SWORD_STATE_NONE_DRAWN) { + *dList = NULL; + } else if (limbIndex == GARO_LIMB_LEFT_SWORD) { + if (this->swordState == EN_JSO_SWORD_STATE_RIGHT_DRAWN) { + *dList = NULL; + } + } else if ((limbIndex == GARO_LIMB_RIGHT_SWORD) && (this->swordState == EN_JSO_SWORD_STATE_LEFT_DRAWN)) { + *dList = NULL; + } + } + + return false; +} + +void EnJso_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + static Vec3f sSwordTipOffset = { 1600.0f, 0.0f, 0.0f }; + static Vec3f sSwordBaseOffset = { 0.0f, 0.0f, 0.0f }; + static Vec3f sSwordTipQuadOffset = { 1700.0f, 0.0f, 0.0f }; + static Vec3f sSwordBaseQuadOffset = { 0.0f, 0.0f, 0.0f }; + EnJso* this = THIS; + Vec3f swordTipPos; + Vec3f swordBasePos; + + Matrix_Push(); + + if (limbIndex == GARO_LIMB_LEFT_SWORD) { + if (this->swordState == EN_JSO_SWORD_STATE_KNOCKED_OUT_OF_HANDS) { + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_15), dList); + } + + Matrix_Translate(0.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Math_Vec3f_Copy(&this->leftSwordCollider.dim.quad[3], &this->leftSwordCollider.dim.quad[1]); + Math_Vec3f_Copy(&this->leftSwordCollider.dim.quad[2], &this->leftSwordCollider.dim.quad[0]); + Matrix_MultVec3f(&sSwordTipQuadOffset, &this->leftSwordCollider.dim.quad[1]); + Matrix_MultVec3f(&sSwordBaseQuadOffset, &this->leftSwordCollider.dim.quad[0]); + Collider_SetQuadVertices(&this->leftSwordCollider, &this->leftSwordCollider.dim.quad[0], + &this->leftSwordCollider.dim.quad[1], &this->leftSwordCollider.dim.quad[2], + &this->leftSwordCollider.dim.quad[3]); + Matrix_MultVec3f(&sSwordTipOffset, &swordTipPos); + Matrix_MultVec3f(&sSwordBaseOffset, &swordBasePos); + + if (((this->action == EN_JSO_ACTION_SLASH) || (this->action == EN_JSO_ACTION_DASH_ATTACK)) && + !this->disableBlure) { + EffectBlure_AddVertex(Effect_GetByIndex(this->leftSwordBlureIndex), &swordTipPos, &swordBasePos); + } else if (this->disableBlure == true) { + EffectBlure_AddSpace(Effect_GetByIndex(this->leftSwordBlureIndex)); + } + } + + if (limbIndex == GARO_LIMB_RIGHT_SWORD) { + if (this->swordState == EN_JSO_SWORD_STATE_KNOCKED_OUT_OF_HANDS) { + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_15), dList); + this->swordState = EN_JSO_SWORD_STATE_NONE_DRAWN; + } + + Matrix_Translate(0.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Math_Vec3f_Copy(&this->rightSwordCollider.dim.quad[3], &this->rightSwordCollider.dim.quad[1]); + Math_Vec3f_Copy(&this->rightSwordCollider.dim.quad[2], &this->rightSwordCollider.dim.quad[0]); + Matrix_MultVec3f(&sSwordTipQuadOffset, &this->rightSwordCollider.dim.quad[1]); + Matrix_MultVec3f(&sSwordBaseQuadOffset, &this->rightSwordCollider.dim.quad[0]); + Collider_SetQuadVertices(&this->rightSwordCollider, &this->rightSwordCollider.dim.quad[0], + &this->rightSwordCollider.dim.quad[1], &this->rightSwordCollider.dim.quad[2], + &this->rightSwordCollider.dim.quad[3]); + Matrix_MultVec3f(&sSwordTipOffset, &swordTipPos); + Matrix_MultVec3f(&sSwordBaseOffset, &swordBasePos); + + if (((this->action == EN_JSO_ACTION_SLASH) || (this->action == EN_JSO_ACTION_DASH_ATTACK)) && + !this->disableBlure) { + EffectBlure_AddVertex(Effect_GetByIndex(this->rightSwordBlureIndex), &swordTipPos, &swordBasePos); + } else if (this->disableBlure == true) { + EffectBlure_AddSpace(Effect_GetByIndex(this->rightSwordBlureIndex)); + this->disableBlure = false; + } + } + + if ((limbIndex == GARO_LIMB_LEFT_SWORD) || (limbIndex == GARO_LIMB_RIGHT_SWORD) || + (limbIndex == GARO_LIMB_ROBE_TOP) || (limbIndex == GARO_LIMB_ROBE_BACK) || (limbIndex == GARO_LIMB_ROBE_LEFT) || + (limbIndex == GARO_LIMB_ROBE_RIGHT) || (limbIndex == GARO_LIMB_ROBE_FRONT) || (limbIndex == GARO_LIMB_HEAD) || + (limbIndex == GARO_LIMB_RIGHT_THIGH) || (limbIndex == GARO_LIMB_RIGHT_FOOT) || + (limbIndex == GARO_LIMB_LEFT_THIGH) || (limbIndex == GARO_LIMB_LEFT_FOOT)) { + Matrix_MultZero(&this->bodyPartsPos[this->bodyPartIndex]); + this->bodyPartIndex++; + if (this->bodyPartIndex >= EN_JSO_BODYPART_MAX) { + this->bodyPartIndex = 0; + } + } + + Matrix_Pop(); +} + +Gfx* EnJso_SetAfterimageRenderMode(GraphicsContext* gfxCtx) { + Gfx* gfxHead = GRAPH_ALLOC(gfxCtx, 2 * sizeof(Gfx)); + Gfx* gfx = gfxHead; + + gDPSetRenderMode( + gfx++, AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL | G_RM_FOG_SHADE_A, + AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL | + GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA)); + gSPEndDisplayList(gfx++); + + return gfxHead; +} + +void EnJso_Draw(Actor* thisx, PlayState* play) { + static s16 sAfterimageAlpha[EN_JSO_AFTERIMAGE_COUNT] = { + 128, 0, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 0, + }; + EnJso* this = THIS; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + Gfx_SetupDL25_Opa(play->state.gfxCtx); + + gSPSegment(POLY_OPA_DISP++, 0x0C, D_801AEFA0); + SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, + EnJso_OverrideLimbDraw, EnJso_PostLimbDraw, &this->actor); + + if (this->afterimageCount > 0) { + s32 i; + s32 index = this->afterimageIndex; + + for (i = 0; i < this->afterimageCount; i++) { + if (sAfterimageAlpha[i] == 0) { + continue; + } + + Matrix_Translate(this->afterimagePos[index].x, this->afterimagePos[index].y, this->afterimagePos[index].z, + MTXMODE_NEW); + Matrix_Scale(this->scale, this->scale, this->scale, MTXMODE_APPLY); + //! @bug: These matrix rotation functions are supposed to be passed binangs, so using BINANG_TO_RAD here + //! is incorrect. Specifically, it results in rotation values far smaller than expected, since + //! BINANG_TO_RAD divides the angle by 0x8000. In-game, this bug manifests as the Garo's afterimages + //! looking the "wrong way", as they have a rotation close to (0, 0, 0) instead of matching the Garo's + //! rotation as intended. This bug can be fixed by using the correct matrix rotation functions (e.g., + //! Matrix_RotateYF instead of Matrix_RotateYS) or by removing BINANG_TO_RAD entirely. + Matrix_RotateYS(BINANG_TO_RAD(this->afterimageRot[index].y), MTXMODE_APPLY); + Matrix_RotateXS(BINANG_TO_RAD(this->afterimageRot[index].x), MTXMODE_APPLY); + Matrix_RotateZS(BINANG_TO_RAD(this->afterimageRot[index].z), MTXMODE_APPLY); + + gDPPipeSync(POLY_XLU_DISP++); + gDPSetEnvColor(POLY_XLU_DISP++, 0, 0, 0, sAfterimageAlpha[i]); + gSPSegment(POLY_XLU_DISP++, 0x0C, EnJso_SetAfterimageRenderMode(play->state.gfxCtx)); + POLY_XLU_DISP = SkelAnime_DrawFlex(play, this->skelAnime.skeleton, this->afterimageJointTable[index], + this->skelAnime.dListCount, NULL, NULL, &this->actor, POLY_XLU_DISP); + + index--; + if (index < 0) { + index = EN_JSO_AFTERIMAGE_COUNT - 1; + } + } + } + + if (this->drawDmgEffAlpha != 0) { + f32 drawDmgEffAlpha = this->drawDmgEffAlpha * 0.05f; + + if ((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) { + this->drawDmgEffScale += 0.3f; + if (this->drawDmgEffScale > 0.5f) { + this->drawDmgEffScale = 0.5f; + } + + Math_ApproachF(&this->drawDmgEffFrozenSteamScale, this->drawDmgEffScale, 0.1f, 0.04f); + } else { + this->drawDmgEffScale = 0.8f; + this->drawDmgEffFrozenSteamScale = 0.8f; + } + + Actor_DrawDamageEffects(play, &this->actor, this->bodyPartsPos, EN_JSO_BODYPART_MAX, this->drawDmgEffScale, + this->drawDmgEffFrozenSteamScale, drawDmgEffAlpha, this->drawDmgEffType); + } + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/src/overlays/actors/ovl_En_Jso/z_en_jso.h b/src/overlays/actors/ovl_En_Jso/z_en_jso.h index 7f5db07570..7f97e50080 100644 --- a/src/overlays/actors/ovl_En_Jso/z_en_jso.h +++ b/src/overlays/actors/ovl_En_Jso/z_en_jso.h @@ -2,16 +2,107 @@ #define Z_EN_JSO_H #include "global.h" +#include "assets/objects/object_jso/object_jso.h" struct EnJso; +#define EN_JSO_GET_HINT_TYPE(thisx) ((thisx)->params) + +#define EN_JSO_AFTERIMAGE_COUNT 20 + typedef void (*EnJsoActionFunc)(struct EnJso*, PlayState*); +// For the four `VARIABLE` hint types, the former hint is used if the player hasn't restored the river in +// Ikana Canyon yet, and the latter hint is used if the player has. If the player has the Elegy of Emptiness, +// then all hints will be replaced with a hint telling the player to use the song to climb Stone Tower. +typedef enum { + /* 0 */ EN_JSO_HINT_FREEZE_OCTOROKS, + /* 1 */ EN_JSO_HINT_FLATS_LOCATION, + /* 2 */ EN_JSO_HINT_VARIABLE_1, // Hints that Pamela's family is in the house or that the Song of Healing can heal Pamela's father + /* 3 */ EN_JSO_HINT_VARIABLE_2, // Hints that Pamela leaves the house to check something or that the well is connected to Ikana Castle + /* 4 */ EN_JSO_HINT_VARIABLE_3, // Hints that they can restore the river by going to Sharp's cave or that the Gidbos in the well want items + /* 5 */ EN_JSO_HINT_VARIABLE_4, // Hints that they need to Song of Storms to stop Sharp or that the items the Gibdos want can be found in the well + /* 6 */ EN_JSO_HINT_DANCING_REDEADS, + /* 7 */ EN_JSO_HINT_DESTRUCTIBLE_CEILING_IN_CASTLE, + /* 8 */ EN_JSO_HINT_SECOND_ENTRANCE_TO_CASTLE, + /* 9 */ EN_JSO_HINT_KING_WEAK_TO_MIRROR_SHIELD +} EnJsoHintType; + +typedef enum { + /* 0 */ EN_JSO_BODYPART_LEFT_SWORD, + /* 1 */ EN_JSO_BODYPART_RIGHT_SWORD, + /* 2 */ EN_JSO_BODYPART_ROBE_TOP, + /* 3 */ EN_JSO_BODYPART_ROBE_BACK, + /* 4 */ EN_JSO_BODYPART_ROBE_LEFT, + /* 5 */ EN_JSO_BODYPART_ROBE_RIGHT, + /* 6 */ EN_JSO_BODYPART_ROBE_FRONT, + /* 7 */ EN_JSO_BODYPART_HEAD, + /* 8 */ EN_JSO_BODYPART_RIGHT_THIGH, + /* 9 */ EN_JSO_BODYPART_RIGHT_FOOT, + /* 10 */ EN_JSO_BODYPART_LEFT_THIGH, + /* 11 */ EN_JSO_BODYPART_LEFT_FOOT, + /* 12 */ EN_JSO_BODYPART_MAX +} EnJsoBodyPart; + + typedef struct EnJso { /* 0x000 */ Actor actor; - /* 0x144 */ char unk_144[0x134]; + /* 0x144 */ SkelAnime skelAnime; + /* 0x188 */ Vec3s jointTable[GARO_LIMB_MAX]; + /* 0x200 */ Vec3s morphTable[GARO_LIMB_MAX]; /* 0x278 */ EnJsoActionFunc actionFunc; - /* 0x27C */ char unk_27C[0xD70]; + /* 0x27C */ s16 action; + /* 0x27E */ s16 circlingAngle; + /* 0x280 */ s16 circlingAngularVelocity; + /* 0x282 */ s16 dashAttackTimer; + /* 0x284 */ UNK_TYPE1 unk_284[0x2]; + /* 0x286 */ s16 attackWaitTimer; // while bouncing, the Garo cannot start an attack until this reaches 0 + /* 0x288 */ s16 timer; + /* 0x28A */ s16 isGuarding; // Set to false when the guard animation ends, never checked or set otherwise. Name is inferred based on how it's being set. + /* 0x28C */ s16 isPlayerLockedOn; + /* 0x28E */ s16 isAttacking; + /* 0x290 */ Vec3s robeRightRot; + /* 0x296 */ Vec3s targetRobeRightRot; + /* 0x29C */ Vec3s rightArmRot; + /* 0x2A2 */ Vec3s targetRightArmRot; + /* 0x2A8 */ s16 drawDmgEffAlpha; + /* 0x2AA */ s16 drawDmgEffType; + /* 0x2AC */ f32 drawDmgEffScale; + /* 0x2B0 */ f32 drawDmgEffFrozenSteamScale; + /* 0x2B4 */ Vec3f bodyPartsPos[EN_JSO_BODYPART_MAX]; + /* 0x344 */ s16 bodyPartIndex; + /* 0x346 */ s16 disableAnimations; // Checked but never set, so it's implicitly always false. Name is inferred based on how it's being checked. + /* 0x348 */ s16 textIndex; + /* 0x34A */ s16 hintType; + /* 0x34C */ u8 disableBlure; + /* 0x34D */ u8 swordState; + /* 0x34E */ u8 swordHitSomething; + /* 0x350 */ f32 animEndFrame; + /* 0x354 */ f32 scale; + /* 0x358 */ UNK_TYPE1 unk_358[0x4]; + /* 0x35C */ ColliderCylinder bodyCollider; + /* 0x3A8 */ ColliderQuad rightSwordCollider; + /* 0x428 */ ColliderQuad leftSwordCollider; + /* 0x4A8 */ s32 rightSwordBlureIndex; + /* 0x4AC */ s32 leftSwordBlureIndex; + /* 0x4B0 */ s16 afterimageIndex; + /* 0x4B4 */ s32 afterimageCount; + /* 0x4B8 */ s16 csId; + /* 0x4BC */ u32 introCsTimer; + /* 0x4C0 */ s16 introCsState; + /* 0x4C2 */ s16 subCamId; + /* 0x4C4 */ UNK_TYPE1 unk_4C4[0x4]; + /* 0x4C8 */ Vec3f subCamEye; + /* 0x4D4 */ Vec3f subCamAt; + /* 0x4E0 */ Vec3f subCamUp; + /* 0x4EC */ Vec3f subCamEyeNext; + /* 0x4F8 */ Vec3f subCamAtNext; + /* 0x504 */ UNK_TYPE1 unk_504[0x10]; + /* 0x514 */ s16 introCsType; + /* 0x518 */ Vec3f afterimagePos[EN_JSO_AFTERIMAGE_COUNT]; + /* 0x608 */ Vec3s afterimageRot[EN_JSO_AFTERIMAGE_COUNT]; + /* 0x680 */ Vec3s afterimageJointTable[EN_JSO_AFTERIMAGE_COUNT][GARO_LIMB_MAX]; + /* 0xFE0 */ Vec3f knockbackVelocity; // Adds a little push backwards when the Garo is bounced off the player's shield, damaged, or stunned } EnJso; // size = 0xFEC #endif // Z_EN_JSO_H diff --git a/src/overlays/actors/ovl_En_Part/z_en_part.h b/src/overlays/actors/ovl_En_Part/z_en_part.h index 5bd4de503d..4a766276d5 100644 --- a/src/overlays/actors/ovl_En_Part/z_en_part.h +++ b/src/overlays/actors/ovl_En_Part/z_en_part.h @@ -5,6 +5,8 @@ struct EnPart; +#define ENPART_PARAMS(type) (type) + typedef void (*EnPartActionFunc)(struct EnPart*, PlayState*); typedef enum { diff --git a/src/overlays/actors/ovl_En_Rail_Skb/z_en_rail_skb.c b/src/overlays/actors/ovl_En_Rail_Skb/z_en_rail_skb.c index 226aec92b6..fdda7ed14b 100644 --- a/src/overlays/actors/ovl_En_Rail_Skb/z_en_rail_skb.c +++ b/src/overlays/actors/ovl_En_Rail_Skb/z_en_rail_skb.c @@ -5,8 +5,9 @@ */ #include "z_en_rail_skb.h" -#include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #include "objects/object_skb/object_skb.h" +#include "overlays/actors/ovl_En_Part/z_en_part.h" +#include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY | ACTOR_FLAG_10) @@ -1119,10 +1120,10 @@ void EnRailSkb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* Collider_UpdateSpheres(limbIndex, &this->collider); if ((limbIndex == 11) && (this->unk_402 & 1) && !(this->unk_402 & 2)) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); this->unk_402 |= 2; } else if ((this->unk_402 & 0x40) && ((limbIndex != 11) || !(this->unk_402 & 1)) && (limbIndex != 12)) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); } if (this->drawDmgEffTimer != 0) { diff --git a/src/overlays/actors/ovl_En_Sb/z_en_sb.c b/src/overlays/actors/ovl_En_Sb/z_en_sb.c index 08b1ad2402..eaf5b88d38 100644 --- a/src/overlays/actors/ovl_En_Sb/z_en_sb.c +++ b/src/overlays/actors/ovl_En_Sb/z_en_sb.c @@ -6,6 +6,7 @@ #include "z_en_sb.h" #include "objects/object_sb/object_sb.h" +#include "overlays/actors/ovl_En_Part/z_en_part.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY) @@ -388,13 +389,13 @@ void EnSb_Update(Actor* thisx, PlayState* play) { } void EnSb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { - s8 phi_a2; + s8 partParams; EnSb* this = THIS; if (this->isDrawn) { if (limbIndex < 7) { - phi_a2 = (this->actor.depthInWater > 0) ? 4 : 1; - Actor_SpawnBodyParts(thisx, play, phi_a2, dList); + partParams = (this->actor.depthInWater > 0) ? ENPART_PARAMS(ENPART_TYPE_4) : ENPART_PARAMS(ENPART_TYPE_1); + Actor_SpawnBodyParts(thisx, play, partParams, dList); } if (limbIndex == 6) { this->isDrawn = false; diff --git a/src/overlays/actors/ovl_En_Skb/z_en_skb.c b/src/overlays/actors/ovl_En_Skb/z_en_skb.c index 98223faeaa..e2f40f35cc 100644 --- a/src/overlays/actors/ovl_En_Skb/z_en_skb.c +++ b/src/overlays/actors/ovl_En_Skb/z_en_skb.c @@ -5,9 +5,10 @@ */ #include "z_en_skb.h" -#include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" -#include "overlays/actors/ovl_En_Encount4/z_en_encount4.h" #include "objects/object_skb/object_skb.h" +#include "overlays/actors/ovl_En_Encount4/z_en_encount4.h" +#include "overlays/actors/ovl_En_Part/z_en_part.h" +#include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY) @@ -1106,12 +1107,12 @@ void EnSkb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Collider_UpdateSpheres(limbIndex, &this->collider); if ((this->unk_3D8 & 1) && !(this->unk_3D8 & 2)) { if (limbIndex == 11) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); this->unk_3D8 |= 2; } } else if ((this->unk_3D8 & 0x40) && !(this->unk_3D8 & 0x80) && ((limbIndex != 11) || !(this->unk_3D8 & 1)) && (limbIndex != 12)) { - Actor_SpawnBodyParts(&this->actor, play, 1, dList); + Actor_SpawnBodyParts(&this->actor, play, ENPART_PARAMS(ENPART_TYPE_1), dList); } if (this->drawDmgEffTimer != 0) { diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index a902899eac..323b6a014f 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -8846,45 +8846,45 @@ 0x809AD614:("EnEncount3_Draw",), 0x809AD8E0:("EnJso_Init",), 0x809ADB24:("EnJso_Destroy",), - 0x809ADBC8:("func_809ADBC8",), - 0x809ADC7C:("func_809ADC7C",), - 0x809ADCB8:("func_809ADCB8",), - 0x809AE754:("func_809AE754",), - 0x809AE87C:("func_809AE87C",), - 0x809AE9B0:("func_809AE9B0",), - 0x809AEA08:("func_809AEA08",), - 0x809AECA8:("func_809AECA8",), - 0x809AED00:("func_809AED00",), - 0x809AED54:("func_809AED54",), - 0x809AEDAC:("func_809AEDAC",), - 0x809AEE44:("func_809AEE44",), - 0x809AEEC0:("func_809AEEC0",), - 0x809AF064:("func_809AF064",), - 0x809AF110:("func_809AF110",), - 0x809AF28C:("func_809AF28C",), - 0x809AF2F8:("func_809AF2F8",), - 0x809AF368:("func_809AF368",), - 0x809AF3C0:("func_809AF3C0",), - 0x809AF3FC:("func_809AF3FC",), - 0x809AF440:("func_809AF440",), - 0x809AF470:("func_809AF470",), - 0x809AF53C:("func_809AF53C",), - 0x809AF5F8:("func_809AF5F8",), - 0x809AF714:("func_809AF714",), - 0x809AF76C:("func_809AF76C",), - 0x809AF7F4:("func_809AF7F4",), - 0x809AF8D0:("func_809AF8D0",), - 0x809AF99C:("func_809AF99C",), - 0x809AFA58:("func_809AFA58",), - 0x809AFAF4:("func_809AFAF4",), - 0x809AFC10:("func_809AFC10",), - 0x809AFE38:("func_809AFE38",), - 0x809B0034:("func_809B0034",), + 0x809ADBC8:("EnJso_ChangeAnim",), + 0x809ADC7C:("EnJso_SetupHandleIntroCutscene",), + 0x809ADCB8:("EnJso_HandleIntroCutscene",), + 0x809AE754:("EnJso_SetupReappear",), + 0x809AE87C:("EnJso_Reappear",), + 0x809AE9B0:("EnJso_SetupCirclePlayer",), + 0x809AEA08:("EnJso_CirclePlayer",), + 0x809AECA8:("EnJso_SetupGuard",), + 0x809AED00:("EnJso_Guard",), + 0x809AED54:("EnJso_SetupSpinBeforeAttack",), + 0x809AEDAC:("EnJso_SpinBeforeAttack",), + 0x809AEE44:("EnJso_SetupDashAttack",), + 0x809AEEC0:("EnJso_DashAttack",), + 0x809AF064:("EnJso_SetupSlash",), + 0x809AF110:("EnJso_Slash",), + 0x809AF28C:("EnJso_SetupWaitAfterSlash",), + 0x809AF2F8:("EnJso_WaitAfterSlash",), + 0x809AF368:("EnJso_SetupKnockedBack",), + 0x809AF3C0:("EnJso_KnockedBack",), + 0x809AF3FC:("EnJso_SetupCower",), + 0x809AF440:("EnJso_Cower",), + 0x809AF470:("EnJso_SetupStunned",), + 0x809AF53C:("EnJso_Stunned",), + 0x809AF5F8:("EnJso_SetupDamaged",), + 0x809AF714:("EnJso_Damaged",), + 0x809AF76C:("EnJso_SetupJumpBack",), + 0x809AF7F4:("EnJso_JumpBack",), + 0x809AF8D0:("EnJso_SetupDead",), + 0x809AF99C:("EnJso_Dead",), + 0x809AFA58:("EnJso_SetupFallDownAndTalk",), + 0x809AFAF4:("EnJso_FallDownAndTalk",), + 0x809AFC10:("EnJso_TellHint",), + 0x809AFE38:("EnJso_BurstIntoFlames",), + 0x809B0034:("EnJso_UpdateDamage",), 0x809B02CC:("EnJso_Update",), - 0x809B0734:("func_809B0734",), - 0x809B0820:("func_809B0820",), - 0x809B0B70:("func_809B0B70",), - 0x809B0BB0:("func_809B0BB0",), + 0x809B0734:("EnJso_OverrideLimbDraw",), + 0x809B0820:("EnJso_PostLimbDraw",), + 0x809B0B70:("EnJso_SetAfterimageRenderMode",), + 0x809B0BB0:("EnJso_Draw",), 0x809B1550:("func_809B1550",), 0x809B162C:("ObjChikuwa_Init",), 0x809B179C:("ObjChikuwa_Destroy",), diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt index ad17bf28ba..2602149cff 100644 --- a/tools/disasm/variables.txt +++ b/tools/disasm/variables.txt @@ -9487,21 +9487,21 @@ 0x809AD82C:("D_809AD82C","f32","",0x4), 0x809AD830:("D_809AD830","f32","",0x4), 0x809AD834:("D_809AD834","f32","",0x4), - 0x809B0F40:("D_809B0F40","UNK_TYPE4","",0x4), - 0x809B0F44:("D_809B0F44","UNK_TYPE4","",0x4), - 0x809B0F48:("D_809B0F48","UNK_TYPE1","",0x1), - 0x809B0F68:("En_Jso_InitVars","UNK_TYPE1","",0x1), - 0x809B0F88:("D_809B0F88","UNK_TYPE1","",0x1), - 0x809B0FB4:("D_809B0FB4","UNK_TYPE1","",0x1), - 0x809B1004:("D_809B1004","UNK_TYPE1","",0x1), - 0x809B100C:("D_809B100C","UNK_TYPE1","",0x1), - 0x809B103C:("D_809B103C","UNK_TYPE1","",0x1), - 0x809B1048:("D_809B1048","UNK_TYPE4","",0x4), - 0x809B10B4:("D_809B10B4","UNK_TYPE1","",0x1), - 0x809B10C0:("D_809B10C0","UNK_TYPE1","",0x1), - 0x809B10CC:("D_809B10CC","UNK_TYPE1","",0x1), - 0x809B10D8:("D_809B10D8","UNK_TYPE1","",0x1), - 0x809B10E4:("D_809B10E4","UNK_TYPE2","",0x2), + 0x809B0F40:("sIsAttacking","s32","",0x4), + 0x809B0F44:("sIsPlayerLockedOn","s32","",0x4), + 0x809B0F48:("sDamageTable","DamageTable","",0x20), + 0x809B0F68:("En_Jso_InitVars","ActorInit","",0x20), + 0x809B0F88:("sCylinderInit","ColliderCylinderInit","",0x2C), + 0x809B0FB4:("sQuadInit","ColliderQuadInit","",0x50), + 0x809B1004:("sTextIds","u16","[4]",0x8), + 0x809B100C:("sAnimations","AnimationHeader*","[12]",0x30), + 0x809B103C:("sAnimationModes","u8","[12]",0xC), + 0x809B1048:("fireVelocityAndAccel","Vec3f","[9]",0x6C), + 0x809B10B4:("sSwordTipOffset","Vec3f","",0xC), + 0x809B10C0:("sSwordBaseOffset","Vec3f","",0xC), + 0x809B10CC:("sSwordTipQuadOffset","Vec3f","",0xC), + 0x809B10D8:("sSwordBaseQuadOffset","Vec3f","",0xC), + 0x809B10E4:("sAfterimageAlpha","s16","[20]",0x28), 0x809B1110:("D_809B1110","f32","",0x4), 0x809B1114:("jtbl_809B1114","UNK_PTR","",0x4), 0x809B112C:("D_809B112C","f32","",0x4), diff --git a/undefined_syms.txt b/undefined_syms.txt index 010f94c053..3a630cd3dd 100644 --- a/undefined_syms.txt +++ b/undefined_syms.txt @@ -462,11 +462,6 @@ D_06015C28 = 0x06015C28; D_060156D8 = 0x060156D8; D_06016720 = 0x06016720; -// ovl_En_Jso - -D_060081F4 = 0x060081F4; -D_0600AA00 = 0x0600AA00; - // ovl_En_Jso2 D_06002ED8 = 0x06002ED8;