diff --git a/include/functions.h b/include/functions.h index 6ba1d1a704..40412e6294 100644 --- a/include/functions.h +++ b/include/functions.h @@ -2021,7 +2021,7 @@ void func_80105818(GlobalContext* globalCtx, u32 uParm2, TransitionActorEntry* p // void func_80109EF8(void); // void func_80109F78(void); s32 func_8010A000(GlobalContext* globalCtx); -// void func_8010A074(void); +s32 func_8010A074(GlobalContext* globalCtx); // void func_8010A0A4(void); // void func_8010A0F0(void); // void func_8010A164(void); @@ -2155,7 +2155,7 @@ void func_8011552C(GlobalContext* globalCtx, s16 arg1); // void func_801155B4(void); // void func_80115764(void); void func_80115844(GlobalContext* globalCtx, s16 param_2); -void func_80115908(GlobalContext* globalCtx, u8 param_2); +s32 func_80115908(GlobalContext* globalCtx, u8 param_2); void func_801159c0(s16 param_1); void func_801159EC(s16 arg0); void func_80115A14(s32 arg0, s16 arg1); diff --git a/include/variables.h b/include/variables.h index ffaadaadfd..d61be83078 100644 --- a/include/variables.h +++ b/include/variables.h @@ -3972,8 +3972,13 @@ extern UNK_TYPE D_04029140; extern Gfx D_04029CB0[]; extern Gfx D_04029CF0[]; extern UNK_TYPE D_04029D20; -extern UNK_TYPE D_0402B494; -extern UNK_TYPE D_0402C908; +extern AnimationHeader D_0402B494; +extern AnimatedMaterial D_0402C818; +extern AnimatedMaterial D_0402C890; +extern AnimatedMaterial D_0402C908; +extern AnimatedMaterial D_0402C980; +extern AnimatedMaterial D_0402C9F8; +extern FlexSkeletonHeader D_0402CA98; extern Gfx D_0402E510[]; extern UNK_TYPE D_0402E65C; extern UNK_TYPE D_0402F0EC; diff --git a/spec b/spec index f8bdae92b2..ce47434ad3 100644 --- a/spec +++ b/spec @@ -3577,8 +3577,7 @@ beginseg name "ovl_En_Elforg" compress include "build/src/overlays/actors/ovl_En_Elforg/z_en_elforg.o" - include "build/data/ovl_En_Elforg/ovl_En_Elforg.data.o" - include "build/data/ovl_En_Elforg/ovl_En_Elforg.reloc.o" + include "build/src/overlays/actors/ovl_En_Elforg/ovl_En_Elforg_reloc.o" endseg beginseg diff --git a/src/boot_O2_g3/CIC6105.c b/src/boot_O2_g3/CIC6105.c index 4ab4ca4e82..7e9d960bf6 100644 --- a/src/boot_O2_g3/CIC6105.c +++ b/src/boot_O2_g3/CIC6105.c @@ -1,3 +1,4 @@ +#include "prevent_bss_reordering.h" #include "global.h" #include "prevent_bss_reordering.h" diff --git a/src/boot_O2_g3/boot_main.c b/src/boot_O2_g3/boot_main.c index ec6b446e71..be281d2489 100644 --- a/src/boot_O2_g3/boot_main.c +++ b/src/boot_O2_g3/boot_main.c @@ -1,3 +1,4 @@ +#include "prevent_bss_reordering.h" #include "global.h" #include "prevent_bss_reordering.h" diff --git a/src/overlays/actors/ovl_En_Elfbub/z_en_elfbub.c b/src/overlays/actors/ovl_En_Elfbub/z_en_elfbub.c index e8145f806e..1e87473cdc 100644 --- a/src/overlays/actors/ovl_En_Elfbub/z_en_elfbub.c +++ b/src/overlays/actors/ovl_En_Elfbub/z_en_elfbub.c @@ -5,6 +5,7 @@ */ #include "z_en_elfbub.h" +#include "overlays/actors/ovl_En_Elforg/z_en_elforg.h" #define FLAGS 0x00000001 @@ -76,7 +77,7 @@ void EnElfbub_Init(Actor* thisx, GlobalContext* globalCtx) { childActor = Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_EN_ELFORG, this->actor.world.pos.x, this->actor.world.pos.y + 12.0f, this->actor.world.pos.z, this->actor.world.rot.x, this->actor.world.rot.y, this->actor.world.rot.z, - ((ENELFBUB_GET_SWITCHFLAG(&this->actor) & 0x7F) << 9) | 2); + ((ENELFBUB_GET_SWITCHFLAG(&this->actor) & 0x7F) << 9) | STRAY_FAIRY_TYPE_BUBBLE); if (childActor != NULL) { childActor->parent = &this->actor; } diff --git a/src/overlays/actors/ovl_En_Elforg/z_en_elforg.c b/src/overlays/actors/ovl_En_Elforg/z_en_elforg.c index 2302e1ccac..3bbe3ba8e2 100644 --- a/src/overlays/actors/ovl_En_Elforg/z_en_elforg.c +++ b/src/overlays/actors/ovl_En_Elforg/z_en_elforg.c @@ -1,3 +1,9 @@ +/* + * File: z_en_elforg.c + * Overlay: ovl_En_Elforg + * Description: Stray Fairy + */ + #include "z_en_elforg.h" #define FLAGS 0x00000010 @@ -9,7 +15,13 @@ void EnElforg_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnElforg_Update(Actor* thisx, GlobalContext* globalCtx); void EnElforg_Draw(Actor* thisx, GlobalContext* globalCtx); -#if 0 +void EnElforg_TrappedByBubble(EnElforg* this, GlobalContext* globalCtx); +void EnElforg_TurnInFairy(EnElforg* this, GlobalContext* globalCtx); +void EnElforg_FreeFloatingFairyFountain(EnElforg* this, GlobalContext* globalCtx); +void EnElforg_FreeFloating(EnElforg* this, GlobalContext* globalCtx); +void EnElforg_SetupTrappedByEnemy(EnElforg* this, GlobalContext* globalCtx); +void EnElforg_HiddenByCollider(EnElforg* this, GlobalContext* globalCtx); + const ActorInit En_Elforg_InitVars = { ACTOR_EN_ELFORG, ACTORCAT_ITEMACTION, @@ -22,63 +34,584 @@ const ActorInit En_Elforg_InitVars = { (ActorFunc)EnElforg_Draw, }; -// static ColliderCylinderInit sCylinderInit = { -static ColliderCylinderInit D_80ACDA30 = { - { COLTYPE_NONE, AT_NONE, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER, OC2_TYPE_1, COLSHAPE_CYLINDER, }, - { ELEMTYPE_UNK0, { 0x00000000, 0x00, 0x00 }, { 0xF7CFFFFF, 0x00, 0x00 }, TOUCH_NONE | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_NONE, }, +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER, + OC2_TYPE_1, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0xF7CFFFFF, 0x00, 0x00 }, + TOUCH_NONE | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_NONE, + }, { 16, 32, 0, { 0, 0, 0 } }, }; -#endif +void EnElforg_InitializeParams(EnElforg* this) { + this->actor.speedXZ = 1.0f; + this->targetSpeedXZ = 1.0f; + this->actor.velocity.y = 0.0f; + this->actor.world.rot.y = randPlusMinusPoint5Scaled(0x10000); + this->timer = 0; + this->secondaryTimer = Rand_ZeroFloat(100.0f); + this->actor.shape.yOffset = 0.0f; + this->skelAnime.curFrame = (s32)Rand_ZeroFloat(5.0f); +} -extern ColliderCylinderInit D_80ACDA30; +void EnElforg_Init(Actor* thisx, GlobalContext* globalCtx) { + s32 pad; + EnElforg* this = THIS; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACC470.s") + Actor_SetScale(&this->actor, 0.01f); + this->flags = 0; + this->direction = 0; + SkelAnime_InitFlex(globalCtx, &this->skelAnime, &D_0402CA98, &D_0402B494, this->jointTable, this->jointTable, 10); + this->skelAnime.playSpeed = 1.0f; + ActorShape_Init(&this->actor.shape, 0.0f, NULL, 0.0f); + this->actor.shape.shadowAlpha = 255; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/EnElforg_Init.s") + switch (STRAY_FAIRY_TYPE(&this->actor)) { + case STRAY_FAIRY_TYPE_CLOCK_TOWN: + if (gSaveContext.weekEventReg[8] & 0x80) { + Actor_MarkForDeath(&this->actor); + return; + } + break; + case STRAY_FAIRY_TYPE_COLLECTIBLE: + if (Actor_GetCollectibleFlag(globalCtx, STRAY_FAIRY_FLAG(&this->actor))) { + Actor_MarkForDeath(&this->actor); + return; + } + break; + default: + if (Flags_GetSwitch(globalCtx, STRAY_FAIRY_FLAG(&this->actor))) { + Actor_MarkForDeath(&this->actor); + return; + } + break; + case STRAY_FAIRY_TYPE_FAIRY_FOUNTAIN: + case STRAY_FAIRY_TYPE_BUBBLE: + case STRAY_FAIRY_TYPE_CHEST: + case STRAY_FAIRY_TYPE_TURN_IN_TO_FAIRY_FOUNTAIN: + break; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/EnElforg_Destroy.s") + if (func_8010A074(globalCtx)) { + this->area = gSaveContext.unk_48C8 + 1; + } else { + // Needs to be thisx in order to match + this->area = STRAY_FAIRY_GET_PARAM_1C0(thisx) >> 6; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACC7E4.s") + switch (STRAY_FAIRY_TYPE(&this->actor)) { + case STRAY_FAIRY_TYPE_FAIRY_FOUNTAIN: + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_FreeFloatingFairyFountain; + this->targetSpeedXZ = Rand_ZeroFloat(2.0f) + 1.0f; + this->targetDistanceFromHome = Rand_ZeroFloat(100.0f) + 50.0f; + break; + case STRAY_FAIRY_TYPE_TURN_IN_TO_FAIRY_FOUNTAIN: + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_TurnInFairy; + this->secondaryTimer = 60; + break; + case STRAY_FAIRY_TYPE_BUBBLE: + this->timer = 0; + this->actionFunc = EnElforg_TrappedByBubble; + break; + case STRAY_FAIRY_TYPE_ENEMY: + this->actionFunc = EnElforg_SetupTrappedByEnemy; + EnElforg_SetupTrappedByEnemy(this, globalCtx); + this->actor.draw = NULL; + break; + case STRAY_FAIRY_TYPE_COLLIDER: + this->actionFunc = EnElforg_HiddenByCollider; + this->actor.draw = NULL; + Collider_InitAndSetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit); + Collider_UpdateCylinder(&this->actor, &this->collider); + break; + default: + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_FreeFloating; + break; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACC8D4.s") + this->actor.shape.rot.y = 0; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACC934.s") +void EnElforg_Destroy(Actor* thisx, GlobalContext* globalCtx) { + EnElforg* this = THIS; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACC994.s") + if (STRAY_FAIRY_TYPE(&this->actor) == STRAY_FAIRY_TYPE_COLLIDER) { + Collider_DestroyCylinder(globalCtx, &this->collider); + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCAC0.s") +void EnElforg_SpawnSparkles(EnElforg* this, GlobalContext* globalCtx, s32 life) { + static Vec3f sVelocity = { 0.0f, -0.05f, 0.0f }; + static Vec3f sAcceleration = { 0.0f, -0.025f, 0.0f }; + static Color_RGBA8 sPrimColors[] = { + { 255, 235, 220, 255 }, { 255, 220, 220, 255 }, { 220, 255, 220, 255 }, + { 220, 220, 255, 255 }, { 255, 255, 200, 255 }, + }; + static Color_RGBA8 sEnvColors[] = { + { 255, 150, 0, 255 }, { 255, 0, 0, 255 }, { 0, 255, 0, 255 }, { 0, 0, 255, 255 }, { 255, 255, 0, 255 }, + }; + Vec3f pos; + s32 pad; + s32 index; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCBB8.s") + pos.x = randPlusMinusPoint5Scaled(6.0f) + this->actor.world.pos.x; + pos.y = (Rand_ZeroOne() * 6.0f) + this->actor.world.pos.y + (this->actor.shape.yOffset * this->actor.scale.y); + pos.z = randPlusMinusPoint5Scaled(6.0f) + this->actor.world.pos.z; + index = (this->area < STRAY_FAIRY_AREA_CLOCK_TOWN || this->area >= STRAY_FAIRY_AREA_MAX) + ? STRAY_FAIRY_AREA_CLOCK_TOWN + : this->area; + EffectSsKiraKira_SpawnDispersed(globalCtx, &pos, &sVelocity, &sAcceleration, &sPrimColors[index], + &sEnvColors[index], 1000, life); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCBD0.s") +void EnElforg_ApproachTargetYPosition(EnElforg* this, Vec3f* targetPos) { + f32 yDifference = targetPos->y - this->actor.world.pos.y; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCC98.s") + if (fabsf(yDifference) < this->actor.speedXZ) { + this->actor.world.pos.y = targetPos->y; + } else if (yDifference > 0.0f) { + this->actor.world.pos.y += this->actor.speedXZ; + } else { + this->actor.world.pos.y -= this->actor.speedXZ; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCE4C.s") +void EnElforg_ApproachTargetSpeedXZ(EnElforg* this) { + if (this->actor.speedXZ > this->targetSpeedXZ) { + this->actor.speedXZ *= 0.9f; + } else if (this->actor.speedXZ < (this->targetSpeedXZ - 0.1f)) { + this->actor.speedXZ += 0.1f; + } else { + this->actor.speedXZ = this->targetSpeedXZ; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACCEB0.s") +void EnElforg_MoveToTargetFairyFountain(EnElforg* this, Vec3f* homePos) { + s32 pad[2]; + f32 xzDistance; + f32 zDifference; + f32 xDifference; + s16 angleAdjustment; + s16 targetAngle; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD088.s") + this->actor.shape.yOffset += 100.0f * Math_SinS(this->timer << 9); + EnElforg_ApproachTargetYPosition(this, homePos); + xDifference = this->actor.world.pos.x - homePos->x; + zDifference = this->actor.world.pos.z - homePos->z; + targetAngle = Math_FAtan2F(-zDifference, -xDifference); + xzDistance = sqrtf(SQ(xDifference) + SQ(zDifference)); + if ((this->targetDistanceFromHome + 10.0f) < xzDistance) { + angleAdjustment = 0x1000; + } else if ((this->targetDistanceFromHome - 10.0f) > xzDistance) { + angleAdjustment = 0x6000; + } else { + angleAdjustment = 0x4000; + } + targetAngle += angleAdjustment; + Math_SmoothStepToS(&this->actor.world.rot.y, targetAngle, 2, 4000, 1000); + EnElforg_ApproachTargetSpeedXZ(this); + Actor_SetVelocityAndMoveYRotationAndGravity(&this->actor); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD164.s") +/** + * Adjust's the Stray Fairy's speed, rotation, and y-position to bring it + * closer to targetPos. Since it doesn't directly point the fairy towards + * its target, it can take strange paths or even "orbit" the target. + */ +void EnElforg_MoveToTarget(EnElforg* this, Vec3f* targetPos) { + s16 targetAngle; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD1B0.s") + this->actor.shape.yOffset += 100.0f * Math_SinS(this->timer << 9); + EnElforg_ApproachTargetYPosition(this, targetPos); + targetAngle = Math_FAtan2F(-(this->actor.world.pos.z - targetPos->z), -(this->actor.world.pos.x - targetPos->x)); + if (this->targetSpeedXZ > 2.0f) { + Math_SmoothStepToS(&this->actor.world.rot.y, targetAngle, 2, 0x400, 0x100); + } else { + // If the speed is below a threshold, deliberately mess up the target + // angle so the fairy "orbits" the target instead of reaching it. + targetAngle += 0x2000; + Math_SmoothStepToS(&this->actor.world.rot.y, targetAngle, 10, 0x200, 0x80); + } + EnElforg_ApproachTargetSpeedXZ(this); + Actor_SetVelocityAndMoveYRotationAndGravity(&this->actor); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD1F0.s") +void func_80ACCBB8(EnElforg* this, GlobalContext* globalCtx) { + globalCtx->actorCtx.unk5 |= 8; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD2E4.s") +void EnElforg_TrappedByBubble(EnElforg* this, GlobalContext* globalCtx) { + SkelAnime_Update(&this->skelAnime); + if ((this->actor.parent == NULL) || (this->actor.parent->update == NULL)) { + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_FreeFloating; + } else { + this->actor.shape.yOffset += 10.0f * Math_SinS(this->timer << 9); + this->actor.world.pos = this->actor.parent->world.pos; + this->actor.world.pos.y += 12.0f; + } + func_80ACCBB8(this, globalCtx); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD59C.s") +void EnElforg_TurnInFairy(EnElforg* this, GlobalContext* globalCtx) { + Player* player = GET_PLAYER(globalCtx); + f32 xzDistToPlayer; + s16 rotationTemp; + s16 newAngle; + s32 pad; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD610.s") + // This code makes the fairy briefly circle the player before + // flying towards the fountain's center. + SkelAnime_Update(&this->skelAnime); + this->actor.shape.yOffset *= 0.9f; + this->actor.speedXZ = 5.0f; + EnElforg_ApproachTargetYPosition(this, &player->bodyPartsPos[0]); + xzDistToPlayer = this->actor.xzDistToPlayer; + if (xzDistToPlayer < 0.0f) { + xzDistToPlayer = 10.0f; + } + newAngle = 0x28000 / xzDistToPlayer; + Math_SmoothStepToF(&xzDistToPlayer, 40.0f, 0.2f, 100.0f, 1.0f); + rotationTemp = this->actor.yawTowardsPlayer - newAngle; + this->actor.world.pos.x = player->actor.world.pos.x - (Math_SinS(rotationTemp) * xzDistToPlayer); + this->actor.world.pos.z = player->actor.world.pos.z - (Math_CosS(rotationTemp) * xzDistToPlayer); + EnElforg_SpawnSparkles(this, globalCtx, 16); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD6A8.s") + if (this->secondaryTimer > 0) { + this->secondaryTimer--; + } else { + this->actor.world.rot.y = rotationTemp + 0x4000; + this->timer = 0; + this->secondaryTimer = Rand_ZeroFloat(100.0f); + this->actor.shape.yOffset = 0.0f; + this->targetSpeedXZ = 3.0f; + this->targetDistanceFromHome = 50.0f; + this->actionFunc = EnElforg_FreeFloatingFairyFountain; + this->flags &= ~STRAY_FAIRY_FLAG_CIRCLES_QUICKLY_IN_FOUNTAIN; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD6EC.s") +void EnElforg_QuicklyCircleFairyFountain(EnElforg* this, GlobalContext* globalCtx) { + SkelAnime_Update(&this->skelAnime); + EnElforg_MoveToTargetFairyFountain(this, &this->actor.home.pos); + if (this->secondaryTimer <= 30) { + this->actionFunc = EnElforg_TurnInFairy; + } + this->secondaryTimer--; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/EnElforg_Update.s") +void EnElforg_FreeFloatingFairyFountain(EnElforg* this, GlobalContext* globalCtx) { + s32 pad; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/func_80ACD878.s") + if (this->flags & STRAY_FAIRY_FLAG_MOVES_QUICKLY_TO_HOME) { + // This happens when "turning in" the last batch of Stray Fairies to + // a Fairy Fountain. The ones being "turned in" will fly very + // quickly to the center of the fountain. + if (this->targetSpeedXZ < 8.0f) { + this->targetSpeedXZ += 0.1f; + } + if (this->targetDistanceFromHome > 0.0f) { + this->targetDistanceFromHome -= 2.0f; + } + } else if ((this->timer & 127) == 127) { + if (Math_Vec3f_DistXZ(&this->actor.world.pos, &this->actor.home.pos) > 150.0f) { + this->targetSpeedXZ = 5.0f; + } else { + this->targetSpeedXZ = Rand_ZeroFloat(2.0f) + 1.0f; + } + this->targetDistanceFromHome = Rand_ZeroFloat(100.0f) + 50.0f; + } + SkelAnime_Update(&this->skelAnime); + EnElforg_MoveToTargetFairyFountain(this, &this->actor.home.pos); + if (this->flags & STRAY_FAIRY_FLAG_CIRCLES_QUICKLY_IN_FOUNTAIN) { + // A small number of fairies will do this when the player walks into + // the center of the fountain to be healed. + this->actionFunc = EnElforg_QuicklyCircleFairyFountain; + } + if (this->flags & STRAY_FAIRY_FLAG_SPARKLES_AND_SHRINKS) { + // This happens right before the Great Fairy appears once all + // Stray Fairies are saved. + if (this->actor.home.rot.x > 0) { + EnElforg_SpawnSparkles(this, globalCtx, 10); + this->actor.home.rot.x--; + } + Actor_SetScale(&this->actor, this->actor.scale.x * 0.9f); + if (this->actor.scale.x < 0.001f) { + Actor_MarkForDeath(&this->actor); + } + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Elforg/EnElforg_Draw.s") +void EnElforg_CirclePlayer(EnElforg* this, GlobalContext* globalCtx) { + s32 pad; + Actor* playerActor = &GET_PLAYER(globalCtx)->actor; + Player* player = GET_PLAYER(globalCtx); + f32 distanceFromPlayer; + + if (gSaveContext.playerForm == PLAYER_FORM_GORON) { + distanceFromPlayer = 40.0f; + } else { + distanceFromPlayer = 20.0f; + } + this->actor.world.pos.x = (Math_SinS(this->timer << 12) * distanceFromPlayer) + playerActor->world.pos.x; + this->actor.world.pos.z = (Math_CosS(this->timer << 12) * distanceFromPlayer) + playerActor->world.pos.z; + this->actor.world.pos.y = player->bodyPartsPos[0].y; + EnElforg_SpawnSparkles(this, globalCtx, 16); +} + +void EnElforg_FairyCollected(EnElforg* this, GlobalContext* globalCtx) { + EnElforg_CirclePlayer(this, globalCtx); + if (this->timer > 80) { + Actor_MarkForDeath(&this->actor); + return; + } + func_800B9010(&this->actor, NA_SE_PL_CHIBI_FAIRY_HEAL - SFX_FLAG); +} + +void EnElforg_SetupFairyCollected(EnElforg* this, GlobalContext* globalCtx) { + Actor* playerActor = &GET_PLAYER(globalCtx)->actor; + Player* player = GET_PLAYER(globalCtx); + + this->actor.world.pos.x = playerActor->world.pos.x; + this->actor.world.pos.y = player->bodyPartsPos[0].y; + this->actor.world.pos.z = playerActor->world.pos.z; + this->actionFunc = EnElforg_FairyCollected; + this->timer = 0; + this->secondaryTimer = 0; + this->actor.shape.yOffset = 0.0f; +} + +void EnElforg_ClockTownFairyCollected(EnElforg* this, GlobalContext* globalCtx) { + Player* player = GET_PLAYER(globalCtx); + + EnElforg_CirclePlayer(this, globalCtx); + player->actor.freezeTimer = 100; + player->stateFlags1 |= 0x20000000; + if (func_800B867C(&this->actor, globalCtx)) { + player->actor.freezeTimer = 0; + player->stateFlags1 &= ~0x20000000; + Actor_MarkForDeath(&this->actor); + gSaveContext.weekEventReg[8] |= 0x80; + ActorCutscene_Stop(0x7C); + } else { + func_800B9010(&this->actor, NA_SE_PL_CHIBI_FAIRY_HEAL - SFX_FLAG); + if (ActorCutscene_GetCurrentIndex() != 0x7C) { + if (ActorCutscene_GetCanPlayNext(0x7C)) { + ActorCutscene_Start(0x7C, &this->actor); + } else { + ActorCutscene_SetIntentToPlay(0x7C); + } + } + } +} + +void EnElforg_FreeFloating(EnElforg* this, GlobalContext* globalCtx) { + Vec3f pos; + f32 scaledYDistance; + Player* player = GET_PLAYER(globalCtx); + + SkelAnime_Update(&this->skelAnime); + if (Player_GetMask(globalCtx) == PLAYER_MASK_GREAT_FAIRYS_MASK) { + pos = player->bodyPartsPos[0]; + this->targetSpeedXZ = 5.0f; + EnElforg_MoveToTarget(this, &pos); + } else { + this->targetSpeedXZ = 1.0f; + EnElforg_MoveToTarget(this, &this->actor.home.pos); + } + + scaledYDistance = this->actor.yDistToPlayer - (this->actor.shape.yOffset * this->actor.scale.y); + if (!func_801233E4(globalCtx)) { + if ((this->actor.xzDistToPlayer < 30.0f) && (scaledYDistance < 12.0f) && (scaledYDistance > -68.0f)) { + EnElforg_SetupFairyCollected(this, globalCtx); + func_80115908(globalCtx, 48); + switch (STRAY_FAIRY_TYPE(&this->actor)) { + case STRAY_FAIRY_TYPE_COLLECTIBLE: + Actor_SetCollectibleFlag(globalCtx, STRAY_FAIRY_FLAG(&this->actor)); + break; + case STRAY_FAIRY_TYPE_CHEST: + Actor_SetChestFlag(globalCtx, STRAY_FAIRY_FLAG(&this->actor)); + break; + default: + Actor_SetSwitchFlag(globalCtx, STRAY_FAIRY_FLAG(&this->actor)); + break; + } + + if (STRAY_FAIRY_TYPE(&this->actor) == STRAY_FAIRY_TYPE_CLOCK_TOWN) { + player->actor.freezeTimer = 100; + player->stateFlags1 |= 0x20000000; + func_801518B0(globalCtx, 0x579, NULL); + this->actionFunc = EnElforg_ClockTownFairyCollected; + ActorCutscene_SetIntentToPlay(0x7C); + return; + } + + if (func_8010A074(globalCtx)) { + gSaveContext.inventory.strayFairies[gSaveContext.unk_48C8]++; + func_801518B0(globalCtx, 0x11, NULL); + if (gSaveContext.inventory.strayFairies[(void)0, gSaveContext.unk_48C8] >= 15) { + func_801A3098(0x922); + } + } + } + + Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 20.0f, 20.0f, 20.0f, 7); + func_80ACCBB8(this, globalCtx); + if (Player_GetMask(globalCtx) == PLAYER_MASK_GREAT_FAIRYS_MASK) { + if (!(this->flags & STRAY_FAIRY_FLAG_GREAT_FAIRYS_MASK_EQUIPPED)) { + play_sound(NA_SE_SY_FAIRY_MASK_SUCCESS); + } + this->flags |= STRAY_FAIRY_FLAG_GREAT_FAIRYS_MASK_EQUIPPED; + } else { + this->flags &= ~STRAY_FAIRY_FLAG_GREAT_FAIRYS_MASK_EQUIPPED; + } + } +} + +/** + * This finds the enemy that is "holding" the Stray Fairy hostage. When + * this enemy is killed, the Stray Fairy will spawn. This function only + * works if the enemy and the Stray Fairy have the exact same home + * coordinates when the Stray Fairy first spawns. + */ +Actor* EnElforg_GetHoldingEnemy(EnElforg* this, GlobalContext* globalCtx) { + Actor* enemy; + + for (enemy = globalCtx->actorCtx.actorList[ACTORCAT_ENEMY].first; enemy != NULL; enemy = enemy->next) { + if ((enemy->home.pos.x == this->actor.home.pos.x) && (enemy->home.pos.y == this->actor.home.pos.y) && + (enemy->home.pos.z == this->actor.home.pos.z)) { + return enemy; + } + } + return NULL; +} + +void EnElforg_TrappedByEnemy(EnElforg* this, GlobalContext* globalCtx) { + f32 posTemp; + + if (this->enemy->update == NULL) { + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_FreeFloating; + this->actor.draw = EnElforg_Draw; + Audio_PlayActorSound2(&this->actor, NA_SE_EV_CHIBI_FAIRY_SAVED); + } else { + // The enemy is still alive, so have the Stray Fairy + // track the enemy in case it's moving around. + posTemp = this->enemy->world.pos.x; + this->actor.world.pos.x = posTemp; + this->actor.home.pos.x = posTemp; + posTemp = this->enemy->world.pos.y + 30.0f; + this->actor.world.pos.y = posTemp; + this->actor.home.pos.y = posTemp; + posTemp = this->enemy->world.pos.z; + this->actor.world.pos.z = posTemp; + this->actor.home.pos.z = posTemp; + } + func_80ACCBB8(this, globalCtx); +} + +void EnElforg_SetupTrappedByEnemy(EnElforg* this, GlobalContext* globalCtx) { + Actor* enemy = EnElforg_GetHoldingEnemy(this, globalCtx); + + if (enemy != NULL && enemy->update != NULL) { + this->actionFunc = EnElforg_TrappedByEnemy; + this->enemy = enemy; + } +} + +void EnElforg_HiddenByCollider(EnElforg* this, GlobalContext* globalCtx) { + if (this->collider.base.acFlags & AC_HIT) { + EnElforg_InitializeParams(this); + this->actionFunc = EnElforg_FreeFloating; + this->actor.draw = EnElforg_Draw; + this->actor.world.pos.y += 40.0f; + this->actor.home.pos.y += 40.0f; + Audio_PlayActorSound2(&this->actor, NA_SE_EV_CHIBI_FAIRY_SAVED); + } else { + CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); + } + func_80ACCBB8(this, globalCtx); +} + +void EnElforg_Update(Actor* thisx, GlobalContext* globalCtx) { + EnElforg* this = THIS; + + this->actionFunc(this, globalCtx); + + if (this->timer == 0 && this->secondaryTimer > 0) { + this->secondaryTimer--; + } else { + this->timer++; + } + + if (this->direction < 0) { + this->direction++; + if (this->direction == 0) { + this->direction = Rand_ZeroFloat(20.0f) + 20.0f; + } + } else if (this->direction > 0) { + this->direction--; + } else { + this->direction = -Rand_ZeroFloat(20.0f) - 20.0f; + } +} + +s32 EnElforg_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, + Actor* thisx, Gfx** gfx) { + EnElforg* this = THIS; + + if (this->direction < 0) { + if (limbIndex == 9) { + *dList = NULL; + } + } else if (limbIndex == 1) { + *dList = NULL; + } + return 0; +} + +void EnElforg_Draw(Actor* thisx, GlobalContext* globalCtx) { + s32 pad; + EnElforg* this = THIS; + + OPEN_DISPS(globalCtx->state.gfxCtx); + func_8012C2DC(globalCtx->state.gfxCtx); + switch (this->area) { + case STRAY_FAIRY_AREA_WOODFALL: + AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&D_0402C908)); + break; + case STRAY_FAIRY_AREA_SNOWHEAD: + AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&D_0402C890)); + break; + case STRAY_FAIRY_AREA_GREAT_BAY: + AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&D_0402C980)); + break; + case STRAY_FAIRY_AREA_STONE_TOWER: + AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&D_0402C9F8)); + break; + default: + AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&D_0402C818)); + break; + } + Matrix_InsertMatrix(&globalCtx->mf_187FC, 1); + + POLY_XLU_DISP = + SkelAnime_DrawFlex(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, + EnElforg_OverrideLimbDraw, NULL, &this->actor, POLY_XLU_DISP); + + CLOSE_DISPS(globalCtx->state.gfxCtx); +} diff --git a/src/overlays/actors/ovl_En_Elforg/z_en_elforg.h b/src/overlays/actors/ovl_En_Elforg/z_en_elforg.h index cd3f037bf5..5195235a31 100644 --- a/src/overlays/actors/ovl_En_Elforg/z_en_elforg.h +++ b/src/overlays/actors/ovl_En_Elforg/z_en_elforg.h @@ -3,14 +3,54 @@ #include "global.h" +#define STRAY_FAIRY_TYPE(thisx) ((thisx)->params & 0xF) +#define STRAY_FAIRY_GET_PARAM_1C0(thisx) ((thisx)->params & 0x1C0) +#define STRAY_FAIRY_FLAG(thisx) (((thisx)->params & 0xFE00) >> 9) + +#define STRAY_FAIRY_FLAG_MOVES_QUICKLY_TO_HOME (1 << 0) +#define STRAY_FAIRY_FLAG_SPARKLES_AND_SHRINKS (1 << 1) +#define STRAY_FAIRY_FLAG_CIRCLES_QUICKLY_IN_FOUNTAIN (1 << 2) +#define STRAY_FAIRY_FLAG_GREAT_FAIRYS_MASK_EQUIPPED (1 << 3) + +typedef enum { + STRAY_FAIRY_TYPE_FREE_FLOATING, // The ones just floating around + STRAY_FAIRY_TYPE_FAIRY_FOUNTAIN, // The ones already present when you enter a Fairy Fountain + STRAY_FAIRY_TYPE_BUBBLE, // The ones trapped in bubbles + STRAY_FAIRY_TYPE_CLOCK_TOWN, // The free-floating Stray Fairies in Clock Town + STRAY_FAIRY_TYPE_ENEMY, // The ones trapped inside enemies + STRAY_FAIRY_TYPE_COLLIDER, // Unused in retail. The fairy is hidden until the collider is hit + STRAY_FAIRY_TYPE_CHEST, // The ones in treasure chests + STRAY_FAIRY_TYPE_COLLECTIBLE, // The ones in boxes, pots, beehives, etc. + STRAY_FAIRY_TYPE_TURN_IN_TO_FAIRY_FOUNTAIN // The ones you "turn in" by walking into a Fairy Fountain +} StrayFairyType; + +typedef enum { + STRAY_FAIRY_AREA_CLOCK_TOWN, + STRAY_FAIRY_AREA_WOODFALL, + STRAY_FAIRY_AREA_SNOWHEAD, + STRAY_FAIRY_AREA_GREAT_BAY, + STRAY_FAIRY_AREA_STONE_TOWER, + STRAY_FAIRY_AREA_MAX +} StrayFairyArea; + struct EnElforg; typedef void (*EnElforgActionFunc)(struct EnElforg*, GlobalContext*); typedef struct EnElforg { - /* 0x0000 */ Actor actor; - /* 0x0144 */ char unk_144[0xE8]; - /* 0x022C */ EnElforgActionFunc actionFunc; + /* 0x000 */ Actor actor; + /* 0x144 */ SkelAnime skelAnime; + /* 0x188 */ Vec3s jointTable[10]; + /* 0x1C4 */ ColliderCylinder collider; + /* 0x210 */ Actor* enemy; + /* 0x214 */ u16 flags; + /* 0x216 */ s16 direction; // negative when facing left, positive when facing right + /* 0x218 */ s16 area; + /* 0x21C */ s32 timer; + /* 0x220 */ s32 secondaryTimer; + /* 0x224 */ f32 targetSpeedXZ; + /* 0x228 */ f32 targetDistanceFromHome; + /* 0x22C */ EnElforgActionFunc actionFunc; } EnElforg; // size = 0x230 extern const ActorInit En_Elforg_InitVars; diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index 910b8c9957..be268d88aa 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -12075,30 +12075,30 @@ 0x80ACBDFC:("func_80ACBDFC",), 0x80ACBEE0:("ObjAqua_Update",), 0x80ACC048:("ObjAqua_Draw",), - 0x80ACC470:("func_80ACC470",), + 0x80ACC470:("EnElforg_InitializeParams",), 0x80ACC50C:("EnElforg_Init",), 0x80ACC7A4:("EnElforg_Destroy",), - 0x80ACC7E4:("func_80ACC7E4",), - 0x80ACC8D4:("func_80ACC8D4",), - 0x80ACC934:("func_80ACC934",), - 0x80ACC994:("func_80ACC994",), - 0x80ACCAC0:("func_80ACCAC0",), + 0x80ACC7E4:("EnElforg_SpawnSparkles",), + 0x80ACC8D4:("EnElforg_ApproachTargetYPosition",), + 0x80ACC934:("EnElforg_ApproachTargetSpeedXZ",), + 0x80ACC994:("EnElforg_MoveToTargetFairyFountain",), + 0x80ACCAC0:("EnElforg_MoveToTarget",), 0x80ACCBB8:("func_80ACCBB8",), - 0x80ACCBD0:("func_80ACCBD0",), - 0x80ACCC98:("func_80ACCC98",), - 0x80ACCE4C:("func_80ACCE4C",), - 0x80ACCEB0:("func_80ACCEB0",), - 0x80ACD088:("func_80ACD088",), - 0x80ACD164:("func_80ACD164",), - 0x80ACD1B0:("func_80ACD1B0",), - 0x80ACD1F0:("func_80ACD1F0",), - 0x80ACD2E4:("func_80ACD2E4",), - 0x80ACD59C:("func_80ACD59C",), - 0x80ACD610:("func_80ACD610",), - 0x80ACD6A8:("func_80ACD6A8",), - 0x80ACD6EC:("func_80ACD6EC",), + 0x80ACCBD0:("EnElforg_TrappedByBubble",), + 0x80ACCC98:("EnElforg_TurnInFairy",), + 0x80ACCE4C:("EnElforg_QuicklyCircleFairyFountain",), + 0x80ACCEB0:("EnElforg_FreeFloatingFairyFountain",), + 0x80ACD088:("EnElforg_CirclePlayer",), + 0x80ACD164:("EnElforg_FairyCollected",), + 0x80ACD1B0:("EnElforg_SetupFairyCollected",), + 0x80ACD1F0:("EnElforg_ClockTownFairyCollected",), + 0x80ACD2E4:("EnElforg_FreeFloating",), + 0x80ACD59C:("EnElforg_GetHoldingEnemy",), + 0x80ACD610:("EnElforg_TrappedByEnemy",), + 0x80ACD6A8:("EnElforg_SetupTrappedByEnemy",), + 0x80ACD6EC:("EnElforg_HiddenByCollider",), 0x80ACD798:("EnElforg_Update",), - 0x80ACD878:("func_80ACD878",), + 0x80ACD878:("EnElforg_OverrideLimbDraw",), 0x80ACD8C0:("EnElforg_Draw",), 0x80ACDCD0:("EnElfbub_Init",), 0x80ACDE34:("EnElfbub_Destroy",),