From aa5498231886fc93ed3f78a61362e9266d0f955b Mon Sep 17 00:00:00 2001 From: Tom Overton Date: Tue, 27 Sep 2022 12:50:02 -0700 Subject: [PATCH] EnGrasshopper (Dragonflies) OK and documented, object_grasshopper documented (#1043) * EnGrasshopper (Dragonflies) OK and documented, object_grasshopper documented * Type macro * Limb enum for sJntSphElementsInit * Respond to hensldm's review * Use TATL_HINT_ID enum * Respond to reviews * Increase size of shadowBodyPartsPos to 24 * Change 14 to a define --- assets/xml/objects/object_grasshopper.xml | 138 +- spec | 3 +- .../ovl_En_Grasshopper/z_en_grasshopper.c | 1153 +++++++++++++++-- .../ovl_En_Grasshopper/z_en_grasshopper.h | 65 +- src/overlays/actors/ovl_En_Pp/z_en_pp.c | 8 +- src/overlays/actors/ovl_En_Pp/z_en_pp.h | 2 +- tools/disasm/functions.txt | 56 +- tools/disasm/variables.txt | 26 +- undefined_syms.txt | 5 - 9 files changed, 1256 insertions(+), 200 deletions(-) diff --git a/assets/xml/objects/object_grasshopper.xml b/assets/xml/objects/object_grasshopper.xml index 3ef7b8e64a..f08536c905 100644 --- a/assets/xml/objects/object_grasshopper.xml +++ b/assets/xml/objects/object_grasshopper.xml @@ -1,67 +1,79 @@  + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec b/spec index 7f44f277e2..8cfc30556c 100644 --- a/spec +++ b/spec @@ -2173,8 +2173,7 @@ beginseg name "ovl_En_Grasshopper" compress include "build/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.o" - include "build/data/ovl_En_Grasshopper/ovl_En_Grasshopper.data.o" - include "build/data/ovl_En_Grasshopper/ovl_En_Grasshopper.reloc.o" + include "build/src/overlays/actors/ovl_En_Grasshopper/ovl_En_Grasshopper_reloc.o" endseg beginseg diff --git a/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.c b/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.c index be274690ca..aa2ce49157 100644 --- a/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.c +++ b/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.c @@ -5,6 +5,7 @@ */ #include "z_en_grasshopper.h" +#include "objects/gameplay_keep/gameplay_keep.h" #define FLAGS (ACTOR_FLAG_1 | ACTOR_FLAG_4 | ACTOR_FLAG_10) @@ -15,41 +16,125 @@ void EnGrasshopper_Destroy(Actor* thisx, PlayState* play); void EnGrasshopper_Update(Actor* thisx, PlayState* play); void EnGrasshopper_Draw(Actor* thisx, PlayState* play); -#if 0 -// static DamageTable sDamageTable = { -static DamageTable D_809A8CDC = { - /* Deku Nut */ DMG_ENTRY(1, 0xF), - /* Deku Stick */ DMG_ENTRY(1, 0xF), - /* Horse trample */ DMG_ENTRY(0, 0x0), - /* Explosives */ DMG_ENTRY(1, 0xF), - /* Zora boomerang */ DMG_ENTRY(1, 0xF), - /* Normal arrow */ DMG_ENTRY(1, 0xF), - /* UNK_DMG_0x06 */ DMG_ENTRY(0, 0x0), - /* Hookshot */ DMG_ENTRY(0, 0xE), - /* Goron punch */ DMG_ENTRY(1, 0xF), - /* Sword */ DMG_ENTRY(1, 0xF), - /* Goron pound */ DMG_ENTRY(0, 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(1, 0xF), - /* Deku launch */ DMG_ENTRY(2, 0xF), - /* UNK_DMG_0x12 */ DMG_ENTRY(1, 0xF), - /* Zora barrier */ DMG_ENTRY(1, 0xF), - /* Normal shield */ DMG_ENTRY(0, 0x0), - /* Light ray */ DMG_ENTRY(0, 0x0), - /* Thrown object */ DMG_ENTRY(1, 0xF), - /* Zora punch */ DMG_ENTRY(1, 0xF), - /* Spin attack */ DMG_ENTRY(1, 0xF), - /* Sword beam */ DMG_ENTRY(0, 0x0), - /* Normal Roll */ DMG_ENTRY(0, 0x0), - /* UNK_DMG_0x1B */ DMG_ENTRY(0, 0x0), - /* UNK_DMG_0x1C */ DMG_ENTRY(0, 0x0), - /* Unblockable */ DMG_ENTRY(0, 0x0), - /* UNK_DMG_0x1E */ DMG_ENTRY(0, 0x0), - /* Powder Keg */ DMG_ENTRY(1, 0xE), +void EnGrasshopper_ChangeAnim(EnGrasshopper* this, s32 animIndex); +void EnGrasshopper_RaiseTail(EnGrasshopper* this); +void EnGrasshopper_LowerTail(EnGrasshopper* this); +void EnGrasshopper_DecideAction(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupFly(EnGrasshopper* this); +void EnGrasshopper_Fly(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_RoamInCircles(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupBank(EnGrasshopper* this); +void EnGrasshopper_Bank(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupBounced(EnGrasshopper* this); +void EnGrasshopper_Bounced(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupApproachPlayer(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_ApproachPlayer(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupAttack(EnGrasshopper* this); +void EnGrasshopper_Attack(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupWaitAfterAttack(EnGrasshopper* this); +void EnGrasshopper_WaitAfterAttack(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_Damaged(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_Dead(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_SetupFall(EnGrasshopper* this); +void EnGrasshopper_Fall(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_InitializeEffect(EnGrasshopper* this, Vec3f* pos); +void EnGrasshopper_UpdateEffects(EnGrasshopper* this, PlayState* play); +void EnGrasshopper_DrawEffects(EnGrasshopper* this, PlayState* play); + +typedef enum { + /* 0 */ EN_GRASSHOPPER_ACTION_FLY, + /* 1 */ EN_GRASSHOPPER_ACTION_ROAM_IN_CIRCLES, + /* 2 */ EN_GRASSHOPPER_ACTION_BANK, + /* 3 */ EN_GRASSHOPPER_ACTION_BOUNCED, + /* 4 */ EN_GRASSHOPPER_ACTION_DECIDE_ACTION, + /* 5 */ EN_GRASSHOPPER_ACTION_APPROACH_PLAYER, + /* 6 */ EN_GRASSHOPPER_ACTION_ATTACK, + /* 7 */ EN_GRASSHOPPER_ACTION_WAIT_AFTER_ATTACK, + /* 8 */ EN_GRASSHOPPER_ACTION_DAMAGED, + /* 9 */ EN_GRASSHOPPER_ACTION_DEAD, + /* 10 */ EN_GRASSHOPPER_ACTION_FALL, +} EnGrasshopperAction; + +typedef enum { + /* 0 */ EN_GRASSHOPPER_DECISION_ATTACK, + /* 1 */ EN_GRASSHOPPER_DECISION_FLY, + /* 2 */ EN_GRASSHOPPER_DECISION_ROAM_IN_CIRCLES, // Never used in the final game +} EnGrasshopperNextAction; + +typedef enum { + /* 0 */ EN_GRASSHOPPER_BANK_STATE_BANKING, + /* 1 */ EN_GRASSHOPPER_BANK_STATE_DONE, +} EnGrasshopperBankState; + +typedef enum { + /* 0 */ EN_GRASSHOPPER_ANIM_RAISE_TAIL, + /* 1 */ EN_GRASSHOPPER_ANIM_LOWER_TAIL, + /* 2 */ EN_GRASSHOPPER_ANIM_FLY, + /* 3 */ EN_GRASSHOPPER_ANIM_ATTACK, + /* 4 */ EN_GRASSHOPPER_ANIM_HOVER, + /* 5 */ EN_GRASSHOPPER_ANIM_DAMAGE, + /* 6 */ EN_GRASSHOPPER_ANIM_DEAD, + /* 7 */ EN_GRASSHOPPER_ANIM_FALL, +} EnGrasshopperAnim; + +static s32 sOccupiedIndices[] = { + false, false, false, false, false, +}; + +static s8 sLimbIndexToShadowBodyPartsIndex[DRAGONFLY_LIMB_MAX] = { + -1, -1, 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, -1, 8, 9, -1, 10, 11, -1, 12, 13, -1, -1, -1, +}; + +static s8 sParentBodyParts[DRAGONFLY_LIMB_MAX] = { + -1, -1, -1, 0, 1, 2, 3, -1, -1, -1, -1, 6, -1, -1, 8, -1, -1, 10, -1, -1, 12, -1, -1, -1, +}; + +static u8 sShadowSizes[DRAGONFLY_LIMB_MAX] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +typedef enum { + /* 0x0 */ EN_GRASSHOPPER_DMGEFF_IMMUNE, // Deals no damage + /* 0x2 */ EN_GRASSHOPPER_DMGEFF_FIRE = 0x2, // Damages and sets the Dragonfly on fire + /* 0x3 */ EN_GRASSHOPPER_DMGEFF_FREEZE, // Damages and freezes the Dragonfly in ice + /* 0x4 */ EN_GRASSHOPPER_DMGEFF_LIGHT_ORB, // Damages and surrounds the Dragonfly with light orbs + /* 0xE */ EN_GRASSHOPPER_DMGEFF_HOOK = 0xE, // If hit by the Hookshot, it pulls the Dragonfly towards the player + /* 0xF */ EN_GRASSHOPPER_DMGEFF_NONE, // Deals regular damage with no extra effect +} EnDragonflyDamageEffect; + +static DamageTable sDamageTable = { + /* Deku Nut */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Deku Stick */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Horse trample */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Explosives */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Zora boomerang */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Normal arrow */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* UNK_DMG_0x06 */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Hookshot */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_HOOK), + /* Goron punch */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Sword */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Goron pound */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_NONE), + /* Fire arrow */ DMG_ENTRY(2, EN_GRASSHOPPER_DMGEFF_FIRE), + /* Ice arrow */ DMG_ENTRY(2, EN_GRASSHOPPER_DMGEFF_FREEZE), + /* Light arrow */ DMG_ENTRY(2, EN_GRASSHOPPER_DMGEFF_LIGHT_ORB), + /* Goron spikes */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Deku spin */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Deku bubble */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Deku launch */ DMG_ENTRY(2, EN_GRASSHOPPER_DMGEFF_NONE), + /* UNK_DMG_0x12 */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Zora barrier */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Normal shield */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Light ray */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Thrown object */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Zora punch */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Spin attack */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_NONE), + /* Sword beam */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Normal Roll */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* UNK_DMG_0x1B */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* UNK_DMG_0x1C */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Unblockable */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* UNK_DMG_0x1E */ DMG_ENTRY(0, EN_GRASSHOPPER_DMGEFF_IMMUNE), + /* Powder Keg */ DMG_ENTRY(1, EN_GRASSHOPPER_DMGEFF_HOOK), }; const ActorInit En_Grasshopper_InitVars = { @@ -64,92 +149,996 @@ const ActorInit En_Grasshopper_InitVars = { (ActorFunc)EnGrasshopper_Draw, }; -// static ColliderJntSphElementInit sJntSphElementsInit[2] = { -static ColliderJntSphElementInit D_809A8D1C[2] = { +static ColliderJntSphElementInit sJntSphElementsInit[2] = { { - { ELEMTYPE_UNK3, { 0xF7CFFFFF, 0x00, 0x00 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON | BUMP_HOOKABLE, OCELEM_ON, }, - { 7, { { 0, 0, 0 }, 0 }, 0 }, + { + ELEMTYPE_UNK3, + { 0xF7CFFFFF, 0x00, 0x00 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON | BUMP_HOOKABLE, + OCELEM_ON, + }, + { DRAGONFLY_LIMB_THORAX, { { 0, 0, 0 }, 0 }, 0 }, }, { - { ELEMTYPE_UNK2, { 0xF7CFFFFF, 0x07, 0x04 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_NONE, }, - { 6, { { 0, 0, 0 }, 0 }, 0 }, + { + ELEMTYPE_UNK2, + { 0xF7CFFFFF, 0x07, 0x04 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_NONE, + }, + { DRAGONFLY_LIMB_TAIL_TIP, { { 0, 0, 0 }, 0 }, 0 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_809A8D64 = { - { COLTYPE_HIT2, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_1, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_809A8D1C, // sJntSphElementsInit, +static ColliderJntSphInit sJntSphInit = { + { + COLTYPE_HIT2, + AT_ON | AT_TYPE_ENEMY, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_1, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sJntSphElementsInit), + sJntSphElementsInit, }; -#endif +void EnGrasshopper_Init(Actor* thisx, PlayState* play) { + EnGrasshopper* this = THIS; + s32 i; -extern DamageTable D_809A8CDC; -extern ColliderJntSphElementInit D_809A8D1C[2]; -extern ColliderJntSphInit D_809A8D64; + this->actor.hintId = TATL_HINT_ID_DRAGONFLY; + this->actor.targetMode = 4; + this->actor.colChkInfo.mass = 60; + this->actor.colChkInfo.health = 2; -extern UNK_TYPE D_06000F9C; + Collider_InitAndSetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderElements); + this->collider.elements[0].dim.modelSphere.radius = 1; + this->collider.elements[0].dim.scale = 22.0f; + this->collider.elements[1].dim.modelSphere.radius = 1; + this->collider.elements[1].dim.scale = 16.0f; + this->collider.elements[1].dim.modelSphere.center.x = 1000; + this->collider.elements[0].dim.modelSphere.center.x = this->collider.elements[0].dim.modelSphere.center.y = + this->collider.elements[0].dim.modelSphere.center.z = this->collider.elements[1].dim.modelSphere.center.y = + this->collider.elements[1].dim.modelSphere.center.z = 0; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/EnGrasshopper_Init.s") + this->actor.flags |= ACTOR_FLAG_200; + ActorShape_Init(&this->actor.shape, 0.0f, NULL, 1.0f); + this->actor.colChkInfo.damageTable = &sDamageTable; + Math_Vec3f_Copy(&this->flyingHomePos, &this->actor.home.pos); + this->action = EN_GRASSHOPPER_ACTION_FLY; + this->index = -1; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/EnGrasshopper_Destroy.s") + for (i = 0; i < ARRAY_COUNT(sOccupiedIndices); i++) { + if (!sOccupiedIndices[i]) { + this->index = i; + sOccupiedIndices[i] = true; + break; + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6524.s") + // Deciding a random index like this if all indices are occupied causes a bug in EnGrasshopper_Destroy. + if (this->index < 0) { + this->index = Rand_ZeroFloat(4.99f); + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A65D8.s") + SkelAnime_Init(play, &this->skelAnime, &gDragonflySkel, &gDragonflyFlyAnim, this->jointTable, this->morphTable, + DRAGONFLY_LIMB_MAX); + this->type = EN_GRASSHOPPER_GET_TYPE(&this->actor); + if (this->type < EN_GRASSHOPPER_TYPE_UNUSED_NORMAL) { + this->type = EN_GRASSHOPPER_TYPE_NORMAL; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6628.s") + if (this->type != EN_GRASSHOPPER_TYPE_GROWS_WHEN_SPAWNED) { + this->dragonflyScale = 0.01f; + } else { + this->dragonflyScale = 0.0f; + Actor_SetScale(&this->actor, 0.0f); + this->actor.world.rot.y = this->actor.yawTowardsPlayer; + this->actor.shape.rot.y = this->actor.yawTowardsPlayer; + Math_Vec3f_Copy(&this->flyingHomePos, &this->actor.world.pos); + this->flyingHomePos.y += 90.0f; + this->collider.elements[0].dim.modelSphere.radius = this->collider.elements[1].dim.modelSphere.radius = + this->type; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6668.s") + this->baseFlyHeight = randPlusMinusPoint5Scaled(50.0f) + this->flyingHomePos.y; + EnGrasshopper_SetupFly(this); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6754.s") +void EnGrasshopper_Destroy(Actor* thisx, PlayState* play) { + EnGrasshopper* this = THIS; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A67A4.s") + Collider_DestroyJntSph(play, &this->collider); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6B6C.s") + //! @bug If the dragonfly selected a random index in EnGrasshopper_Init (because all indices were occupied), + //! then two dragonflies will have the same index. When one of those dragonflies sharing an index is destroyed, + //! it will mark the index as unoccupied, when it still occupied by at least one dragonfly. + sOccupiedIndices[this->index] = false; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6E18.s") +static AnimationHeader* sAnimations[] = { + &gDragonflyRaiseTailAnim, // EN_GRASSHOPPER_ANIM_RAISE_TAIL + &gDragonflyLowerTailAnim, // EN_GRASSHOPPER_ANIM_LOWER_TAIL + &gDragonflyFlyAnim, // EN_GRASSHOPPER_ANIM_FLY + &gDragonflyAttackAnim, // EN_GRASSHOPPER_ANIM_ATTACK + &gDragonflyHoverAnim, // EN_GRASSHOPPER_ANIM_HOVER + &gDragonflyDamageAnim, // EN_GRASSHOPPER_ANIM_DAMAGE + &gDragonflyDeadAnim, // EN_GRASSHOPPER_ANIM_DEAD + &gDragonflyFallAnim, // EN_GRASSHOPPER_ANIM_FALL +}; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6E74.s") +static u8 sAnimationModes[] = { + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_RAISE_TAIL + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_LOWER_TAIL + ANIMMODE_LOOP, // EN_GRASSHOPPER_ANIM_FLY + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_ATTACK + ANIMMODE_LOOP, // EN_GRASSHOPPER_ANIM_HOVER + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_DAMAGE + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_DEAD + ANIMMODE_ONCE, // EN_GRASSHOPPER_ANIM_FALL +}; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A6F8C.s") +void EnGrasshopper_ChangeAnim(EnGrasshopper* this, s32 animIndex) { + f32 morphFrames; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A700C.s") + this->endFrame = Animation_GetLastFrame(sAnimations[animIndex]); + morphFrames = 0.0f; + if ((animIndex == EN_GRASSHOPPER_ANIM_ATTACK) || (animIndex == EN_GRASSHOPPER_ANIM_HOVER) || + (animIndex == EN_GRASSHOPPER_ANIM_DAMAGE)) { + morphFrames = -3.0f; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7134.s") + Animation_Change(&this->skelAnime, sAnimations[animIndex], 1.0f, 0.0f, this->endFrame, sAnimationModes[animIndex], + morphFrames); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A71CC.s") +void EnGrasshopper_RaiseTail(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_RAISE_TAIL); + if (this->decision != EN_GRASSHOPPER_DECISION_ROAM_IN_CIRCLES) { + this->decision = EN_GRASSHOPPER_DECISION_FLY; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7494.s") + this->action = EN_GRASSHOPPER_ACTION_DECIDE_ACTION; + this->actionFunc = EnGrasshopper_DecideAction; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A753C.s") +void EnGrasshopper_LowerTail(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_LOWER_TAIL); + this->action = EN_GRASSHOPPER_ACTION_DECIDE_ACTION; + this->decision = EN_GRASSHOPPER_DECISION_ATTACK; + this->actionFunc = EnGrasshopper_DecideAction; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7844.s") +void EnGrasshopper_DecideAction(EnGrasshopper* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A78EC.s") + if (curFrame >= this->endFrame) { + if (this->decision == EN_GRASSHOPPER_DECISION_ATTACK) { + EnGrasshopper_SetupAttack(this); + } else { + Math_Vec3f_Copy(&this->flyingHomePos, &this->actor.world.pos); + this->flyingHomePos.y = this->actor.floorHeight + 90.0f; + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_FLY); + if (this->decision != EN_GRASSHOPPER_DECISION_ROAM_IN_CIRCLES) { + if (Player_GetMask(play) == PLAYER_MASK_STONE) { + EnGrasshopper_SetupFly(this); + } else { + EnGrasshopper_SetupApproachPlayer(this, play); + } + } else { + this->timer = 0; + this->action = EN_GRASSHOPPER_ACTION_ROAM_IN_CIRCLES; + this->decision = EN_GRASSHOPPER_DECISION_ATTACK; + this->waitTimer = this->timer; + this->actionFunc = EnGrasshopper_RoamInCircles; + } + } + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A797C.s") +void EnGrasshopper_SetupFly(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_FLY); + this->baseFlyHeight = randPlusMinusPoint5Scaled(50.0f) + this->flyingHomePos.y; + this->action = EN_GRASSHOPPER_ACTION_FLY; + this->actionFunc = EnGrasshopper_Fly; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7A8C.s") +void EnGrasshopper_Fly(EnGrasshopper* this, PlayState* play) { + f32 diffX; + f32 diffZ; + f32 targetSpeed; + Vec3f collisionCheckPos; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7AE4.s") + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + diffX = this->flyingHomePos.x - this->actor.world.pos.x; + diffZ = this->flyingHomePos.z - this->actor.world.pos.z; + if ((this->type != EN_GRASSHOPPER_TYPE_WOODFALL_TEMPLE_FINAL_ROOM) && + (this->type != EN_GRASSHOPPER_TYPE_WOODFALL)) { + this->bobPhase += 0xAF0; + this->targetPosY = (Math_SinS(this->bobPhase) * 10.0f) + this->baseFlyHeight; + Math_ApproachF(&this->actor.world.pos.y, this->targetPosY, 0.1f, 10.0f); + collisionCheckPos.x = (Math_SinS(this->actor.shape.rot.y) * 100.0f) + this->actor.world.pos.x; + collisionCheckPos.y = this->actor.world.pos.y; + collisionCheckPos.z = (Math_CosS(this->actor.shape.rot.y) * 100.0f) + this->actor.world.pos.z; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7BBC.s") + if (this->collider.elements[0].info.ocElemFlags & OCELEM_HIT) { + this->shouldTurn = true; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7C98.s") + if (BgCheck_SphVsFirstPoly(&play->colCtx, &collisionCheckPos, 10.0f)) { + this->shouldTurn = true; + } else { + this->shouldTurn = false; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A7CE0.s") + //! @bug Unreachable code. To get here, the type must NOT be EN_GRASSHOPPER_TYPE_WOODFALL + if (this->type == EN_GRASSHOPPER_TYPE_WOODFALL) { + if (sqrtf(SQ(this->actor.world.pos.x) + SQ(this->actor.world.pos.z)) < 600.0f) { + this->shouldTurn = true; + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A8044.s") + if (this->shouldTurn) { + this->baseFlyHeight = randPlusMinusPoint5Scaled(50.0f) + this->flyingHomePos.y; + this->targetRot.y = Math_Atan2S(diffX, diffZ); + this->timer = Rand_S16Offset(30, 30); + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/EnGrasshopper_Update.s") + if ((Player_GetMask(play) != PLAYER_MASK_STONE) && !(gSaveContext.eventInf[4] & 2) && !this->shouldTurn && + (this->actor.xzDistToPlayer < 200.0f)) { + EnGrasshopper_SetupApproachPlayer(this, play); + } else { + Math_SmoothStepToS(&this->actor.world.rot.z, this->targetRot.z, 5, 0x3E8, 5); + this->targetRot.z *= 0.8f; + if (this->waitTimer != 0) { + Math_ApproachZeroF(&this->actor.speedXZ, 0.2f, 0.5f); + } else { + this->targetRot.z = (this->actor.world.rot.y - this->targetRot.y) * 0.2f; + targetSpeed = (this->index * 0.1f) + 4.0f; + Math_ApproachF(&this->actor.speedXZ, targetSpeed, 0.4f, 0.7f); + Math_ApproachF(&this->rotationalVelocity, 2000.0f, 1.0f, 50.0f); + Math_SmoothStepToS(&this->actor.world.rot.y, this->targetRot.y, 5, this->rotationalVelocity, 5); + if (this->timer == 0) { + if (Rand_ZeroFloat(1.0f) < 0.3f) { + this->waitTimer = Rand_S16Offset(10, 10); + this->rotationalVelocity = 0.0f; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A847C.s") + this->targetRot.y = Math_Atan2S(diffX, diffZ); + this->timer = Rand_S16Offset(30, 70); + this->baseFlyHeight = randPlusMinusPoint5Scaled(50.0f) + this->flyingHomePos.y; + } + } + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/EnGrasshopper_Draw.s") +/** + * Unused in the final game. Makes the dragonfly roam the area in a very wide circular arc. + */ +void EnGrasshopper_RoamInCircles(EnGrasshopper* this, PlayState* play) { + f32 diffX; + f32 diffZ; + f32 targetSpeed; + s16 rotationSpeed; + s32 pad; + Vec3f collisionCheckPos; + Player* player = GET_PLAYER(play); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A8870.s") + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + if (Player_GetMask(play) == PLAYER_MASK_STONE) { + EnGrasshopper_SetupFly(this); + } else { + diffX = player->actor.world.pos.x - this->actor.world.pos.x; + diffZ = player->actor.world.pos.z - this->actor.world.pos.z; + this->bobPhase += 0xAF0; + this->targetPosY = player->actor.world.pos.y + 60.0f; + this->baseFlyHeight = Math_SinS(this->bobPhase) * 10.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A8924.s") + if (this->timer == 0) { + this->baseFlyHeight = randPlusMinusPoint5Scaled(10.0f); + this->timer = Rand_S16Offset(30, 30); + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Grasshopper/func_809A8A64.s") + Math_ApproachF(&this->actor.world.pos.y, this->targetPosY, 0.1f, 10.0f); + collisionCheckPos.x = (Math_SinS(this->actor.shape.rot.y) * 100.0f) + this->actor.world.pos.x; + collisionCheckPos.y = this->actor.world.pos.y; + collisionCheckPos.z = (Math_CosS(this->actor.shape.rot.y) * 100.0f) + this->actor.world.pos.z; + + if ((this->actor.bgCheckFlags & 8) || BgCheck_SphVsFirstPoly(&play->colCtx, &collisionCheckPos, 10.0f)) { + EnGrasshopper_SetupBank(this); + } else if (player->stateFlags1 & PLAYER_STATE1_8000000) { + this->collider.elements[0].info.toucherFlags |= (TOUCH_ON | TOUCH_SFX_WOOD); + EnGrasshopper_RaiseTail(this); + } else if (this->collider.base.atFlags & AT_BOUNCED) { + this->collider.elements[0].info.toucherFlags &= ~(TOUCH_ON | TOUCH_SFX_WOOD); + EnGrasshopper_SetupBounced(this); + } else { + this->targetRot.z = (this->actor.world.rot.y - this->targetRot.y) * 0.2f; + Math_SmoothStepToS(&this->actor.world.rot.z, this->targetRot.z, 5, 0x3E8, 5); + rotationSpeed = this->index + 70; + targetSpeed = (this->index * 0.05f) + 4.0f; + this->targetRot.y = Math_Atan2S(diffX, diffZ); + Math_ApproachF(&this->actor.speedXZ, targetSpeed, 0.4f, 0.8f); + Math_SmoothStepToS(&this->actor.world.rot.y, this->targetRot.y, rotationSpeed, 0xFA0, 0xA); + } + } +} + +/** + * Unused in the final game. + */ +void EnGrasshopper_SetupBank(EnGrasshopper* this) { + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + this->targetBankRot.y = this->actor.world.rot.y + 0x8000; + this->action = EN_GRASSHOPPER_ACTION_BANK; + this->bankState = EN_GRASSHOPPER_BANK_STATE_BANKING; + this->actor.speedXZ = 2.0f; + this->actionFunc = EnGrasshopper_Bank; +} + +/** + * Unused in the final game. Makes the dragonfly perform a wide banking turn on its side, then + * it flies forward for 100 frames. + */ +void EnGrasshopper_Bank(EnGrasshopper* this, PlayState* play) { + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + switch (this->bankState) { + case EN_GRASSHOPPER_BANK_STATE_BANKING: + Math_SmoothStepToS(&this->actor.world.rot.y, this->targetBankRot.y, 0x64, 0x3E8, 0x3E8); + Math_SmoothStepToS(&this->actor.world.rot.z, 0x4000, 0x64, 0x1F40, 0xBB8); + if (fabsf(this->actor.world.rot.y - (f32)this->targetBankRot.y) < 10.0f) { + this->postBankTimer = 100; + this->bankState = EN_GRASSHOPPER_BANK_STATE_DONE; + } + break; + + case EN_GRASSHOPPER_BANK_STATE_DONE: + Math_SmoothStepToS(&this->actor.world.rot.z, 0, 0x64, 0x1F40, 0xBB8); + if (this->postBankTimer == 0) { + this->timer = 0; + this->action = EN_GRASSHOPPER_ACTION_ROAM_IN_CIRCLES; + this->decision = EN_GRASSHOPPER_DECISION_ATTACK; + this->waitTimer = this->timer; + this->actionFunc = EnGrasshopper_RoamInCircles; + } + break; + } +} + +/** + * Unused in the final game. + */ +void EnGrasshopper_SetupBounced(EnGrasshopper* this) { + this->targetRot.y = -this->actor.yawTowardsPlayer; + this->timer = Rand_S16Offset(30, 30); + this->targetRot.z = (this->actor.world.rot.y - this->targetRot.y) * 0.2f; + this->action = EN_GRASSHOPPER_ACTION_BOUNCED; + this->actionFunc = EnGrasshopper_Bounced; +} + +/** + * Unused in the final game. Makes the dragonfly rotate away from the player, then go + * back to roaming in circles. + */ +void EnGrasshopper_Bounced(EnGrasshopper* this, PlayState* play) { + f32 targetSpeed; + + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + targetSpeed = (this->index * 0.05f) + 7.0f; + Math_ApproachF(&this->actor.speedXZ, targetSpeed, 0.4f, 0.8f); + Math_SmoothStepToS(&this->actor.world.rot.z, this->targetRot.z, 5, 0x3E8, 5); + this->targetRot.z *= 0.8f; + Math_SmoothStepToS(&this->actor.world.rot.y, this->targetRot.y, 5, this->rotationalVelocity, 5); + if (this->timer == 0) { + this->collider.elements[0].info.toucherFlags |= (TOUCH_ON | TOUCH_SFX_WOOD); + this->timer = 0; + this->action = EN_GRASSHOPPER_ACTION_ROAM_IN_CIRCLES; + this->waitTimer = this->timer; + this->actionFunc = EnGrasshopper_RoamInCircles; + } +} + +void EnGrasshopper_SetupApproachPlayer(EnGrasshopper* this, PlayState* play) { + s32 pad; + Player* player = GET_PLAYER(play); + + this->timer = 50; + this->action = EN_GRASSHOPPER_ACTION_APPROACH_PLAYER; + this->approachSpeed = 0.0f; + this->targetApproachPos.x = (Math_SinS(player->actor.shape.rot.y) * 130.0f) + player->actor.world.pos.x; + this->targetApproachPos.z = (Math_CosS(player->actor.shape.rot.y) * 130.0f) + player->actor.world.pos.z; + this->actionFunc = EnGrasshopper_ApproachPlayer; +} + +/** + * Approaches the player in preparation for an attack. It will also attack if the timer reaches 0. + */ +void EnGrasshopper_ApproachPlayer(EnGrasshopper* this, PlayState* play) { + Player* player = GET_PLAYER(play); + WaterBox* waterBox; + Vec3f splashPos; + + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_FLY - SFX_FLAG); + this->bobPhase += 0xAF0; + this->targetApproachPos.y = (Math_SinS(this->bobPhase) * 10.0f) + (player->actor.world.pos.y + 120.0f); + + Math_SmoothStepToS(&this->actor.world.rot.z, 0, 5, 0x3E8, 5); + Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.shape.rot.y, 0xA, 0xFA0, 0xA); + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xFA0, 0xA); + Math_ApproachF(&this->actor.world.pos.x, this->targetApproachPos.x, 0.3f, this->approachSpeed); + Math_ApproachF(&this->actor.world.pos.y, this->targetApproachPos.y, 0.1f, this->approachSpeed); + Math_ApproachF(&this->actor.world.pos.z, this->targetApproachPos.z, 0.3f, this->approachSpeed); + Math_ApproachF(&this->approachSpeed, 7.0f, 0.3f, 1.0f); + + if (WaterBox_GetSurface1(play, &play->colCtx, this->tailTipPos.x, this->tailTipPos.z, &this->waterSurface, + &waterBox)) { + if (this->tailTipPos.y < this->waterSurface) { + if ((this->splashCount < 3) || !(play->gameplayFrames % 8)) { + this->splashCount++; + Math_Vec3f_Copy(&splashPos, &this->tailTipPos); + splashPos.x += randPlusMinusPoint5Scaled(20.0f); + splashPos.z += randPlusMinusPoint5Scaled(20.0f); + EffectSsGSplash_Spawn(play, &splashPos, NULL, NULL, 0, (((s32)Rand_ZeroOne() * 100) + 400)); + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 50, NA_SE_EV_BOMB_DROP_WATER); + } + } else { + this->splashCount = 0; + } + } + + if ((this->timer == 0) || ((fabsf(this->targetApproachPos.x - this->actor.world.pos.x) <= 10.0f) && + (fabsf(this->targetApproachPos.y - this->actor.world.pos.y) <= 20.0f) && + (fabsf(this->targetApproachPos.z - this->actor.world.pos.z) <= 10.0f))) { + EnGrasshopper_LowerTail(this); + } +} + +void EnGrasshopper_SetupAttack(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_ATTACK); + this->approachSpeed = 0.0f; + Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xFA0, 0xA); + this->actor.speedXZ = 3.0f; + this->baseFlyHeight = this->actor.world.pos.y; + this->collider.elements[0].info.toucherFlags &= ~(TOUCH_ON | TOUCH_SFX_WOOD); + this->collider.elements[1].info.toucherFlags |= (TOUCH_ON | TOUCH_SFX_WOOD); + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_ATTACK); + this->action = EN_GRASSHOPPER_ACTION_ATTACK; + this->actionFunc = EnGrasshopper_Attack; +} + +void EnGrasshopper_Attack(EnGrasshopper* this, PlayState* play) { + Player* player = GET_PLAYER(play); + f32 curFrame = this->skelAnime.curFrame; + Vec3f hitPos; + Vec3f diff; + f32 playerToHitPosDist; + s32 i; + WaterBox* waterBox; + Vec3f splashPos; + + for (i = 0; i < 6; i++) { + EnGrasshopper_InitializeEffect(this, &this->tailTipPos); + } + + if (WaterBox_GetSurface1(play, &play->colCtx, this->tailTipPos.x, this->tailTipPos.z, &this->waterSurface, + &waterBox)) { + if (this->tailTipPos.y < this->waterSurface) { + if ((this->splashCount < 3) || !(play->gameplayFrames % 8)) { + this->splashCount++; + Math_Vec3f_Copy(&splashPos, &this->tailTipPos); + splashPos.x += randPlusMinusPoint5Scaled(20.0f); + splashPos.z += randPlusMinusPoint5Scaled(20.0f); + EffectSsGSplash_Spawn(play, &splashPos, NULL, NULL, 0, ((s32)Rand_ZeroOne() * 100) + 400); + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 50, NA_SE_EV_BOMB_DROP_WATER); + } + } else { + this->splashCount = 0; + } + } + + this->bobPhase += 0xAF0; + this->targetApproachPos.y = (Math_SinS(this->bobPhase) * 10.0f) + (player->actor.world.pos.y + 60.0f); + + hitPos.x = this->collider.elements[1].info.bumper.hitPos.x; + hitPos.y = this->collider.elements[1].info.bumper.hitPos.y; + hitPos.z = this->collider.elements[1].info.bumper.hitPos.z; + diff.x = hitPos.x - player->actor.world.pos.x; + diff.y = hitPos.y - player->actor.world.pos.y; + diff.z = hitPos.z - player->actor.world.pos.z; + playerToHitPosDist = sqrtf(SQXYZ(diff)); + + if ((this->collider.base.atFlags & AT_BOUNCED) || + ((player->stateFlags1 & PLAYER_STATE1_400000) && (playerToHitPosDist <= 60.0f) && + ((s16)((player->actor.shape.rot.y - this->actor.shape.rot.y) + 0x8000) < 0x2000) && + ((s16)((player->actor.shape.rot.y - this->actor.shape.rot.y) + 0x8000) > -0x2000))) { + this->collider.elements[1].info.toucherFlags &= ~(TOUCH_ON | TOUCH_SFX_WOOD); + } + + Math_ApproachF(&this->actor.world.pos.y, this->targetApproachPos.y, 0.1f, this->approachSpeed); + Math_ApproachF(&this->approachSpeed, 10.0f, 0.1f, 1.0f); + Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 0xA, 0xFA0, 0xA); + if (curFrame >= this->endFrame) { + EnGrasshopper_SetupWaitAfterAttack(this); + } +} + +void EnGrasshopper_SetupWaitAfterAttack(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_HOVER); + this->bobPhase += 0xAF0; + this->targetPosY = (Math_SinS(this->bobPhase) * 10.0f) + this->baseFlyHeight; + Math_ApproachF(&this->actor.world.pos.y, this->targetPosY, 0.1f, 10.0f); + this->action = EN_GRASSHOPPER_ACTION_WAIT_AFTER_ATTACK; + this->waitTimer = 20; + this->actor.speedXZ = 0.0f; + this->collider.elements[1].info.toucherFlags &= ~(TOUCH_ON | TOUCH_SFX_WOOD); + this->actionFunc = EnGrasshopper_WaitAfterAttack; +} + +/** + * Hover in place for a bit after an attack before raising the tail back up. + */ +void EnGrasshopper_WaitAfterAttack(EnGrasshopper* this, PlayState* play) { + this->bobPhase += 0xAF0; + this->targetPosY = (Math_SinS(this->bobPhase) * 10.0f) + this->baseFlyHeight; + Math_ApproachF(&this->actor.world.pos.y, this->targetPosY, 0.1f, 10.0f); + if (this->waitTimer == 0) { + this->collider.elements[0].info.toucherFlags |= (TOUCH_ON | TOUCH_SFX_WOOD); + EnGrasshopper_RaiseTail(this); + } +} + +void EnGrasshopper_SetupDamaged(EnGrasshopper* this, PlayState* play) { + Vec3f damagedVelocity; + + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_DAMAGE); + this->actor.speedXZ = 0.0f; + this->actor.flags |= ACTOR_FLAG_1; + this->approachSpeed = 0.0f; + this->collider.elements[1].info.toucherFlags &= ~(TOUCH_ON | TOUCH_SFX_WOOD); + Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW); + Matrix_MultVecZ(-20.0f, &damagedVelocity); + Math_Vec3f_Copy(&this->damagedVelocity, &damagedVelocity); + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffTimer != 0)) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, ARRAY_COUNT(this->bodyPartsPos), 2, 0.3f, 0.2f); + this->drawDmgEffTimer = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + Actor_SetColorFilter(&this->actor, 0x4000, 255, 0, 8); + this->action = EN_GRASSHOPPER_ACTION_DAMAGED; + this->actionFunc = EnGrasshopper_Damaged; +} + +void EnGrasshopper_Damaged(EnGrasshopper* this, PlayState* play) { + Math_SmoothStepToS(&this->actor.world.rot.z, 0, 5, 0x3E8, 5); + if (this->actor.colorFilterTimer == 0) { + EnGrasshopper_RaiseTail(this); + } +} + +void EnGrasshopper_SetupDead(EnGrasshopper* this, PlayState* play) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_DEAD); + this->actor.flags |= ACTOR_FLAG_8000000; + this->actor.speedXZ = 0.0f; + this->approachSpeed = 0.0f; + this->actor.velocity.y = 5.0f; + this->actor.gravity = -0.5f; + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffTimer == 0)) { + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + Actor_SetColorFilter(&this->actor, 0x4000, 255, 0, 25); + Enemy_StartFinishingBlow(play, &this->actor); + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_DEAD); + this->action = EN_GRASSHOPPER_ACTION_DEAD; + this->actionFunc = EnGrasshopper_Dead; +} + +/** + * Plays the death animation before transitioning to falling from the sky. + */ +void EnGrasshopper_Dead(EnGrasshopper* this, PlayState* play) { + f32 curFrame = this->skelAnime.curFrame; + + Math_SmoothStepToS(&this->actor.world.rot.z, 0, 5, 0x3E8, 5); + if (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffTimer < 2)) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, ARRAY_COUNT(this->bodyPartsPos), 2, 0.3f, 0.2f); + this->drawDmgEffTimer = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + if (curFrame >= this->endFrame) { + this->actor.flags &= ~ACTOR_FLAG_10; + EnGrasshopper_SetupFall(this); + } +} + +void EnGrasshopper_SetupFall(EnGrasshopper* this) { + EnGrasshopper_ChangeAnim(this, EN_GRASSHOPPER_ANIM_FALL); + this->action = EN_GRASSHOPPER_ACTION_FALL; + this->actor.speedXZ = 0.0f; + this->approachSpeed = 0.0f; + this->actionFunc = EnGrasshopper_Fall; +} + +/** + * Falls from the sky and bursts into flames once it touches land or water. + */ +void EnGrasshopper_Fall(EnGrasshopper* this, PlayState* play) { + WaterBox* waterBox; + f32 waterSurface; + s32 isUnderWater = false; + + this->actor.shape.rot.y += 0x1388; + if ((this->actor.floorHeight <= -32000.0f) || (this->actor.floorHeight >= 32000.0f)) { + Actor_MarkForDeath(&this->actor); + return; + } + + if (WaterBox_GetSurface1(play, &play->colCtx, this->actor.world.pos.x, this->actor.world.pos.z, &waterSurface, + &waterBox)) { + if (this->actor.world.pos.y < waterSurface) { + Vec3f splashPos; + s32 i; + + for (i = 0; i < 3; i++) { + Math_Vec3f_Copy(&splashPos, &this->actor.world.pos); + splashPos.x += randPlusMinusPoint5Scaled((i * 5.0f) + 20.0f); + splashPos.z += randPlusMinusPoint5Scaled((i * 5.0f) + 20.0f); + EffectSsGSplash_Spawn(play, &splashPos, NULL, NULL, 0, (((s32)Rand_ZeroOne() * 100) + 400)); + } + + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 50, NA_SE_EV_BOMB_DROP_WATER); + isUnderWater = true; + } + } + + if ((BgCheck_SphVsFirstPoly(&play->colCtx, &this->tailTipPos, 40.0f)) && + (((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX)) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) && + (this->drawDmgEffTimer != 0)) { + Actor_SpawnIceEffects(play, &this->actor, this->bodyPartsPos, ARRAY_COUNT(this->bodyPartsPos), 2, 0.3f, 0.2f); + this->drawDmgEffTimer = 0; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + } + + if (isUnderWater || (BgCheck_SphVsFirstPoly(&play->colCtx, &this->tailTipPos, 10.0f))) { + Vec3f firePos; + Vec3f sFireVelocityAndAccel[] = { + { 1.0f, 0.0f, 0.5f }, { 1.0f, 0.0f, -0.5f }, { -1.0f, 0.0f, 0.5f }, + { -1.0f, 0.0f, -0.5f }, { 0.5f, 0.0f, 1.0f }, { -0.5f, 0.0f, 1.0f }, + { 0.5f, 0.0f, -1.0f }, { -0.5f, 0.0f, -1.0f }, { 0.0f, 0.0f, 0.0f }, + }; + s32 i; + + for (i = 0; i < ARRAY_COUNT(sFireVelocityAndAccel); i++) { + Math_Vec3f_Copy(&firePos, &this->actor.world.pos); + firePos.x += randPlusMinusPoint5Scaled(30.0f); + if (!isUnderWater) { + firePos.y = this->actor.floorHeight; + } else if (WaterBox_GetSurface1(play, &play->colCtx, this->actor.world.pos.x, this->actor.world.pos.z, + &waterSurface, &waterBox)) { + firePos.y = waterSurface; + } + + firePos.z += randPlusMinusPoint5Scaled(30.0f); + func_800B3030(play, &firePos, &sFireVelocityAndAccel[i], &sFireVelocityAndAccel[i], 100, 0, 2); + } + + SoundSource_PlaySfxEachFrameAtFixedWorldPos(play, &this->actor.world.pos, 10, + NA_SE_EN_COMMON_EXTINCT_LEV - SFX_FLAG); + Item_DropCollectibleRandom(play, NULL, &this->actor.world.pos, 0x60); + Actor_MarkForDeath(&this->actor); + } +} + +void EnGrasshopper_UpdateDamage(EnGrasshopper* this, PlayState* play) { + s32 pad; + s16 attackDealsDamage = false; + + if ((this->collider.elements[0].info.bumperFlags & BUMP_HIT) || + (this->collider.elements[1].info.bumperFlags & BUMP_HIT)) { + this->collider.base.acFlags &= ~AC_HIT; + if ((this->action != EN_GRASSHOPPER_ACTION_DAMAGED) && (this->action != EN_GRASSHOPPER_ACTION_DEAD) && + (this->action != EN_GRASSHOPPER_ACTION_FALL)) { + if (this->actor.colChkInfo.damageEffect == EN_GRASSHOPPER_DMGEFF_NONE) { + attackDealsDamage = true; + } else if (this->actor.colChkInfo.damageEffect == EN_GRASSHOPPER_DMGEFF_FIRE) { + this->drawDmgEffTimer = 40; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FIRE; + attackDealsDamage = true; + } else if (this->actor.colChkInfo.damageEffect == EN_GRASSHOPPER_DMGEFF_FREEZE) { + if ((this->drawDmgEffType != ACTOR_DRAW_DMGEFF_BLUE_FIRE) || (this->drawDmgEffTimer == 0)) { + Actor_ApplyDamage(&this->actor); + attackDealsDamage = false; + this->drawDmgEffTimer = 80; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX; + this->drawDmgEffScale = 0.0f; + this->drawDmgEffFrozenSteamScale = 1.5f; + } + + if (this->actor.colChkInfo.health <= 0) { + EnGrasshopper_SetupDead(this, play); + return; + } + } else if (this->actor.colChkInfo.damageEffect == EN_GRASSHOPPER_DMGEFF_LIGHT_ORB) { + Actor_SetColorFilter(&this->actor, 0x8000, 255, 0, 25); + this->drawDmgEffTimer = 20; + this->drawDmgEffType = ACTOR_DRAW_DMGEFF_LIGHT_ORBS; + 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_LARGE_LIGHT_RAYS); + attackDealsDamage = true; + } + } + } + + if (attackDealsDamage) { + Actor_ApplyDamage(&this->actor); + if (this->actor.colChkInfo.health > 0) { + Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_BATTA_DAMAGE); + EnGrasshopper_SetupDamaged(this, play); + } else { + EnGrasshopper_SetupDead(this, play); + } + } +} + +void EnGrasshopper_Update(Actor* thisx, PlayState* play) { + s32 pad; + EnGrasshopper* this = THIS; + + SkelAnime_Update(&this->skelAnime); + EnGrasshopper_UpdateDamage(this, play); + this->actionFunc(this, play); + + Actor_SetFocus(&this->actor, 0.0f); + Actor_SetScale(&this->actor, this->dragonflyScale); + + DECR(this->timer); + DECR(this->waitTimer); + DECR(this->postBankTimer); + DECR(this->drawDmgEffTimer); + + Actor_MoveWithGravity(&this->actor); + this->actor.world.pos.x += this->damagedVelocity.x; + this->actor.world.pos.y += this->damagedVelocity.y; + this->actor.world.pos.z += this->damagedVelocity.z; + Math_ApproachZeroF(&this->damagedVelocity.x, 1.0f, 2.0f); + Math_ApproachZeroF(&this->damagedVelocity.y, 1.0f, 2.0f); + Math_ApproachZeroF(&this->damagedVelocity.z, 1.0f, 2.0f); + if ((this->action != EN_GRASSHOPPER_ACTION_FALL) && (this->type != EN_GRASSHOPPER_TYPE_WOODFALL)) { + Actor_UpdateBgCheckInfo(play, &this->actor, 35.0f, 60.0f, 60.0f, 0x1D); + } + + this->actor.shape.rot.z = this->actor.world.rot.z; + if (this->type == EN_GRASSHOPPER_TYPE_GROWS_WHEN_SPAWNED) { + f32 targetScale = this->type * 0.01f; + + if (targetScale >= 0.018f) { + targetScale = 0.018f; + } + + Math_ApproachF(&this->dragonflyScale, targetScale, 0.1f, 0.01f); + } + + if ((this->action != EN_GRASSHOPPER_ACTION_FALL) && (this->action != EN_GRASSHOPPER_ACTION_APPROACH_PLAYER)) { + Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.world.rot.y, 5, 0x3E8, 5); + } + + EnGrasshopper_UpdateEffects(this, play); + if ((this->action != EN_GRASSHOPPER_ACTION_DEAD) && (this->action != EN_GRASSHOPPER_ACTION_FALL)) { + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); + if ((this->action == EN_GRASSHOPPER_ACTION_APPROACH_PLAYER) || (this->action == EN_GRASSHOPPER_ACTION_ATTACK)) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); + } + } +} + +void EnGrasshopper_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Actor* thisx) { + EnGrasshopper* this = THIS; + Vec3f sEffectOffsetFromTailTop = { 500.0f, 0.0f, 0.0f }; + Vec3f sZeroVec3f = { 0.0f, 0.0f, 0.0f }; + + Matrix_Push(); + + if ((this->type != EN_GRASSHOPPER_TYPE_WOODFALL_TEMPLE_FINAL_ROOM) && + (this->type != EN_GRASSHOPPER_TYPE_WOODFALL)) { + if ((s8)(sLimbIndexToShadowBodyPartsIndex[limbIndex] >= 0)) { + Matrix_MultVec3f(&sZeroVec3f, &this->shadowBodyPartsPos[sLimbIndexToShadowBodyPartsIndex[limbIndex]]); + } + } + + if (limbIndex == DRAGONFLY_LIMB_TAIL_TIP) { + Matrix_Translate(0.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_MultVec3f(&gZeroVec3f, &this->tailTipPos); + Matrix_MultVec3f(&sEffectOffsetFromTailTop, &this->effectBasePos); + } + + Collider_UpdateSpheres(limbIndex, &this->collider); + if ((limbIndex == DRAGONFLY_LIMB_ROOT) || (limbIndex == DRAGONFLY_LIMB_TAIL_SEGMENT_2) || + (limbIndex == DRAGONFLY_LIMB_TAIL_TIP) || (limbIndex == DRAGONFLY_LIMB_BACK_LEFT_UPPER_LEG) || + (limbIndex == DRAGONFLY_LIMB_BACK_RIGHT_UPPER_LEG) || (limbIndex == DRAGONFLY_LIMB_FRONT_LEFT_UPPER_LEG) || + (limbIndex == DRAGONFLY_LIMB_FRONT_LEFT_FOOT) || (limbIndex == DRAGONFLY_LIMB_FRONT_RIGHT_UPPER_LEG) || + (limbIndex == DRAGONFLY_LIMB_HEAD) || + //! @bug: These do not check for valid limbs. Limb index 24 is DRAGONFLY_LIMB_MAX, so it (and any index + //! larger than it) is not tied to an actual limb. + (limbIndex == 24) || (limbIndex == 25) || + // While checking for DRAGONFLY_LIMB_ROOT twice is not a bug by itself, it causes another bug below. + (limbIndex == DRAGONFLY_LIMB_ROOT)) { + //! @bug: This code only works properly if all 12 elements of bodyPartsPos are updated every frame, since + //! otherwise bodyPartsPosIndex will be non-zero at the end of a frame. Despite the fact that there are 12 + //! checks in the above if-statement, only 9 of them are valid and non-duplicate, so only 9 elements of + //! bodyPartsPos are updated on each frame. As a result, three elements in bodyPartsPos will either be (0, 0, 0) + //! or be a value carried over from the previous frame. + Matrix_MultZero(&this->bodyPartsPos[this->bodyPartsPosIndex]); + this->bodyPartsPosIndex++; + if (this->bodyPartsPosIndex >= ARRAY_COUNT(this->bodyPartsPos)) { + this->bodyPartsPosIndex = 0; + } + } + + Matrix_Pop(); +} + +// We need to pass in 14 into SubS_GenShadowTex for the bodyPartsNum, NOT the actual size of the +// parent body parts array. This is both necessary to match and to prevent extra dots from being +// drawn with the shadow. +#define SHADOW_BODY_PARTS_NUM 14 + +void EnGrasshopper_Draw(Actor* thisx, PlayState* play) { + EnGrasshopper* this = THIS; + s32 i; + u8* shadowTex = GRAPH_ALLOC(play->state.gfxCtx, SUBS_SHADOW_TEX_SIZE); + u8* shadowTexIter; + + func_8012C2DC(play->state.gfxCtx); + SkelAnime_DrawOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, NULL, EnGrasshopper_PostLimbDraw, + &this->actor); + if ((this->type != EN_GRASSHOPPER_TYPE_WOODFALL_TEMPLE_FINAL_ROOM) && + (this->type != EN_GRASSHOPPER_TYPE_WOODFALL)) { + Matrix_RotateXS(0, MTXMODE_NEW); + + for (i = 0, shadowTexIter = shadowTex; i < SUBS_SHADOW_TEX_SIZE; i++) { + *shadowTexIter++ = 0; + } + + for (i = 0; i < 5; i++) { + SubS_GenShadowTex(this->shadowBodyPartsPos, &this->actor.world.pos, shadowTex, i / 5.0f, + SHADOW_BODY_PARTS_NUM, sShadowSizes, sParentBodyParts); + //! FAKE: Needed to fix some regs and stack + //! https://decomp.me/scratch/4wJBW + if ((shadowTex && shadowTex) && shadowTex) {} + } + + SubS_DrawShadowTex(&this->actor, &play->state, shadowTex); + } + + if (this->drawDmgEffTimer != 0) { + f32 drawDmgEffAlpha = this->drawDmgEffTimer * 0.05f; + + if ((this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_SFX) || + (this->drawDmgEffType == ACTOR_DRAW_DMGEFF_FROZEN_NO_SFX)) { + this->drawDmgEffScale += 0.3f; + if (this->drawDmgEffScale > 0.5f) { + this->drawDmgEffScale = 0.5f; + } + + Math_ApproachF(&this->drawDmgEffFrozenSteamScale, this->drawDmgEffScale, 0.1f, 0.04f); + } else { + this->drawDmgEffScale = 0.8f; + this->drawDmgEffFrozenSteamScale = 0.8f; + } + + Actor_DrawDamageEffects(play, &this->actor, this->bodyPartsPos, ARRAY_COUNT(this->bodyPartsPos), + this->drawDmgEffScale, this->drawDmgEffFrozenSteamScale, drawDmgEffAlpha, + this->drawDmgEffType); + } + + EnGrasshopper_DrawEffects(this, play); +} + +void EnGrasshopper_InitializeEffect(EnGrasshopper* this, Vec3f* pos) { + EnGrasshopperEffect* effect = this->effects; + s16 i; + + for (i = 0; i < ARRAY_COUNT(this->effects); i++, effect++) { + if (!effect->isEnabled) { + effect->isEnabled = true; + effect->pos = *pos; + effect->timer = 10; + effect->velocity.x = randPlusMinusPoint5Scaled(20.0f); + effect->velocity.y = randPlusMinusPoint5Scaled(20.0f); + effect->velocity.z = randPlusMinusPoint5Scaled(20.0f); + effect->yaw = randPlusMinusPoint5Scaled(30000.0f); + return; + } + } +} + +void EnGrasshopper_UpdateEffects(EnGrasshopper* this, PlayState* play) { + EnGrasshopperEffect* effect = this->effects; + s32 i; + + for (i = 0; i < ARRAY_COUNT(this->effects); i++, effect++) { + if (effect->isEnabled) { + effect->pos.x = this->effectBasePos.x + effect->velocity.x; + effect->pos.y = this->effectBasePos.y + effect->velocity.y; + effect->pos.z = this->effectBasePos.z + effect->velocity.z; + if (effect->timer != 0) { + effect->timer--; + effect->lightningIndex++; + Math_ApproachF(&effect->scale.x, 0.1f, 0.1f, 0.05f); + if (effect->lightningIndex >= 8) { + effect->lightningIndex = 0; + } + } else { + Math_ApproachZeroF(&effect->scale.x, 0.1f, 0.1f); + if (effect->scale.x < 0.2f) { + effect->isEnabled = 0; + } + } + + effect->scale.y = effect->scale.z = effect->scale.x; + } + } +} + +static TexturePtr sLightningTextures[] = { + gEffLightning1Tex, gEffLightning2Tex, gEffLightning3Tex, gEffLightning4Tex, + gEffLightning5Tex, gEffLightning6Tex, gEffLightning7Tex, gEffLightning8Tex, +}; + +void EnGrasshopper_DrawEffects(EnGrasshopper* this, PlayState* play) { + s32 pad[2]; + EnGrasshopperEffect* effect; + s16 i; + MtxF mfResult; + MtxF mfTrans; + MtxF mfScale; + MtxF mfRot; + MtxF mfTransBillboard; + MtxF mfTransBillboardRot; + Mtx* mtx; + + OPEN_DISPS(play->state.gfxCtx); + + effect = this->effects; + for (i = 0; i < ARRAY_COUNT(this->effects); i++, effect++) { + if (effect->isEnabled) { + SkinMatrix_SetTranslate(&mfTrans, effect->pos.x, effect->pos.y, effect->pos.z); + SkinMatrix_SetScale(&mfScale, effect->scale.x, effect->scale.y, effect->scale.z); + SkinMatrix_SetRotateRPY(&mfRot, 0, 0, effect->yaw); + SkinMatrix_MtxFMtxFMult(&mfTrans, &play->billboardMtxF, &mfTransBillboard); + SkinMatrix_MtxFMtxFMult(&mfTransBillboard, &mfRot, &mfTransBillboardRot); + SkinMatrix_MtxFMtxFMult(&mfTransBillboardRot, &mfScale, &mfResult); + + gSPMatrix(POLY_XLU_DISP++, &gIdentityMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + mtx = SkinMatrix_MtxFToNewMtx(play->state.gfxCtx, &mfResult); + + if (mtx != NULL) { + gSPMatrix(POLY_XLU_DISP++, mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + func_8012C9BC(play->state.gfxCtx); + gSPSegment(POLY_XLU_DISP++, 0x08, Lib_SegmentedToVirtual(sLightningTextures[effect->lightningIndex])); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, 255); + gDPSetEnvColor(POLY_XLU_DISP++, 200, 255, 255, 255); + gSPDisplayList(POLY_XLU_DISP++, Lib_SegmentedToVirtual(gEffLightningDL)); + } + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.h b/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.h index dc8e95008a..d414e4100c 100644 --- a/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.h +++ b/src/overlays/actors/ovl_En_Grasshopper/z_en_grasshopper.h @@ -2,16 +2,77 @@ #define Z_EN_GRASSHOPPER_H #include "global.h" +#include "objects/object_grasshopper/object_grasshopper.h" + +#define EN_GRASSHOPPER_GET_TYPE(thisx) ((thisx)->params) struct EnGrasshopper; typedef void (*EnGrasshopperActionFunc)(struct EnGrasshopper*, PlayState*); +typedef enum EnGrasshopperType { + /* -1 */ EN_GRASSHOPPER_TYPE_UNUSED_NORMAL = -1, // Acts exactly like EN_GRASSHOPPER_TYPE_NORMAL + /* 0 */ EN_GRASSHOPPER_TYPE_NORMAL, + /* 1 */ EN_GRASSHOPPER_TYPE_GROWS_WHEN_SPAWNED, // Spawned by EnEncount1 + /* 2 */ EN_GRASSHOPPER_TYPE_WOODFALL_TEMPLE_FINAL_ROOM, + /* 3 */ EN_GRASSHOPPER_TYPE_WOODFALL, +} EnGrasshopperType; + +typedef struct EnGrasshopperEffect { + /* 0x00 */ u8 isEnabled; + /* 0x04 */ Vec3f pos; + /* 0x10 */ Vec3f velocity; + /* 0x1C */ f32 yaw; + /* 0x20 */ Vec3f scale; + /* 0x2C */ s16 timer; + /* 0x2E */ s16 lightningIndex; +} EnGrasshopperEffect; // size = 0x30 + +#define EN_GRASSHOPPER_EFFECT_COUNT 100 + typedef struct EnGrasshopper { /* 0x0000 */ Actor actor; - /* 0x0144 */ char unk_144[0x164]; + /* 0x0144 */ SkelAnime skelAnime; + /* 0x0188 */ Vec3s jointTable[DRAGONFLY_LIMB_MAX]; + /* 0x0218 */ Vec3s morphTable[DRAGONFLY_LIMB_MAX]; /* 0x02A8 */ EnGrasshopperActionFunc actionFunc; - /* 0x02AC */ char unk_2AC[0x15C4]; + /* 0x02AC */ u8 decision; + /* 0x02AD */ u8 shouldTurn; + /* 0x02AE */ UNK_TYPE1 unk_2AE[2]; + /* 0x02B0 */ s16 timer; + /* 0x02B2 */ s16 waitTimer; + /* 0x02B4 */ s16 postBankTimer; + /* 0x02B6 */ s16 drawDmgEffTimer; + /* 0x02B8 */ s16 drawDmgEffType; + /* 0x02BC */ f32 drawDmgEffScale; + /* 0x02C0 */ f32 drawDmgEffFrozenSteamScale; + /* 0x02C4 */ Vec3f bodyPartsPos[12]; + /* 0x0354 */ s16 bodyPartsPosIndex; + /* 0x0356 */ s16 splashCount; + /* 0x0358 */ s16 action; + /* 0x035A */ s16 index; // Has a minor effect on speed for some reason. + /* 0x035C */ s16 bankState; + /* 0x035E */ s16 type; + /* 0x0360 */ f32 dragonflyScale; + /* 0x0364 */ f32 rotationalVelocity; + /* 0x0368 */ f32 endFrame; + /* 0x036C */ f32 approachSpeed; + /* 0x0370 */ f32 baseFlyHeight; + /* 0x0374 */ f32 targetPosY; + /* 0x0376 */ Vec3s targetRot; + /* 0x0380 */ Vec3f flyingHomePos; + /* 0x038C */ s16 bobPhase; + /* 0x038E */ UNK_TYPE1 unk_38E[0x26]; + /* 0x03B4 */ Vec3f targetApproachPos; + /* 0x03C0 */ Vec3f tailTipPos; + /* 0x03CC */ Vec3f effectBasePos; + /* 0x03D8 */ Vec3f shadowBodyPartsPos[DRAGONFLY_LIMB_MAX]; + /* 0x04F8 */ f32 waterSurface; + /* 0x04FC */ Vec3f damagedVelocity; + /* 0x0508 */ Vec3s targetBankRot; + /* 0x0510 */ ColliderJntSph collider; + /* 0x0530 */ ColliderJntSphElement colliderElements[2]; + /* 0x0570 */ EnGrasshopperEffect effects[EN_GRASSHOPPER_EFFECT_COUNT]; } EnGrasshopper; // size = 0x1870 extern const ActorInit En_Grasshopper_InitVars; diff --git a/src/overlays/actors/ovl_En_Pp/z_en_pp.c b/src/overlays/actors/ovl_En_Pp/z_en_pp.c index 7587e38ca7..5d04b7567a 100644 --- a/src/overlays/actors/ovl_En_Pp/z_en_pp.c +++ b/src/overlays/actors/ovl_En_Pp/z_en_pp.c @@ -1539,10 +1539,10 @@ void EnPp_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, (limbIndex == HIPLOOP_LIMB_CENTER_WING_BASE) || (limbIndex == HIPLOOP_LIMB_CENTER_WING_MIDDLE) || (limbIndex == HIPLOOP_LIMB_BACK_LEFT_LOWER_LEG) || (limbIndex == HIPLOOP_LIMB_RIGHT_EYE) || (limbIndex == HIPLOOP_LIMB_LEFT_EYE)) { - Matrix_MultZero(&this->bodyPartsPos[this->bodyPartsPosCount]); - this->bodyPartsPosCount++; - if (this->bodyPartsPosCount >= ARRAY_COUNT(this->bodyPartsPos)) { - this->bodyPartsPosCount = 0; + Matrix_MultZero(&this->bodyPartsPos[this->bodyPartsPosIndex]); + this->bodyPartsPosIndex++; + if (this->bodyPartsPosIndex >= ARRAY_COUNT(this->bodyPartsPos)) { + this->bodyPartsPosIndex = 0; } if ((this->action == EN_PP_ACTION_SPAWN_BODY_PARTS) && (this->deadBodyPartsSpawnedCount < 6) && diff --git a/src/overlays/actors/ovl_En_Pp/z_en_pp.h b/src/overlays/actors/ovl_En_Pp/z_en_pp.h index 291c6473b6..d181c08de7 100644 --- a/src/overlays/actors/ovl_En_Pp/z_en_pp.h +++ b/src/overlays/actors/ovl_En_Pp/z_en_pp.h @@ -67,7 +67,7 @@ typedef struct EnPp { /* 0x400 */ f32 attackRange; /* 0x404 */ s32 hasBeenDamaged; /* 0x408 */ Vec3f bodyPartsPos[11]; - /* 0x48C */ s16 bodyPartsPosCount; + /* 0x48C */ s16 bodyPartsPosIndex; /* 0x490 */ f32 maskAccelY; /* 0x494 */ ColliderJntSph maskCollider; /* 0x4B4 */ ColliderJntSphElement maskColliderElements[1]; diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index 40a824f15f..11d38fc432 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -8751,36 +8751,36 @@ 0x809A610C:("ObjBoyo_Draw",), 0x809A6280:("EnGrasshopper_Init",), 0x809A64E0:("EnGrasshopper_Destroy",), - 0x809A6524:("func_809A6524",), - 0x809A65D8:("func_809A65D8",), - 0x809A6628:("func_809A6628",), - 0x809A6668:("func_809A6668",), - 0x809A6754:("func_809A6754",), - 0x809A67A4:("func_809A67A4",), - 0x809A6B6C:("func_809A6B6C",), - 0x809A6E18:("func_809A6E18",), - 0x809A6E74:("func_809A6E74",), - 0x809A6F8C:("func_809A6F8C",), - 0x809A700C:("func_809A700C",), - 0x809A7134:("func_809A7134",), - 0x809A71CC:("func_809A71CC",), - 0x809A7494:("func_809A7494",), - 0x809A753C:("func_809A753C",), - 0x809A7844:("func_809A7844",), - 0x809A78EC:("func_809A78EC",), - 0x809A797C:("func_809A797C",), - 0x809A7A8C:("func_809A7A8C",), - 0x809A7AE4:("func_809A7AE4",), - 0x809A7BBC:("func_809A7BBC",), - 0x809A7C98:("func_809A7C98",), - 0x809A7CE0:("func_809A7CE0",), - 0x809A8044:("func_809A8044",), + 0x809A6524:("EnGrasshopper_ChangeAnim",), + 0x809A65D8:("EnGrasshopper_RaiseTail",), + 0x809A6628:("EnGrasshopper_LowerTail",), + 0x809A6668:("EnGrasshopper_DecideAction",), + 0x809A6754:("EnGrasshopper_SetupFly",), + 0x809A67A4:("EnGrasshopper_Fly",), + 0x809A6B6C:("EnGrasshopper_RoamInCircles",), + 0x809A6E18:("EnGrasshopper_SetupBank",), + 0x809A6E74:("EnGrasshopper_Turn",), + 0x809A6F8C:("EnGrasshopper_SetupBounced",), + 0x809A700C:("EnGrasshopper_Bounced",), + 0x809A7134:("EnGrasshopper_SetupApproachPlayer",), + 0x809A71CC:("EnGrasshopper_ApproachPlayer",), + 0x809A7494:("EnGrasshopper_SetupAttack",), + 0x809A753C:("EnGrasshopper_Attack",), + 0x809A7844:("EnGrasshopper_SetupWaitAfterAttack",), + 0x809A78EC:("EnGrasshopper_WaitAfterAttack",), + 0x809A797C:("EnGrasshopper_SetupDamaged",), + 0x809A7A8C:("EnGrasshopper_Damaged",), + 0x809A7AE4:("EnGrasshopper_SetupDead",), + 0x809A7BBC:("EnGrasshopper_Dead",), + 0x809A7C98:("EnGrasshopper_SetupFall",), + 0x809A7CE0:("EnGrasshopper_Fall",), + 0x809A8044:("EnGrasshopper_UpdateDamage",), 0x809A8224:("EnGrasshopper_Update",), - 0x809A847C:("func_809A847C",), + 0x809A847C:("EnGrasshopper_PostLimbDraw",), 0x809A8640:("EnGrasshopper_Draw",), - 0x809A8870:("func_809A8870",), - 0x809A8924:("func_809A8924",), - 0x809A8A64:("func_809A8A64",), + 0x809A8870:("EnGrasshopper_InitializeEffect",), + 0x809A8924:("EnGrasshopper_UpdateEffects",), + 0x809A8A64:("EnGrasshopper_DrawEffects",), 0x809A9110:("func_809A9110",), 0x809A91FC:("func_809A91FC",), 0x809A92D0:("func_809A92D0",), diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt index c07dd39b22..26cb0b9fb9 100644 --- a/tools/disasm/variables.txt +++ b/tools/disasm/variables.txt @@ -9525,20 +9525,20 @@ 0x809A61E0:("D_809A61E0","f32","",0x4), 0x809A61E4:("D_809A61E4","f32","",0x4), 0x809A61E8:("D_809A61E8","f32","",0x4), - 0x809A8C80:("D_809A8C80","UNK_TYPE4","",0x4), - 0x809A8C94:("D_809A8C94","UNK_TYPE1","",0x1), - 0x809A8CAC:("D_809A8CAC","UNK_TYPE1","",0x1), - 0x809A8CC4:("D_809A8CC4","UNK_TYPE1","",0x1), - 0x809A8CDC:("D_809A8CDC","UNK_TYPE1","",0x1), + 0x809A8C80:("sOccupiedIndices","UNK_TYPE4","",0x4), + 0x809A8C94:("sLimbIndexToShadowBodyPartsIndex","UNK_TYPE1","",0x1), + 0x809A8CAC:("sParentBodyParts","UNK_TYPE1","",0x1), + 0x809A8CC4:("sShadowSizes","UNK_TYPE1","",0x1), + 0x809A8CDC:("sDamageTable","UNK_TYPE1","",0x1), 0x809A8CFC:("En_Grasshopper_InitVars","UNK_TYPE1","",0x1), - 0x809A8D1C:("D_809A8D1C","UNK_TYPE1","",0x1), - 0x809A8D64:("D_809A8D64","UNK_TYPE1","",0x1), - 0x809A8D74:("D_809A8D74","UNK_TYPE1","",0x1), - 0x809A8D94:("D_809A8D94","UNK_TYPE1","",0x1), - 0x809A8D9C:("D_809A8D9C","UNK_TYPE4","",0x4), - 0x809A8E08:("D_809A8E08","UNK_TYPE4","",0x4), - 0x809A8E14:("D_809A8E14","UNK_TYPE4","",0x4), - 0x809A8E20:("D_809A8E20","UNK_TYPE1","",0x1), + 0x809A8D1C:("sJntSphElementsInit","UNK_TYPE1","",0x1), + 0x809A8D64:("sJntSphInit","UNK_TYPE1","",0x1), + 0x809A8D74:("sAnimations","UNK_TYPE1","",0x1), + 0x809A8D94:("sAnimationModes","UNK_TYPE1","",0x1), + 0x809A8D9C:("sFireVelocityAndAccel","UNK_TYPE4","",0x4), + 0x809A8E08:("sEffectOffsetFromTailTop","UNK_TYPE4","",0x4), + 0x809A8E14:("sZeroVec3f","UNK_TYPE4","",0x4), + 0x809A8E20:("sLightningTextures","UNK_TYPE1","",0x1), 0x809A8E40:("D_809A8E40","f32","",0x4), 0x809A8E44:("D_809A8E44","f32","",0x4), 0x809A8E48:("D_809A8E48","f32","",0x4), diff --git a/undefined_syms.txt b/undefined_syms.txt index 5cb5cdf0b6..658dd5f055 100644 --- a/undefined_syms.txt +++ b/undefined_syms.txt @@ -1069,11 +1069,6 @@ D_0600A344 = 0x0600A344; D_06001EFC = 0x06001EFC; D_0600A808 = 0x0600A808; -// ovl_En_Grasshopper - -D_06000F9C = 0x06000F9C; -D_06003F00 = 0x06003F00; - // ovl_En_Hgo D_0600B644 = 0x0600B644;