From 2859cbbf5e0da927c8a61897ed23869442969bcb Mon Sep 17 00:00:00 2001 From: Tom Overton Date: Sat, 28 Oct 2023 14:29:43 -0700 Subject: [PATCH] Boss01 (Odolwa) OK and documented (#1452) * Boss01 (Odolwa) OK and documented * Unify existing InitRand implementations * Remove newlines after `// fallthrough` * Respond to the rest of engineer's review * Respond to most of anon's review * Try to comment the shadow thing correctly * Fix boss_01 after merge * SEGMENTED_TO_VIRTUAL -> SEGMENTED_TO_K0 * Review * Use DMG_SWORD_BEAM and update the comment in z64collision_check.h * Respond to engineer's review --- assets/xml/objects/object_boss01.xml | 4 +- include/z64actor.h | 6 +- include/z64collision_check.h | 3 +- spec | 4 +- src/overlays/actors/ovl_Boss_01/z_boss_01.c | 3846 +++++++++++++++-- src/overlays/actors/ovl_Boss_01/z_boss_01.h | 141 +- src/overlays/actors/ovl_Boss_02/z_boss_02.c | 20 +- src/overlays/actors/ovl_Boss_03/z_boss_03.c | 34 +- .../actors/ovl_En_Fishing/z_en_fishing.c | 34 +- tools/disasm/functions.txt | 146 +- tools/disasm/variables.txt | 135 +- undefined_syms.txt | 36 - 12 files changed, 3827 insertions(+), 582 deletions(-) diff --git a/assets/xml/objects/object_boss01.xml b/assets/xml/objects/object_boss01.xml index 08730f0345..ff37d2bc9e 100644 --- a/assets/xml/objects/object_boss01.xml +++ b/assets/xml/objects/object_boss01.xml @@ -142,10 +142,10 @@ - + - + diff --git a/include/z64actor.h b/include/z64actor.h index 9ecbb75db2..e89267690f 100644 --- a/include/z64actor.h +++ b/include/z64actor.h @@ -649,12 +649,12 @@ typedef enum { /* 0x5B */ TATL_HINT_ID_SKULLFISH, /* 0x5C */ TATL_HINT_ID_DESBREKO, /* 0x5D */ TATL_HINT_ID_GREEN_CHUCHU, - /* 0x5E */ TATL_HINT_ID_ODOLWA_1, + /* 0x5E */ TATL_HINT_ID_ODOLWA_PHASE_ONE, // 799 or fewer frames have passed, says Odolwa is dangerous to get close to /* 0x5F */ TATL_HINT_ID_GEKKO_GIANT_SLIME, /* 0x60 */ TATL_HINT_ID_BAD_BAT, /* 0x61 */ TATL_HINT_ID_REAL_BOMBCHU, - /* 0x62 */ TATL_HINT_ID_ODOLWA_2, - /* 0x63 */ TATL_HINT_ID_ODOLWA_3, + /* 0x62 */ TATL_HINT_ID_ODOLWA_CLOSE_TO_PHASE_TWO, // 800 frames have passed, warns that Odolwa will attack after dancing + /* 0x63 */ TATL_HINT_ID_ODOLWA_PHASE_TWO, // 1000 or more frames have passed, explains that the bugs are drawn to fire /* 0x64 */ TATL_HINT_ID_MUSHROOM, /* 0xFF */ TATL_HINT_ID_NONE = 0xFF } TatlHintId; diff --git a/include/z64collision_check.h b/include/z64collision_check.h index 403ffa5461..ac1b97752f 100644 --- a/include/z64collision_check.h +++ b/include/z64collision_check.h @@ -394,7 +394,8 @@ typedef enum { #define DMG_ENTRY(damage, effect) ((damage) | ((effect) << 4)) -// These flags are not to be used in code until we figure out how we want to format them. They are only here for reference +// Don't use combinations of these flags in code until we figure out how we want to format them. +// It's okay to use these flags if the code is only checking a single flag, though. #define DMG_DEKU_NUT (1 << 0x00) #define DMG_DEKU_STICK (1 << 0x01) #define DMG_HORSE_TRAMPLE (1 << 0x02) diff --git a/spec b/spec index a8be960fba..60dfc733df 100644 --- a/spec +++ b/spec @@ -2297,9 +2297,7 @@ beginseg name "ovl_Boss_01" compress include "build/src/overlays/actors/ovl_Boss_01/z_boss_01.o" - include "build/data/ovl_Boss_01/ovl_Boss_01.data.o" - include "build/data/ovl_Boss_01/ovl_Boss_01.bss.o" - include "build/data/ovl_Boss_01/ovl_Boss_01.reloc.o" + include "build/src/overlays/actors/ovl_Boss_01/ovl_Boss_01_reloc.o" endseg beginseg diff --git a/src/overlays/actors/ovl_Boss_01/z_boss_01.c b/src/overlays/actors/ovl_Boss_01/z_boss_01.c index 826e126dc9..6adf386d7a 100644 --- a/src/overlays/actors/ovl_Boss_01/z_boss_01.c +++ b/src/overlays/actors/ovl_Boss_01/z_boss_01.c @@ -1,236 +1,638 @@ /* * File: z_boss_01.c * Overlay: ovl_Boss_01 - * Description: Odolwa + * Description: Odolwa, his bugs, and his afterimages. + * + * In addition to handling Odolwa, this actor is also responsible for handling the bugs that Odolwa can spawn as well as + * the afterimages that appear when Odolwa jumps or does a spin attack. The bugs are effectively their own actor with + * their own Update and Draw functions and a whole dedicated section of the file specifically for them. Afterimages are + * much simpler, and their code is more interleaved with Odolwa's code. This actor also handles the falling block and + * ring of fire effects that Odolwa can summon. + * + * Odolwa's behavior can primarily be divided into two states: waiting and attacking. He has a variety of wait actions + * and attack types that he can select between, and this can make it difficult to tell when he is waiting and when he is + * attacking. Two of his wait actions look like attacks, and when selecting an attack, he can randomly choose to dance + * for a bit before attacking. Additionally, Odolwa can attack the player via more indirect means while waiting by + * summoning bugs, by dropping falling blocks on them, or by summoning a ring of fire to surround them. + * + * Outside of waiting and attacking, most of Odolwa's behavior involves either dodging attacks (e.g., by jumping, + * running around, or guarding) or reacting to attacks. He also handles all his cutscenes (his intro cutscene, the + * cutscene where he summons bugs, and his death cutscene) manually via actionFuncs. + * + * Odolwa's fight can be divided roughly into two phases, but unlike most bosses, the second phase does not happen when + * his health reaches a certain point. Rather, the second phase starts when 1000 or more frames have passed since the + * fight began. At this point, he will play the bug summoning cutscene, and more wait actions will be available for him + * to randomly select; these new wait actions are what allow him to summon a ring of fire, drop falling blocks, etc. */ #include "z_boss_01.h" #include "z64rumble.h" #include "z64shrink_window.h" +#include "assets/objects/gameplay_keep/gameplay_keep.h" +#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h" +#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h" +#include "overlays/actors/ovl_En_Tanron1/z_en_tanron1.h" +#include "overlays/actors/ovl_Item_B_Heart/z_item_b_heart.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UNFRIENDLY | ACTOR_FLAG_10 | ACTOR_FLAG_20) #define THIS ((Boss01*)thisx) +#define ODOLWA_EFFECT_COUNT 100 + +// This actor has an array of timers in its instance, but it only ever uses the first entry. +#define TIMER_CURRENT_ACTION 0 +#define TIMER_BUG_CURRENT_ACTION 0 +#define TIMER_AFTERIMAGE_DESPAWN 0 + void Boss01_Init(Actor* thisx, PlayState* play); void Boss01_Destroy(Actor* thisx, PlayState* play); -void Boss01_Update(Actor* thisx, PlayState* play); +void Boss01_Update(Actor* thisx, PlayState* play2); void Boss01_Draw(Actor* thisx, PlayState* play); -void func_809D12B4(Boss01* this, PlayState* play); -void func_809D1B2C(Boss01* this, PlayState* play); -void func_809D1E74(Boss01* this, PlayState* play); -void func_809D20D0(Boss01* this, PlayState* play); -void func_809D25E8(Boss01* this, PlayState* play); -void func_809D26B8(Boss01* this, PlayState* play); -void func_809D27D4(Boss01* this, PlayState* play); -void func_809D2AA0(Boss01* this, PlayState* play); -void func_809D2BCC(Boss01* this, PlayState* play); -void func_809D2CDC(Boss01* this, PlayState* play); -void func_809D2E4C(Boss01* this, PlayState* play); -void func_809D30D0(Boss01* this, PlayState* play); -void func_809D345C(Boss01* this, PlayState* play); -void func_809D3530(Boss01* this, PlayState* play); -void func_809D3ADC(Boss01* this, PlayState* play); -void func_809D3CD0(Boss01* this, PlayState* play); -void func_809D4464(Boss01* this, PlayState* play); -void func_809D6314(Boss01* this, PlayState* play); -void func_809D6488(Boss01* this, PlayState* play); -void func_809D6588(Boss01* this, PlayState* play); -void func_809D65E0(Boss01* this, PlayState* play); +void Boss01_SetupIntroCutscene(Boss01* this, PlayState* play); +void Boss01_IntroCutscene(Boss01* this, PlayState* play); +void Boss01_SummonBugsCutscene(Boss01* this, PlayState* play); +void Boss01_Afterimage_SetupWaitToDespawn(Boss01* this, PlayState* play); +void Boss01_Afterimage_WaitToDespawn(Boss01* this, PlayState* play); +void Boss01_SetupWait(Boss01* this, PlayState* play, u8 waitType); +void Boss01_Wait(Boss01* this, PlayState* play); +void Boss01_Dazed(Boss01* this, PlayState* play); +void Boss01_SetupSpinAttack(Boss01* this, PlayState* play); +void Boss01_SpinAttack(Boss01* this, PlayState* play); +void Boss01_SetupDanceBeforeAttack(Boss01* this, PlayState* play); +void Boss01_DanceBeforeAttack(Boss01* this, PlayState* play); +void Boss01_SetupRun(Boss01* this, PlayState* play); +void Boss01_Run(Boss01* this, PlayState* play); +void Boss01_SetupJump(Boss01* this, PlayState* play, u8 shouldPerformFallingSlash); +void Boss01_JumpSquat(Boss01* this, PlayState* play); +void Boss01_Jump(Boss01* this, PlayState* play); +void Boss01_JumpLand(Boss01* this, PlayState* play); +void Boss01_SetupVerticalSlash(Boss01* this, PlayState* play); +void Boss01_VerticalSlash(Boss01* this, PlayState* play); +void Boss01_SetupHorizontalSlash(Boss01* this, PlayState* play); +void Boss01_HorizontalSlash(Boss01* this, PlayState* play); +void Boss01_Guard(Boss01* this, PlayState* play); +void Boss01_SetupKick(Boss01* this, PlayState* play); +void Boss01_Kick(Boss01* this, PlayState* play); +void Boss01_SetupShieldBash(Boss01* this, PlayState* play); +void Boss01_ShieldBash(Boss01* this, PlayState* play); +void Boss01_Damaged(Boss01* this, PlayState* play); +void Boss01_SetupSummonMoths(Boss01* this, PlayState* play); +void Boss01_SummonMoths(Boss01* this, PlayState* play); +void Boss01_SetupDeathCutscene(Boss01* this, PlayState* play); +void Boss01_DeathCutscene(Boss01* this, PlayState* play); +void Boss01_SetupStunned(Boss01* this, PlayState* play); +void Boss01_Stunned(Boss01* this, PlayState* play); +void Boss01_Afterimage_Draw(Actor* thisx, PlayState* play); +void Boss01_GenShadowTex(u8* tex, Boss01* this, PlayState* play); +void Boss01_DrawShadowTex(u8* tex, Boss01* this, PlayState* play); +void Boss01_Bug_SetupCrawl(Boss01* this, PlayState* play); +void Boss01_Bug_Crawl(Boss01* this, PlayState* play); +void Boss01_Bug_Damaged(Boss01* this, PlayState* play); +void Boss01_Bug_SetupDead(Boss01* this, PlayState* play); +void Boss01_Bug_Stunned(Boss01* this, PlayState* play); +void Boss01_Bug_Dead(Boss01* this, PlayState* play); +void Boss01_Bug_Update(Actor* thisx, PlayState* play); +void Boss01_Bug_Draw(Actor* thisx, PlayState* play); +void Boss01_UpdateEffects(Boss01* this, PlayState* play); +void Boss01_DrawEffects(PlayState* play); -#if 0 -// static DamageTable sDamageTable = { -static DamageTable D_809D7990 = { - /* Deku Nut */ DMG_ENTRY(0, 0x1), - /* Deku Stick */ DMG_ENTRY(1, 0xF), - /* Horse trample */ DMG_ENTRY(0, 0x0), - /* Explosives */ DMG_ENTRY(1, 0xE), - /* Zora boomerang */ DMG_ENTRY(1, 0xF), - /* Normal arrow */ DMG_ENTRY(1, 0xF), - /* UNK_DMG_0x06 */ DMG_ENTRY(0, 0x0), - /* Hookshot */ DMG_ENTRY(0, 0x0), - /* Goron punch */ DMG_ENTRY(1, 0xE), - /* Sword */ DMG_ENTRY(1, 0xE), - /* 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(1, 0xF), - /* Deku bubble */ DMG_ENTRY(0, 0x1), - /* Deku launch */ DMG_ENTRY(1, 0xE), - /* UNK_DMG_0x12 */ DMG_ENTRY(0, 0x1), - /* Zora barrier */ DMG_ENTRY(0, 0xB), - /* Normal shield */ DMG_ENTRY(0, 0x0), - /* Light ray */ DMG_ENTRY(0, 0x0), - /* Thrown object */ DMG_ENTRY(1, 0xF), - /* Zora punch */ DMG_ENTRY(1, 0xE), - /* Spin attack */ DMG_ENTRY(1, 0xD), - /* Sword beam */ DMG_ENTRY(2, 0xC), - /* 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, 0xE), +typedef enum { + /* 0 */ ODOLWA_EFFECT_NONE, + /* 1 */ ODOLWA_EFFECT_FALLING_BLOCK, // The blocks that fall from the ceiling and their fragments + /* 3 */ ODOLWA_EFFECT_RING_OF_FIRE = 3 // The ring of fire that surrounds the player +} OdolwaEffectType; + +typedef enum { + /* 0 */ ODOLWA_WAIT_READY, + /* 1 */ ODOLWA_WAIT_SPIN_SWORD, + /* 2 */ ODOLWA_WAIT_VERTICAL_HOP, + /* 3 */ ODOLWA_WAIT_SHAKE_DANCE, + /* 4 */ ODOLWA_WAIT_UP_AND_DOWN_DANCE, + /* 5 */ ODOLWA_WAIT_ARM_SWING_DANCE, + /* 6 */ ODOLWA_WAIT_THRUST_ATTACK, + /* 7 */ ODOLWA_WAIT_DOUBLE_SLASH, + /* 8 */ ODOLWA_WAIT_SIDE_TO_SIDE_HOP, + /* 9 */ ODOLWA_WAIT_SIDE_TO_SIDE_DANCE, + /* 10 */ ODOLWA_WAIT_SPIN_DANCE, + /* 11 */ ODOLWA_WAIT_JUMP_DANCE, + /* 12 */ ODOLWA_WAIT_MAX, + + // This doesn't correspond to an actual wait action that Odolwa can perform, but it can be passed as a parameter to + // Boss01_SetupWait to randomly select between one of Odolwa's available wait types (as well as having a random + // chance to summon moths instead), assuming that the fight is in its second phase. + /* 100 */ ODOLWA_WAIT_RANDOM = 100 +} OdolwaWaitType; + +typedef enum { + // Waits for the player to walk forward, then starts the cutscene and transitions to the next state immediately. + // This state is also used to indiciate the cutscene ended. + /* 0 */ ODOLWA_INTRO_CS_STATE_WAITING_FOR_PLAYER_OR_DONE, + + // Points the camera directly at the player, plays some of Odolwa's SFXs, and waits for 120 frames. + /* 1 */ ODOLWA_INTRO_CS_STATE_LOOK_AT_PLAYER, + + // Pans up to watch Odolwa fall from the ceiling. Transitions to the next state once he's close to the ground. + /* 2 */ ODOLWA_INTRO_CS_STATE_ODOLWA_FALLING, + + // Plays Odolwa's intro animation and shows his title card. Ends the cutscene after 140 frames. + /* 3 */ ODOLWA_INTRO_CS_STATE_ODOLWA_LANDED +} OdolwaIntroCsState; + +typedef enum { + // Starts the cutscene and transitions to the next state immediately. + /* 0 */ ODOLWA_BUG_SUMMONING_CS_STATE_STARTED, + + // This state lasts for the entire duration of the cutscene, and cutsceneState is left in this state when it ends. + /* 1 */ ODOLWA_BUG_SUMMONING_CS_STATE_PLAYING_OR_DONE +} OdolwaBugSummoningCsState; + +typedef enum { + // Starts the cutscene and transitions to the next state immediately. + /* 0 */ ODOLWA_DEATH_CS_STATE_STARTED, + + // Plays Odolwa's death animation and makes him fall face-first towards the ground. Ends after 71 frames. + /* 1 */ ODOLWA_DEATH_CS_STATE_PLAY_ANIM_AND_FALL_FORWARD, + + // Surrounds Odolwa with fire and slowly shrinks him. After 180 frames, spawns the blue warp and ends the cutscene. + /* 2 */ ODOLWA_DEATH_CS_STATE_BURST_INTO_FLAMES_AND_SHRINK, + + // Signals that the cutscene has ended. Also teleports Odolwa far above the ceiling. + /* 3 */ ODOLWA_DEATH_CS_STATE_DONE +} OdolwaDeathCsState; + +typedef enum { + /* 0 */ ODOLWA_DRAW_DMGEFF_STATE_NONE, + /* 1 */ ODOLWA_DRAW_DMGEFF_STATE_FIRE_INIT, + /* 2 */ ODOLWA_DRAW_DMGEFF_STATE_FIRE_ACTIVE, + /* 10 */ ODOLWA_DRAW_DMGEFF_STATE_FROZEN_INIT = 10, + /* 11 */ ODOLWA_DRAW_DMGEFF_STATE_FROZEN_ACTIVE, + /* 20 */ ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_INIT = 20, + /* 21 */ ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_ACTIVE, + /* 30 */ ODOLWA_DRAW_DMGEFF_STATE_BLUE_LIGHT_ORB_INIT = 30, + /* 40 */ ODOLWA_DRAW_DMGEFF_STATE_ELECTRIC_SPARKS_INIT = 40, + /* 41 */ ODOLWA_DRAW_DMGEFF_STATE_ELECTRIC_SPARKS_ACTIVE +} OdolwaDrawDmgEffState; + +typedef enum { + /* 0 */ ODOLWA_SHADOW_SIZE_MEDIUM, + /* 1 */ ODOLWA_SHADOW_SIZE_LARGE, + /* 2 */ ODOLWA_SHADOW_SIZE_EXTRA_LARGE, + /* 3 */ ODOLWA_SHADOW_SIZE_SMALL +} OdolwaShadowSize; + +typedef enum { + // There are no AT colliders enabled; the player can pass through the sword without reacting or taking any damage. + /* 0 */ ODOLWA_SWORD_STATE_INACTIVE, + + // The sword's two AT colliders are enabled; the player will be knocked back and take damage if they touch the + // sword. There is a third collider originating from Odolwa's pelvis that is also active, but it is offset very far + // out-of-bounds in this state, so the player can never touch it. + /* 1 */ ODOLWA_SWORD_STATE_ACTIVE, + + // Similar to the previous state, but the pelvis collider is now placed on the floor in front of Odolwa. + /* 2 */ ODOLWA_SWORD_STATE_HORIZONTAL_SLASH +} OdolwaSwordState; + +/** + * When falling blocks spawn, the sound effect of them falling always plays from this position, regardless of where they + * actually spawn. + */ +static Vec3f sFallingBlockSfxPos = { 0.0f, 1000.0f, 0.0f }; + +/** + * Odolwa's sword trail is a circular arc, and this variable is used to determine the angular range of this arc (and + * thus the total size of the trail). The trail consists of 10 segments in an arc, and the angle of each of those + * segments is M_PI / sSwordTrailAngularRangeDivisor; in other words, as this variable decreases, Odolwa's sword trail + * covers a larger angular range, and as it increaes, the sword trail covers a smaller angular range. + */ +static f32 sSwordTrailAngularRangeDivisor = 10.0f; + +typedef enum { + // Named based on the fact that everything with this damage effect deals zero damage. If this effect is given to an + // attack that deals non-zero damage, it will behave exactly like ODOLWA_DMGEFF_DAMAGE. + /* 0x0 */ ODOLWA_DMGEFF_IMMUNE, + + // Deals no damage, but turns Odolwa blue, stops all animations, and makes him wait in place for 40 frames. + /* 0x1 */ ODOLWA_DMGEFF_STUN, + + // Deals damage and surrounds Odolwa with fire. + /* 0x2 */ ODOLWA_DMGEFF_FIRE, + + // Behaves exactly like ODOLWA_DMGEFF_STUN, but also surrounds Odolwa with ice. + /* 0x3 */ ODOLWA_DMGEFF_FREEZE, + + // Deals damage and surrounds Odolwa with yellow light orbs. + /* 0x4 */ ODOLWA_DMGEFF_LIGHT_ORB, + + // Behaves exactly like ODOLWA_DMGEFF_STUN, but also surrounds Odolwa in electric sparks. + /* 0xB */ ODOLWA_DMGEFF_ELECTRIC_STUN = 0xB, + + // Deals damage and surrounds Odolwa with blue light orbs. + /* 0xC */ ODOLWA_DMGEFF_BLUE_LIGHT_ORB, + + // Deals damage and has no special effect. + /* 0xD */ ODOLWA_DMGEFF_DAMAGE, + + // Deals damage and checks the timer that tracks how long Odolwa should be in his damaged state. If the timer is 7 + // or more, it will reset the timer to 20 frames keep Odolwa in the damaged state for longer. If the timer is 6 or + // less, it will disable Odolwa's collision for 20 frames to ensure he can jump away without taking further damage. + /* 0xE */ ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK, + + // Deals no damage, but makes Odolwa play his dazed animation for 70 frames and be vulnerable to attacks. + /* 0xF */ ODOLWA_DMGEFF_DAZE +} OdolwaDamageEffect; + +static DamageTable sOdolwaDamageTable = { + /* Deku Nut */ DMG_ENTRY(0, ODOLWA_DMGEFF_STUN), + /* Deku Stick */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Horse trample */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Explosives */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), + /* Zora boomerang */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Normal arrow */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* UNK_DMG_0x06 */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Hookshot */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Goron punch */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), + /* Sword */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), + /* Goron pound */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Fire arrow */ DMG_ENTRY(2, ODOLWA_DMGEFF_FIRE), + /* Ice arrow */ DMG_ENTRY(2, ODOLWA_DMGEFF_FREEZE), + /* Light arrow */ DMG_ENTRY(2, ODOLWA_DMGEFF_LIGHT_ORB), + /* Goron spikes */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Deku spin */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Deku bubble */ DMG_ENTRY(0, ODOLWA_DMGEFF_STUN), + /* Deku launch */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), + /* UNK_DMG_0x12 */ DMG_ENTRY(0, ODOLWA_DMGEFF_STUN), + /* Zora barrier */ DMG_ENTRY(0, ODOLWA_DMGEFF_ELECTRIC_STUN), + /* Normal shield */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Light ray */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Thrown object */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAZE), + /* Zora punch */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), + /* Spin attack */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE), + /* Sword beam */ DMG_ENTRY(2, ODOLWA_DMGEFF_BLUE_LIGHT_ORB), + /* Normal Roll */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* UNK_DMG_0x1B */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* UNK_DMG_0x1C */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Unblockable */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* UNK_DMG_0x1E */ DMG_ENTRY(0, ODOLWA_DMGEFF_IMMUNE), + /* Powder Keg */ DMG_ENTRY(1, ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK), }; -// static DamageTable sDamageTable = { -static DamageTable D_809D79B0 = { - /* Deku Nut */ DMG_ENTRY(0, 0x1), - /* Deku Stick */ DMG_ENTRY(1, 0xF), - /* Horse trample */ DMG_ENTRY(0, 0x0), - /* Explosives */ DMG_ENTRY(2, 0xF), - /* Zora boomerang */ DMG_ENTRY(0, 0x1), - /* Normal arrow */ DMG_ENTRY(2, 0xF), - /* UNK_DMG_0x06 */ DMG_ENTRY(0, 0x0), - /* Hookshot */ DMG_ENTRY(0, 0x0), - /* 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(0, 0x1), - /* Deku launch */ DMG_ENTRY(1, 0xF), - /* UNK_DMG_0x12 */ DMG_ENTRY(0, 0x1), - /* Zora barrier */ DMG_ENTRY(0, 0x1), - /* Normal shield */ DMG_ENTRY(0, 0x0), - /* Light ray */ DMG_ENTRY(0, 0x0), - /* Thrown object */ DMG_ENTRY(1, 0xF), - /* Zora punch */ DMG_ENTRY(1, 0xE), - /* Spin attack */ DMG_ENTRY(2, 0xD), - /* Sword beam */ DMG_ENTRY(2, 0xD), - /* 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 { + // Named based on the fact that everything with this damage effect deals zero damage. If this effect is given to an + // attack that deals non-zero damage, it will behave exactly like BUG_DMGEFF_DAMAGE. + /* 0x0 */ BUG_DMGEFF_IMMUNE, + + // Deals no damage, but turns the bug blue, stops all animations, and makes it wait in place for 40 frames. + /* 0x1 */ BUG_DMGEFF_STUN, + + // Named after the only attack that uses it. Behaves exactly like BUG_DMGEFF_DAMAGE. + /* 0x2 */ BUG_DMGEFF_FIRE_ARROW, + + // Named after the only attack that uses it. Behaves exactly like BUG_DMGEFF_DAMAGE. + /* 0x3 */ BUG_DMGEFF_ICE_ARROW, + + // Named after the only attack that uses it. Behaves exactly like BUG_DMGEFF_DAMAGE. + /* 0x4 */ BUG_DMGEFF_LIGHT_ARROW, + + // Named after the only two attacks that use it. Behaves exactly like BUG_DMGEFF_DAMAGE. + /* 0xD */ BUG_DMGEFF_SPIN_ATTACK_AND_SWORD_BEAM = 0xD, + + // Named after the only attack that uses it. Behaves exactly like BUG_DMGEFF_DAMAGE. + /* 0xE */ BUG_DMGEFF_ZORA_PUNCH, + + // Deals damage and has no special effect. + /* 0xF */ BUG_DMGEFF_DAMAGE +} BugDamageEffect; + +static DamageTable sBugDamageTable = { + /* Deku Nut */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Deku Stick */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Horse trample */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Explosives */ DMG_ENTRY(2, BUG_DMGEFF_DAMAGE), + /* Zora boomerang */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Normal arrow */ DMG_ENTRY(2, BUG_DMGEFF_DAMAGE), + /* UNK_DMG_0x06 */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Hookshot */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Goron punch */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Sword */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Goron pound */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Fire arrow */ DMG_ENTRY(2, BUG_DMGEFF_FIRE_ARROW), + /* Ice arrow */ DMG_ENTRY(2, BUG_DMGEFF_ICE_ARROW), + /* Light arrow */ DMG_ENTRY(2, BUG_DMGEFF_LIGHT_ARROW), + /* Goron spikes */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Deku spin */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Deku bubble */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Deku launch */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* UNK_DMG_0x12 */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Zora barrier */ DMG_ENTRY(0, BUG_DMGEFF_STUN), + /* Normal shield */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Light ray */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Thrown object */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), + /* Zora punch */ DMG_ENTRY(1, BUG_DMGEFF_ZORA_PUNCH), + /* Spin attack */ DMG_ENTRY(2, BUG_DMGEFF_SPIN_ATTACK_AND_SWORD_BEAM), + /* Sword beam */ DMG_ENTRY(2, BUG_DMGEFF_SPIN_ATTACK_AND_SWORD_BEAM), + /* Normal Roll */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* UNK_DMG_0x1B */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* UNK_DMG_0x1C */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Unblockable */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* UNK_DMG_0x1E */ DMG_ENTRY(0, BUG_DMGEFF_IMMUNE), + /* Powder Keg */ DMG_ENTRY(1, BUG_DMGEFF_DAMAGE), }; -// static ColliderJntSphElementInit sJntSphElementsInit[3] = { -static ColliderJntSphElementInit D_809D79D0[3] = { +// The limbs referenced here are not used. The spheres are positioned manually by Boss01_PostLimbDraw. +static ColliderJntSphElementInit sSwordColliderJntSphElementsInit[3] = { { - { ELEMTYPE_UNK2, { 0xF7CFFFFF, 0x04, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 35 }, 100 }, + { + ELEMTYPE_UNK2, + { 0xF7CFFFFF, 0x04, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 35 }, 100 }, }, { - { ELEMTYPE_UNK2, { 0xF7CFFFFF, 0x04, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 35 }, 100 }, + { + ELEMTYPE_UNK2, + { 0xF7CFFFFF, 0x04, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 35 }, 100 }, }, { - { ELEMTYPE_UNK2, { 0xF7CFFFFF, 0x04, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 70 }, 100 }, + { + ELEMTYPE_UNK2, + { 0xF7CFFFFF, 0x04, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 70 }, 100 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_809D7A3C = { - { COLTYPE_METAL, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_HARD | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_809D79D0, // sJntSphElementsInit, +static ColliderJntSphInit sSwordColliderJntSphInit = { + { + COLTYPE_METAL, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_HARD | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sSwordColliderJntSphElementsInit), + sSwordColliderJntSphElementsInit, }; -// static ColliderJntSphElementInit sJntSphElementsInit[1] = { -static ColliderJntSphElementInit D_809D7A4C[1] = { +// The limbs referenced here are not used. The spheres are positioned manually by Boss01_PostLimbDraw. +static ColliderJntSphElementInit sShieldColliderJntSphElementsInit[1] = { { - { ELEMTYPE_UNK2, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 36 }, 100 }, + { + ELEMTYPE_UNK2, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 36 }, 100 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_809D7A70 = { - { COLTYPE_METAL, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_HARD | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_809D7A4C, // sJntSphElementsInit, +static ColliderJntSphInit sShieldColliderJntSphInit = { + { + COLTYPE_METAL, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_HARD | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sShieldColliderJntSphElementsInit), + sShieldColliderJntSphElementsInit, }; -// static ColliderJntSphElementInit sJntSphElementsInit[11] = { -static ColliderJntSphElementInit D_809D7A80[11] = { +// The limbs referenced here are not used. The spheres are positioned manually by Boss01_PostLimbDraw. +static ColliderJntSphElementInit sBodyColliderJntSphElementsInit[11] = { { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 20 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 20 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 30 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 30 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 25 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 25 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, - { 1, { { 0, 0, 0 }, 15 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_ROOT, { { 0, 0, 0 }, 15 }, 100 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_809D7C0C = { - { COLTYPE_HIT3, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_809D7A80, // sJntSphElementsInit, +static ColliderJntSphInit sBodyColliderJntSphInit = { + { + COLTYPE_HIT3, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sBodyColliderJntSphElementsInit), + sBodyColliderJntSphElementsInit, }; -// static ColliderJntSphElementInit sJntSphElementsInit[2] = { -static ColliderJntSphElementInit D_809D7C1C[2] = { +// The limbs referenced here are not used. The spheres are positioned manually by Boss01_PostLimbDraw. +static ColliderJntSphElementInit sKickAndShieldBashColliderJntSphElementsInit[2] = { { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x04, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 36 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x04, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_HARD, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 36 }, 100 }, }, { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x04, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON, OCELEM_ON, }, - { 0, { { 0, 0, 0 }, 36 }, 100 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x04, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_HARD, + BUMP_ON, + OCELEM_ON, + }, + { ODOLWA_LIMB_NONE, { { 0, 0, 0 }, 36 }, 100 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_809D7C64 = { - { COLTYPE_HIT3, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_809D7C1C, // sJntSphElementsInit, +static ColliderJntSphInit sKickAndShieldBashColliderJntSphInit = { + { + COLTYPE_HIT3, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sKickAndShieldBashColliderJntSphElementsInit), + sKickAndShieldBashColliderJntSphElementsInit, }; -// static ColliderCylinderInit sCylinderInit = { -static ColliderCylinderInit D_809D7C74 = { - { COLTYPE_HIT3, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, }, - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7FFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, +static ColliderCylinderInit sBugACColliderCylinderInit = { + { + COLTYPE_HIT3, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_1, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7FFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, { 15, 15, 10, { 0, 0, 0 } }, }; -// static ColliderCylinderInit sCylinderInit = { -static ColliderCylinderInit D_809D7CA0 = { - { COLTYPE_HIT3, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, }, - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x04 }, { 0xF7FFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, +static ColliderCylinderInit sBugATColliderCylinderInit = { + { + COLTYPE_HIT3, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_1, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x04 }, + { 0xF7FFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, { 8, 15, 10, { 0, 0, 0 } }, }; @@ -246,191 +648,2943 @@ ActorInit Boss_01_InitVars = { /**/ Boss01_Draw, }; -#endif - -extern DamageTable D_809D7990; -extern DamageTable D_809D79B0; -extern ColliderJntSphElementInit D_809D79D0[3]; -extern ColliderJntSphInit D_809D7A3C; -extern ColliderJntSphElementInit D_809D7A4C[1]; -extern ColliderJntSphInit D_809D7A70; -extern ColliderJntSphElementInit D_809D7A80[11]; -extern ColliderJntSphInit D_809D7C0C; -extern ColliderJntSphElementInit D_809D7C1C[2]; -extern ColliderJntSphInit D_809D7C64; -extern ColliderCylinderInit D_809D7C74; -extern ColliderCylinderInit D_809D7CA0; - -extern UNK_TYPE D_06000C44; -extern UNK_TYPE D_06001884; -extern UNK_TYPE D_0600C338; -extern UNK_TYPE D_0600C5E0; -extern UNK_TYPE D_0600C7A8; -extern UNK_TYPE D_0600E3E8; -extern UNK_TYPE D_0600FDEC; -extern UNK_TYPE D_0600FF94; -extern UNK_TYPE D_06010980; -extern UNK_TYPE D_060124CC; -extern UNK_TYPE D_06012B70; -extern UNK_TYPE D_06012D10; -extern UNK_TYPE D_06012EBC; -extern UNK_TYPE D_06013480; -extern UNK_TYPE D_0601407C; -extern UNK_TYPE D_06014F14; -extern UNK_TYPE D_06015A30; -extern UNK_TYPE D_06016168; -extern UNK_TYPE D_060164CC; -extern UNK_TYPE D_0601F6A4; -extern UNK_TYPE D_060204AC; -extern UNK_TYPE D_060213A8; -extern UNK_TYPE D_06022550; - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D0530.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D0550.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D0678.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D082C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D089C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D092C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D0AA4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/Boss01_Init.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/Boss01_Destroy.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D119C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1258.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D12B4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1AB8.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1B2C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1E5C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1E74.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D1EA4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D20D0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2588.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D25E8.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2664.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D26B8.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2780.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D27D4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2858.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2914.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2A44.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2AA0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2BCC.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2CDC.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2DE8.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D2E4C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3074.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D30D0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D32B4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3374.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3400.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D345C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D34D4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3530.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D35A8.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D365C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D370C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3A7C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3ADC.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3C10.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D3CD0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D441C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D4464.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D44C0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D4668.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/Boss01_Update.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D519C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D5584.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D55CC.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/Boss01_Draw.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D5B0C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D5BC4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D5FB4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D606C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D62D4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6314.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6424.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6488.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D64E0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6540.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6588.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D65E0.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D670C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D694C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6B08.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6BB4.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6C98.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D6E7C.s") - -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Boss_01/func_809D73D4.s") +static Color_RGBA8 sDustPrimColor = { 60, 50, 20, 255 }; + +static Color_RGBA8 sDustEnvColor = { 40, 30, 30, 255 }; + +typedef struct { + /* 0x00 */ Vec3f pos; + /* 0x0C */ Vec3f velocity; + /* 0x18 */ f32 gravity; + /* 0x1C */ s16 rotX; + /* 0x1E */ s16 rotY; + /* 0x20 */ UNK_TYPE1 unk_20[0x8]; + /* 0x28 */ u8 type; + /* 0x2A */ s16 timer; + /* 0x2C */ s16 isFallingBlockFragment; + /* 0x2E */ s16 angularVelocityX; + /* 0x30 */ s16 angularVelocityY; + /* 0x34 */ f32 scale; + /* 0x38 */ f32 alpha; +} OdolwaEffect; // size = 0x3C + +/** + * Keeps track of how many bugs are currently spawned and alive. When there are four or fewer bugs alive, Odolwa can + * summon more bugs or falling blocks. + */ +s16 sOdolwaBugCount; + +/** + * Pointer to the main Odolwa instance so that bugs and effects can check and manipulate his state. + */ +Boss01* sOdolwa; + +/** + * Pointer to the swarm of moths that Odolwa can summon. + */ +EnTanron1* sMothSwarm; + +/** + * Odolwa draws his sword trail manually by manipulating these variables during his horizontal and vertical slashes. + * Note that the position of the sword is offset from Odolwa's position. + */ +f32 sOdolwaSwordTrailPosX; +f32 sOdolwaSwordTrailPosY; +f32 sOdolwaSwordTrailPosZ; +f32 sOdolwaSwordTrailRotX; +f32 sOdolwaSwordTrailRotY; +f32 sOdolwaSwordTrailRotZ; +f32 sOdolwaSwordTrailAlpha; + +/** + * If the intro cutscene was skipped (because it was already watched), this timer will be used to determine when to + * start playing the boss theme. + */ +u8 sOdolwaMusicStartTimer; + +/** + * When Odolwa is damaged or dazed, the sound effect will play from this position. It is set to his projectedPos. + */ +Vec3f sOdolwaDamageSfxPos; + +s32 sOdolwaRandSeed1; +s32 sOdolwaRandSeed2; +s32 sOdolwaRandSeed3; + +/** + * Stores all of Odolwa's effects. Note that the first entry in this array is always reserved for the ring of fire, so + * falling blocks can only be placed from the second entry onwards. + */ +OdolwaEffect sOdolwaEffects[ODOLWA_EFFECT_COUNT]; + +void Boss01_InitRand(s32 seedInit1, s32 seedInit2, s32 seedInit3) { + sOdolwaRandSeed1 = seedInit1; + sOdolwaRandSeed2 = seedInit2; + sOdolwaRandSeed3 = seedInit3; +} + +f32 Boss01_RandZeroOne(void) { + // Wichmann-Hill algorithm + f32 randFloat; + + sOdolwaRandSeed1 = (sOdolwaRandSeed1 * 171) % 30269; + sOdolwaRandSeed2 = (sOdolwaRandSeed2 * 172) % 30307; + sOdolwaRandSeed3 = (sOdolwaRandSeed3 * 170) % 30323; + + randFloat = (sOdolwaRandSeed1 / 30269.0f) + (sOdolwaRandSeed2 / 30307.0f) + (sOdolwaRandSeed3 / 30323.0f); + + while (randFloat >= 1.0f) { + randFloat -= 1.0f; + } + + return fabsf(randFloat); +} + +/** + * Spawns a falling block or a fragment of a block that has hit the ground at the specified position. + */ +void Boss01_SpawnEffectFallingBlock(OdolwaEffect* effect, Vec3f* pos, s16 isFragment) { + s16 i; + + for (i = 1; i < ODOLWA_EFFECT_COUNT; i++, effect++) { + if (effect->type == ODOLWA_EFFECT_NONE) { + effect->type = ODOLWA_EFFECT_FALLING_BLOCK; + effect->pos = *pos; + effect->timer = 0; + effect->rotX = Rand_ZeroFloat(0x10000); + effect->rotY = Rand_ZeroFloat(0x10000); + effect->isFallingBlockFragment = isFragment; + effect->gravity = -1.0f; + + if (!isFragment) { + effect->angularVelocityY = Rand_CenteredFloat(0x320); + effect->angularVelocityX = Rand_CenteredFloat(0x320); + effect->scale = 8.0f * 0.001f; + effect->velocity = gZeroVec3f; + } else { + effect->angularVelocityY = Rand_CenteredFloat(0x1F40); + effect->angularVelocityX = Rand_CenteredFloat(0x1F40); + effect->scale = Rand_CenteredFloat(1.6f * 0.001f) + (3.2f * 0.001f); + effect->velocity.x = Rand_CenteredFloat(13.0f); + effect->velocity.y = Rand_ZeroFloat(4.0f) + 7.0f; + effect->velocity.z = Rand_CenteredFloat(13.0f); + } + + break; + } + } +} + +/** + * Spawns the ring of fire at the specified position. + */ +void Boss01_SpawnEffectRingOfFire(OdolwaEffect* effect, Vec3f* pos) { + if (effect->type == ODOLWA_EFFECT_NONE) { + effect->type = ODOLWA_EFFECT_RING_OF_FIRE; + effect->pos = *pos; + effect->timer = 0; + effect->velocity = gZeroVec3f; + effect->gravity = 0.0f; + effect->scale = 0.0f; + effect->alpha = 230.0f; + } +} + +/** + * Manually sets the position of a sphere collider to a specific position. + */ +void Boss01_SetColliderSphere(s32 index, ColliderJntSph* collider, Vec3f* sphereCenter) { + collider->elements[index].dim.worldSphere.center.x = sphereCenter->x; + collider->elements[index].dim.worldSphere.center.y = sphereCenter->y; + collider->elements[index].dim.worldSphere.center.z = sphereCenter->z; + collider->elements[index].dim.worldSphere.radius = + collider->elements[index].dim.modelSphere.radius * collider->elements[index].dim.scale; +} + +/** + * For the most part, this function is responsible for selecting which attack Odolwa will do after he's done waiting. + * Which attack he chooses depends on Odolwa's health, his distance to the player, and random chance. However, this + * function can also make Odolwa do a specific dance before attacking; if the mustAttack parameter is false, then Odolwa + * has a 20% chance of choosing to dance instead of attacking. This function will also make Odolwa go back to waiting if + * the player is too far off the ground (e.g., by jumping out of the Deku Flower in the center of the arena). + */ +void Boss01_SelectAttack(Boss01* this, PlayState* play, u8 mustAttack) { + Player* player = GET_PLAYER(play); + + if (player->actor.world.pos.y > 200.0f) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } else if ((Rand_ZeroOne() < 0.2f) && !mustAttack) { + // When Odolwa is done dancing, this function calls Boss01_SelectAttack with mustAttack set to true, so he will + // be guaranteed to choose an attack later, so long as the player isn't too far off the ground. + Boss01_SetupDanceBeforeAttack(this, play); + } else if (this->actor.xzDistToPlayer <= 250.0f) { + if (this->actor.xzDistToPlayer <= 150.0f) { + if (Rand_ZeroOne() < 0.5f) { + Boss01_SetupKick(this, play); + } else { + Boss01_SetupShieldBash(this, play); + } + } else { + Boss01_SetupHorizontalSlash(this, play); + } + } else if (((s8)this->actor.colChkInfo.health < 8) && (Rand_ZeroOne() < 0.75f)) { + Boss01_SetupSpinAttack(this, play); + } else { + Boss01_SetupVerticalSlash(this, play); + } +} + +/** + * Spawns dust at both of Odolwa's feet if *any* of the following conditions are true: + * - Odolwa's speed is greater than 1.0f + * - Odolwa's additional velocity (in either the X or Z direction) is greater than 1.0f + * - this->frameCounter & dustSpawnFrameMask is equal to 0 + * - dustSpawnFrameMask is 0 + */ +void Boss01_SpawnDustAtFeet(Boss01* this, PlayState* play, u8 dustSpawnFrameMask) { + u8 i; + Vec3f pos; + Vec3f velocity; + Vec3f accel; + + if (((this->frameCounter & dustSpawnFrameMask) == 0) && + ((this->additionalVelocityX > 1.0f) || (this->additionalVelocityZ > 1.0f) || (dustSpawnFrameMask == 0) || + (this->actor.speed > 1.0f))) { + for (i = 0; i < ARRAY_COUNT(this->feetPos); i++) { + velocity.x = Rand_CenteredFloat(5.0f); + velocity.y = Rand_ZeroFloat(2.0f) + 1.0f; + velocity.z = Rand_CenteredFloat(5.0f); + accel.y = -0.1f; + accel.x = accel.z = 0.0f; + pos.x = this->feetPos[i].x + Rand_CenteredFloat(20.0f); + pos.y = Rand_ZeroFloat(10.0f) + 3.0f; + pos.z = this->feetPos[i].z + Rand_CenteredFloat(20.0f); + func_800B0EB0(play, &pos, &velocity, &accel, &sDustPrimColor, &sDustEnvColor, + Rand_ZeroFloat(150.0f) + 350.0f, 10, Rand_ZeroFloat(5.0f) + 14.0f); + } + } +} + +void Boss01_Init(Actor* thisx, PlayState* play) { + Boss01* this = THIS; + s32 pad; + s16 i; + + Actor_SetScale(&this->actor, 0.015f); + if (ODOLWA_GET_TYPE(&this->actor) == ODOLWA_TYPE_BUG) { + SkelAnime_InitFlex(play, &this->skelAnime, &gOdolwaBugSkel, &gOdolwaBugCrawlAnim, this->jointTable, + this->morphTable, ODOLWA_BUG_LIMB_MAX); + this->actor.update = Boss01_Bug_Update; + this->actor.draw = Boss01_Bug_Draw; + this->bugDrawDmgEffType = ACTOR_DRAW_DMGEFF_BLUE_FIRE; + Boss01_Bug_SetupCrawl(this, play); + this->actor.gravity = -2.0f; + Collider_InitAndSetCylinder(play, &this->bugACCollider, &this->actor, &sBugACColliderCylinderInit); + Collider_InitAndSetCylinder(play, &this->bugATCollider, &this->actor, &sBugATColliderCylinderInit); + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 12.0f); + Actor_SetScale(&this->actor, 0.025f); + this->actor.colChkInfo.health = 2; + sOdolwaBugCount++; + Actor_PlaySfx(&this->actor, NA_SE_EV_ROCK_FALL); + this->actor.colChkInfo.damageTable = &sBugDamageTable; + sOdolwa->actor.hintId = TATL_HINT_ID_ODOLWA_PHASE_TWO; + } else if (ODOLWA_GET_TYPE(&this->actor) == ODOLWA_TYPE_AFTERIMAGE) { + SkelAnime_InitFlex(play, &this->skelAnime, &gOdolwaSkel, &gOdolwaReadyAnim, this->jointTable, this->morphTable, + ODOLWA_LIMB_MAX); + Boss01_Afterimage_SetupWaitToDespawn(this, play); + this->timers[TIMER_AFTERIMAGE_DESPAWN] = ODOLWA_GET_AFTERIMAGE_DESPAWN_TIMER(&this->actor); + this->actor.world.rot.z = 0; + this->actor.draw = Boss01_Afterimage_Draw; + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + } else { + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_WOODFALL_TEMPLE)) { + Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 0.0f, 0.0f, 0.0f, 0, 0, 0, + ENDOORWARP1_FF_1); + Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, 0.0f, 0.0f, 250.0f, 0, 0, 0, BHEART_PARAM_NORMAL); + Actor_Kill(&this->actor); + return; + } + + play->envCtx.lightSettingOverride = 0; + play->envCtx.lightBlendOverride = LIGHT_BLEND_OVERRIDE_FULL_CONTROL; + play->envCtx.lightBlend = 0.0f; + sOdolwa = this; + sOdolwaBugCount = 0; + play->specialEffects = sOdolwaEffects; + + for (i = 0; i < ODOLWA_EFFECT_COUNT; i++) { + sOdolwaEffects[i].type = ODOLWA_EFFECT_NONE; + } + + this->actor.hintId = TATL_HINT_ID_ODOLWA_PHASE_ONE; + this->actor.targetMode = TARGET_MODE_5; + this->actor.colChkInfo.mass = MASS_HEAVY; + this->actor.colChkInfo.damageTable = &sOdolwaDamageTable; + this->actor.colChkInfo.health = 20; + + ActorShape_Init(&this->actor.shape, 0.0f, NULL, 0.0f); + Collider_InitAndSetJntSph(play, &this->swordCollider, &this->actor, &sSwordColliderJntSphInit, + this->swordColliderElements); + Collider_InitAndSetJntSph(play, &this->shieldCollider, &this->actor, &sShieldColliderJntSphInit, + this->shieldColliderElements); + Collider_InitAndSetJntSph(play, &this->bodyCollider, &this->actor, &sBodyColliderJntSphInit, + this->bodyColliderElements); + Collider_InitAndSetJntSph(play, &this->kickAndShieldBashCollider, &this->actor, + &sKickAndShieldBashColliderJntSphInit, this->kickAndShieldBashColliderElements); + SkelAnime_InitFlex(play, &this->skelAnime, &gOdolwaSkel, &gOdolwaReadyAnim, this->jointTable, this->morphTable, + ODOLWA_LIMB_MAX); + + if ((KREG(64) != 0) || CHECK_EVENTINF(EVENTINF_54)) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_READY); + this->actor.gravity = -2.5f; + sOdolwaMusicStartTimer = KREG(15) + 20; + } else { + Boss01_SetupIntroCutscene(this, play); + } + + sMothSwarm = (EnTanron1*)Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_TANRON1, 0.0f, 437.0f, + -750.0f, 0, 0, 0, 0x100); + } + + this->animMorphFrames1 = -2.0f; + this->animMorphFrames2 = -4.0f; +} + +void Boss01_Destroy(Actor* thisx, PlayState* play) { +} + +/** + * Checks every explosive actor to see if Odolwa is close enough to any of them. If he is, then he'll jump. + */ +void Boss01_JumpAwayFromExplosive(Boss01* this, PlayState* play) { + Actor* explosive = play->actorCtx.actorLists[ACTORCAT_EXPLOSIVES].first; + + while (explosive != NULL) { + if (sqrtf(SQ(explosive->world.pos.x - this->actor.world.pos.x) + + SQ(explosive->world.pos.y - this->actor.world.pos.y) + + SQ(explosive->world.pos.z - this->actor.world.pos.z)) < 150.0f) { + Boss01_SetupJump(this, play, false); + } + + explosive = explosive->next; + } +} + +/** + * Note that this function will move Odolwa to (0, 2400, 0), regardless of where he was originally spawned. This is so + * he can fall from the ceiling in his intro cutscene, and this is also why he's not visible until the cutscene starts. + */ +void Boss01_SetupIntroCutscene(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_IntroCutscene; + this->actor.world.pos.x = 0.0f; + this->actor.world.pos.z = 0.0f; + this->actor.gravity = 0.0f; + this->actor.world.pos.y = 2400.0f; + Animation_MorphToLoop(&this->skelAnime, &gOdolwaCrouchAnim, 0.0f); +} + +/** + * Handles the entirety of Odolwa's intro cutscene, including manipulating the camera, starting the boss theme, showing + * Odolwa's title card, etc. It also handles waiting for the player to move forward from the door before actually + * starting the cutscene. + */ +void Boss01_IntroCutscene(Boss01* this, PlayState* play) { + Player* player = GET_PLAYER(play); + + this->cutsceneTimer++; + SkelAnime_Update(&this->skelAnime); + + switch (this->cutsceneState) { + case ODOLWA_INTRO_CS_STATE_WAITING_FOR_PLAYER_OR_DONE: + if ((CutsceneManager_GetCurrentCsId() != -1) || !(player->actor.world.pos.z < 590.0f)) { + break; + } + + Cutscene_StartManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_WAIT); + this->subCamId = Play_CreateSubCamera(play); + Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STATUS_WAIT); + Play_ChangeCameraStatus(play, this->subCamId, CAM_STATUS_ACTIVE); + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + this->cutsceneTimer = 0; + this->cutsceneState = ODOLWA_INTRO_CS_STATE_LOOK_AT_PLAYER; + this->subCamUp.x = 0.0f; + this->subCamUp.y = 1.0f; + this->subCamUp.z = 0.0f; + // fallthrough + case ODOLWA_INTRO_CS_STATE_LOOK_AT_PLAYER: + player->actor.world.rot.y = -0x8000; + player->actor.shape.rot.y = -0x8000; + player->actor.world.pos.x = -9.0f; + player->actor.world.pos.z = 587.0f; + + this->subCamEye.x = -9.0f; + this->subCamEye.y = (Player_GetHeight(player) + player->actor.world.pos.y) - 24.0f; + this->subCamEye.z = (player->actor.world.pos.z - 200.0f) + 110.0f; + + this->subCamAt.x = player->actor.world.pos.x; + this->subCamAt.y = (Player_GetHeight(player) + player->actor.world.pos.y) - 14.0f; + this->subCamAt.z = player->actor.world.pos.z; + + if (player->transformation == PLAYER_FORM_FIERCE_DEITY) { + this->subCamEye.y -= 30.0f + BREG(16); + this->subCamAt.y -= 30.0f + BREG(17); + } + + if (this->cutsceneTimer >= 20) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_RHYTHM_OLD - SFX_FLAG); + } + + if (this->cutsceneTimer == 40) { + func_800B7298(play, &this->actor, PLAYER_CSACTION_21); + } + + if (this->cutsceneTimer == 100) { + func_800B7298(play, &this->actor, PLAYER_CSACTION_4); + } + + if (this->cutsceneTimer >= 90) { + Audio_PlaySfx(NA_SE_EN_MIBOSS_FALL_OLD_OLD - SFX_FLAG); + } + + if (this->cutsceneTimer == 120) { + this->actor.world.pos.y = 1500.0f; + this->actor.gravity = -2.5f; + this->cutsceneState = ODOLWA_INTRO_CS_STATE_ODOLWA_FALLING; + this->cutsceneTimer = 0; + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaJumpAnim, 0.0f); + this->subCamEye.x = this->actor.world.pos.x; + this->subCamAt.x = this->actor.world.pos.x; + this->subCamAt.y = 80.0f; + this->subCamEye.y = 30.0f; + this->subCamEye.z = (this->actor.world.pos.z + 200.0f + 50.0f) - 150.0f; + this->subCamAt.z = this->actor.world.pos.z; + this->subCamUp.x = 2.0f; + } + break; + + case ODOLWA_INTRO_CS_STATE_ODOLWA_FALLING: + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_RHYTHM_OLD - SFX_FLAG); + Math_ApproachZeroF(&this->subCamUp.x, 0.1f, 0.1f); + Audio_PlaySfx(NA_SE_EN_MIBOSS_FALL_OLD_OLD - SFX_FLAG); + this->afterimageSpawnFrameMask = 2; + Math_ApproachF(&this->subCamAt.y, this->actor.world.pos.y + 80.0f, 0.25f, 30.0f); + if (this->actor.world.pos.y < 40.0f) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaCrouchAnim, -2.0f); + this->cutsceneState = ODOLWA_INTRO_CS_STATE_ODOLWA_LANDED; + this->cutsceneTimer = 0; + } + break; + + case ODOLWA_INTRO_CS_STATE_ODOLWA_LANDED: + if (this->cutsceneTimer < 51) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_RHYTHM_OLD - SFX_FLAG); + } + + if ((this->cutsceneTimer == 1) || (this->cutsceneTimer == 3)) { + u8 i; + + for (i = 0; i < 10; i++) { + Boss01_SpawnDustAtFeet(this, play, 0); + } + + Audio_PlaySfx(NA_SE_EN_MIBOSS_GND1_OLD); + Rumble_Override(0.0f, 200, 20, 20); + this->screenShakeMagnitude = 10.0f; + } + + if (this->cutsceneTimer == 5) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaIntroSlashAnim, -20.0f); + } + + if ((this->cutsceneTimer >= 6) && + (Animation_OnFrame(&this->skelAnime, 30.0f) || Animation_OnFrame(&this->skelAnime, 54.0f))) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_SWORD_OLD); + } + + if (this->cutsceneTimer < 30) { + Math_ApproachF(&this->subCamAt.y, this->actor.world.pos.y + 80.0f, 0.25f, 1000.0f); + } else { + Math_ApproachF(&this->subCamEye.x, this->actor.world.pos.x + 70.0f, 0.05f, + this->subCamVelocity * 70.0f); + Math_ApproachF(&this->subCamEye.y, 200.0f, 0.05f, this->subCamVelocity * 170.0f); + Math_ApproachF(&this->subCamEye.z, (this->actor.world.pos.z + 200.0f + 50.0f) - 30.0f, 0.05f, + this->subCamVelocity * 120.0f); + Math_ApproachF(&this->subCamAt.y, this->actor.world.pos.y + 80.0f + 20.0f, 0.05f, + this->subCamVelocity * 20.0f); + Math_ApproachF(&this->subCamVelocity, 1.0f, 1.0f, 0.001f); + } + + if (this->cutsceneTimer == 20) { + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, NA_BGM_BOSS | SEQ_FLAG_ASYNC); + } + + if (this->cutsceneTimer == 50) { + TitleCard_InitBossName(&play->state, &play->actorCtx.titleCtxt, + Lib_SegmentedToVirtual(&gOdolwaTitleCardTex), 160, 180, 128, 40); + } + + if (this->cutsceneTimer == 140) { + Camera* mainCam = Play_GetCamera(play, CAM_ID_MAIN); + + this->cutsceneState = ODOLWA_INTRO_CS_STATE_WAITING_FOR_PLAYER_OR_DONE; + this->phaseFrameCounter = 0; + Boss01_SetupWait(this, play, ODOLWA_WAIT_READY); + mainCam->eye = this->subCamEye; + mainCam->eyeNext = this->subCamEye; + mainCam->at = this->subCamAt; + func_80169AFC(play, this->subCamId, 0); + this->subCamId = SUB_CAM_ID_DONE; + Cutscene_StopManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_END); + this->actor.flags |= ACTOR_FLAG_TARGETABLE; + SET_EVENTINF(EVENTINF_54); + } + break; + + default: + break; + } + + if (this->subCamId != SUB_CAM_ID_DONE) { + Vec3f at; + + ShrinkWindow_Letterbox_SetSizeTarget(27); + this->screenShakeOffsetY = Math_CosS(play->gameplayFrames * 0x8000) * this->screenShakeMagnitude; + Math_ApproachZeroF(&this->screenShakeMagnitude, 1.0f, 0.75f); + at = this->subCamAt; + at.y += this->screenShakeOffsetY; + Play_SetCameraAtEyeUp(play, this->subCamId, &at, &this->subCamEye, &this->subCamUp); + } +} + +void Boss01_SetupSummonBugsCutscene(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_SummonBugsCutscene; + Animation_MorphToLoop(&this->skelAnime, &gOdolwaSideToSideDanceAnim, -10.0f); + this->cutsceneState = ODOLWA_BUG_SUMMONING_CS_STATE_STARTED; + this->cutsceneTimer = 0; + this->hasPlayedSummonBugCs++; + this->disableCollisionTimer = 30; + this->actor.speed = 0.0f; + this->additionalVelocityZ = 0.0f; + this->additionalVelocityX = 0.0f; +} + +/** + * Handles everything involving the bug summoning cutscene, including manipulating the camera, spawning the bugs, etc. + */ +void Boss01_SummonBugsCutscene(Boss01* this, PlayState* play) { + Player* player = GET_PLAYER(play); + Vec3f offset; + Vec3f pos; + + this->cutsceneTimer++; + this->disableCollisionTimer = 30; + SkelAnime_Update(&this->skelAnime); + + switch (this->cutsceneState) { + case ODOLWA_BUG_SUMMONING_CS_STATE_STARTED: + if (CutsceneManager_GetCurrentCsId() != -1) { + break; + } + + Cutscene_StartManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_WAIT); + this->subCamId = Play_CreateSubCamera(play); + Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STATUS_WAIT); + Play_ChangeCameraStatus(play, this->subCamId, CAM_STATUS_ACTIVE); + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + this->cutsceneState = ODOLWA_BUG_SUMMONING_CS_STATE_PLAYING_OR_DONE; + this->actor.shape.rot.y = 0; + this->actor.world.pos.z = 0.0f; + this->actor.world.pos.x = 0.0f; + this->subCamVelocity = 0.0f; + this->subCamEyeNext.y = 100.0f; + // fallthrough + case ODOLWA_BUG_SUMMONING_CS_STATE_PLAYING_OR_DONE: + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_VOICE1_OLD - SFX_FLAG); + Matrix_RotateYS(this->actor.shape.rot.y, MTXMODE_NEW); + Matrix_MultVecZ(250.0f, &offset); + + this->subCamEye.x = this->actor.world.pos.x + offset.x; + this->subCamEye.y = this->actor.world.pos.y + offset.y + 40.0f; + this->subCamEye.z = this->actor.world.pos.z + offset.z; + + this->subCamAt.x = this->actor.world.pos.x; + this->subCamAt.y = this->actor.world.pos.y + this->subCamEyeNext.y; + this->subCamAt.z = this->actor.world.pos.z; + + if (this->cutsceneTimer > 40) { + Math_ApproachF(&this->subCamEyeNext.y, 800.0f, 0.1f, this->subCamVelocity); + Math_ApproachF(&this->subCamVelocity, 100.0f, 1.0f, 1.0f); + if ((this->cutsceneTimer > 80) && ((this->cutsceneTimer % 16) == 0)) { + Matrix_MultVecZ(100.0f, &offset); + pos.x = Rand_CenteredFloat(200.0f) + (this->actor.world.pos.x + offset.x); + pos.z = Rand_CenteredFloat(200.0f) + (this->actor.world.pos.z + offset.z); + Audio_PlaySfx(NA_SE_PL_DEKUNUTS_DROP_BOMB); + Actor_Spawn(&play->actorCtx, play, ACTOR_BOSS_01, pos.x, 1200.0f, pos.z, 0, Rand_ZeroFloat(0x10000), + 0, ODOLWA_TYPE_BUG); + } + } + + if (this->cutsceneTimer >= 170) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_READY); + func_80169AFC(play, this->subCamId, 0); + this->subCamId = SUB_CAM_ID_DONE; + Cutscene_StopManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_END); + this->actor.flags |= ACTOR_FLAG_TARGETABLE; + player->actor.world.rot.y = player->actor.shape.rot.y = -0x8000; + player->actor.world.pos.x = 0.0f; + player->actor.world.pos.z = -600.0f; + } + break; + } + + if (this->subCamId != SUB_CAM_ID_DONE) { + ShrinkWindow_Letterbox_SetSizeTarget(27); + Play_SetCameraAtEye(play, this->subCamId, &this->subCamAt, &this->subCamEye); + } +} + +void Boss01_Afterimage_SetupWaitToDespawn(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_Afterimage_WaitToDespawn; +} + +/** + * Waits until the despawn timer reaches 0, then kills the actor. + */ +void Boss01_Afterimage_WaitToDespawn(Boss01* this, PlayState* play) { + if (this->timers[TIMER_AFTERIMAGE_DESPAWN] == 0) { + Actor_Kill(&this->actor); + } +} + +static AnimationHeader* sWaitAnimations[ODOLWA_WAIT_MAX] = { + &gOdolwaReadyAnim, // ODOLWA_WAIT_READY, + &gOdolwaSpinSwordAnim, // ODOLWA_WAIT_SPIN_SWORD, + &gOdolwaVerticalHopAnim, // ODOLWA_WAIT_VERTICAL_HOP, + &gOdolwaHipShakeDanceAnim, // ODOLWA_WAIT_SHAKE_DANCE, + &gOdolwaUpAndDownDanceAnim, // ODOLWA_WAIT_UP_AND_DOWN_DANCE, + &gOdolwaArmSwingDanceAnim, // ODOLWA_WAIT_ARM_SWING_DANCE, + &gOdolwaThurstAttackAnim, // ODOLWA_WAIT_THRUST_ATTACK, + &gOdolwaDoubleSlashAnim, // ODOLWA_WAIT_DOUBLE_SLASH, + &gOdolwaSideToSideHopAnim, // ODOLWA_WAIT_SIDE_TO_SIDE_HOP, + &gOdolwaSideToSideDanceAnim, // ODOLWA_WAIT_SIDE_TO_SIDE_DANCE, + &gOdolwaSpinDanceAnim, // ODOLWA_WAIT_SPIN_DANCE + &gOdolwaJumpDanceAnim, // ODOLWA_WAIT_JUMP_DANCE +}; + +/** + * Prepares Odolwa to enter his "wait" state, i.e., his primary state where he waits for a bit of time before attacking. + * If the fight has been going for 1000 or more frames, this function is also responsible for starting the bug summoning + * cutscene. Callers of this function can choose which type of wait action he will do using the waitType parameter, + * though this parameter is ignored if the fight is still in its first phase (i.e, when the fight has been going on for + * 999 or fewer frames); Odolwa will always perform the "ready" wait action in the first phase. + * + * If ODOLWA_WAIT_RANDOM is passed for the waitType, then Odolwa has a 30% chance of summoning moths instead of doing a + * standard wait action. Otherwise, he will randomly choose his own wait type. + */ +void Boss01_SetupWait(Boss01* this, PlayState* play, u8 waitType) { + if (this->phaseFrameCounter > 1000) { + if (!this->hasPlayedSummonBugCs) { + Boss01_SetupSummonBugsCutscene(this, play); + return; + } + + if (waitType == ODOLWA_WAIT_RANDOM) { + if (Rand_ZeroOne() < 0.3f) { + Boss01_SetupSummonMoths(this, play); + return; + } + + this->waitType = Rand_ZeroFloat(ODOLWA_WAIT_MAX - 0.001f); + } else { + this->waitType = waitType; + } + } else { + this->waitType = ODOLWA_WAIT_READY; + } + + Animation_MorphToLoop(&this->skelAnime, sWaitAnimations[this->waitType], 2.0f * this->animMorphFrames2); + this->animEndFrame = Animation_GetLastFrame(sWaitAnimations[this->waitType]); + this->actionFunc = Boss01_Wait; + this->timers[TIMER_CURRENT_ACTION] = 80; + this->jumpIfPlayerIsClose = Rand_ZeroFloat(1.999f); + this->waitTimer = 0; +} + +/** + * This function will make Odolwa perform one of 12 different "wait actions," after which he will either select an + * attack, jump, or start running. Because of the variety of actions this function handles, it has a large number of + * responsibilities, including summoning the ring of fire and spawning more bugs and falling blocks. + */ +void Boss01_Wait(Boss01* this, PlayState* play) { + s16 i; + Player* player = GET_PLAYER(play); + + this->lookAtPlayer = true; + + if ((this->waitType == ODOLWA_WAIT_SPIN_SWORD) || (this->waitType == ODOLWA_WAIT_VERTICAL_HOP) || + (this->waitType == ODOLWA_WAIT_ARM_SWING_DANCE) || (this->waitType == ODOLWA_WAIT_THRUST_ATTACK)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_VOICE1_OLD - SFX_FLAG); + } else { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_VOICE2_OLD - SFX_FLAG); + } + + if ((this->waitType == ODOLWA_WAIT_READY) || (this->waitType == ODOLWA_WAIT_SPIN_SWORD) || + (this->waitType == ODOLWA_WAIT_VERTICAL_HOP) || (this->waitType == ODOLWA_WAIT_SHAKE_DANCE)) { + this->canGuardOrEvade = true; + this->swordAndShieldCollisionEnabled = true; + } + + if (((this->waitType == ODOLWA_WAIT_VERTICAL_HOP) || (this->waitType == ODOLWA_WAIT_SIDE_TO_SIDE_HOP) || + (this->waitType == ODOLWA_WAIT_SIDE_TO_SIDE_DANCE)) && + Animation_OnFrame(&this->skelAnime, 6.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_GND1_OLD); + for (i = 0; i < 3; i++) { + Boss01_SpawnDustAtFeet(this, play, 0); + } + } + + if (this->waitType == ODOLWA_WAIT_SIDE_TO_SIDE_DANCE) { + this->animMorphFrames2 = 0.0f; + this->animMorphFrames1 = 0.0f; + } + + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->waitTimer++; + + // This will play "slash" sound effects at appropriate points in the thrust attack or double slash action. + if (this->waitType == ODOLWA_WAIT_THRUST_ATTACK) { + if (this->waitTimer == 7) { + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_ENTRY); + } + } else if ((this->waitType == ODOLWA_WAIT_DOUBLE_SLASH) && (((this->waitTimer == 12)) || (this->waitTimer == 20))) { + Actor_PlaySfx(&this->actor, NA_SE_EN_ANSATSUSYA_ENTRY); + } + + SkelAnime_Update(&this->skelAnime); + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 0xA, 0x800); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.5f); + + // Whether or not Odolwa should jump if the player gets close was randomly decided earlier in Boss01_SetupWait, and + // if it was decided that he *should* jump, this can interrupt any wait action. + if ((this->actor.xzDistToPlayer <= 150.0f) && this->jumpIfPlayerIsClose) { + Boss01_SetupJump(this, play, false); + this->canGuardOrEvade = false; + } + + // The thrust attack and double slash wait types select Odolwa's next action after their animations end rather than + // waiting until the full 80 frames like every other wait type. The timer for the current action is not set to 0 + // here, though, which may cause some unintended behavior in Boss01_HorizontalSlash. + if (((this->timers[TIMER_CURRENT_ACTION] == 0) && (this->waitType != ODOLWA_WAIT_THRUST_ATTACK) && + (this->waitType != ODOLWA_WAIT_DOUBLE_SLASH)) || + (Animation_OnFrame(&this->skelAnime, this->animEndFrame) && + ((this->waitType == ODOLWA_WAIT_THRUST_ATTACK) || (this->waitType == ODOLWA_WAIT_DOUBLE_SLASH)))) { + if (this->actor.xzDistToPlayer <= 450.0f) { + Boss01_SelectAttack(this, play, false); + } else if (Rand_ZeroOne() < 0.5f) { + Boss01_SetupJump(this, play, true); + } else { + Boss01_SetupRun(this, play); + } + } + + Boss01_SpawnDustAtFeet(this, play, 1); + this->animMorphFrames1 = -2.0f; + this->animMorphFrames2 = -4.0f; + + if (((this->waitType == ODOLWA_WAIT_SHAKE_DANCE) || (this->waitType == ODOLWA_WAIT_UP_AND_DOWN_DANCE) || + (this->waitType == ODOLWA_WAIT_DOUBLE_SLASH) || (this->waitType == ODOLWA_WAIT_SIDE_TO_SIDE_HOP)) && + (this->waitTimer == 30)) { + Boss01_SpawnEffectRingOfFire((OdolwaEffect*)play->specialEffects, &player->actor.world.pos); + this->timers[TIMER_CURRENT_ACTION] = 120; + } + + if (((this->timers[TIMER_CURRENT_ACTION] % 16) == 0) && (this->waitType != ODOLWA_WAIT_READY) && + (sOdolwaBugCount < 5)) { + Vec3f pos; + Player* player2 = GET_PLAYER(play); + s32 pad; + + if (Rand_ZeroOne() < 0.2f) { + pos = player2->actor.world.pos; + } else { + pos.x = Rand_CenteredFloat(1200.0f); + pos.z = Rand_CenteredFloat(1200.0f); + } + + pos.y = 1200.0f; + + switch (this->waitType) { + case ODOLWA_WAIT_SPIN_SWORD: + case ODOLWA_WAIT_VERTICAL_HOP: + case ODOLWA_WAIT_ARM_SWING_DANCE: + case ODOLWA_WAIT_THRUST_ATTACK: + Actor_Spawn(&play->actorCtx, play, ACTOR_BOSS_01, pos.x, pos.y, pos.z, 0, Rand_ZeroFloat(0x10000), 0, + ODOLWA_TYPE_BUG); + break; + + case ODOLWA_WAIT_SIDE_TO_SIDE_DANCE: + case ODOLWA_WAIT_SPIN_DANCE: + case ODOLWA_WAIT_JUMP_DANCE: + Audio_PlaySfx_AtPos(&sFallingBlockSfxPos, NA_SE_EV_ROCK_FALL); + Boss01_SpawnEffectFallingBlock((OdolwaEffect*)play->specialEffects, &pos, false); + break; + + default: + break; + } + } +} + +void Boss01_SetupDazed(Boss01* this, PlayState* play) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaStunAnim, this->animMorphFrames2); + if (this->actionFunc != Boss01_Dazed) { + this->timers[TIMER_CURRENT_ACTION] = 70; + this->actionFunc = Boss01_Dazed; + } + + this->canGuardOrEvade = false; +} + +/** + * Plays the dazed animation for 70 frames, leaving Odolwa vulnerable to attacks. This state is often referred to as + * Odolwa being "stunned," though it was deliberately named something different here to avoid confusion with the state + * where Odolwa turns blue and stops all his animations (e.g., when he is hit with a Deku Nut). The latter state is + * consistently named "stunned" across all enemies in the codebase, so this function was given a different name to + * signify it does something else. + */ +void Boss01_Dazed(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_WEAKENED - SFX_FLAG); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.5f); + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + this->canGuardOrEvade = false; + Boss01_SpawnDustAtFeet(this, play, 1); +} + +void Boss01_SetupSpinAttack(Boss01* this, PlayState* play) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaSpinAttackAnim, this->animMorphFrames2); + this->timers[TIMER_CURRENT_ACTION] = 120; + this->actionFunc = Boss01_SpinAttack; + this->canGuardOrEvade = false; +} + +/** + * Rapidly spin around and approach the player for 120 frames, spawning a new afterimage every frame. + * Transitions back to waiting once the attack completes. + */ +void Boss01_SpinAttack(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachF(&this->actor.speed, 7.0f, 1.0f, 1.5f); + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 4, 0x1000); + + if (Animation_OnFrame(&this->skelAnime, 5.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_SWORD_OLD); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_ROLLING_OLD); + } + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + this->canGuardOrEvade = false; + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->swordAndShieldCollisionEnabled = true; + Boss01_SpawnDustAtFeet(this, play, 1); + this->afterimageSpawnFrameMask = 1; +} + +void Boss01_SetupDanceBeforeAttack(Boss01* this, PlayState* play) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaJumpDanceAnim, this->animMorphFrames2); + this->timers[TIMER_CURRENT_ACTION] = 40; + this->actionFunc = Boss01_DanceBeforeAttack; + this->canGuardOrEvade = false; +} + +/** + * Dance for 40 frames, then select an attack. Odolwa can only end up in this state if he randomly chose to dance + * instead of attack the last time Boss01_SelectAttack was called. This function calls Boss01_SelectAttack with the + * mustAttack parameter set to true, ensuring that he will not dance again immediately after this. + */ +void Boss01_DanceBeforeAttack(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.5f); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_VOICE2_OLD - SFX_FLAG); + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SelectAttack(this, play, true); + } + + this->canGuardOrEvade = false; + this->swordAndShieldCollisionEnabled = true; + Boss01_SpawnDustAtFeet(this, play, 1); +} + +void Boss01_SetupRun(Boss01* this, PlayState* play) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaRunAnim, this->animMorphFrames2); + this->actionFunc = Boss01_Run; + this->timers[TIMER_CURRENT_ACTION] = Rand_ZeroFloat(100.0f) + 50.0f; + this->runTargetPosAngularVelocityY = 0.07f; + + if (Rand_ZeroOne() < 0.5f) { + this->runTargetPosAngularVelocityY *= -1.0f; + } + + this->actor.gravity = -3.0f; +} + +/** + * Run in a circle for 50-150 frames by chasing a rotating target around the room, then go back to waiting. + */ +void Boss01_Run(Boss01* this, PlayState* play) { + Vec3f targetPos; + f32 diffX; + f32 diffZ; + + this->lookAtPlayer = true; + SkelAnime_Update(&this->skelAnime); + + if ((this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) && + (Animation_OnFrame(&this->skelAnime, 6.0f) || Animation_OnFrame(&this->skelAnime, 16.0f))) { + this->actor.velocity.y = 10.0f; + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_GND1_OLD); + } + + this->runTargetPosRotY += this->runTargetPosAngularVelocityY; + Matrix_RotateYF(this->runTargetPosRotY, MTXMODE_NEW); + Matrix_MultVecZ(450.0f, &targetPos); + diffX = targetPos.x - this->actor.world.pos.x; + diffZ = targetPos.z - this->actor.world.pos.z; + Math_ApproachS(&this->actor.world.rot.y, Math_Atan2S_XY(diffZ, diffX), 0xA, 0x1000); + Math_ApproachF(&this->actor.speed, 12.0f, 1.0f, 3.0f); + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + Boss01_SpawnDustAtFeet(this, play, 3); + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->swordAndShieldCollisionEnabled = true; +} + +/** + * This starts the process making Odolwa jump, though the actual jump is divided into three different action functions. + * The shouldPerformFallingSlash parameter controls whether Odolwa should try to perform a falling slash during the + * middle part of the jump or if he should just jump normally. + */ +void Boss01_SetupJump(Boss01* this, PlayState* play, u8 shouldPerformFallingSlash) { + this->shouldPerformFallingSlash = shouldPerformFallingSlash; + Animation_MorphToLoop(&this->skelAnime, &gOdolwaCrouchAnim, this->animMorphFrames2); + this->actionFunc = Boss01_JumpSquat; + this->timers[TIMER_CURRENT_ACTION] = 5; +} + +/** + * Plays the crouch animation for 5 frames, then jumps off. + */ +void Boss01_JumpSquat(Boss01* this, PlayState* play) { + s32 pad[2]; + u8 i; + Vec3f additionalVelocity; + f32 magnitude; + + SkelAnime_Update(&this->skelAnime); + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + this->actionFunc = Boss01_Jump; + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaJumpAnim, this->animMorphFrames1); + this->actor.velocity.y = 35.0f; + this->actor.gravity = -2.5f; + Matrix_RotateYS(this->actor.world.rot.y, MTXMODE_NEW); + + if (!this->shouldPerformFallingSlash) { + magnitude = Rand_ZeroFloat(10.0f) + 10.0f; + } else { + magnitude = 12.0f; + } + + Matrix_MultVecZ(magnitude, &additionalVelocity); + this->additionalVelocityX = additionalVelocity.x; + this->additionalVelocityZ = additionalVelocity.z; + + for (i = 0; i < 5; i++) { + Boss01_SpawnDustAtFeet(this, play, 0); + } + + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_JUMP1); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_JUMP2 - SFX_FLAG); + this->disableCollisionTimer = 5; + } + + this->swordAndShieldCollisionEnabled = true; +} + +/** + * As Odolwa moves through the air, this function will spawn an afterimage every other frame and rotate him to face the + * player. If Odolwa should perform a falling slash, and if he's falling fast enough, then this function will stop + * rotating him and transition him to the slash animation. Lastly, this function will transition Odolwa to his "landing" + * action once he gets close enough to the ground. + */ +void Boss01_Jump(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_JUMP2 - SFX_FLAG); + + if (!this->shouldPerformFallingSlash) { + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 5, 0x1000); + } else if ((this->actor.velocity.y < -5.0f) && (this->prevJumpVelocityY >= -5.0f)) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaFallingSlashAnim, this->animMorphFrames1); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_SWORD_OLD); + } + + if (this->actor.world.pos.y < 40.0f) { + this->actionFunc = Boss01_JumpLand; + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaCrouchAnim, this->animMorphFrames1); + this->timers[TIMER_CURRENT_ACTION] = 5; + this->landedFromJump = false; + } + + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->swordAndShieldCollisionEnabled = true; + this->afterimageSpawnFrameMask = 2; + this->prevJumpVelocityY = this->actor.velocity.y; +} + +/** + * Plays the crouch animation for five frames, then transitions either to selecting an attack or waiting based on + * Odolwa's distance to the player. + */ +void Boss01_JumpLand(Boss01* this, PlayState* play) { + u8 i; + + SkelAnime_Update(&this->skelAnime); + + if (!this->landedFromJump) { + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + this->landedFromJump = true; + for (i = 0; i < 5; i++) { + Boss01_SpawnDustAtFeet(this, play, 0); + } + + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_GND1_OLD); + } + } + + if (!this->shouldPerformFallingSlash) { + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 4, 0x2800); + } + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + if (this->actor.xzDistToPlayer <= 450.0f) { + Boss01_SelectAttack(this, play, false); + } else { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + } + + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->swordAndShieldCollisionEnabled = true; +} + +void Boss01_SetupVerticalSlash(Boss01* this, PlayState* play) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaVerticalSlashAnim, this->animMorphFrames2); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaVerticalSlashAnim); + this->actionFunc = Boss01_VerticalSlash; + this->timers[TIMER_CURRENT_ACTION] = 20; +} + +/** + * Charges forward and performs a vertical slash. Transitions back to waiting once the attack completes. + */ +void Boss01_VerticalSlash(Boss01* this, PlayState* play) { + Vec3f additionalVelocity; + + SkelAnime_Update(&this->skelAnime); + + if ((this->skelAnime.curFrame >= 10.0f) && (this->skelAnime.curFrame <= 15.0f)) { + this->isPerformingVerticalSlash = true; + } + + if ((this->timers[TIMER_CURRENT_ACTION] >= 7) && (this->timers[TIMER_CURRENT_ACTION] < 13)) { + Matrix_RotateYF(BINANG_TO_RAD_ALT(this->actor.world.rot.y), MTXMODE_NEW); + Matrix_MultVecZ(20.0f, &additionalVelocity); + this->additionalVelocityX = additionalVelocity.x; + this->additionalVelocityZ = additionalVelocity.z; + Boss01_SpawnDustAtFeet(this, play, 0); + } + + sOdolwaSwordTrailPosX = 0.0f; + sOdolwaSwordTrailPosY = 90.0f; + sOdolwaSwordTrailPosZ = -70.0f; + sOdolwaSwordTrailRotX = 0.4712388f; + sOdolwaSwordTrailRotY = M_PI; + sOdolwaSwordTrailRotZ = 1.7278761f; + + if (Animation_OnFrame(&this->skelAnime, 12.0f)) { + sOdolwaSwordTrailAlpha = 255.0f; + sSwordTrailAngularRangeDivisor = 100.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 13.0f)) { + sSwordTrailAngularRangeDivisor = 20.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 14.0f)) { + sSwordTrailAngularRangeDivisor = 7.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 7.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_DASH_OLD); + } + + if (Animation_OnFrame(&this->skelAnime, 10.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_SWORD_OLD); + } + + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + this->additionalVelocityZ = 0.0f; + this->additionalVelocityX = 0.0f; + } + + this->swordState = ODOLWA_SWORD_STATE_ACTIVE; + this->swordAndShieldCollisionEnabled = true; +} + +void Boss01_SetupHorizontalSlash(Boss01* this, PlayState* play) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaHorizontalSlashAnim, this->animMorphFrames2); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaHorizontalSlashAnim); + this->actionFunc = Boss01_HorizontalSlash; +} + +/** + * Stands in place and performs a horizontal slash. Transitions back to waiting once the attack completes. + */ +void Boss01_HorizontalSlash(Boss01* this, PlayState* play) { + Vec3f additionalVelocity; + + SkelAnime_Update(&this->skelAnime); + + if ((this->skelAnime.curFrame >= 10.0f) && (this->skelAnime.curFrame <= 15.0f)) { + this->swordState = ODOLWA_SWORD_STATE_HORIZONTAL_SLASH; + } + + // This code is written very strangely and might never have been intended to run. The timer values checked here seem + // copied from Boss01_VerticalSlash, and they make sense there to control when Odolwa should dash forward. Unlike + // Boss01_VerticalSlash, however, this function uses Matrix_MultZero to initialize additionalVelocity; in other + // words, the additional velocity in all directions will be zero, and Odolwa will stay in place. + // + // Additionally, Boss01_SetupHorizontalSlash doesn't initialize this->timers[TIMER_CURRENT_ACTION] to anything, so + // most of the time, the timer will just be zero, and this code will never run in the first place. However, if + // Odolwa did a double slash or thrust attack wait action in Boss01_Wait, it is possible to enter this function with + // a non-zero current action timer, potentially allowing this code to run. The only consequence of this is that + // Odolwa will sometimes spawn dust at his feet during a horizontal slash when he was probably never supposed to. + if ((this->timers[TIMER_CURRENT_ACTION] >= 7) && (this->timers[TIMER_CURRENT_ACTION] < 13)) { + Matrix_RotateYF(BINANG_TO_RAD_ALT(this->actor.world.rot.y), MTXMODE_NEW); + Matrix_MultZero(&additionalVelocity); + this->additionalVelocityX = additionalVelocity.x; + this->additionalVelocityZ = additionalVelocity.z; + Boss01_SpawnDustAtFeet(this, play, 0); + } + + sOdolwaSwordTrailPosX = 0.0f; + sOdolwaSwordTrailPosY = 140.0f; + sOdolwaSwordTrailPosZ = 0.0f; + sOdolwaSwordTrailRotX = 0.4712388f; + sOdolwaSwordTrailRotY = 0.0f; + sOdolwaSwordTrailRotZ = 0.0f; + + if (Animation_OnFrame(&this->skelAnime, 12.0f)) { + sOdolwaSwordTrailAlpha = 255.0f; + sSwordTrailAngularRangeDivisor = 100.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 13.0f)) { + sSwordTrailAngularRangeDivisor = 20.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 14.0f)) { + sSwordTrailAngularRangeDivisor = 7.0f; + } + + if (Animation_OnFrame(&this->skelAnime, 10.0f)) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_SWORD_OLD); + } + + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + this->additionalVelocityZ = 0.0f; + this->additionalVelocityX = 0.0f; + } + + this->swordAndShieldCollisionEnabled = true; +} + +/** + * Prepares Odolwa to guard using either his shield or sword depending on the supplied parameter. The difference is + * entirely aesthetic, with him behaving in the exact same way regardless of which type of guard is chosen. + */ +void Boss01_SetupGuard(Boss01* this, PlayState* play, u8 guardUsingSword) { + if (this->actionFunc != Boss01_Guard) { + if (!guardUsingSword) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaShieldGuardAnim, this->animMorphFrames1); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaShieldGuardAnim); + } else { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaSwordGuardAnim, this->animMorphFrames1); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaSwordGuardAnim); + } + + this->actionFunc = Boss01_Guard; + } + + this->timers[TIMER_CURRENT_ACTION] = 5; +} + +/** + * Plays a guard animation (either blocking with his shield or his sword) for five frames, then transitions back to + * waiting. + */ +void Boss01_Guard(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 2, 0x500); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (this->timers[TIMER_CURRENT_ACTION] != 0) { + this->swordAndShieldCollisionEnabled = true; + } else { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + this->canGuardOrEvade = true; +} + +void Boss01_SetupKick(Boss01* this, PlayState* play) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaKickAnim, this->animMorphFrames2); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaKickAnim); + this->actionFunc = Boss01_Kick; +} + +/** + * Plays the kick animation to completion, then transitions back to waiting. + */ +void Boss01_Kick(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + this->kickAndShieldBashCollisionEnabled = true; +} + +void Boss01_SetupShieldBash(Boss01* this, PlayState* play) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaShieldBashAnim, this->animMorphFrames2); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaShieldBashAnim); + this->actionFunc = Boss01_ShieldBash; +} + +/** + * Plays the shield bash animation to completion, then transitions back to waiting. + */ +void Boss01_ShieldBash(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } + + this->kickAndShieldBashCollisionEnabled = true; +} + +/** + * If Odolwa is not currently in the "damaged" state (where he's crouched on one knee and breathing heavily), this + * function will transition him to that state and start a 20 frame timer; when this timer reaches 0, he will recover and + * jump away. However, if he is already in the "damaged" state, and if the player attacked him with an attack that has + * the ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK damage effect, then one of two things will happen: + * - If the attack hit while his current action timer is 7 or more, the timer will be set to 20 again; this will keep + * Odolwa in the "damaged" state for longer and allow the player to attack him more. + * - If the attack hit while his current action timer is 6 or less, Odolwa will disable all of his collision for 20 + * frames to ensure that the player cannot hit him with subsequent attacks. + */ +void Boss01_SetupDamaged(Boss01* this, PlayState* play, u8 damageEffect) { + if (this->actionFunc != Boss01_Damaged) { + this->timers[TIMER_CURRENT_ACTION] = 20; + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaDamagedStartAnim, 0.0f); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaDamagedStartAnim); + this->actionFunc = Boss01_Damaged; + } else if (damageEffect == ODOLWA_DMGEFF_DAMAGE_TIMER_CHECK) { + if (this->timers[TIMER_CURRENT_ACTION] > 5) { + this->disableCollisionTimer = 20; + } else { + this->timers[TIMER_CURRENT_ACTION] = 20; + } + } +} + +/** + * Plays Odolwa's damaged animation until the current action timer reaches 0, then makes him jump away. If the player + * attacks Odolwa while he's "downed" in this state, the current action timer can be manipulated in Boss01_SetupDamaged, + * so it's possible for Odolwa to stay in this state for a long time with well-timed attacks. + */ +void Boss01_Damaged(Boss01* this, PlayState* play) { + SkelAnime_Update(&this->skelAnime); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaDamagedLoopAnim, this->animMorphFrames2); + this->animEndFrame = 1000.0f; + } + + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SetupJump(this, play, false); + } + + Boss01_SpawnDustAtFeet(this, play, 1); +} + +void Boss01_UpdateDamage(Boss01* this, PlayState* play) { + Player* player = GET_PLAYER(play); + u8 damage; + s32 i; + + if (this->shieldCollider.elements[ODOLWA_SHIELD_COLLIDER_SHIELD].info.bumperFlags & BUMP_HIT) { + this->bodyInvincibilityTimer = 5; + if (this->damageTimer == 0) { + ColliderInfo* acHitInfo = this->shieldCollider.elements[ODOLWA_SHIELD_COLLIDER_SHIELD].info.acHitInfo; + + if (acHitInfo->toucher.dmgFlags == DMG_SWORD_BEAM) { + 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, 3, CLEAR_TAG_PARAMS(CLEAR_TAG_LARGE_LIGHT_RAYS)); + Actor_PlaySfx(&this->actor, NA_SE_IT_SHIELD_BOUND); + this->damageTimer = 5; + } + } + } else if (this->damageTimer == 0) { + for (i = 0; i < ODOLWA_SWORD_COLLIDER_MAX; i++) { + if (this->swordCollider.elements[i].info.toucherFlags & TOUCH_HIT) { + this->swordCollider.elements[i].info.toucherFlags &= ~TOUCH_HIT; + player->pushedYaw = this->actor.yawTowardsPlayer; + player->pushedSpeed = 15.0f; + } + } + + for (i = 0; i < ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_MAX; i++) { + if (this->kickAndShieldBashCollider.elements[i].info.toucherFlags & TOUCH_HIT) { + this->kickAndShieldBashCollider.elements[i].info.toucherFlags &= ~TOUCH_HIT; + player->pushedYaw = this->actor.yawTowardsPlayer; + player->pushedSpeed = 20.0f; + } + } + + for (i = 0; i < ODOLWA_COLLIDER_BODYPART_MAX; i++) { + if (this->bodyCollider.elements[i].info.bumperFlags & BUMP_HIT) { + this->bodyCollider.elements[i].info.bumperFlags &= ~BUMP_HIT; + + switch (this->actor.colChkInfo.damageEffect) { + case ODOLWA_DMGEFF_FREEZE: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_FROZEN_INIT; + goto stunned; + + case ODOLWA_DMGEFF_FIRE: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_FIRE_INIT; + break; + + case ODOLWA_DMGEFF_LIGHT_ORB: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_INIT; + 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)); + break; + + case ODOLWA_DMGEFF_ELECTRIC_STUN: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_ELECTRIC_SPARKS_INIT; + goto stunned; + + case ODOLWA_DMGEFF_BLUE_LIGHT_ORB: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_BLUE_LIGHT_ORB_INIT; + 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, 3, + CLEAR_TAG_PARAMS(CLEAR_TAG_LARGE_LIGHT_RAYS)); + break; + + case ODOLWA_DMGEFF_STUN: + stunned: + Boss01_SetupStunned(this, play); + this->damageTimer = 15; + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_FREEZE); + this->canGuardOrEvade = false; + return; + + default: + break; + } + + damage = this->actor.colChkInfo.damage; + sMothSwarm->unk_148 = 0; + + if (this->actor.colChkInfo.damageEffect == ODOLWA_DMGEFF_STUN) { + //! @bug: unreachable code. If Odolwa's damage effect is ODOLWA_DMGEFF_STUN, we early-return out of + //! the function in the above switch statement. + Boss01_SetupStunned(this, play); + this->damageTimer = 15; + } else if (this->actor.colChkInfo.damageEffect == ODOLWA_DMGEFF_DAZE) { + Boss01_SetupDazed(this, play); + Audio_PlaySfx_AtPos(&sOdolwaDamageSfxPos, NA_SE_EN_MIBOSS_DAMAGE_OLD); + this->damageTimer = 15; + } else { + this->damageFlashTimer = 15; + this->damageTimer = 5; + this->actor.colChkInfo.health -= damage; + if ((s8)this->actor.colChkInfo.health <= 0) { + Boss01_SetupDeathCutscene(this, play); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_DEAD_OLD); + Enemy_StartFinishingBlow(play, &this->actor); + } else { + Boss01_SetupDamaged(this, play, this->actor.colChkInfo.damageEffect); + Audio_PlaySfx_AtPos(&sOdolwaDamageSfxPos, NA_SE_EN_MIBOSS_DAMAGE_OLD); + } + } + + this->canGuardOrEvade = false; + break; + } + } + } +} + +void Boss01_SetupSummonMoths(Boss01* this, PlayState* play) { + Animation_MorphToLoop(&this->skelAnime, &gOdolwaMothSummonDanceAnim, -5.0f); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaMothSummonDanceAnim); + this->actionFunc = Boss01_SummonMoths; + this->summonMothsTimer = 0; +} + +/** + * Plays a specific dancing animation and summons moths after 30 frames. After 131 frames pass, Odolwa will transition + * back to waiting; notably, this function always transitions him to a specific wait rather than choosing a random wait + * action like every other non-cutscene function. + */ +void Boss01_SummonMoths(Boss01* this, PlayState* play) { + this->summonMothsTimer++; + SkelAnime_Update(&this->skelAnime); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_VOICE3_OLD - SFX_FLAG); + + if (Animation_OnFrame(&this->skelAnime, this->animEndFrame)) { + this->skelAnime.curFrame = this->animEndFrame - 20.0f; + } + + if (this->summonMothsTimer >= 30) { + sMothSwarm->actor.world.pos.x = this->bodyPartsPos[ODOLWA_BODYPART_HEAD].x; + sMothSwarm->actor.world.pos.y = this->bodyPartsPos[ODOLWA_BODYPART_HEAD].y; + sMothSwarm->actor.world.pos.z = this->bodyPartsPos[ODOLWA_BODYPART_HEAD].z; + sMothSwarm->actor.world.rot.y = this->actor.world.rot.y; + + if (this->summonMothsTimer == 30) { + sMothSwarm->unk_148 = 100; + Actor_PlaySfx(&this->actor, NA_SE_SY_TRANSFORM_MASK_FLASH); + } + } + + Math_ApproachS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 5, 0x500); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (this->summonMothsTimer >= 131) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_SPIN_DANCE); + } +} + +void Boss01_SetupDeathCutscene(Boss01* this, PlayState* play) { + Animation_MorphToPlayOnce(&this->skelAnime, &gOdolwaDeathAnim, this->animMorphFrames1); + this->animEndFrame = Animation_GetLastFrame(&gOdolwaDeathAnim); + this->actionFunc = Boss01_DeathCutscene; + Actor_PlaySfx(&this->actor, NA_SE_EN_DAIOCTA_DAMAGE); + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + this->disableCollisionTimer = 1000; + this->cutsceneTimer = 0; + this->cutsceneState = ODOLWA_DEATH_CS_STATE_STARTED; + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 1); + sMothSwarm->unk_144 = 250; + func_800BC154(play, &play->actorCtx, &sMothSwarm->actor, ACTORCAT_BOSS); +} + +/** + * Handles the entirety of Odolwa's death cutscene, including manipulating the camera, starting the "boss defeated" + * theme, spawning the Heart Container and blue warp, surrounding Odolwa with flames, etc. Notably, the main Odolwa + * instance is *not* killed in this function, and it stays in this state forever. Instead, Odolwa is teleported far + * above the ceiling so the player cannot see or interact with him. + */ +void Boss01_DeathCutscene(Boss01* this, PlayState* play) { + Vec3f subCamOffset; + f32 diffX; + f32 diffZ; + Camera* mainCam = Play_GetCamera(play, CAM_ID_MAIN); + + this->disableCollisionTimer = 1000; + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + SkelAnime_Update(&this->skelAnime); + this->cutsceneTimer++; + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + + switch (this->cutsceneState) { + case ODOLWA_DEATH_CS_STATE_STARTED: + if (CutsceneManager_GetCurrentCsId() != -1) { + break; + } + + Cutscene_StartManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_1); + this->subCamId = Play_CreateSubCamera(play); + Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STATUS_WAIT); + Play_ChangeCameraStatus(play, this->subCamId, CAM_STATUS_ACTIVE); + this->cutsceneTimer = 0; + this->cutsceneState = ODOLWA_DEATH_CS_STATE_PLAY_ANIM_AND_FALL_FORWARD; + this->subCamEye.x = mainCam->eye.x; + this->subCamEye.y = mainCam->eye.y; + this->subCamEye.z = mainCam->eye.z; + this->subCamAt.x = mainCam->at.x; + this->subCamAt.y = mainCam->at.y; + this->subCamAt.z = mainCam->at.z; + diffX = this->subCamEye.x - this->actor.world.pos.x; + diffZ = this->subCamEye.z - this->actor.world.pos.z; + this->deathCsInitialSubCamRot = Math_Atan2F_XY(diffZ, diffX); + this->deathCsSubCamRot = -0.5f; + // fallthrough + case ODOLWA_DEATH_CS_STATE_PLAY_ANIM_AND_FALL_FORWARD: + if (this->cutsceneTimer < 15) { + Math_ApproachF(&this->actor.world.pos.x, 0.0f, 0.1f, 5.0f); + Math_ApproachF(&this->actor.world.pos.z, 0.0f, 0.1f, 5.0f); + } + + if (this->cutsceneTimer == 70) { + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, NA_BGM_CLEAR_BOSS | SEQ_FLAG_ASYNC); + } + + if (this->cutsceneTimer == 71) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIBOSS_GND2_OLD); + this->cutsceneState = ODOLWA_DEATH_CS_STATE_BURST_INTO_FLAMES_AND_SHRINK; + this->cutsceneTimer = 0; + } + // fallthrough + case ODOLWA_DEATH_CS_STATE_BURST_INTO_FLAMES_AND_SHRINK: + Math_ApproachF(&this->deathCsSubCamRot, 1.3f, 0.1f, 0.008f); + subCamOffset.x = 0.0f; + subCamOffset.y = 30.0f; + subCamOffset.z = 300.0f; + Matrix_RotateYF(this->deathCsInitialSubCamRot + this->deathCsSubCamRot, MTXMODE_NEW); + Matrix_MultVec3f(&subCamOffset, &this->subCamEyeNext); + this->subCamEyeNext.x += this->pelvisPos.x; + this->subCamEyeNext.y += this->pelvisPos.y; + this->subCamEyeNext.z += this->pelvisPos.z; + Math_ApproachF(&this->subCamEye.x, this->subCamEyeNext.x, 0.1f, 40.0f); + Math_ApproachF(&this->subCamEye.y, this->subCamEyeNext.y, 0.1f, 40.0f); + Math_ApproachF(&this->subCamEye.z, this->subCamEyeNext.z, 0.1f, 40.0f); + Math_ApproachF(&this->subCamAt.x, this->pelvisPos.x, 0.1f, 70.0f); + Math_ApproachF(&this->subCamAt.y, this->pelvisPos.y + 50.0f, 0.1f, 70.0f); + Math_ApproachF(&this->subCamAt.z, this->pelvisPos.z, 0.1f, 70.0f); + + if (this->cutsceneTimer >= 71) { + s16 i; + s16 fireCount; + Vec3f fireAccel; + Vec3f firePos; + s16 bodyPartIndex; + + fireAccel = gZeroVec3f; + fireAccel.y = 0.03f; + + if (this->cutsceneTimer > 80) { + Math_ApproachZeroF(&this->actor.scale.y, 0.1f, this->deathShrinkSpeed * 0.00075f); + Math_ApproachF(&this->deathShrinkSpeed, 1.0f, 1.0f, 0.01f); + } + + if (this->cutsceneTimer > 120) { + fireCount = 1; + } else { + fireCount = 2; + } + + for (i = 0; i < fireCount; i++) { + bodyPartIndex = Rand_ZeroFloat(14.9f); + firePos.x = this->bodyPartsPos[bodyPartIndex].x + Rand_CenteredFloat(40.0f); + firePos.y = this->bodyPartsPos[bodyPartIndex].y - 10.0f; + firePos.z = this->bodyPartsPos[bodyPartIndex].z + Rand_CenteredFloat(40.0f); + EffectSsKFire_Spawn(play, &firePos, &gZeroVec3f, &fireAccel, Rand_ZeroFloat(30.0f) + 30.0f, 0); + } + + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_EXTINCT_LEV - SFX_FLAG); + } + + if (this->cutsceneTimer == 71) { + Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.focus.pos.x, this->actor.focus.pos.y, + this->actor.focus.pos.z, 0, 0, 0, BHEART_PARAM_NORMAL); + } + + if (this->cutsceneTimer == 180) { + static f32 sBlueWarpSpawnsX[] = { 0.0f, 350.0f, -350.0f, 350.0f, -350.0f }; + static f32 sBlueWarpSpawnsZ[] = { 0.0f, 350.0f, 350.0f, -350.0f, -350.0f }; + Player* player = GET_PLAYER(play); + f32 warpX; + f32 warpZ; + s32 i; + + for (i = 0; i < ARRAY_COUNT(sBlueWarpSpawnsX); i++) { + warpX = sBlueWarpSpawnsX[i]; + warpZ = sBlueWarpSpawnsZ[i]; + + if (((fabsf(warpX - this->actor.focus.pos.x) < 220.0f) && + (fabsf(warpZ - this->actor.focus.pos.z) < 220.0f)) || + ((fabsf(warpX - player->actor.world.pos.x) < 220.0f) && + (fabsf(warpZ - player->actor.world.pos.z) < 220.0f))) { + } else { + break; + } + } + + Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, warpX, 0.0f, warpZ, 0, 0, 0, + ENDOORWARP1_FF_1); + this->cutsceneState = ODOLWA_DEATH_CS_STATE_DONE; + mainCam->eye = this->subCamEye; + mainCam->eyeNext = this->subCamEye; + mainCam->at = this->subCamAt; + func_80169AFC(play, this->subCamId, 0); + this->subCamId = SUB_CAM_ID_DONE; + Cutscene_StopManual(play, &play->csCtx); + func_800B7298(play, &this->actor, PLAYER_CSACTION_END); + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + } + break; + + case ODOLWA_DEATH_CS_STATE_DONE: + this->actor.world.pos.y = 5000.0f; + break; + } + + if (this->subCamId != SUB_CAM_ID_DONE) { + Vec3f at; + + // It seems like this code was copied from Boss01_IntroCutscene, since this function doesn't set + // screenShakeMagnitude to anything, and thus it is always 0.0f; all of the code involving screen + // shake here could be removed, since it is effectively useless. + this->screenShakeOffsetY = Math_CosS(play->gameplayFrames * 0x8000) * this->screenShakeMagnitude; + Math_ApproachZeroF(&this->screenShakeMagnitude, 1.0f, 0.75f); + at = this->subCamAt; + at.y += this->screenShakeOffsetY; + Play_SetCameraAtEye(play, this->subCamId, &at, &this->subCamEye); + } +} + +void Boss01_SetupStunned(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_Stunned; + this->timers[TIMER_CURRENT_ACTION] = 40; + Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_BLUE, 120, COLORFILTER_BUFFLAG_OPA, 40); +} + +/** + * Turns Odolwa blue, disables all animations, and makes him wait in place for 40 frames, after which he will transition + * to waiting. This is not to be confused with his "dazed" state, which is commonly referred to as Odolwa "being + * stunned"; this form of stun, where the enemy turns blue and stops their animations, is very common among regular + * enemies, but it's very rare to see on a boss, so the term "stunned" was used here for consistency with other enemies. + */ +void Boss01_Stunned(Boss01* this, PlayState* play) { + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (this->timers[TIMER_CURRENT_ACTION] == 0) { + Boss01_SetupWait(this, play, ODOLWA_WAIT_RANDOM); + } +} + +/** + * Spawns two ice shards on all of Odolwa's body parts that fly off in random directions. + */ +void Boss01_Thaw(Boss01* this, PlayState* play) { + static Color_RGBA8 sIcePrimColor = { 170, 255, 255, 255 }; + static Color_RGBA8 sIceEnvColor = { 200, 200, 255, 255 }; + static Vec3f sIceAccel = { 0.0f, -1.0f, 0.0f }; + Vec3f pos; + Vec3f velocity; + s32 i; + + SoundSource_PlaySfxAtFixedWorldPos(play, this->bodyPartsPos, 30, NA_SE_EV_ICE_BROKEN); + + for (i = 0; i < ODOLWA_BODYPART_MAX * 2; i++) { + velocity.x = Rand_CenteredFloat(7.0f); + velocity.z = Rand_CenteredFloat(7.0f); + velocity.y = Rand_ZeroFloat(6.0f) + 4.0f; + + pos.x = this->bodyPartsPos[i / 2].x + velocity.x; + pos.y = this->bodyPartsPos[i / 2].y + velocity.y; + pos.z = this->bodyPartsPos[i / 2].z + velocity.z; + + EffectSsEnIce_Spawn(play, &pos, Rand_ZeroFloat(1.0f) + 1.5f, &velocity, &sIceAccel, &sIcePrimColor, + &sIceEnvColor, 30); + } +} + +/** + * Returns true if Odolwa's model is rotated such that he is looking at the player *and* if the player's model is + * rotated such that they are looking at Odolwa. + */ +s32 Boss01_ArePlayerAndOdolwaFacing(Boss01* this, PlayState* play) { + Player* player = GET_PLAYER(play); + + if ((ABS_ALT(BINANG_SUB(this->actor.yawTowardsPlayer, this->actor.shape.rot.y)) < 0x3000) && + (ABS_ALT(BINANG_SUB(this->actor.yawTowardsPlayer, BINANG_ROT180(player->actor.shape.rot.y))) < 0x3000)) { + return true; + } + + return false; +} + +void Boss01_Update(Actor* thisx, PlayState* play2) { + Boss01* this = THIS; + PlayState* play = play2; + s32 i; + Player* player = GET_PLAYER(play); + f32 diffX; + f32 diffY; + f32 diffZ; + s16 targetHeadRotY; + s16 targetHeadRotX; + s32 pad; + + if (ODOLWA_GET_TYPE(&this->actor) == ODOLWA_TYPE_AFTERIMAGE) { + if (KREG(63) == 0) { + DECR(this->timers[TIMER_AFTERIMAGE_DESPAWN]); + this->actionFunc(this, play); + } + return; + } + + Math_Vec3f_Copy(&sOdolwaDamageSfxPos, &this->actor.projectedPos); + + play->envCtx.lightSetting = 0; + play->envCtx.prevLightSetting = 1; + Math_ApproachZeroF(&play->envCtx.lightBlend, 1.0f, 0.03f); + + this->frameCounter++; + + if (KREG(63) == 0) { + this->phaseFrameCounter++; + if (this->phaseFrameCounter == 800) { + sOdolwa->actor.hintId = TATL_HINT_ID_ODOLWA_CLOSE_TO_PHASE_TWO; + } + + this->canGuardOrEvade = false; + this->swordAndShieldCollisionEnabled = false; + this->isPerformingVerticalSlash = false; + this->afterimageSpawnFrameMask = 0; + this->kickAndShieldBashCollisionEnabled = false; + this->swordState = ODOLWA_SWORD_STATE_INACTIVE; + + for (i = 0; i < ARRAY_COUNT(this->timers); i++) { + DECR(this->timers[i]); + } + + DECR(this->damageTimer); + DECR(this->damageFlashTimer); + + this->actor.flags |= ACTOR_FLAG_TARGETABLE; + this->actionFunc(this, play); + Actor_MoveWithGravity(&this->actor); + this->actor.world.pos.x += this->additionalVelocityX; + this->actor.world.pos.z += this->additionalVelocityZ; + } + + Actor_UpdateBgCheckInfo(play, &this->actor, 50.0f, 150.0f, 100.0f, UPDBGCHECKINFO_FLAG_1 | UPDBGCHECKINFO_FLAG_4); + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + Math_ApproachZeroF(&this->additionalVelocityX, 1.0f, 1.0f); + Math_ApproachZeroF(&this->additionalVelocityZ, 1.0f, 1.0f); + } + + this->actor.shape.rot = this->actor.world.rot; + + if (this->disableCollisionTimer == 0) { + Boss01_UpdateDamage(this, play); + + if (this->bodyInvincibilityTimer == 0) { + CollisionCheck_SetAC(play, &play->colChkCtx, &this->bodyCollider.base); + } else { + this->bodyInvincibilityTimer--; + for (i = 0; i < ODOLWA_COLLIDER_BODYPART_MAX; i++) { + this->bodyCollider.elements[i].info.bumperFlags &= ~BUMP_HIT; + } + } + + if (this->swordAndShieldCollisionEnabled) { + CollisionCheck_SetAC(play, &play->colChkCtx, &this->shieldCollider.base); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->shieldCollider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->swordCollider.base); + } + + if (this->swordState != ODOLWA_SWORD_STATE_INACTIVE) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->swordCollider.base); + } + + if (this->kickAndShieldBashCollisionEnabled) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->kickAndShieldBashCollider.base); + } + } else { + this->disableCollisionTimer--; + for (i = 0; i < ODOLWA_COLLIDER_BODYPART_MAX; i++) { + this->bodyCollider.elements[i].info.bumperFlags &= ~BUMP_HIT; + } + } + + CollisionCheck_SetOC(play, &play->colChkCtx, &this->bodyCollider.base); + + // If Odolwa and the player are *not* facing each other, Odolwa will *not* block or jump away from the player's + // attacks, even if canGuardOrEvade is set to true. This allows the player to hit Odolwa even during states where he + // normally evades attacks, so long as the player is far enough to the side or behind him. + if (this->canGuardOrEvade && + ((player->unk_D57 != 0) || ((player->unk_ADC != 0) && (this->actor.xzDistToPlayer <= 120.0f))) && + Boss01_ArePlayerAndOdolwaFacing(this, play)) { + if ((Rand_ZeroOne() < 0.25f) && (this->actionFunc != Boss01_Guard)) { + Boss01_SetupJump(this, play, false); + this->disableCollisionTimer = 10; + } else if ((player->unk_ADC != 0) && (this->actor.xzDistToPlayer <= 120.0f)) { + Boss01_SetupGuard(this, play, true); + } else { + Boss01_SetupGuard(this, play, false); + } + } + + if (this->canGuardOrEvade) { + Boss01_JumpAwayFromExplosive(this, play); + } + + if (((this->frameCounter & (this->afterimageSpawnFrameMask - 1)) == 0) && (this->afterimageSpawnFrameMask != 0)) { + s16 afterimageTimer = (this->actionFunc == Boss01_SpinAttack) ? 4 : 10; + s32 pad; + Boss01* child = + (Boss01*)Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_BOSS_01, 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, afterimageTimer, ODOLWA_TYPE_AFTERIMAGE); + + if (child != NULL) { + for (i = 0; i < ODOLWA_LIMB_MAX; i++) { + child->skelAnime.jointTable[i] = this->skelAnime.jointTable[i]; + } + } + } + + Boss01_UpdateEffects(this, play); + + if (sOdolwaMusicStartTimer != 0) { + sOdolwaMusicStartTimer--; + if (sOdolwaMusicStartTimer == 0) { + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, NA_BGM_BOSS | SEQ_FLAG_ASYNC); + } + } + + if (this->lookAtPlayer) { + targetHeadRotY = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; + if ((targetHeadRotY > 0x3800) || (targetHeadRotY < -0x3800)) { + targetHeadRotY = 0; + } + + diffX = player->actor.world.pos.x - this->actor.focus.pos.x; + diffY = KREG(36) + ((player->actor.world.pos.y + 25.0f) - this->actor.focus.pos.y); + diffZ = player->actor.world.pos.z - this->actor.focus.pos.z; + targetHeadRotX = Math_Atan2S(diffY, sqrtf(SQ(diffX) + SQ(diffZ))); + + // This line of code ensures that *not* following the player is the default behavior; if you want Odolwa's head + // to track the player, you'll need to set this variable to true for every single frame you want this behavior. + this->lookAtPlayer = false; + } else { + targetHeadRotX = 0; + targetHeadRotY = 0; + } + + Math_ApproachS(&this->headRotY, targetHeadRotY, 3, 0x2000); + Math_ApproachS(&this->headRotX, targetHeadRotX, 3, 0x2000); + + DECR(this->drawDmgEffTimer); + + switch (this->drawDmgEffState) { + case ODOLWA_DRAW_DMGEFF_STATE_NONE: + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + this->drawDmgEffTimer = 0; + this->drawDmgEffAlpha = 0.0f; + break; + + case ODOLWA_DRAW_DMGEFF_STATE_FIRE_INIT: + this->drawDmgEffAlpha = 1.0f; + this->drawDmgEffScale = 0.0f; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + this->drawDmgEffTimer = 40; + this->drawDmgEffState++; + Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 120, COLORFILTER_BUFFLAG_OPA, 60); + // fallthrough + case ODOLWA_DRAW_DMGEFF_STATE_FIRE_ACTIVE: + if (this->drawDmgEffTimer == 0) { + Math_ApproachZeroF(&this->drawDmgEffAlpha, 1.0f, 0.02f); + if (this->drawDmgEffAlpha == 0.0f) { + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_NONE; + } + } else { + Math_ApproachF(&this->drawDmgEffScale, 1.0f, 0.1f, 0.5f); + } + break; + + case ODOLWA_DRAW_DMGEFF_STATE_FROZEN_INIT: + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_SFX; + this->drawDmgEffTimer = 40; + this->drawDmgEffState++; + this->drawDmgEffAlpha = 1.0f; + this->drawDmgEffScale = 0.0f; + this->drawDmgEffFrozenSteamScale = 1.0f; + // fallthrough + case ODOLWA_DRAW_DMGEFF_STATE_FROZEN_ACTIVE: + if (this->drawDmgEffTimer == 0) { + Boss01_Thaw(this, play); + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_NONE; + break; + } + + if (this->drawDmgEffTimer == 50) { + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX; + } + + Math_ApproachF(&this->drawDmgEffScale, 1.0f, 1.0f, 0.08f); + Math_ApproachF(&this->drawDmgEffFrozenSteamScale, 1.0f, 0.05f, 0.05f); + break; + + case ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_INIT: + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_LIGHT_ORBS; + this->drawDmgEffTimer = 40; + this->drawDmgEffScale = 1.0f; + goto lightOrbInitCommon; + + case ODOLWA_DRAW_DMGEFF_STATE_BLUE_LIGHT_ORB_INIT: + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_BLUE_LIGHT_ORBS; + this->drawDmgEffTimer = 40; + this->drawDmgEffScale = 3.0f; + lightOrbInitCommon: + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_ACTIVE; + this->drawDmgEffAlpha = 1.0f; + // fallthrough + case ODOLWA_DRAW_DMGEFF_STATE_LIGHT_ORB_ACTIVE: + if (this->drawDmgEffTimer == 0) { + Math_ApproachZeroF(&this->drawDmgEffScale, 1.0f, 0.03f); + if (this->drawDmgEffScale == 0.0f) { + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_NONE; + this->drawDmgEffAlpha = 0.0f; + } + } else { + Math_ApproachF(&this->drawDmgEffScale, 1.5f, 0.5f, 0.5f); + } + break; + + case ODOLWA_DRAW_DMGEFF_STATE_ELECTRIC_SPARKS_INIT: + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_ELECTRIC_SPARKS_SMALL; + this->drawDmgEffTimer = 50; + this->drawDmgEffAlpha = 1.0f; + this->drawDmgEffScale = (KREG(18) * 0.1f) + 1.0f; + this->drawDmgEffState++; + // fallthrough + case ODOLWA_DRAW_DMGEFF_STATE_ELECTRIC_SPARKS_ACTIVE: + if (this->drawDmgEffTimer == 0) { + Math_ApproachZeroF(&this->drawDmgEffScale, 1.0f, 0.05f); + if (this->drawDmgEffScale == 0.0f) { + this->drawDmgEffState = ODOLWA_DRAW_DMGEFF_STATE_NONE; + this->drawDmgEffAlpha = 0.0f; + } + } + break; + + default: + break; + } +} + +/** + * Draws the sword trail that follows Odolwa's sword during horizontal and vertical slashes. + */ +void Boss01_DrawSwordTrail(Boss01* this, PlayState* play) { + static u8 sSwordTrailOuterVertexIndices[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 }; + static u8 sSwordTrailInnerVertexIndices[] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21 }; + Vtx* vtx; + u32 i; + + OPEN_DISPS(play->state.gfxCtx); + + vtx = Lib_SegmentedToVirtual(&gOdolwaSwordTrailVtx); + + for (i = 0; i < ARRAY_COUNT(sSwordTrailOuterVertexIndices); i++) { + vtx[sSwordTrailOuterVertexIndices[i]].v.ob[0] = cosf((i * M_PI) / sSwordTrailAngularRangeDivisor) * 200.0f; + vtx[sSwordTrailOuterVertexIndices[i]].v.ob[1] = 0; + vtx[sSwordTrailOuterVertexIndices[i]].v.ob[2] = sinf((i * M_PI) / sSwordTrailAngularRangeDivisor) * 200.0f; + + vtx[sSwordTrailInnerVertexIndices[i]].v.ob[0] = cosf((i * M_PI) / sSwordTrailAngularRangeDivisor) * 100.0f; + vtx[sSwordTrailInnerVertexIndices[i]].v.ob[1] = 0; + vtx[sSwordTrailInnerVertexIndices[i]].v.ob[2] = sinf((i * M_PI) / sSwordTrailAngularRangeDivisor) * 100.0f; + } + + gSPSegment( + POLY_XLU_DISP++, 0x08, + Gfx_TwoTexScroll(play->state.gfxCtx, G_TX_RENDERTILE, 0, 0, 32, 32, 1, play->gameplayFrames * 18, 0, 32, 32)); + gDPPipeSync(POLY_XLU_DISP++); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, (u8)sOdolwaSwordTrailAlpha); + + Matrix_Translate(this->actor.world.pos.x + sOdolwaSwordTrailPosX, this->actor.world.pos.y + sOdolwaSwordTrailPosY, + this->actor.world.pos.z + sOdolwaSwordTrailPosZ, MTXMODE_NEW); + Matrix_RotateYF(BINANG_TO_RAD(this->actor.shape.rot.y), MTXMODE_APPLY); + Matrix_RotateXFApply(sOdolwaSwordTrailRotX); + Matrix_RotateZF(sOdolwaSwordTrailRotZ, MTXMODE_APPLY); + Matrix_RotateYF(sOdolwaSwordTrailRotY, MTXMODE_APPLY); + + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, gOdolwaSwordTrailDL); + + CLOSE_DISPS(play->state.gfxCtx); +} + +s32 Boss01_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) { + Boss01* this = THIS; + + if (limbIndex == ODOLWA_LIMB_HEAD) { + // The rot variable here is in model space, whereas the headRot variables are in world space. + // Odolwa's head is lying on its side in model space, which is why this assignment looks weird. + rot->x += this->headRotY; + rot->y += this->headRotX; + } + + return false; +} + +static s8 sLimbToColliderBodyParts[] = { + BODYPART_NONE, // ODOLWA_LIMB_NONE + BODYPART_NONE, // ODOLWA_LIMB_ROOT + ODOLWA_COLLIDER_BODYPART_PELVIS, // ODOLWA_LIMB_PELVIS + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LEG_ROOT + ODOLWA_COLLIDER_BODYPART_RIGHT_THIGH, // ODOLWA_LIMB_RIGHT_THIGH + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_LEG_ROOT + ODOLWA_COLLIDER_BODYPART_RIGHT_SHIN, // ODOLWA_LIMB_RIGHT_SHIN + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_FOOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LEG_ROOT + ODOLWA_COLLIDER_BODYPART_LEFT_THIGH, // ODOLWA_LIMB_LEFT_THIGH + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_LEG_ROOT + ODOLWA_COLLIDER_BODYPART_LEFT_SHIN, // ODOLWA_LIMB_LEFT_SHIN + BODYPART_NONE, // ODOLWA_LIMB_LEFT_FOOT + BODYPART_NONE, // ODOLWA_LIMB_UPPER_BODY_ROOT + BODYPART_NONE, // ODOLWA_LIMB_UPPER_BODY_WRAPPER + ODOLWA_COLLIDER_BODYPART_TORSO, // ODOLWA_LIMB_TORSO + BODYPART_NONE, // ODOLWA_LIMB_LEFT_ARM_ROOT + ODOLWA_COLLIDER_BODYPART_LEFT_UPPER_ARM, // ODOLWA_LIMB_LEFT_UPPER_ARM + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_ARM_ROOT + ODOLWA_COLLIDER_BODYPART_LEFT_FOREARM, // ODOLWA_LIMB_LEFT_FOREARM + BODYPART_NONE, // ODOLWA_LIMB_LEFT_BANGLE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_HAND_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_HAND + BODYPART_NONE, // ODOLWA_LIMB_SHIELD + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_ARM_ROOT + ODOLWA_COLLIDER_BODYPART_RIGHT_UPPER_ARM, // ODOLWA_LIMB_RIGHT_UPPER_ARM + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_ARM_ROOT + ODOLWA_COLLIDER_BODYPART_RIGHT_FOREARM, // ODOLWA_LIMB_RIGHT_FOREARM + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_BANGLE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_HAND_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_HAND + BODYPART_NONE, // ODOLWA_LIMB_SWORD + ODOLWA_COLLIDER_BODYPART_HEAD, // ODOLWA_LIMB_HEAD + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_EARRING_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_EARRING + BODYPART_NONE, // ODOLWA_LIMB_LEFT_EARRING_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_EARRING + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_TIP + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_TIP + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_CENTER_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_TIP + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa +}; + +static Vec3f sLimbColliderOffsets[ODOLWA_COLLIDER_BODYPART_MAX] = { + { 1300.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_HEAD + { 1000.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_TORSO + { 0.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_PELVIS + { 1000.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_LEFT_UPPER_ARM + { 1000.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_LEFT_FOREARM + { 1000.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_RIGHT_UPPER_ARM + { 1000.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_RIGHT_FOREARM + { 1500.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_LEFT_THIGH + { 1500.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_LEFT_SHIN + { 1500.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_RIGHT_THIGH + { 1500.0f, 0.0f, 0.0f }, // ODOLWA_COLLIDER_BODYPART_RIGHT_SHIN +}; + +static Vec3f sShieldColliderOffset = { 0.0f, 500.0f, 0.0f }; + +static Vec3f sSwordBaseColliderOffset = { 500.0f, -2500.0f, 0.0f }; + +static Vec3f sSwordTipColliderOffset = { 1500.0f, -7000.0f, 0.0f }; + +/** + * When Odolwa performs a horizontal slash, his sword is very likely to be too high to hit the player. In order to make + * the attack effective, there is an additional collider projected from Odolwa's pelvis that sweeps across the floor + * during his horizontal slash. This variable controls the offset of this collider from his pelvis limb. + */ +static Vec3f sHorizontalSlashPelvisColliderOffset = { 5000.0f, 0.0f, 9000.0f }; + +static s8 sLimbToBodyParts[] = { + BODYPART_NONE, // ODOLWA_LIMB_NONE + BODYPART_NONE, // ODOLWA_LIMB_ROOT + ODOLWA_BODYPART_PELVIS, // ODOLWA_LIMB_PELVIS + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LEG_ROOT + ODOLWA_BODYPART_RIGHT_THIGH, // ODOLWA_LIMB_RIGHT_THIGH + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_LEG_ROOT + ODOLWA_BODYPART_RIGHT_SHIN, // ODOLWA_LIMB_RIGHT_SHIN + ODOLWA_BODYPART_RIGHT_FOOT, // ODOLWA_LIMB_RIGHT_FOOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LEG_ROOT + ODOLWA_BODYPART_LEFT_THIGH, // ODOLWA_LIMB_LEFT_THIGH + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_LEG_ROOT + ODOLWA_BODYPART_LEFT_SHIN, // ODOLWA_LIMB_LEFT_SHIN + ODOLWA_BODYPART_LEFT_FOOT, // ODOLWA_LIMB_LEFT_FOOT + BODYPART_NONE, // ODOLWA_LIMB_UPPER_BODY_ROOT + BODYPART_NONE, // ODOLWA_LIMB_UPPER_BODY_WRAPPER + ODOLWA_BODYPART_TORSO, // ODOLWA_LIMB_TORSO + BODYPART_NONE, // ODOLWA_LIMB_LEFT_ARM_ROOT + ODOLWA_BODYPART_LEFT_UPPER_ARM, // ODOLWA_LIMB_LEFT_UPPER_ARM + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_ARM_ROOT + ODOLWA_BODYPART_LEFT_FOREARM, // ODOLWA_LIMB_LEFT_FOREARM + BODYPART_NONE, // ODOLWA_LIMB_LEFT_BANGLE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_HAND_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_HAND + ODOLWA_BODYPART_SHIELD, // ODOLWA_LIMB_SHIELD + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_ARM_ROOT + ODOLWA_BODYPART_RIGHT_UPPER_ARM, // ODOLWA_LIMB_RIGHT_UPPER_ARM + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_ARM_ROOT + ODOLWA_BODYPART_RIGHT_FOREARM, // ODOLWA_LIMB_RIGHT_FOREARM + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_BANGLE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_HAND_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_HAND + ODOLWA_BODYPART_SWORD, // ODOLWA_LIMB_SWORD + ODOLWA_BODYPART_HEAD, // ODOLWA_LIMB_HEAD + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_EARRING_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_EARRING + BODYPART_NONE, // ODOLWA_LIMB_LEFT_EARRING_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_EARRING + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_RIGHT_PLUME_TIP + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_LEFT_PLUME_TIP + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_BASE + BODYPART_NONE, // ODOLWA_LIMB_CENTER_LOWER_PLUME_ROOT + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_MIDDLE + BODYPART_NONE, // ODOLWA_LIMB_CENTER_PLUME_TIP + BODYPART_NONE, // Doesn't correspond to a real limb on Odolwa +}; + +void Boss01_PostLimbDraw(PlayState* play2, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + Boss01* this = THIS; + PlayState* play = play2; + s8 index; + Vec3f pos; + + if (limbIndex == ODOLWA_LIMB_HEAD) { + Matrix_MultZero(&this->actor.focus.pos); + } + + index = sLimbToBodyParts[limbIndex]; + if (index > BODYPART_NONE) { + Matrix_MultZero(&this->bodyPartsPos[index]); + } + + index = sLimbToColliderBodyParts[limbIndex]; + if (index > BODYPART_NONE) { + Matrix_MultVec3f(&sLimbColliderOffsets[index], &pos); + Boss01_SetColliderSphere(index, &this->bodyCollider, &pos); + } + + if (limbIndex == ODOLWA_LIMB_PELVIS) { + Matrix_MultZero(&this->pelvisPos); + if (this->swordState == ODOLWA_SWORD_STATE_HORIZONTAL_SLASH) { + Matrix_MultVec3f(&sHorizontalSlashPelvisColliderOffset, &pos); + Boss01_SetColliderSphere(ODOLWA_SWORD_COLLIDER_PELVIS, &this->swordCollider, &pos); + } + } + + if (limbIndex == ODOLWA_LIMB_SWORD) { + Matrix_MultVec3f(&sSwordBaseColliderOffset, &pos); + Boss01_SetColliderSphere(ODOLWA_SWORD_COLLIDER_SWORD_BASE, &this->swordCollider, &pos); + Matrix_MultVec3f(&sSwordTipColliderOffset, &pos); + Boss01_SetColliderSphere(ODOLWA_SWORD_COLLIDER_SWORD_TIP, &this->swordCollider, &pos); + } + + if (limbIndex == ODOLWA_LIMB_SHIELD) { + Matrix_MultVec3f(&sShieldColliderOffset, &pos); + Boss01_SetColliderSphere(ODOLWA_SHIELD_COLLIDER_SHIELD, &this->shieldCollider, &pos); + Boss01_SetColliderSphere(ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_SHIELD, &this->kickAndShieldBashCollider, &pos); + } + + if (limbIndex == ODOLWA_LIMB_RIGHT_FOOT) { + Matrix_MultZero(&this->feetPos[0]); + } + + if (limbIndex == ODOLWA_LIMB_LEFT_FOOT) { + Matrix_MultZero(&this->feetPos[1]); + Matrix_MultZero(&pos); + Boss01_SetColliderSphere(ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_LEFT_FOOT, &this->kickAndShieldBashCollider, + &pos); + } + + if (limbIndex == ODOLWA_LIMB_HEAD) { + OPEN_DISPS(play->state.gfxCtx); + + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 0, 0, 255); + gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 0, 0); + + Matrix_Push(); + Matrix_Translate(1470.0f, 400.0f, 450.0f, MTXMODE_APPLY); + Matrix_Scale(0.35f, 0.35f, 0.35f, MTXMODE_APPLY); + Matrix_ReplaceRotation(&play->billboardMtxF); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, gOdolwaEyeDL); + Matrix_Pop(); + + Matrix_Push(); + Matrix_Translate(1470.0f, -360.0f, 450.0f, MTXMODE_APPLY); + Matrix_Scale(0.35f, 0.35f, 0.35f, MTXMODE_APPLY); + Matrix_ReplaceRotation(&play->billboardMtxF); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, gOdolwaEyeDL); + Matrix_Pop(); + + CLOSE_DISPS(play->state.gfxCtx); + } +} + +void Boss01_Draw(Actor* thisx, PlayState* play) { + static Vec3f sDefaultPelvisColliderOffset = { 10000.0f, 10000.0f, 10000.0f }; + Boss01* this = THIS; + s32 pad; + u8* tex = GRAPH_ALLOC(play->state.gfxCtx, ODOLWA_SHADOW_TEX_SIZE); + + OPEN_DISPS(play->state.gfxCtx); + + // When Odolwa is performing a horizontal slash, an additional collider offset from his pelvis will sweep across the + // floor and damage the player. However, this collider should not be present all the time, so it is offset very far + // out-of-bounds here. If Odolwa is performing a horizontal slash, then Boss01_PostLimbDraw (which is called later + // in this function) will offset it properly for this frame. + Boss01_SetColliderSphere(ODOLWA_SWORD_COLLIDER_PELVIS, &this->swordCollider, &sDefaultPelvisColliderOffset); + + Gfx_SetupDL25_Opa(play->state.gfxCtx); + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + + if (this->damageFlashTimer & 1) { + POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, 255, 0, 0, 255, 900, 1099); + } + + SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, + Boss01_OverrideLimbDraw, Boss01_PostLimbDraw, &this->actor); + + POLY_OPA_DISP = Play_SetFog(play, POLY_OPA_DISP); + + Boss01_GenShadowTex(tex, this, play); + Boss01_DrawShadowTex(tex, this, play); + + if (sOdolwaSwordTrailAlpha > 0.0f) { + Boss01_DrawSwordTrail(this, play); + Math_ApproachZeroF(&sOdolwaSwordTrailAlpha, 1.0f, 50.0f); + } + + Actor_DrawDamageEffects(play, &this->actor, this->bodyPartsPos, ODOLWA_BODYPART_MAX, this->drawDmgEffScale, + this->drawDmgEffFrozenSteamScale, this->drawDmgEffAlpha, this->drawDmgEffType); + + Boss01_DrawEffects(play); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void Boss01_Afterimage_Draw(Actor* thisx, PlayState* play) { + Boss01* this = THIS; + s32 pad; + Boss01* parent = (Boss01*)this->actor.parent; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL25_Opa(play->state.gfxCtx); + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, 50, 0, 40, 255, 900, 1099); + SkelAnime_DrawFlexOpa(play, parent->skelAnime.skeleton, this->skelAnime.jointTable, parent->skelAnime.dListCount, + NULL, NULL, &this->actor); + POLY_OPA_DISP = Play_SetFog(play, POLY_OPA_DISP); + + CLOSE_DISPS(play->state.gfxCtx); +} + +/** + * These four arrays encode circular shadow maps of various sizes. For an array of length N, the shadow map is N rows + * tall, and each entry in the array describes the start and end point of the shadow within a given row (the exact + * values of the start and end points are determined by the loops within Boss01_FillShadowTex). To illustrate using the + * sShadowSmallMap as an example: + * -3 -2 -1 0 1 + * ------------- + * 0 0 1 0 0 + * 0 1 1 1 0 + * 1 1 1 1 1 + * 1 1 1 1 1 + * 0 1 1 1 0 + * 0 0 1 0 0 + */ +static s32 sShadowSmallMap[] = { + 1, 2, 3, 3, 2, 1, +}; + +static s32 sShadowMediumMap[] = { + 2, 3, 4, 4, 4, 3, 2, +}; + +static s32 sShadowLargeMap[] = { + 2, 3, 4, 4, 4, 4, 3, 2, +}; + +static s32 sShadowExtraLargeMap[] = { + 2, 4, 5, 5, 6, 6, 6, 6, 5, 5, 4, 2, +}; + +static s32 sParentShadowBodyParts[ODOLWA_BODYPART_MAX] = { + ODOLWA_BODYPART_TORSO, // ODOLWA_BODYPART_HEAD + BODYPART_NONE, // ODOLWA_BODYPART_TORSO + ODOLWA_BODYPART_TORSO, // ODOLWA_BODYPART_PELVIS + ODOLWA_BODYPART_TORSO, // ODOLWA_BODYPART_LEFT_UPPER_ARM + ODOLWA_BODYPART_LEFT_UPPER_ARM, // ODOLWA_BODYPART_LEFT_FOREARM + ODOLWA_BODYPART_LEFT_FOREARM, // ODOLWA_BODYPART_SHIELD + ODOLWA_BODYPART_TORSO, // ODOLWA_BODYPART_RIGHT_UPPER_ARM + ODOLWA_BODYPART_RIGHT_UPPER_ARM, // ODOLWA_BODYPART_RIGHT_FOREARM + ODOLWA_BODYPART_RIGHT_FOREARM, // ODOLWA_BODYPART_SWORD + ODOLWA_BODYPART_PELVIS, // ODOLWA_BODYPART_RIGHT_THIGH + ODOLWA_BODYPART_RIGHT_THIGH, // ODOLWA_BODYPART_RIGHT_SHIN + ODOLWA_BODYPART_RIGHT_SHIN, // ODOLWA_BODYPART_RIGHT_FOOT + ODOLWA_BODYPART_PELVIS, // ODOLWA_BODYPART_LEFT_THIGH + ODOLWA_BODYPART_LEFT_THIGH, // ODOLWA_BODYPART_LEFT_SHIN + ODOLWA_BODYPART_LEFT_SHIN, // ODOLWA_BODYPART_LEFT_FOOT +}; + +static u8 sShadowSizes[ODOLWA_BODYPART_MAX] = { + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_HEAD + ODOLWA_SHADOW_SIZE_EXTRA_LARGE, // ODOLWA_BODYPART_TORSO + ODOLWA_SHADOW_SIZE_EXTRA_LARGE, // ODOLWA_BODYPART_PELVIS + ODOLWA_SHADOW_SIZE_LARGE, // ODOLWA_BODYPART_LEFT_UPPER_ARM + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_LEFT_FOREARM + ODOLWA_SHADOW_SIZE_EXTRA_LARGE, // ODOLWA_BODYPART_SHIELD + ODOLWA_SHADOW_SIZE_LARGE, // ODOLWA_BODYPART_RIGHT_UPPER_ARM + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_RIGHT_FOREARM + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_SWORD + ODOLWA_SHADOW_SIZE_LARGE, // ODOLWA_BODYPART_RIGHT_THIGH + ODOLWA_SHADOW_SIZE_MEDIUM, // ODOLWA_BODYPART_RIGHT_SHIN + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_RIGHT_FOOT + ODOLWA_SHADOW_SIZE_LARGE, // ODOLWA_BODYPART_LEFT_THIGH + ODOLWA_SHADOW_SIZE_MEDIUM, // ODOLWA_BODYPART_LEFT_SHIN + ODOLWA_SHADOW_SIZE_SMALL, // ODOLWA_BODYPART_LEFT_FOOT +}; + +void Boss01_FillShadowTex(Boss01* this, u8* tex, f32 weight) { + s32 index; + s32 i; + s32 baseX; + s32 baseY; + s32 x; + s32 y = 0; + s32 addY; + Vec3f lerp; + Vec3f pos; + Vec3f startVec; + + for (i = 0; i < ODOLWA_BODYPART_MAX; i++) { + if ((weight == 0.0f) || (y = sParentShadowBodyParts[i]) > BODYPART_NONE) { + if (weight > 0.0f) { + VEC3F_LERPIMPDST(&lerp, &this->bodyPartsPos[i], &this->bodyPartsPos[y], weight); + + pos.x = lerp.x - this->actor.world.pos.x; + pos.y = lerp.y - this->actor.world.pos.y + 76.0f + 30.0f + 30.0f + 100.0f; + pos.z = lerp.z - this->actor.world.pos.z; + } else { + pos.x = this->bodyPartsPos[i].x - this->actor.world.pos.x; + pos.y = this->bodyPartsPos[i].y - this->actor.world.pos.y + 76.0f + 30.0f + 30.0f + 100.0f; + pos.z = this->bodyPartsPos[i].z - this->actor.world.pos.z; + } + + Matrix_MultVec3f(&pos, &startVec); + + startVec.x *= 0.2f; + startVec.y *= 0.2f; + + baseX = (u16)(s32)(startVec.x + 32.0f); + baseY = (u16)((s32)startVec.y * 64); + + if (sShadowSizes[i] == ODOLWA_SHADOW_SIZE_EXTRA_LARGE) { + for (y = 0, addY = -0x180; y < ARRAY_COUNT(sShadowExtraLargeMap); y++, addY += 0x40) { + for (x = -sShadowExtraLargeMap[y]; x < sShadowExtraLargeMap[y]; x++) { + index = baseX + x + baseY + addY; + if ((index >= 0) && (index < ODOLWA_SHADOW_TEX_SIZE)) { + tex[index] = 255; + } + } + } + } else if (sShadowSizes[i] == ODOLWA_SHADOW_SIZE_LARGE) { + for (y = 0, addY = -0x100; y < ARRAY_COUNT(sShadowLargeMap); y++, addY += 0x40) { + for (x = -sShadowLargeMap[y]; x < sShadowLargeMap[y]; x++) { + index = baseX + x + baseY + addY; + if ((index >= 0) && (index < ODOLWA_SHADOW_TEX_SIZE)) { + tex[index] = 255; + } + } + } + } else if (sShadowSizes[i] == ODOLWA_SHADOW_SIZE_MEDIUM) { + for (y = 0, addY = -0xC0; y < ARRAY_COUNT(sShadowMediumMap); y++, addY += 0x40) { + for (x = -sShadowMediumMap[y]; x < sShadowMediumMap[y] - 1; x++) { + index = baseX + x + baseY + addY; + if ((index >= 0) && (index < ODOLWA_SHADOW_TEX_SIZE)) { + tex[index] = 255; + } + } + } + } else { + for (y = 0, addY = -0x80; y < ARRAY_COUNT(sShadowSmallMap); y++, addY += 0x40) { + for (x = -sShadowSmallMap[y]; x < sShadowSmallMap[y] - 1; x++) { + index = baseX + x + baseY + addY; + if ((index >= 0) && (index < ODOLWA_SHADOW_TEX_SIZE)) { + tex[index] = 255; + } + } + } + } + } + } +} + +void Boss01_GenShadowTex(u8* tex, Boss01* this, PlayState* play) { + s32* iter = (s32*)tex; + s16 i; + + for (i = 0; i < (s32)(ODOLWA_SHADOW_TEX_SIZE / sizeof(s32)); i++, iter++) { + *iter = 0; + } + + Matrix_RotateXFNew(1.0f); + + for (i = 0; i <= 5; i++) { + Boss01_FillShadowTex(this, tex, i / 5.0f); + } +} + +/** + * Draws Odolwa's dynamic shadow underneath him. + */ +void Boss01_DrawShadowTex(u8* tex, Boss01* this, PlayState* play) { + s32 pad[2]; + f32 alpha; + GraphicsContext* gfxCtx = play->state.gfxCtx; + + OPEN_DISPS(gfxCtx); + + Gfx_SetupDL25_Opa(play->state.gfxCtx); + + alpha = (400.0f - this->actor.world.pos.y) * (1.0f / 400.0f); + alpha = CLAMP_MIN(alpha, 0.0f); + alpha = CLAMP_MAX(alpha, 1.0f); + + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, (s8)(alpha * 80.0f)); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0); + Matrix_Translate(this->actor.world.pos.x, this->actor.floorHeight, this->actor.world.pos.z - 20.0f, MTXMODE_NEW); + Matrix_Scale(1.65f, 1.0f, 1.65f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gOdolwaShadowMaterialDL); + gDPLoadTextureBlock(POLY_OPA_DISP++, tex, G_IM_FMT_I, G_IM_SIZ_8b, ODOLWA_SHADOW_TEX_WIDTH, + ODOLWA_SHADOW_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 6, 6, + G_TX_NOLOD, G_TX_NOLOD); + gSPDisplayList(POLY_OPA_DISP++, gOdolwaShadowModelDL); + + CLOSE_DISPS(gfxCtx); +} + +void Boss01_Bug_SetupCrawl(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_Bug_Crawl; + Animation_MorphToLoop(&this->skelAnime, &gOdolwaBugCrawlAnim, -5.0f); +} + +/** + * If an explosive is present, the bug will crawl quickly towards it. Otherwise, it will crawl slowly towards the player + * and chase them around. + */ +void Boss01_Bug_Crawl(Boss01* this, PlayState* play) { + s32 pad; + Player* player = GET_PLAYER(play); + f32 targetSpeed = 3.0f; + Actor* targetActor = &player->actor; + Actor* explosive = play->actorCtx.actorLists[ACTORCAT_EXPLOSIVES].first; + s16 maxStep = 0x3E8; + + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + this->skelAnime.playSpeed = 1.0f; + + while (explosive != NULL) { + if (explosive->params == 1) { + explosive = explosive->next; + } else { + targetSpeed = 5.0f; + targetActor = explosive; + maxStep = 0x7D0; + this->skelAnime.playSpeed = 1.5f; + break; + } + } + + SkelAnime_Update(&this->skelAnime); + Actor_PlaySfx(&this->actor, NA_SE_EN_MB_INSECT_WALK - SFX_FLAG); + Math_ApproachF(&this->actor.speed, targetSpeed, 1.0f, 1.0f); + Math_ApproachS(&this->actor.world.rot.y, + Math_Atan2S(targetActor->world.pos.x - this->actor.world.pos.x, + targetActor->world.pos.z - this->actor.world.pos.z), + 5, maxStep); + } +} + +void Boss01_Bug_SetupDamaged(Boss01* this, PlayState* play) { + if ((s8)this->actor.colChkInfo.health > 0) { + this->actionFunc = Boss01_Bug_Damaged; + this->timers[TIMER_BUG_CURRENT_ACTION] = 30; + } else { + this->timers[TIMER_BUG_CURRENT_ACTION] = 15; + Enemy_StartFinishingBlow(play, &this->actor); + Boss01_Bug_SetupDead(this, play); + } +} + +/** + * Stops the bug's animations and makes it wait in place for 30 frames, then it starts crawling again. + */ +void Boss01_Bug_Damaged(Boss01* this, PlayState* play) { + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (this->timers[TIMER_BUG_CURRENT_ACTION] == 0) { + Boss01_Bug_SetupCrawl(this, play); + } +} + +void Boss01_Bug_SetupDead(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_Bug_Dead; + this->additionalVelocityZ = 0.0f; + this->additionalVelocityX = 0.0f; + this->bugDrawDmgEffAlpha = 1.0f; + this->actor.speed = -15.0f; + this->actor.velocity.y = 12.0f; + this->actor.world.rot.y = this->actor.yawTowardsPlayer; + this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; +} + +void Boss01_Bug_SetupStunned(Boss01* this, PlayState* play) { + this->actionFunc = Boss01_Bug_Stunned; + this->timers[TIMER_BUG_CURRENT_ACTION] = 40; + Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_BLUE, 120, COLORFILTER_BUFFLAG_OPA, 40); +} + +/** + * Turns the bug blue, stops its animations, and makes it wait in place for 40 frames, then it starts crawling again. + */ +void Boss01_Bug_Stunned(Boss01* this, PlayState* play) { + Math_ApproachZeroF(&this->actor.speed, 1.0f, 1.0f); + if (this->timers[TIMER_BUG_CURRENT_ACTION] == 0) { + Boss01_Bug_SetupCrawl(this, play); + } +} + +/** + * Flings the bug backwards, spinning randomly, until it hits the ground. After it lands, it shrinks and spawns a single + * blue flame. The bug will die and drop an item once the flame fades enough. + */ +void Boss01_Bug_Dead(Boss01* this, PlayState* play) { + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_EXTINCT_LEV - SFX_FLAG); + Math_ApproachZeroF(&this->actor.speed, 1.0f, 2.0f); + Math_ApproachZeroF(&this->actor.scale.y, 1.0f, 0.00075f); + Math_ApproachF(&this->bugDrawDmgEffScale, 1.0f, 0.5f, 0.15f); + + if (this->actor.scale.y < 0.001f) { + Math_ApproachZeroF(&this->bugDrawDmgEffAlpha, 1.0f, 0.05f); + if (this->bugDrawDmgEffAlpha < 0.01f) { + Actor_Kill(&this->actor); + Item_DropCollectibleRandom(play, NULL, &this->actor.world.pos, 0x60); + sOdolwaBugCount--; + } + } + + Math_ApproachS(&this->actor.shape.rot.x, 0, 1, 0x800); + } else { + this->actor.shape.rot.x -= 0x2000; + this->actor.shape.rot.z += 0x1000; + } +} + +void Boss01_Bug_UpdateDamage(Boss01* this, PlayState* play) { + Vec3f additionalVelocity; + s32 pad[2]; + u8 damage; + ColliderInfo* acHitInfo; + OdolwaEffect* effect = play->specialEffects; + + if (this->bugACCollider.base.acFlags & AC_HIT) { + this->bugACCollider.base.acFlags &= ~AC_HIT; + acHitInfo = this->bugACCollider.info.acHitInfo; + + if (this->damageTimer == 0) { + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + if (acHitInfo->toucher.dmgFlags & 0x300000) { + this->damageTimer = 10; + Matrix_MultVecZ(-10.0f, &additionalVelocity); + this->additionalVelocityX = additionalVelocity.x; + this->additionalVelocityZ = additionalVelocity.z; + } else { + this->damageTimer = 15; + this->damageFlashTimer = 15; + Matrix_MultVecZ(-20.0f, &additionalVelocity); + this->additionalVelocityX = additionalVelocity.x; + this->additionalVelocityZ = additionalVelocity.z; + if (this->actor.colChkInfo.damageEffect == BUG_DMGEFF_STUN) { + Boss01_Bug_SetupStunned(this, play); + Actor_PlaySfx(&this->actor, NA_SE_EN_COMMON_FREEZE); + } else { + damage = this->actor.colChkInfo.damage; + this->actor.colChkInfo.health -= damage; + Boss01_Bug_SetupDamaged(this, play); + Actor_PlaySfx(&this->actor, NA_SE_EN_MIZUBABA2_DAMAGE); + } + } + } + } + + // If Odolwa dies, instantly kill any bugs in the room too. Setting their y-velocity to 0 will skip the part of + // their death where they fly backwards; they will immediately shrink and burst into a single blue flame. + if ((sOdolwa != NULL) && (sOdolwa->actionFunc == Boss01_DeathCutscene)) { + Boss01_Bug_SetupDead(this, play); + this->actor.velocity.y = 0.0f; + this->actor.speed = 0.0f; + } + + if ((effect->type == ODOLWA_EFFECT_RING_OF_FIRE) && (effect->timer < 150)) { + f32 distXZ = sqrtf(SQ(effect->pos.x - this->actor.world.pos.x) + SQ(effect->pos.z - this->actor.world.pos.z)); + + // If the bugs touch the ring of fire, it will instantly kill them. Like before, setting the y-velocity to zero + // here will immediately make the bug shrink and burst into a single red flame. + if ((distXZ < (KREG(49) + 210.0f)) && (distXZ > (KREG(49) + 190.0f))) { + Actor_PlaySfx(&this->actor, NA_SE_EN_MIZUBABA2_DAMAGE); + Boss01_Bug_SetupDead(this, play); + this->damageFlashTimer = 15; + this->bugDrawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + this->actor.speed = 0.0f; + this->actor.velocity.y = 5.0f; + } + } +} + +void Boss01_Bug_Update(Actor* thisx, PlayState* play) { + Boss01* this = THIS; + s32 pad; + s32 i; + + this->frameCounter++; + + for (i = 0; i < ARRAY_COUNT(this->timers); i++) { + DECR(this->timers[i]); + } + + DECR(this->damageTimer); + DECR(this->damageFlashTimer); + + this->actionFunc(this, play); + + Actor_MoveWithGravity(&this->actor); + this->actor.world.pos.x += this->additionalVelocityX; + this->actor.world.pos.z += this->additionalVelocityZ; + Actor_SetFocus(&this->actor, 10.0f); + Actor_UpdateBgCheckInfo(play, &this->actor, 50.0f, 21.0f, 100.0f, UPDBGCHECKINFO_FLAG_1 | UPDBGCHECKINFO_FLAG_4); + if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { + Math_ApproachZeroF(&this->additionalVelocityX, 1.0f, 1.0f); + Math_ApproachZeroF(&this->additionalVelocityZ, 1.0f, 1.0f); + } + + if (this->actionFunc != Boss01_Bug_Dead) { + Boss01_Bug_UpdateDamage(this, play); + Collider_UpdateCylinder(&this->actor, &this->bugACCollider); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->bugACCollider.base); + Collider_UpdateCylinder(&this->actor, &this->bugATCollider); + CollisionCheck_SetAT(play, &play->colChkCtx, &this->bugATCollider.base); + this->actor.shape.rot = this->actor.world.rot; + } +} + +s32 Boss01_Bug_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, Actor* thisx) { + if ((limbIndex == ODOLWA_BUG_LIMB_BODY) && (sOdolwa->actionFunc == Boss01_SummonBugsCutscene)) { + *dList = gOdolwaBugDarkBodyDL; + } + + if (limbIndex == ODOLWA_BUG_LIMB_FRONT_RIGHT_UPPER_LEG) { + OPEN_DISPS(play->state.gfxCtx); + + if (sOdolwa->actionFunc == Boss01_SummonBugsCutscene) { + gSPDisplayList(POLY_OPA_DISP++, gOdolwaBugBrightLegMaterialDL); + } else { + gSPDisplayList(POLY_OPA_DISP++, gOdolwaBugDullLegMaterialDL); + } + + CLOSE_DISPS(play->state.gfxCtx); + } + + return false; +} + +void Boss01_Bug_Draw(Actor* thisx, PlayState* play) { + Boss01* this = THIS; + s32 pad; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL25_Opa(play->state.gfxCtx); + + if (this->damageFlashTimer & 1) { + POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, 255, 0, 0, 255, 900, 1099); + } + + SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, + Boss01_Bug_OverrideLimbDraw, NULL, &this->actor); + + POLY_OPA_DISP = Play_SetFog(play, POLY_OPA_DISP); + + Actor_DrawDamageEffects(play, &this->actor, &this->actor.world.pos, 1, this->bugDrawDmgEffScale, 0.0f, + this->bugDrawDmgEffAlpha, this->bugDrawDmgEffType); + + CLOSE_DISPS(play->state.gfxCtx); +} + +/** + * Spawns five dust clouds randomly around the block's current position. + */ +void Boss01_SpawnDustForFallingBlock(PlayState* play, Vec3f* blockPos, f32 scale) { + s16 i; + Vec3f velocity; + Vec3f accel; + Vec3f pos; + + for (i = 0; i < 5; i++) { + velocity.x = Rand_CenteredFloat(5.0f); + velocity.y = Rand_ZeroFloat(2.0f) + 1.0f; + velocity.z = Rand_CenteredFloat(5.0f); + accel.x = accel.z = 0.0f; + accel.y = -0.1f; + pos.x = (Rand_CenteredFloat(70.0f) * scale) + blockPos->x; + pos.y = (Rand_ZeroFloat(10.0f) * scale) + blockPos->y; + pos.z = (Rand_CenteredFloat(70.0f) * scale) + blockPos->z; + func_800B0EB0(play, &pos, &velocity, &accel, &sDustPrimColor, &sDustEnvColor, + (Rand_ZeroFloat(150.0f) + 350.0f) * scale, 10, Rand_ZeroFloat(5.0f) + 14.0f); + } +} + +/** + * Responsible for updating the falling blocks and the ring of fire, including spawning block fragments when a block + * hits the floor and damaging the player. + */ +void Boss01_UpdateEffects(Boss01* this, PlayState* play) { + OdolwaEffect* effect = (OdolwaEffect*)play->specialEffects; + Player* player = GET_PLAYER(play); + s16 i; + s16 j; + s16 temp; + f32 diffX; + f32 diffZ; + f32 temp2; + s32 type; + + for (i = 0; i < ODOLWA_EFFECT_COUNT; i++, effect++) { + if (effect->type != ODOLWA_EFFECT_NONE) { + effect->timer++; + type = effect->type; + if (type == ODOLWA_EFFECT_RING_OF_FIRE) { + if (effect->timer < 150) { + Math_ApproachF(&play->envCtx.lightBlend, (Math_SinS(effect->timer * 0x1000) * 0.1f) + 0.9f, 1.0f, + 0.2f); + sMothSwarm->unk_144 = 250; + + // If Odolwa dies, this will make the ring of fire start fading away immediately. + if ((sOdolwa != NULL) && (sOdolwa->actionFunc == Boss01_DeathCutscene)) { + effect->timer = 150; + } + + Audio_PlaySfx(NA_SE_EV_FIRE_PLATE - SFX_FLAG); + Math_ApproachF(&effect->scale, 1.0f, 0.1f, 0.3f); + + // The ring of fire has no collision, so this code is responsible for checking to see if the player + // is touching it. If they are, then it will set them on fire and damage them manually. + if (!(player->stateFlags3 & PLAYER_STATE3_1000) && (player->actor.world.pos.y < 70.0f)) { + diffX = effect->pos.x - player->actor.world.pos.x; + diffZ = effect->pos.z - player->actor.world.pos.z; + temp2 = sqrtf(SQ(diffX) + SQ(diffZ)); + + if (player->invincibilityTimer == 0) { + if ((temp2 < (KREG(49) + 210.0f)) && ((KREG(49) + 190.0f) < temp2)) { + for (j = 0; j < PLAYER_BODYPART_MAX; j++) { + player->flameTimers[j] = Rand_S16Offset(0, 200); + } + + player->isBurning = true; + temp = Math_Atan2S_XY(diffZ, diffX); + if ((KREG(49) + 100.0f) < temp2) { + temp += 0x8000; + } + + func_800B8D50(play, &this->actor, 10.0f, temp, 0.0f, 8); + } + } + } + } else { + Math_ApproachZeroF(&effect->alpha, 1.0f, 10.0f); + if (effect->alpha < 0.1f) { + effect->type = ODOLWA_EFFECT_NONE; + } + } + } else { + effect->pos.x += effect->velocity.x; + effect->pos.y += effect->velocity.y; + effect->pos.z += effect->velocity.z; + effect->velocity.y += effect->gravity; + + if (type == ODOLWA_EFFECT_FALLING_BLOCK) { + effect->rotY += effect->angularVelocityY; + effect->rotX += effect->angularVelocityX; + + if (!effect->isFallingBlockFragment) { + // Falling blocks have no collision, so this code is responsible for checking to see if the + // player is touching one. If they are, then it will damage them manually. + diffX = player->actor.world.pos.x - effect->pos.x; + temp2 = (player->actor.world.pos.y + 20.0f) - effect->pos.y; + diffZ = player->actor.world.pos.z - effect->pos.z; + + if ((SQ(diffX) + SQ(diffZ) + SQ(temp2)) < SQ(50.0f)) { + func_800B8D50(play, NULL, 0.0f, Rand_ZeroFloat(65526.0f), 0.0f, 8); + } + + if (effect->pos.y < 10.0f) { + effect->pos.y = 10.0f; + temp = (Rand_ZeroFloat(2.0f) + 4.0f); + for (j = 0; j < temp; j++) { + Boss01_SpawnEffectFallingBlock((OdolwaEffect*)play->specialEffects, &effect->pos, true); + } + + effect->type = ODOLWA_EFFECT_NONE; + SoundSource_PlaySfxAtFixedWorldPos(play, &effect->pos, 40, NA_SE_EV_WALL_BROKEN); + Boss01_SpawnDustForFallingBlock(play, &effect->pos, 1.0f); + CollisionCheck_SpawnShieldParticles(play, &effect->pos); + Actor_RequestQuakeAndRumble(&this->actor, play, 3, 10); + } + } else if ((effect->pos.y < 10.0f) && (effect->velocity.y < 0.0f)) { + effect->pos.y = 10.0f; + effect->type = ODOLWA_EFFECT_NONE; + Boss01_SpawnDustForFallingBlock(play, &effect->pos, 0.5f); + } + } + } + } + } +} + +/** + * Draws the falling blocks, the circle shadows that appear under the blocks as they fall, and the ring of fire. + */ +void Boss01_DrawEffects(PlayState* play) { + s32 pad; + s16 i; + f32 alpha; + OdolwaEffect* effect = play->specialEffects; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL25_Opa(play->state.gfxCtx); + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + + for (i = 1; i < ODOLWA_EFFECT_COUNT; i++, effect++) { + if (effect->type == ODOLWA_EFFECT_FALLING_BLOCK) { + Matrix_Translate(effect->pos.x, effect->pos.y, effect->pos.z, MTXMODE_NEW); + Matrix_RotateYS(effect->rotY, MTXMODE_APPLY); + Matrix_RotateXS(effect->rotX, MTXMODE_APPLY); + Matrix_Scale(effect->scale, effect->scale, effect->scale, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gOdolwaFallingBlockDL); + } + } + + effect = play->specialEffects; + Gfx_SetupDL44_Xlu(play->state.gfxCtx); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, 100); + + for (i = 1; i < ODOLWA_EFFECT_COUNT; i++, effect++) { + if (effect->type == ODOLWA_EFFECT_FALLING_BLOCK) { + Matrix_Translate(effect->pos.x, 0.0f, effect->pos.z, MTXMODE_NEW); + Matrix_Scale(effect->scale * 50.0f, 1.0f, effect->scale * 50.0f, MTXMODE_APPLY); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_K0(gCircleShadowDL)); + } + } + + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + effect = play->specialEffects; + Boss01_InitRand(1, 0x71A5, 0x263A); + + if (effect->type == ODOLWA_EFFECT_RING_OF_FIRE) { + gDPSetEnvColor(POLY_XLU_DISP++, 255, 10, 0, 0); + Matrix_Translate(effect->pos.x, 0.0f, effect->pos.z, MTXMODE_NEW); + + for (i = 0; i < 32; i++) { + Matrix_Push(); + alpha = effect->alpha - (Boss01_RandZeroOne() * 50.0f); + if (alpha < 0.0f) { + alpha = 0.0f; + } + + gDPSetPrimColor(POLY_XLU_DISP++, 0x80, 0x80, 255, 255, 0, (u8)alpha); + gSPSegment(POLY_XLU_DISP++, 0x08, + Gfx_TwoTexScroll(play->state.gfxCtx, G_TX_RENDERTILE, 0, 0, 32, 64, 1, 0, + ((effect->timer + (i * 10)) * -20) & 0x1FF, 32, 128)); + + Matrix_RotateYF(i * (M_PI / 16.0f), MTXMODE_APPLY); + Matrix_Translate(0.0f, 0.0f, KREG(49) + 200.0f, MTXMODE_APPLY); + Matrix_ReplaceRotation(&play->billboardMtxF); + if (Boss01_RandZeroOne() < 0.5f) { + Matrix_RotateYF(M_PI, MTXMODE_APPLY); + } + + Matrix_Scale(KREG(48) * 0.0001f + 0.018f, + ((0.007f + KREG(54) * 0.0001f) + (Boss01_RandZeroOne() * 30.0f * 0.0001f)) * effect->scale, + 1.0f, MTXMODE_APPLY); + + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, gEffFire1DL); + Matrix_Pop(); + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/src/overlays/actors/ovl_Boss_01/z_boss_01.h b/src/overlays/actors/ovl_Boss_01/z_boss_01.h index 37c8d4b006..3587751790 100644 --- a/src/overlays/actors/ovl_Boss_01/z_boss_01.h +++ b/src/overlays/actors/ovl_Boss_01/z_boss_01.h @@ -2,16 +2,153 @@ #define Z_BOSS_01_H #include "global.h" +#include "assets/objects/object_boss01/object_boss01.h" struct Boss01; +#define ODOLWA_GET_TYPE(thisx) ((thisx)->params) +#define ODOLWA_GET_AFTERIMAGE_DESPAWN_TIMER(thisx) ((thisx)->world.rot.z) + +#define ODOLWA_SHADOW_TEX_WIDTH 64 +#define ODOLWA_SHADOW_TEX_HEIGHT 64 +#define ODOLWA_SHADOW_TEX_SIZE ((s32)sizeof(u8[ODOLWA_SHADOW_TEX_HEIGHT][ODOLWA_SHADOW_TEX_WIDTH])) + typedef void (*Boss01ActionFunc)(struct Boss01*, PlayState*); +typedef enum OdolwaType { + /* 0 */ ODOLWA_TYPE_ODOLWA, + /* 10 */ ODOLWA_TYPE_BUG = 10, + /* 35 */ ODOLWA_TYPE_AFTERIMAGE = 35 +} OdolwaType; + +typedef enum OdolwaBodyPart { + /* 0 */ ODOLWA_BODYPART_HEAD, + /* 1 */ ODOLWA_BODYPART_TORSO, + /* 2 */ ODOLWA_BODYPART_PELVIS, + /* 3 */ ODOLWA_BODYPART_LEFT_UPPER_ARM, + /* 4 */ ODOLWA_BODYPART_LEFT_FOREARM, + /* 5 */ ODOLWA_BODYPART_SHIELD, + /* 6 */ ODOLWA_BODYPART_RIGHT_UPPER_ARM, + /* 7 */ ODOLWA_BODYPART_RIGHT_FOREARM, + /* 8 */ ODOLWA_BODYPART_SWORD, + /* 9 */ ODOLWA_BODYPART_RIGHT_THIGH, + /* 10 */ ODOLWA_BODYPART_RIGHT_SHIN, + /* 11 */ ODOLWA_BODYPART_RIGHT_FOOT, + /* 12 */ ODOLWA_BODYPART_LEFT_THIGH, + /* 13 */ ODOLWA_BODYPART_LEFT_SHIN, + /* 14 */ ODOLWA_BODYPART_LEFT_FOOT, + /* 15 */ ODOLWA_BODYPART_MAX +} OdolwaBodyPart; + +typedef enum OdolwaColliderBodyPart { + /* 0 */ ODOLWA_COLLIDER_BODYPART_HEAD, + /* 1 */ ODOLWA_COLLIDER_BODYPART_TORSO, + /* 2 */ ODOLWA_COLLIDER_BODYPART_PELVIS, + /* 3 */ ODOLWA_COLLIDER_BODYPART_LEFT_UPPER_ARM, + /* 4 */ ODOLWA_COLLIDER_BODYPART_LEFT_FOREARM, + /* 5 */ ODOLWA_COLLIDER_BODYPART_RIGHT_UPPER_ARM, + /* 6 */ ODOLWA_COLLIDER_BODYPART_RIGHT_FOREARM, + /* 7 */ ODOLWA_COLLIDER_BODYPART_LEFT_THIGH, + /* 8 */ ODOLWA_COLLIDER_BODYPART_LEFT_SHIN, + /* 9 */ ODOLWA_COLLIDER_BODYPART_RIGHT_THIGH, + /* 10 */ ODOLWA_COLLIDER_BODYPART_RIGHT_SHIN, + /* 11 */ ODOLWA_COLLIDER_BODYPART_MAX +} OdolwaColliderBodyPart; + +typedef enum OdolwaSwordCollider { + /* 0 */ ODOLWA_SWORD_COLLIDER_SWORD_BASE, + /* 1 */ ODOLWA_SWORD_COLLIDER_SWORD_TIP, + /* 2 */ ODOLWA_SWORD_COLLIDER_PELVIS, + /* 3 */ ODOLWA_SWORD_COLLIDER_MAX +} OdolwaSwordCollider; + +typedef enum OdolwaShieldCollider { + /* 0 */ ODOLWA_SHIELD_COLLIDER_SHIELD, + /* 1 */ ODOLWA_SHIELD_COLLIDER_MAX +} OdolwaShieldCollider; + +typedef enum OdolwaKickAndShieldBashCollider { + /* 0 */ ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_SHIELD, + /* 1 */ ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_LEFT_FOOT, + /* 2 */ ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_MAX +} OdolwaKickAndShieldBashCollider; + typedef struct Boss01 { /* 0x000 */ Actor actor; - /* 0x144 */ char unk_144[0x320]; + /* 0x144 */ s16 frameCounter; + /* 0x148 */ s32 phaseFrameCounter; + /* 0x14C */ union { + u8 jumpIfPlayerIsClose; + u8 shouldPerformFallingSlash; + u8 bugDrawDmgEffType; + }; + /* 0x14E */ s16 timers[3]; + /* 0x154 */ f32 animMorphFrames1; + /* 0x158 */ f32 animMorphFrames2; + /* 0x15C */ s16 damageTimer; + /* 0x15E */ s16 damageFlashTimer; + /* 0x160 */ u8 isPerformingVerticalSlash; // set, but never checked + /* 0x160 */ u8 landedFromJump; + /* 0x162 */ u8 waitType; + /* 0x163 */ u8 lookAtPlayer; + /* 0x164 */ SkelAnime skelAnime; + /* 0x1A8 */ f32 animEndFrame; + /* 0x1AC */ f32 prevJumpVelocityY; + /* 0x1B0 */ f32 runTargetPosAngularVelocityY; + /* 0x1B4 */ f32 runTargetPosRotY; + /* 0x1B8 */ u8 swordState; + /* 0x1B9 */ u8 kickAndShieldBashCollisionEnabled; + /* 0x1BA */ u8 swordAndShieldCollisionEnabled; + /* 0x1BB */ u8 canGuardOrEvade; + /* 0x1BC */ u8 bodyInvincibilityTimer; + /* 0x1BE */ s16 disableCollisionTimer; + /* 0x1C0 */ u8 afterimageSpawnFrameMask; // used as a bitmask with frameCounter to control how often to spawn afterimages + /* 0x1C1 */ u8 hasPlayedSummonBugCs; + /* 0x1C4 */ f32 additionalVelocityX; + /* 0x1C8 */ f32 additionalVelocityZ; + /* 0x1CC */ s16 headRotY; + /* 0x1CE */ s16 headRotX; + /* 0x1D0 */ Vec3s jointTable[ODOLWA_LIMB_MAX]; + /* 0x308 */ Vec3s morphTable[ODOLWA_LIMB_MAX]; + /* 0x440 */ Vec3f feetPos[2]; + /* 0x458 */ Vec3f pelvisPos; /* 0x464 */ Boss01ActionFunc actionFunc; - /* 0x468 */ char unk_468[0x674]; + /* 0x468 */ ColliderJntSph swordCollider; + /* 0x488 */ ColliderJntSphElement swordColliderElements[ODOLWA_SWORD_COLLIDER_MAX]; + /* 0x548 */ ColliderJntSph shieldCollider; + /* 0x568 */ ColliderJntSphElement shieldColliderElements[ODOLWA_SHIELD_COLLIDER_MAX]; + /* 0x5A8 */ ColliderJntSph bodyCollider; + /* 0x5C8 */ ColliderJntSphElement bodyColliderElements[ODOLWA_COLLIDER_BODYPART_MAX]; + /* 0x888 */ ColliderJntSph kickAndShieldBashCollider; + /* 0x8A8 */ ColliderJntSphElement kickAndShieldBashColliderElements[ODOLWA_KICK_AND_SHIELD_BASH_COLLIDER_MAX]; + /* 0x928 */ Vec3f bodyPartsPos[ODOLWA_BODYPART_MAX]; + /* 0x9DC */ union { + u32 cutsceneTimer; + u32 waitTimer; + u32 summonMothsTimer; + }; + /* 0x9E0 */ s16 cutsceneState; + /* 0x9E2 */ s16 subCamId; + /* 0x9E4 */ Vec3f subCamEye; + /* 0x9F0 */ Vec3f subCamAt; + /* 0x9FC */ Vec3f subCamUp; + /* 0xA08 */ Vec3f subCamEyeNext; + /* 0xA14 */ f32 deathCsInitialSubCamRot; + /* 0xA18 */ f32 deathCsSubCamRot; + /* 0xA1C */ f32 subCamVelocity; + /* 0xA20 */ f32 deathShrinkSpeed; + /* 0xA24 */ f32 screenShakeOffsetY; + /* 0xA28 */ f32 screenShakeMagnitude; + /* 0xA2C */ ColliderCylinder bugACCollider; + /* 0xA78 */ ColliderCylinder bugATCollider; + /* 0xAC4 */ f32 bugDrawDmgEffScale; + /* 0xAC8 */ f32 bugDrawDmgEffAlpha; + /* 0xACC */ f32 drawDmgEffScale; + /* 0xAD0 */ f32 drawDmgEffFrozenSteamScale; + /* 0xAD4 */ f32 drawDmgEffAlpha; + /* 0xAD8 */ u8 drawDmgEffType; + /* 0xAD9 */ u8 drawDmgEffState; + /* 0xADA */ s16 drawDmgEffTimer; } Boss01; // size = 0xADC #endif // Z_BOSS_01_H diff --git a/src/overlays/actors/ovl_Boss_02/z_boss_02.c b/src/overlays/actors/ovl_Boss_02/z_boss_02.c index 1957ac3695..e202a08cb8 100644 --- a/src/overlays/actors/ovl_Boss_02/z_boss_02.c +++ b/src/overlays/actors/ovl_Boss_02/z_boss_02.c @@ -69,9 +69,9 @@ u8 sIsInGiantMode; Boss02* sRedTwinmold; Boss02* sBlueTwinmold; Boss02* sTwinmoldStatic; -u8 sMusicStartTimer; +u8 sTwinmoldMusicStartTimer; DoorWarp1* sBlueWarp; -TwinmoldEffect sEffects[TWINMOLD_EFFECT_COUNT]; +TwinmoldEffect sTwinmoldEffects[TWINMOLD_EFFECT_COUNT]; static DamageTable sBlueTwinmoldDamageTable = { /* Deku Nut */ DMG_ENTRY(0, 0x0), @@ -588,7 +588,7 @@ void Boss02_Init(Actor* thisx, PlayState* play) { if (CHECK_WEEKEVENTREG(WEEKEVENTREG_CLEARED_STONE_TOWER_TEMPLE) && (TWINMOLD_GET_TYPE(&this->actor) == TWINMOLD_TYPE_RED)) { sBlueWarp = (DoorWarp1*)Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 0.0f, 60.0f, - 0.0f, 0, 0, 0, 1); + 0.0f, 0, 0, 0, ENDOORWARP1_FF_1); Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, 0.0f, 30.0f, -150.0f, 0, 1, 0, BHEART_PARAM_NORMAL); } @@ -597,14 +597,14 @@ void Boss02_Init(Actor* thisx, PlayState* play) { this->subCamUp.y = 1.0f; if (TWINMOLD_GET_TYPE(&this->actor) == TWINMOLD_TYPE_STATIC) { sTwinmoldStatic = this; - play->specialEffects = (void*)sEffects; + play->specialEffects = (void*)sTwinmoldEffects; this->actor.update = Boss02_Static_Update; this->actor.draw = Boss02_Static_Draw; this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; this->playerScale = 0.01f; if ((KREG(64) != 0) || CHECK_EVENTINF(EVENTINF_55) || (sBlueWarp != NULL)) { this->unk_1D20 = 0; - sMusicStartTimer = KREG(15) + 20; + sTwinmoldMusicStartTimer = KREG(15) + 20; } else { this->unk_1D20 = 1; } @@ -1346,9 +1346,9 @@ void Boss02_Static_Update(Actor* thisx, PlayState* play) { } if (sBlueWarp == NULL) { - if (sMusicStartTimer != 0) { - sMusicStartTimer--; - if (sMusicStartTimer == 0) { + if (sTwinmoldMusicStartTimer != 0) { + sTwinmoldMusicStartTimer--; + if (sTwinmoldMusicStartTimer == 0) { SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0, NA_BGM_BOSS | SEQ_FLAG_ASYNC); } } @@ -2173,7 +2173,7 @@ void func_809DEAC4(Boss02* this, PlayState* play) { if (this->unk_1D1C == 45) { func_800B7298(play, &this->actor, PLAYER_CSACTION_21); - sMusicStartTimer = KREG(91) + 43; + sTwinmoldMusicStartTimer = KREG(91) + 43; } if (this->unk_1D1C == 85) { @@ -2304,7 +2304,7 @@ void func_809DEAC4(Boss02* this, PlayState* play) { phi_f0 = 3155.0f; } sBlueWarp = (DoorWarp1*)Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, - 0.0f, phi_f0, 0.0f, 0, 0, 0, 1); + 0.0f, phi_f0, 0.0f, 0, 0, 0, ENDOORWARP1_FF_1); if (!sIsInGiantMode) { sBlueWarp->unk_203 = 0; diff --git a/src/overlays/actors/ovl_Boss_03/z_boss_03.c b/src/overlays/actors/ovl_Boss_03/z_boss_03.c index a9c2fd9d73..f96626785b 100644 --- a/src/overlays/actors/ovl_Boss_03/z_boss_03.c +++ b/src/overlays/actors/ovl_Boss_03/z_boss_03.c @@ -231,30 +231,30 @@ void Boss03_UpdateSphereElement(s32 index, ColliderJntSph* collider, Vec3f* sphe /* Start of RNG section */ -static s32 sRandSeed0; -static s32 sRandSeed1; -static s32 sRandSeed2; +s32 sGyorgRandSeed1; +s32 sGyorgRandSeed2; +s32 sGyorgRandSeed3; -void Boss03_SeedRand(s32 seed0, s32 seed1, s32 seed2) { - sRandSeed0 = seed0; - sRandSeed1 = seed1; - sRandSeed2 = seed2; +void Boss03_InitRand(s32 seedInit1, s32 seedInit2, s32 seedInit3) { + sGyorgRandSeed1 = seedInit1; + sGyorgRandSeed2 = seedInit2; + sGyorgRandSeed3 = seedInit3; } f32 Boss03_RandZeroOne(void) { - f32 rand; - // Wichmann-Hill algorithm - sRandSeed0 = (sRandSeed0 * 171) % 30269; - sRandSeed1 = (sRandSeed1 * 172) % 30307; - sRandSeed2 = (sRandSeed2 * 170) % 30323; + f32 randFloat; - rand = (sRandSeed0 / 30269.0f) + (sRandSeed1 / 30307.0f) + (sRandSeed2 / 30323.0f); - while (rand >= 1.0f) { - rand -= 1.0f; + sGyorgRandSeed1 = (sGyorgRandSeed1 * 171) % 30269; + sGyorgRandSeed2 = (sGyorgRandSeed2 * 172) % 30307; + sGyorgRandSeed3 = (sGyorgRandSeed3 * 170) % 30323; + + randFloat = (sGyorgRandSeed1 / 30269.0f) + (sGyorgRandSeed2 / 30307.0f) + (sGyorgRandSeed3 / 30323.0f); + while (randFloat >= 1.0f) { + randFloat -= 1.0f; } - return fabsf(rand); + return fabsf(randFloat); } /* End of RNG section */ @@ -483,7 +483,7 @@ void Boss03_Init(Actor* thisx, PlayState* play2) { this->actor.world.pos = sGyorgInitialPos; // Since Boss03_RandZeroOne is only used on this Init function, the resulting values end up being deterministic - Boss03_SeedRand(1, 29093, 9786); + Boss03_InitRand(1, 29093, 9786); for (i = 0; i < 5; i++) { f32 rand; diff --git a/src/overlays/actors/ovl_En_Fishing/z_en_fishing.c b/src/overlays/actors/ovl_En_Fishing/z_en_fishing.c index 9c1a810f81..c1320150a4 100644 --- a/src/overlays/actors/ovl_En_Fishing/z_en_fishing.c +++ b/src/overlays/actors/ovl_En_Fishing/z_en_fishing.c @@ -147,9 +147,9 @@ f32 sSubCamVelFactor; f32 D_80911F50; Vec3f sSinkingLureBasePos; f32 D_80911F64; -s32 sRandSeed0; -s32 sRandSeed1; -s32 sRandSeed2; +s32 sFishingRandSeed1; +s32 sFishingRandSeed2; +s32 sFishingRandSeed3; FishingProp sPondProps[POND_PROP_COUNT]; FishingGroupFish sGroupFishes[GROUP_FISH_COUNT]; f32 sFishGroupAngle1; @@ -404,26 +404,26 @@ void EnFishing_SetColliderElement(s32 index, ColliderJntSph* collider, Vec3f* po collider->elements[index].dim.modelSphere.radius * collider->elements[index].dim.scale * scale * 1.6f; } -void EnFishing_SeedRand(s32 seed0, s32 seed1, s32 seed2) { - sRandSeed0 = seed0; - sRandSeed1 = seed1; - sRandSeed2 = seed2; +void EnFishing_InitRand(s32 seedInit1, s32 seedInit2, s32 seedInit3) { + sFishingRandSeed1 = seedInit1; + sFishingRandSeed2 = seedInit2; + sFishingRandSeed3 = seedInit3; } f32 EnFishing_RandZeroOne(void) { - f32 rand; - // Wichmann-Hill algorithm - sRandSeed0 = (sRandSeed0 * 171) % 30269; - sRandSeed1 = (sRandSeed1 * 172) % 30307; - sRandSeed2 = (sRandSeed2 * 170) % 30323; + f32 randFloat; - rand = (sRandSeed0 / 30269.0f) + (sRandSeed1 / 30307.0f) + (sRandSeed2 / 30323.0f); - while (rand >= 1.0f) { - rand -= 1.0f; + sFishingRandSeed1 = (sFishingRandSeed1 * 171) % 30269; + sFishingRandSeed2 = (sFishingRandSeed2 * 172) % 30307; + sFishingRandSeed3 = (sFishingRandSeed3 * 170) % 30323; + + randFloat = (sFishingRandSeed1 / 30269.0f) + (sFishingRandSeed2 / 30307.0f) + (sFishingRandSeed3 / 30323.0f); + while (randFloat >= 1.0f) { + randFloat -= 1.0f; } - return fabsf(rand); + return fabsf(randFloat); } s16 EnFishing_SmoothStepToS(s16* pValue, s16 target, s16 scale, s16 step) { @@ -730,7 +730,7 @@ void EnFishing_InitPondProps(EnFishing* this, PlayState* play) { Vec3f colliderPos; s16 i; - EnFishing_SeedRand(1, 29100, 9786); + EnFishing_InitRand(1, 29100, 9786); for (i = 0; i < POND_PROP_COUNT; i++) { if (sPondPropInits[i].type == FS_PROP_INIT_STOP) { diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index 8690c35c85..78775d9e2a 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -6819,7 +6819,7 @@ 0x808FC5AC:("EnFr_Destroy",), 0x808FC5BC:("EnFr_Update",), 0x808FC6C0:("EnFishing_SetColliderElement",), - 0x808FC770:("EnFishing_SeedRand",), + 0x808FC770:("EnFishing_InitRand",), 0x808FC790:("EnFishing_RandZeroOne",), 0x808FC8B8:("EnFishing_SmoothStepToS",), 0x808FC964:("EnFishing_SpawnRipple",), @@ -9217,81 +9217,81 @@ 0x809D0090:("EnMttag_Init",), 0x809D0138:("EnMttag_Destroy",), 0x809D0168:("EnMttag_Update",), - 0x809D0530:("func_809D0530",), - 0x809D0550:("func_809D0550",), - 0x809D0678:("func_809D0678",), - 0x809D082C:("func_809D082C",), - 0x809D089C:("func_809D089C",), - 0x809D092C:("func_809D092C",), - 0x809D0AA4:("func_809D0AA4",), + 0x809D0530:("Boss01_InitRand",), + 0x809D0550:("Boss01_RandZeroOne",), + 0x809D0678:("Boss01_SpawnEffectFallingBlock",), + 0x809D082C:("Boss01_SpawnEffectRingOfFire",), + 0x809D089C:("Boss01_SetColliderSphere",), + 0x809D092C:("Boss01_SelectAttack",), + 0x809D0AA4:("Boss01_SpawnDustAtFeet",), 0x809D0CE8:("Boss01_Init",), 0x809D118C:("Boss01_Destroy",), - 0x809D119C:("func_809D119C",), - 0x809D1258:("func_809D1258",), - 0x809D12B4:("func_809D12B4",), - 0x809D1AB8:("func_809D1AB8",), - 0x809D1B2C:("func_809D1B2C",), - 0x809D1E5C:("func_809D1E5C",), - 0x809D1E74:("func_809D1E74",), - 0x809D1EA4:("func_809D1EA4",), - 0x809D20D0:("func_809D20D0",), - 0x809D2588:("func_809D2588",), - 0x809D25E8:("func_809D25E8",), - 0x809D2664:("func_809D2664",), - 0x809D26B8:("func_809D26B8",), - 0x809D2780:("func_809D2780",), - 0x809D27D4:("func_809D27D4",), - 0x809D2858:("func_809D2858",), - 0x809D2914:("func_809D2914",), - 0x809D2A44:("func_809D2A44",), - 0x809D2AA0:("func_809D2AA0",), - 0x809D2BCC:("func_809D2BCC",), - 0x809D2CDC:("func_809D2CDC",), - 0x809D2DE8:("func_809D2DE8",), - 0x809D2E4C:("func_809D2E4C",), - 0x809D3074:("func_809D3074",), - 0x809D30D0:("func_809D30D0",), - 0x809D32B4:("func_809D32B4",), - 0x809D3374:("func_809D3374",), - 0x809D3400:("func_809D3400",), - 0x809D345C:("func_809D345C",), - 0x809D34D4:("func_809D34D4",), - 0x809D3530:("func_809D3530",), - 0x809D35A8:("func_809D35A8",), - 0x809D365C:("func_809D365C",), - 0x809D370C:("func_809D370C",), - 0x809D3A7C:("func_809D3A7C",), - 0x809D3ADC:("func_809D3ADC",), - 0x809D3C10:("func_809D3C10",), - 0x809D3CD0:("func_809D3CD0",), - 0x809D441C:("func_809D441C",), - 0x809D4464:("func_809D4464",), - 0x809D44C0:("func_809D44C0",), - 0x809D4668:("func_809D4668",), + 0x809D119C:("Boss01_JumpAwayFromExplosive",), + 0x809D1258:("Boss01_SetupIntroCutscene",), + 0x809D12B4:("Boss01_IntroCutscene",), + 0x809D1AB8:("Boss01_SetupSummonBugsCutscene",), + 0x809D1B2C:("Boss01_SummonBugsCutscene",), + 0x809D1E5C:("Boss01_Afterimage_SetupWaitToDespawn",), + 0x809D1E74:("Boss01_Afterimage_WaitToDespawn",), + 0x809D1EA4:("Boss01_SetupWait",), + 0x809D20D0:("Boss01_Wait",), + 0x809D2588:("Boss01_SetupDazed",), + 0x809D25E8:("Boss01_Dazed",), + 0x809D2664:("Boss01_SetupSpinAttack",), + 0x809D26B8:("Boss01_SpinAttack",), + 0x809D2780:("Boss01_SetupDanceBeforeAttack",), + 0x809D27D4:("Boss01_DanceBeforeAttack",), + 0x809D2858:("Boss01_SetupRun",), + 0x809D2914:("Boss01_Run",), + 0x809D2A44:("Boss01_SetupJump",), + 0x809D2AA0:("Boss01_JumpSquat",), + 0x809D2BCC:("Boss01_Jump",), + 0x809D2CDC:("Boss01_JumpLand",), + 0x809D2DE8:("Boss01_SetupVerticalSlash",), + 0x809D2E4C:("Boss01_VerticalSlash",), + 0x809D3074:("Boss01_SetupHorizontalSlash",), + 0x809D30D0:("Boss01_HorizontalSlash",), + 0x809D32B4:("Boss01_SetupGuard",), + 0x809D3374:("Boss01_Guard",), + 0x809D3400:("Boss01_SetupKick",), + 0x809D345C:("Boss01_Kick",), + 0x809D34D4:("Boss01_SetupShieldBash",), + 0x809D3530:("Boss01_ShieldBash",), + 0x809D35A8:("Boss01_SetupDamaged",), + 0x809D365C:("Boss01_Damaged",), + 0x809D370C:("Boss01_UpdateDamage",), + 0x809D3A7C:("Boss01_SetupSummonMoths",), + 0x809D3ADC:("Boss01_SummonMoths",), + 0x809D3C10:("Boss01_SetupDeathCutscene",), + 0x809D3CD0:("Boss01_DeathCutscene",), + 0x809D441C:("Boss01_SetupStunned",), + 0x809D4464:("Boss01_Stunned",), + 0x809D44C0:("Boss01_Thaw",), + 0x809D4668:("Boss01_ArePlayerAndOdolwaFacing",), 0x809D46E4:("Boss01_Update",), - 0x809D519C:("func_809D519C",), - 0x809D5584:("func_809D5584",), - 0x809D55CC:("func_809D55CC",), + 0x809D519C:("Boss01_DrawSwordTrail",), + 0x809D5584:("Boss01_OverrideLimbDraw",), + 0x809D55CC:("Boss01_PostLimbDraw",), 0x809D5988:("Boss01_Draw",), - 0x809D5B0C:("func_809D5B0C",), - 0x809D5BC4:("func_809D5BC4",), - 0x809D5FB4:("func_809D5FB4",), - 0x809D606C:("func_809D606C",), - 0x809D62D4:("func_809D62D4",), - 0x809D6314:("func_809D6314",), - 0x809D6424:("func_809D6424",), - 0x809D6488:("func_809D6488",), - 0x809D64E0:("func_809D64E0",), - 0x809D6540:("func_809D6540",), - 0x809D6588:("func_809D6588",), - 0x809D65E0:("func_809D65E0",), - 0x809D670C:("func_809D670C",), - 0x809D694C:("func_809D694C",), - 0x809D6B08:("func_809D6B08",), - 0x809D6BB4:("func_809D6BB4",), - 0x809D6C98:("func_809D6C98",), - 0x809D6E7C:("func_809D6E7C",), - 0x809D73D4:("func_809D73D4",), + 0x809D5B0C:("Boss01_Afterimage_Draw",), + 0x809D5BC4:("Boss01_FillShadowTex",), + 0x809D5FB4:("Boss01_GenShadowTex",), + 0x809D606C:("Boss01_DrawShadowTex",), + 0x809D62D4:("Boss01_Bug_SetupCrawl",), + 0x809D6314:("Boss01_Bug_Crawl",), + 0x809D6424:("Boss01_Bug_SetupDamaged",), + 0x809D6488:("Boss01_Bug_Damaged",), + 0x809D64E0:("Boss01_Bug_SetupDead",), + 0x809D6540:("Boss01_Bug_SetupStunned",), + 0x809D6588:("Boss01_Bug_Stunned",), + 0x809D65E0:("Boss01_Bug_Dead",), + 0x809D670C:("Boss01_Bug_UpdateDamage",), + 0x809D694C:("Boss01_Bug_Update",), + 0x809D6B08:("Boss01_Bug_OverrideLimbDraw",), + 0x809D6BB4:("Boss01_Bug_Draw",), + 0x809D6C98:("Boss01_SpawnDustForFallingBlock",), + 0x809D6E7C:("Boss01_UpdateEffects",), + 0x809D73D4:("Boss01_DrawEffects",), 0x809DA1D0:("Boss02_FillScreen",), 0x809DA22C:("Boss02_SetFillScreenAlpha",), 0x809DA24C:("Boss02_StopFillScreen",), @@ -9321,7 +9321,7 @@ 0x809E299C:("Boss03_SpawnEffectSplash",), 0x809E2AB4:("Boss03_SpawnEffectBubble",), 0x809E2B8C:("Boss03_UpdateSphereElement",), - 0x809E2C1C:("Boss03_SeedRand",), + 0x809E2C1C:("Boss03_InitRand",), 0x809E2C3C:("Boss03_RandZeroOne",), 0x809E2D64:("Boss03_FindActorDblueMovebg",), 0x809E2DA0:("Boss03_SpawnDust",), diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt index 57ed5c173a..b1e1a8b2ea 100644 --- a/tools/disasm/variables.txt +++ b/tools/disasm/variables.txt @@ -7537,9 +7537,9 @@ 0x80911F50:("D_80911F50","UNK_TYPE1","",0x1), 0x80911F58:("sSinkingLureBasePos","Vec3f","",0xC), 0x80911F64:("D_80911F64","f32","",0x4), - 0x80911F68:("sRandSeed0","s32","",0x4), - 0x80911F6C:("sRandSeed1","s32","",0x4), - 0x80911F70:("sRandSeed2","s32","",0x4), + 0x80911F68:("sFishingRandSeed1","s32","",0x4), + 0x80911F6C:("sFishingRandSeed2","s32","",0x4), + 0x80911F70:("sFishingRandSeed3","s32","",0x4), 0x80911F78:("sPondProps","UNK_TYPE1","[140]",0x20D0), 0x80914048:("sGroupFishes","UNK_TYPE1","[60]",0x10E0), 0x80915128:("sFishGroupAngle1","f32","",0x4), @@ -9862,54 +9862,45 @@ 0x809D0434:("D_809D0434","f32","",0x4), 0x809D0438:("D_809D0438","f32","",0x4), 0x809D043C:("D_809D043C","f32","",0x4), - 0x809D7980:("D_809D7980","UNK_TYPE1","",0x1), - 0x809D798C:("D_809D798C","f32","",0x4), - 0x809D7990:("D_809D7990","UNK_TYPE1","",0x1), - 0x809D79B0:("D_809D79B0","UNK_TYPE1","",0x1), - 0x809D79D0:("D_809D79D0","UNK_TYPE1","",0x1), - 0x809D7A3C:("D_809D7A3C","UNK_TYPE1","",0x1), - 0x809D7A4C:("D_809D7A4C","UNK_TYPE1","",0x1), - 0x809D7A70:("D_809D7A70","UNK_TYPE1","",0x1), - 0x809D7A80:("D_809D7A80","UNK_TYPE1","",0x1), - 0x809D7C0C:("D_809D7C0C","UNK_TYPE1","",0x1), - 0x809D7C1C:("D_809D7C1C","UNK_TYPE1","",0x1), - 0x809D7C64:("D_809D7C64","UNK_TYPE1","",0x1), - 0x809D7C74:("D_809D7C74","UNK_TYPE1","",0x1), - 0x809D7CA0:("D_809D7CA0","UNK_TYPE1","",0x1), - 0x809D7CCC:("Boss_01_InitVars","UNK_TYPE1","",0x1), - 0x809D7CEC:("D_809D7CEC","UNK_TYPE1","",0x1), - 0x809D7CF0:("D_809D7CF0","UNK_TYPE1","",0x1), - 0x809D7CF4:("D_809D7CF4","UNK_TYPE1","",0x1), - 0x809D7D24:("D_809D7D24","UNK_TYPE4","",0x4), - 0x809D7D38:("D_809D7D38","UNK_TYPE4","",0x4), - 0x809D7D4C:("D_809D7D4C","UNK_TYPE1","",0x1), - 0x809D7D50:("D_809D7D50","UNK_TYPE1","",0x1), - 0x809D7D54:("D_809D7D54","UNK_TYPE1","",0x1), - 0x809D7D60:("D_809D7D60","UNK_TYPE1","",0x1), - 0x809D7D6C:("D_809D7D6C","UNK_TYPE1","",0x1), - 0x809D7D78:("D_809D7D78","UNK_TYPE1","",0x1), - 0x809D7D7A:("D_809D7D7A","UNK_TYPE1","",0x1), - 0x809D7D7E:("D_809D7D7E","UNK_TYPE1","",0x1), - 0x809D7D7F:("D_809D7D7F","UNK_TYPE1","",0x1), - 0x809D7D84:("D_809D7D84","UNK_TYPE1","",0x1), - 0x809D7D8E:("D_809D7D8E","UNK_TYPE1","",0x1), - 0x809D7D8F:("D_809D7D8F","UNK_TYPE1","",0x1), - 0x809D7D96:("D_809D7D96","UNK_TYPE1","",0x1), - 0x809D7D97:("D_809D7D97","UNK_TYPE1","",0x1), - 0x809D7D98:("D_809D7D98","UNK_TYPE1","",0x1), - 0x809D7DB4:("D_809D7DB4","UNK_TYPE1","",0x1), - 0x809D7E38:("D_809D7E38","UNK_TYPE1","",0x1), - 0x809D7E44:("D_809D7E44","UNK_TYPE1","",0x1), - 0x809D7E50:("D_809D7E50","UNK_TYPE1","",0x1), - 0x809D7E5C:("D_809D7E5C","UNK_TYPE1","",0x1), - 0x809D7E68:("D_809D7E68","UNK_TYPE1","",0x1), - 0x809D7EA0:("D_809D7EA0","UNK_TYPE1","",0x1), - 0x809D7EAC:("D_809D7EAC","UNK_TYPE4","",0x4), - 0x809D7EC4:("D_809D7EC4","UNK_TYPE4","",0x4), - 0x809D7EE0:("D_809D7EE0","UNK_TYPE4","",0x4), - 0x809D7F00:("D_809D7F00","UNK_TYPE4","",0x4), - 0x809D7F30:("D_809D7F30","UNK_TYPE4","",0x4), - 0x809D7F6C:("D_809D7F6C","UNK_TYPE1","",0x1), + 0x809D7980:("sFallingBlockSfxPos","Vec3f","",0xC), + 0x809D798C:("sSwordTrailAngularRangeDivisor","f32","",0x4), + 0x809D7990:("sOdolwaDamageTable","DamageTable","",0x20), + 0x809D79B0:("sBugDamageTable","DamageTable","",0x20), + 0x809D79D0:("sSwordColliderJntSphElementsInit","ColliderJntSphElementInit","[3]",0x6C), + 0x809D7A3C:("sSwordColliderJntSphInit","ColliderJntSphInit","",0x10), + 0x809D7A4C:("sShieldColliderJntSphElementsInit","ColliderJntSphElementInit","[1]",0x24), + 0x809D7A70:("sShieldColliderJntSphInit","ColliderJntSphInit","",0x10), + 0x809D7A80:("sBodyColliderJntSphElementsInit","ColliderJntSphElementInit","[11]", 0x18C), + 0x809D7C0C:("sBodyColliderJntSphInit","ColliderJntSphInit","",0x10), + 0x809D7C1C:("sKickAndShieldBashColliderJntSphElementsInit","ColliderJntSphElementInit","[2]",0x48), + 0x809D7C64:("sKickAndShieldBashColliderJntSphInit","ColliderJntSphInit","",0x10), + 0x809D7C74:("sBugACColliderCylinderInit","ColliderCylinderInit","",0x2C), + 0x809D7CA0:("sBugATColliderCylinderInit","ColliderCylinderInit","",0x2C), + 0x809D7CCC:("Boss_01_InitVars","ActorInit","",0x20), + 0x809D7CEC:("sDustPrimColor","Color_RGBA8","",0x4), + 0x809D7CF0:("sDustEnvColor","Color_RGBA8","",0x4), + 0x809D7CF4:("sWaitAnimations","AnimationHeader*","[12]",0x30), + 0x809D7D24:("sBlueWarpSpawnsX","f32","[5]",0x14), + 0x809D7D38:("sBlueWarpSpawnsZ","f32","[5]",0x14), + 0x809D7D4C:("sIcePrimColor","Color_RGBA8","",0x4), + 0x809D7D50:("sIceEnvColor","Color_RGBA8","",0x4), + 0x809D7D54:("sIceAccel","Vec3f","",0xC), + 0x809D7D60:("sSwordTrailOuterVertexIndices","u8","[11]",0xB), + 0x809D7D6C:("sSwordTrailInnerVertexIndices","u8","[11]",0xB), + 0x809D7D78:("sLimbToColliderBodyParts","s8","[60]",0x3C), + 0x809D7DB4:("sLimbColliderOffsets","Vec3f","[11]",0x84), + 0x809D7E38:("sShieldColliderOffset","Vec3f","",0xC), + 0x809D7E44:("sSwordBaseColliderOffset","Vec3f","",0xC), + 0x809D7E50:("sSwordTipColliderOffset","Vec3f","",0xC), + 0x809D7E5C:("sHorizontalSlashPelvisColliderOffset","Vec3f","",0xC), + 0x809D7E68:("sLimbToBodyParts","s8","[53]",0x35), + 0x809D7EA0:("sDefaultPelvisColliderOffset","Vec3f","",0xC), + 0x809D7EAC:("sShadowSmallMap","s32","[6]",0x18), + 0x809D7EC4:("sShadowMediumMap","s32","[7]",0x1C), + 0x809D7EE0:("sShadowLargeMap","s32","[8]",0x20), + 0x809D7F00:("sShadowExtraLargeMap","s32","[12]",0x30), + 0x809D7F30:("sParentShadowBodyParts","s32","[15]",0x3C), + 0x809D7F6C:("sShadowSizes","u8","[15]",0xF), 0x809D7F80:("D_809D7F80","f32","",0x4), 0x809D7F84:("D_809D7F84","f32","",0x4), 0x809D7F88:("D_809D7F88","f32","",0x4), @@ -9967,22 +9958,22 @@ 0x809D8150:("D_809D8150","f32","",0x4), 0x809D8154:("D_809D8154","f32","",0x4), 0x809D8158:("D_809D8158","f32","",0x4), - 0x809D8A10:("D_809D8A10","UNK_TYPE2","",0x2), - 0x809D8A14:("D_809D8A14","UNK_TYPE4","",0x4), - 0x809D8A18:("D_809D8A18","UNK_TYPE4","",0x4), - 0x809D8A1C:("D_809D8A1C","f32","",0x4), - 0x809D8A20:("D_809D8A20","f32","",0x4), - 0x809D8A24:("D_809D8A24","f32","",0x4), - 0x809D8A28:("D_809D8A28","f32","",0x4), - 0x809D8A2C:("D_809D8A2C","f32","",0x4), - 0x809D8A30:("D_809D8A30","f32","",0x4), - 0x809D8A34:("D_809D8A34","f32","",0x4), - 0x809D8A38:("D_809D8A38","UNK_TYPE1","",0x1), - 0x809D8A40:("D_809D8A40","UNK_TYPE1","",0x1), - 0x809D8A4C:("D_809D8A4C","UNK_TYPE4","",0x4), - 0x809D8A50:("D_809D8A50","UNK_TYPE4","",0x4), - 0x809D8A54:("D_809D8A54","UNK_TYPE4","",0x4), - 0x809D8A58:("D_809D8A58","UNK_TYPE1","",0x1), + 0x809D8A10:("sOdolwaBugCount","s16","",0x2), + 0x809D8A14:("sOdolwa","Boss01*","",0x4), + 0x809D8A18:("sMothSwarm","EnTanron1*","",0x4), + 0x809D8A1C:("sOdolwaSwordTrailPosX","f32","",0x4), + 0x809D8A20:("sOdolwaSwordTrailPosY","f32","",0x4), + 0x809D8A24:("sOdolwaSwordTrailPosZ","f32","",0x4), + 0x809D8A28:("sOdolwaSwordTrailRotX","f32","",0x4), + 0x809D8A2C:("sOdolwaSwordTrailRotY","f32","",0x4), + 0x809D8A30:("sOdolwaSwordTrailRotZ","f32","",0x4), + 0x809D8A34:("sOdolwaSwordTrailAlpha","f32","",0x4), + 0x809D8A38:("sOdolwaMusicStartTimer","u8","",0x1), + 0x809D8A40:("sOdolwaDamageSfxPos","Vec3f","",0xC), + 0x809D8A4C:("sOdolwaRandSeed1","s32","",0x4), + 0x809D8A50:("sOdolwaRandSeed2","s32","",0x4), + 0x809D8A54:("sOdolwaRandSeed3","s32","",0x4), + 0x809D8A58:("sOdolwaEffects","OdolwaEffect","[100]",0x1770), 0x809DF550:("D_809DF550","UNK_TYPE1","",0x1), 0x809DF570:("D_809DF570","UNK_TYPE1","",0x1), 0x809DF590:("Boss_02_InitVars","UNK_TYPE1","",0x1), @@ -10077,9 +10068,9 @@ 0x809E0424:("sRedTwinmold","UNK_TYPE4","",0x4), 0x809E0428:("sBlueTwinmold","UNK_TYPE4","",0x4), 0x809E042C:("sTwinmoldStatic","UNK_TYPE4","",0x4), - 0x809E0430:("sMusicStartTimer","UNK_TYPE1","",0x1), + 0x809E0430:("sTwinmoldMusicStartTimer","UNK_TYPE1","",0x1), 0x809E0434:("sBlueWarp","UNK_TYPE4","",0x4), - 0x809E0438:("sEffects","UNK_TYPE1","",0x1), + 0x809E0438:("sTwinmoldEffects","UNK_TYPE1","",0x1), 0x809E8EA0:("D_809E8EA0","UNK_TYPE4","",0x4), 0x809E8EAC:("Boss_03_InitVars","UNK_TYPE1","",0x1), 0x809E8ECC:("D_809E8ECC","UNK_TYPE1","",0x1), @@ -10161,9 +10152,9 @@ 0x809E9848:("D_809E9848","UNK_TYPE1","",0x1), 0x809E9858:("sGyorgEffects","UNK_TYPE1","[150]",0x27D8), 0x809EC030:("sGyorgBossInstance","UNK_TYPE4","",0x4), - 0x809EC034:("sRandSeed0","UNK_TYPE4","",0x4), - 0x809EC038:("sRandSeed1","UNK_TYPE4","",0x4), - 0x809EC03C:("sRandSeed2","UNK_TYPE4","",0x4), + 0x809EC034:("sGyorgRandSeed1","UNK_TYPE4","",0x4), + 0x809EC038:("sGyorgRandSeed2","UNK_TYPE4","",0x4), + 0x809EC03C:("sGyorgRandSeed3","UNK_TYPE4","",0x4), 0x809EE150:("D_809EE150","UNK_TYPE1","",0x1), 0x809EE170:("Boss_04_InitVars","UNK_TYPE1","",0x1), 0x809EE190:("D_809EE190","UNK_TYPE1","",0x1), diff --git a/undefined_syms.txt b/undefined_syms.txt index ecae60b8c4..cc6a29ebf6 100644 --- a/undefined_syms.txt +++ b/undefined_syms.txt @@ -196,42 +196,6 @@ D_060005C4 = 0x060005C4; D_06000A20 = 0x06000A20; D_06000040 = 0x06000040; -// ovl_Boss_01 - -D_06000C44 = 0x06000C44; -D_06001884 = 0x06001884; -D_0600C338 = 0x0600C338; -D_0600C498 = 0x0600C498; -D_0600C5E0 = 0x0600C5E0; -D_0600C7A8 = 0x0600C7A8; -D_0600C7C8 = 0x0600C7C8; -D_0600C7F8 = 0x0600C7F8; -D_0600E3E8 = 0x0600E3E8; -D_0600F0A8 = 0x0600F0A8; -D_0600FDEC = 0x0600FDEC; -D_0600FF94 = 0x0600FF94; -D_06010150 = 0x06010150; -D_06010980 = 0x06010980; -D_060124CC = 0x060124CC; -D_06012B70 = 0x06012B70; -D_06012D10 = 0x06012D10; -D_06012EBC = 0x06012EBC; -D_06013480 = 0x06013480; -D_0601407C = 0x0601407C; -D_06014F14 = 0x06014F14; -D_06015A30 = 0x06015A30; -D_06016168 = 0x06016168; -D_060164CC = 0x060164CC; -D_06018438 = 0x06018438; -D_06019C10 = 0x06019C10; -D_0601F6A4 = 0x0601F6A4; -D_060204AC = 0x060204AC; -D_060213A8 = 0x060213A8; -D_060220A0 = 0x060220A0; -D_06022118 = 0x06022118; -D_060222D0 = 0x060222D0; -D_06022550 = 0x06022550; - // ovl_Boss_05 D_060006A4 = 0x060006A4;