diff --git a/include/variables.h b/include/variables.h index 2876f14320..3770820bfa 100644 --- a/include/variables.h +++ b/include/variables.h @@ -4034,7 +4034,7 @@ extern Gfx D_04075B30[]; extern Gfx D_04076BC0[]; extern Gfx D_04077480[]; extern UNK_TYPE D_04079B10; -extern Gfx D_0407AB10[]; +extern Gfx D_0407AB10[]; // sun (sparkles when small) displaylist extern Gfx D_0407AB58[]; extern UNK_TYPE D_0407AFB0; extern Gfx D_0407D590[]; diff --git a/include/z64.h b/include/z64.h index 66a6e6fd50..dae4195b2a 100644 --- a/include/z64.h +++ b/include/z64.h @@ -942,7 +942,7 @@ typedef struct { /* 0xEC */ u8 unk_EC; /* 0xED */ u8 unk_ED; /* 0xEE */ u8 unk_EE[4]; - /* 0xF2 */ u8 unk_F2[8]; + /* 0xF2 */ u8 unk_F2[8]; // [3] is used by both DemoKankyo and ObjectKankyo particle count /* 0xFA */ u8 unk_FA[4]; } EnvironmentContext; // size = 0x100 diff --git a/spec b/spec index ac312ea307..7251410c28 100644 --- a/spec +++ b/spec @@ -1156,8 +1156,7 @@ beginseg name "ovl_Demo_Kankyo" compress include "build/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.o" - include "build/data/ovl_Demo_Kankyo/ovl_Demo_Kankyo.data.o" - include "build/data/ovl_Demo_Kankyo/ovl_Demo_Kankyo.reloc.o" + include "build/src/overlays/actors/ovl_Demo_Kankyo/ovl_Demo_Kankyo_reloc.o" endseg beginseg diff --git a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c index fcf3e0dec5..6ff4f90965 100644 --- a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c +++ b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c @@ -15,12 +15,15 @@ void DemoKankyo_Destroy(Actor* thisx, GlobalContext* globalCtx); void DemoKankyo_Update(Actor* thisx, GlobalContext* globalCtx); void DemoKankyo_Draw(Actor* thisx, GlobalContext* globalCtx); -void func_808CF06C(DemoKankyo* this, GlobalContext* globalCtx); -void func_808CF0CC(DemoKankyo* this, GlobalContext* globalCtx); +void DemoKakyo_MoonSparklesActionFunc(DemoKankyo* this, GlobalContext* globalCtx); -void DemoKankyo_SetupAction(DemoKankyo* this, DemoKankyoActionFunc actionFunc); +extern Gfx D_0407AB58[]; +extern Gfx D_06001000[]; // the bubble display list used by shabom in giants type +extern Gfx D_04023428[]; + +static u8 sLostWoodsSparklesMutex = false; // make sure only one can exist at once +static s16 sLostWoodsSkyFishParticleNum = 0; -#if 0 const ActorInit Demo_Kankyo_InitVars = { ACTOR_DEMO_KANKYO, ACTORCAT_ITEMACTION, @@ -33,24 +36,659 @@ const ActorInit Demo_Kankyo_InitVars = { (ActorFunc)DemoKankyo_Draw, }; -#endif +static s32 sObjectBubbleIndex = OBJECT_BUBBLE | 0x10000; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/DemoKankyo_SetupAction.s") +void DemoKankyo_SetupAction(DemoKankyo* this, DemoKankyoActionFunc actionFunc) { + this->actionFunc = actionFunc; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/func_808CE45C.s") +void DemoKakyo_LostWoodsSparkleActionFunc(DemoKankyo* this, GlobalContext* globalCtx) { + s32 pad; + s32 i; + f32 randSkyfishParticleNum; + f32 repositionLimit; // Distance from posCenter when particles are relocated. Always set to 130.0f + f32 eyeToAtNormX; + f32 eyeToAtNormY; + f32 eyeToAtNormZ; + f32 eyeToAtMag; + f32 posCenterX; + f32 posCenterY; + f32 posCenterZ; + Vec3f eyeToAt; + Player* player = GET_PLAYER(globalCtx); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/func_808CF06C.s") + if (globalCtx->roomCtx.unk7A[1] != 0) { + if (globalCtx->envCtx.unk_F2[3] != 0) { + globalCtx->envCtx.unk_F2[3]--; + } else { + Actor_MarkForDeath(&this->actor); + } + } else if (globalCtx->envCtx.unk_F2[3] < DEMOKANKYO_PARTICLE_COUNT) { + globalCtx->envCtx.unk_F2[3] += 16; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/func_808CF0CC.s") + // note: DemoKankyo can crash if placed in an area that snows (ObjectKankyo) + // because they both use unk_F2 as a particle counter, + // causing DemoKankyo to write beyond its particle array boundry + // this crash can occur if the two actors are in different scenes connected by an exit + // e.g. if you add DemoKankyo to GoronShrine, you will crash entering/leaving through door + for (i = 0; i < globalCtx->envCtx.unk_F2[3]; i++) { + repositionLimit = 130.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/DemoKankyo_Init.s") + eyeToAt.x = globalCtx->view.at.x - globalCtx->view.eye.x; + eyeToAt.y = globalCtx->view.at.y - globalCtx->view.eye.y; + eyeToAt.z = globalCtx->view.at.z - globalCtx->view.eye.z; + eyeToAtMag = sqrtf(SQXYZ(eyeToAt)); + eyeToAtNormX = eyeToAt.x / eyeToAtMag; + eyeToAtNormY = eyeToAt.y / eyeToAtMag; + eyeToAtNormZ = eyeToAt.z / eyeToAtMag; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/DemoKankyo_Destroy.s") + switch (this->particles[i].state) { + case DEMO_KANKYO_STATE_INIT: + this->particles[i].posBase.x = globalCtx->view.eye.x + (eyeToAtNormX * 80.0f); + this->particles[i].posBase.y = globalCtx->view.eye.y + (eyeToAtNormY * 80.0f); + this->particles[i].posBase.z = globalCtx->view.eye.z + (eyeToAtNormZ * 80.0f); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/DemoKankyo_Update.s") + this->particles[i].posOffset.x = (Rand_ZeroOne() - 0.5f) * 160.0f; + this->particles[i].posOffset.y = 30.0f; + this->particles[i].posOffset.z = (Rand_ZeroOne() - 0.5f) * 160.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/func_808CF970.s") + this->particles[i].speedTarget = (Rand_ZeroOne() * 1.6f) + 0.5f; + this->particles[i].alpha = 0; + this->particles[i].alphaClock = Rand_ZeroOne() * 65535; // random 0 to max of u16 + this->particles[i].scale = 0.1f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/func_808CFE04.s") + // speedClock is angles in radians, + // should have used Rand_ZeroOne() * 2 * M_PI + // however, due to properties of sine waves, this is effectively still random + this->particles[i].speedClock.x = Rand_ZeroOne() * 360.0f; + this->particles[i].speedClock.y = Rand_ZeroOne() * 360.0f; + this->particles[i].speedClock.z = Rand_ZeroOne() * 360.0f; + this->particles[i].pad50 = 0; + this->particles[i].state += DEMO_KANKYO_STATE_SINGLE; + break; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Demo_Kankyo/DemoKankyo_Draw.s") + case DEMO_KANKYO_STATE_SINGLE: + case DEMO_KANKYO_STATE_SKYFISH: + this->particles[i].alphaClock++; + posCenterX = globalCtx->view.eye.x + (eyeToAtNormX * 80.0f); + posCenterY = globalCtx->view.eye.y + (eyeToAtNormY * 80.0f); + posCenterZ = globalCtx->view.eye.z + (eyeToAtNormZ * 80.0f); + this->particles[i].posOffsetPrev.x = this->particles[i].posOffset.x; + this->particles[i].posOffsetPrev.y = this->particles[i].posOffset.y; + this->particles[i].posOffsetPrev.z = this->particles[i].posOffset.z; + + if (this->particles[i].state == DEMO_KANKYO_STATE_SINGLE) { + + // The first 32 particles will become skyfish particles + // This block is also init code and only runs once + if (i < 32) { + if (Rand_ZeroOne() < 0.5f) { + this->particles[i].LostWoodsSkyFishSpeedXZ = (s16)(Rand_ZeroOne() * 200.0f) + 200; + } else { + this->particles[i].LostWoodsSkyFishSpeedXZ = -200 - (s16)(Rand_ZeroOne() * 200.0f); + } + this->particles[i].LostWoodsSkyFishPosOffsetMax = (s16)(Rand_ZeroOne() * 50.0f) + 15; + this->particles[i].LostWoodsSkyFishSpeedY = ((Rand_ZeroOne() * 10.0f) + 10.0f) * 0.01f; + + // Only the 31st particle matters as sLostWoodsSkyFishParticleNum will be overwritten + // every particle until the last skyfish particle is initialized + randSkyfishParticleNum = Rand_ZeroOne(); + if (randSkyfishParticleNum < 0.2f) { + sLostWoodsSkyFishParticleNum = 1; + } else if (randSkyfishParticleNum < 0.2f) { + // This case is never taken as the else-if conditional is identical to the previous one + sLostWoodsSkyFishParticleNum = 3; + } else if (randSkyfishParticleNum < 0.4f) { + sLostWoodsSkyFishParticleNum = 7; + } else { + sLostWoodsSkyFishParticleNum = 15; + } + + if ((i & sLostWoodsSkyFishParticleNum) == 0) { + // Head particle + this->particles[i].posOffset.y = 0.0f; + } + this->particles[i].state = DEMO_KANKYO_STATE_SKYFISH; + this->particles[i].speedTarget = 0.0f; + } + + Math_SmoothStepToF(&this->particles[i].scale, 0.1, 0.1f, 0.001f, 0.00001f); + Math_SmoothStepToF(&this->particles[i].speed, this->particles[i].speedTarget, 0.5f, 0.2f, 0.02f); + this->particles[i].posOffset.x += + __sinf(this->particles[i].speedClock.x) * this->particles[i].speed; + this->particles[i].posOffset.y += + __sinf(this->particles[i].speedClock.y) * this->particles[i].speed; + this->particles[i].posOffset.z += + __sinf(this->particles[i].speedClock.z) * this->particles[i].speed; + + switch ((i >> 1) & 3) { + case 0: + this->particles[i].speedClock.x += 0.008f; + this->particles[i].speedClock.y += 0.05f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.015f; + break; + + case 1: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.05f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.005f * Rand_ZeroOne(); + break; + + case 2: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.4f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.004f * Rand_ZeroOne(); + break; + + case 3: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.08f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.05f * Rand_ZeroOne(); + break; + } + + } else if (this->particles[i].state == DEMO_KANKYO_STATE_SKYFISH) { + if ((i & sLostWoodsSkyFishParticleNum) == 0) { + // Head particle + Math_SmoothStepToF(&this->particles[i].scale, 0.25f, 0.1f, 0.001f, 0.00001f); + + Math_SmoothStepToF(&this->particles[i].posBase.x, player->actor.world.pos.x, 0.5f, 1.0f, 0.2f); + Math_SmoothStepToF(&this->particles[i].posBase.y, player->actor.world.pos.y + 50.0f, 0.5f, 1.0f, + 0.2f); + Math_SmoothStepToF(&this->particles[i].posBase.z, player->actor.world.pos.z, 0.5f, 1.0f, 0.2f); + + Math_SmoothStepToF(&this->particles[i].posOffset.x, + Math_SinS(this->particles[i].LostWoodsSkyFishSpeedXZClock - 0x8000) * + this->particles[i].LostWoodsSkyFishPosOffsetMax, + 0.5f, 2.0f, 0.2f); + Math_SmoothStepToF(&this->particles[i].posOffset.z, + Math_CosS(this->particles[i].LostWoodsSkyFishSpeedXZClock - 0x8000) * + this->particles[i].LostWoodsSkyFishPosOffsetMax, + 0.5f, 2.0f, 0.2f); + this->particles[i].LostWoodsSkyFishSpeedXZClock += this->particles[i].LostWoodsSkyFishSpeedXZ; + this->particles[i].posOffset.y += __sinf(this->particles[i].speedClock.y); + this->particles[i].speedClock.x += 0.2f * Rand_ZeroOne(); // unused calculation + this->particles[i].speedClock.y += this->particles[i].LostWoodsSkyFishSpeedY; + this->particles[i].speedClock.z += 0.1f * Rand_ZeroOne(); // unused calculation + + this->particles[i].posOffset.x = + Math_SinS(this->particles[i].LostWoodsSkyFishSpeedXZClock - 0x8000) * + this->particles[i].LostWoodsSkyFishPosOffsetMax; + this->particles[i].posOffset.z = + Math_CosS(this->particles[i].LostWoodsSkyFishSpeedXZClock - 0x8000) * + this->particles[i].LostWoodsSkyFishPosOffsetMax; + } else { + // Tail Particles + Math_SmoothStepToF(&this->particles[i].scale, 0.1, 0.1f, 0.001f, 0.00001f); + + // Unused calculation, speed only used in posOffset calculations, + // but posOffset gets overwritten for tail particles immediately below + Math_SmoothStepToF(&this->particles[i].speed, 1.5f, 0.5f, 0.1f, 0.0002f); + + // particles in the skyfish's tail are moved to the previous position of the particle directly + // in front + this->particles[i].posOffset.x = + this->particles[i - 1].posOffsetPrev.x + + (this->particles[i - 1].posBase.x - this->particles[i].posBase.x); + this->particles[i].posOffset.y = + this->particles[i - 1].posOffsetPrev.y + + (this->particles[i - 1].posBase.y - this->particles[i].posBase.y); + this->particles[i].posOffset.z = + this->particles[i - 1].posOffsetPrev.z + + (this->particles[i - 1].posBase.z - this->particles[i].posBase.z); + } + } + + if ((this->particles[i].state != DEMO_KANKYO_STATE_SKYFISH) && + ((((this->particles[i].posBase.x + this->particles[i].posOffset.x) - posCenterX) > + repositionLimit) || + (((this->particles[i].posBase.x + this->particles[i].posOffset.x) - posCenterX) < + -repositionLimit) || + (((this->particles[i].posBase.y + this->particles[i].posOffset.y) - posCenterY) > + repositionLimit) || + (((this->particles[i].posBase.y + this->particles[i].posOffset.y) - posCenterY) < + -repositionLimit) || + (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - posCenterZ) > + repositionLimit) || + (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - posCenterZ) < + -repositionLimit))) { + if (((this->particles[i].posOffset.x + this->particles[i].posBase.x) - posCenterX) > + repositionLimit) { + this->particles[i].posOffset.x = 0.0f; + this->particles[i].posBase.x = posCenterX - repositionLimit; + } + + if (((this->particles[i].posBase.x + this->particles[i].posOffset.x) - posCenterX) < + -repositionLimit) { + this->particles[i].posOffset.x = 0.0f; + this->particles[i].posBase.x = posCenterX + repositionLimit; + } + + if (((this->particles[i].posBase.y + this->particles[i].posOffset.y) - posCenterY) > 50.0f) { + this->particles[i].posOffset.y = 0.0f; + this->particles[i].posBase.y = posCenterY - 50.0f; + } + + if (((this->particles[i].posBase.y + this->particles[i].posOffset.y) - posCenterY) < -50.0f) { + this->particles[i].posOffset.y = 0.0f; + this->particles[i].posBase.y = posCenterY + 50.0f; + } + + if (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - posCenterZ) > + repositionLimit) { + this->particles[i].posOffset.z = 0.0f; + this->particles[i].posBase.z = posCenterZ - repositionLimit; + } + + if (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - posCenterZ) < + -repositionLimit) { + this->particles[i].posOffset.z = 0.0f; + this->particles[i].posBase.z = posCenterZ + repositionLimit; + } + } + break; + + case DEMO_KANKYO_STATE_DISABLED: + this->particles[i].state = DEMO_KANKYO_STATE_INIT; + break; + } + } +} + +void DemoKakyo_GiantObjectCheck(DemoKankyo* this, GlobalContext* globalCtx) { + if (Object_IsLoaded(&globalCtx->objectCtx, this->objectId)) { + this->isSafeToDrawGiants = true; + this->actor.objBankIndex = this->objectId; + DemoKankyo_SetupAction(this, DemoKakyo_MoonSparklesActionFunc); + } +} + +/* + * used by Moon AND giants types + */ +void DemoKakyo_MoonSparklesActionFunc(DemoKankyo* this, GlobalContext* globalCtx) { + s32 i; + Vec3f eyeToAt; + f32 eyeToAtNormX; + f32 eyeToAtNormY; + f32 eyeToAtNormZ; + f32 eyeToAtMag; + f32 halfScreenWidth; + f32 randZeroOne; + f32 pad0; + Vec3f newEye; + f32 halfScreenHeight; + s32 pad1; + Vec3f worldPos; + + if (globalCtx->envCtx.unk_F2[3] < DEMOKANKYO_PARTICLE_COUNT) { + globalCtx->envCtx.unk_F2[3] += 16; + } + + eyeToAt.x = globalCtx->view.at.x - globalCtx->view.eye.x; + eyeToAt.y = globalCtx->view.at.y - globalCtx->view.eye.y; + eyeToAt.z = globalCtx->view.at.z - globalCtx->view.eye.z; + eyeToAtMag = sqrtf(SQXYZ(eyeToAt)); + eyeToAtNormX = eyeToAt.x / eyeToAtMag; + eyeToAtNormY = eyeToAt.y / eyeToAtMag; + eyeToAtNormZ = eyeToAt.z / eyeToAtMag; + + halfScreenHeight = SCREEN_HEIGHT / 2; + + for (i = 0; i < globalCtx->envCtx.unk_F2[3]; i++) { + switch (this->particles[i].state) { + case DEMO_KANKYO_STATE_INIT: + this->particles[i].posBase.x = globalCtx->view.eye.x + (eyeToAtNormX * halfScreenHeight); + this->particles[i].posBase.y = globalCtx->view.eye.y + (eyeToAtNormY * halfScreenHeight); + this->particles[i].posBase.z = globalCtx->view.eye.z + (eyeToAtNormZ * halfScreenHeight); + + this->particles[i].posOffset.x = (Rand_ZeroOne() - 0.5f) * (2.0f * halfScreenHeight); + this->particles[i].posOffset.y = (Rand_ZeroOne() - 0.5f) * (2.0f * halfScreenHeight); + this->particles[i].posOffset.z = (Rand_ZeroOne() - 0.5f) * (2.0f * halfScreenHeight); + + this->particles[i].speedTarget = (Rand_ZeroOne() * 1.6f) + 0.5f; + this->particles[i].alpha = 0; + this->particles[i].alphaClock = (Rand_ZeroOne() * 65535); + this->particles[i].scale = 0.2f; + + // speedClock is angles in radians, + // should have used Rand_ZeroOne() * 2 * M_PI + // however, due to properties of sine waves, this is effectively still random + this->particles[i].speedClock.x = Rand_ZeroOne() * 360.0f; + this->particles[i].speedClock.y = Rand_ZeroOne() * 360.0f; + this->particles[i].speedClock.z = Rand_ZeroOne() * 360.0f; + + this->particles[i].pad50 = 0; + this->particles[i].state += DEMO_KANKYO_STATE_SINGLE; + break; + + case DEMO_KANKYO_STATE_SINGLE: + case DEMO_KANKYO_STATE_SKYFISH: + this->particles[i].alphaClock++; + + if (this->actor.params == DEMO_KANKYO_TYPE_MOON) { // this function gets reused for giants too + this->particles[i].posBase.y = + globalCtx->view.eye.y + (eyeToAtNormY * halfScreenHeight) + (SCREEN_HEIGHT / 3); + } + + newEye.x = globalCtx->view.eye.x + (eyeToAtNormX * halfScreenHeight); + newEye.y = globalCtx->view.eye.y + (eyeToAtNormY * halfScreenHeight); + newEye.z = globalCtx->view.eye.z + (eyeToAtNormZ * halfScreenHeight); + + Math_SmoothStepToF(&this->particles[i].scale, 0.2f, 0.1f, 0.001f, 0.00001f); + Math_SmoothStepToF(&this->particles[i].speed, this->particles[i].speedTarget, 0.5f, 0.2f, 0.02f); + + this->particles[i].posOffset.x += __sinf(this->particles[i].speedClock.x) * this->particles[i].speed; + this->particles[i].posOffset.y += __sinf(this->particles[i].speedClock.y) * this->particles[i].speed; + this->particles[i].posOffset.z += __sinf(this->particles[i].speedClock.z) * this->particles[i].speed; + + switch ((i >> 1) & 3) { + case 0: + this->particles[i].speedClock.x += 0.008f; + this->particles[i].speedClock.y += 0.05f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.015f; + break; + + case 1: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.05f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.005f * Rand_ZeroOne(); + break; + + case 2: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.4f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.004f * Rand_ZeroOne(); + break; + + case 3: + this->particles[i].speedClock.x += 0.01f * Rand_ZeroOne(); + this->particles[i].speedClock.y += 0.08f * Rand_ZeroOne(); + this->particles[i].speedClock.z += 0.05f * Rand_ZeroOne(); + break; + } + + if (((this->particles[i].posBase.x + this->particles[i].posOffset.x) - newEye.x) > halfScreenHeight) { + this->particles[i].posBase.x = newEye.x - halfScreenHeight; + } + + if (((this->particles[i].posBase.x + this->particles[i].posOffset.x) - newEye.x) < -halfScreenHeight) { + this->particles[i].posBase.x = newEye.x + halfScreenHeight; + } + + worldPos.x = this->particles[i].posBase.x + this->particles[i].posOffset.x; + worldPos.y = this->particles[i].posBase.y + this->particles[i].posOffset.y; + worldPos.z = this->particles[i].posBase.z + this->particles[i].posOffset.z; + + randZeroOne = Math_Vec3f_DistXZ(&worldPos, &globalCtx->view.eye) / 200.0f; + randZeroOne = CLAMP(randZeroOne, 0.0f, 1.0f); + halfScreenWidth = 100.0f + randZeroOne + 60.0f; // range 160 to 161...? thats about half screen width + + // I think this code is shifting the particles 1 frame -> half screen at a time to keep it in-view + if (halfScreenWidth < ((this->particles[i].posBase.y + this->particles[i].posOffset.y) - newEye.y)) { + this->particles[i].posBase.y = newEye.y - halfScreenWidth; + } + + if (((this->particles[i].posBase.y + this->particles[i].posOffset.y) - newEye.y) < -halfScreenWidth) { + this->particles[i].posBase.y = newEye.y + halfScreenWidth; + } + + if (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - newEye.z) > halfScreenHeight) { + this->particles[i].posBase.z = newEye.z - halfScreenHeight; + } + + if (((this->particles[i].posBase.z + this->particles[i].posOffset.z) - newEye.z) < -halfScreenHeight) { + this->particles[i].posBase.z = newEye.z + halfScreenHeight; + } + + break; + + case DEMO_KANKYO_STATE_DISABLED: + this->particles[i].state = DEMO_KANKYO_STATE_INIT; + break; + } + } +} + +void DemoKankyo_Init(Actor* thisx, GlobalContext* globalCtx) { + DemoKankyo* this = THIS; + s32 pad; + s32 i; + s32 objId; + + // This must be a single line to match, possibly a macro? + // clang-format off + for (i = 0; i < ARRAY_COUNT(this->particles); i++) { this->particles[i].state = DEMO_KANKYO_STATE_INIT; } + // clang-format on + + if (1) {}; + + switch (this->actor.params) { + case DEMO_KANKYO_TYPE_LOSTWOODS: + objId = OBJECT_UNSET_0; + this->actor.room = -1; + if (sLostWoodsSparklesMutex == false) { + DemoKankyo_SetupAction(this, DemoKakyo_LostWoodsSparkleActionFunc); + sLostWoodsSparklesMutex = true; + } else { + Actor_MarkForDeath(&this->actor); + } + break; + + case DEMO_KANKYO_TYPE_GIANTS: + this->isSafeToDrawGiants = false; + objId = Object_GetIndex(&globalCtx->objectCtx, sObjectBubbleIndex); + DemoKankyo_SetupAction(this, DemoKakyo_GiantObjectCheck); + break; + + case DEMO_KANKYO_TYPE_MOON: + objId = OBJECT_UNSET_0; + this->isSafeToDrawGiants = true; + DemoKankyo_SetupAction(this, DemoKakyo_MoonSparklesActionFunc); + break; + + default: + //! @bug: this causes a crash because the actionfunc is never set + objId = -1; + break; + } + + if (objId > -1) { + this->objectId = objId; + } +} + +void DemoKankyo_Destroy(Actor* thisx, GlobalContext* globalCtx) { + DemoKankyo* this = THIS; + + Actor_MarkForDeath(&this->actor); +} + +void DemoKankyo_Update(Actor* thisx, GlobalContext* globalCtx) { + DemoKankyo* this = THIS; + + this->actionFunc(this, globalCtx); +} + +void DemoKakyo_DrawLostWoodsSparkle(Actor* thisx, GlobalContext* globalCtx2) { + DemoKankyo* this = THIS; + GlobalContext* globalCtx = globalCtx2; + s16 i; + f32 scaleAlpha; + Vec3f worldPos; + Vec3f screenPos; + + // if not underwater + if (!(globalCtx->cameraPtrs[MAIN_CAM]->flags2 & 0x100)) { + OPEN_DISPS(globalCtx->state.gfxCtx); + + POLY_XLU_DISP = Gfx_CallSetupDL(POLY_XLU_DISP, 20); + + gSPSegment(POLY_XLU_DISP++, 0x08, Lib_SegmentedToVirtual(&D_04079B10)); + gSPDisplayList(POLY_XLU_DISP++, D_0407AB10); + + for (i = 0; i < globalCtx->envCtx.unk_F2[3]; i++) { + worldPos.x = this->particles[i].posBase.x + this->particles[i].posOffset.x; + worldPos.y = this->particles[i].posBase.y + this->particles[i].posOffset.y; + worldPos.z = this->particles[i].posBase.z + this->particles[i].posOffset.z; + + func_80169474(globalCtx, &worldPos, &screenPos); // unnamed Play_ function, func_800C016C from OoT + + // checking if particle is on screen + if (screenPos.x >= 0.0f && screenPos.x < SCREEN_WIDTH && screenPos.y >= 0.0f && + screenPos.y < SCREEN_HEIGHT) { + Matrix_InsertTranslation(worldPos.x, worldPos.y, worldPos.z, MTXMODE_NEW); + scaleAlpha = this->particles[i].alpha / 50.0f; + if (scaleAlpha > 1.0f) { + scaleAlpha = 1.0f; + } + + Matrix_Scale(this->particles[i].scale * scaleAlpha, this->particles[i].scale * scaleAlpha, + this->particles[i].scale * scaleAlpha, MTXMODE_APPLY); + + // adjust transparency of this particle + if (i < 32) { + // Skyfish particles + if (this->particles[i].state != DEMO_KANKYO_STATE_SKYFISH) { + // still initializing + if (this->particles[i].alpha > 0) { // NOT DECR + this->particles[i].alpha--; + } + } else if (this->particles[i].alpha < 100) { + this->particles[i].alpha++; + } + } else if (this->particles[i].state != DEMO_KANKYO_STATE_SKYFISH) { + if ((this->particles[i].alphaClock & 31) < 16) { + if (this->particles[i].alpha < 235) { + this->particles[i].alpha += 20; + } + } else if (this->particles[i].alpha > 20) { + this->particles[i].alpha -= 20; + } + } else if ((this->particles[i].alphaClock & 15) < 8) { + if (this->particles[i].alpha < 255) { + this->particles[i].alpha += 100; + } + } else if (this->particles[i].alpha > 10) { + this->particles[i].alpha -= 10; + } + + gDPPipeSync(POLY_XLU_DISP++); + + switch (i & 1) { + case 0: // gold particles + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 155, this->particles[i].alpha); + gDPSetEnvColor(POLY_XLU_DISP++, 250, 180, 0, this->particles[i].alpha); + break; + + case 1: // silver particles + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, this->particles[i].alpha); + gDPSetEnvColor(POLY_XLU_DISP++, 0, 100, 255, this->particles[i].alpha); + break; + } + + Matrix_InsertMatrix(&globalCtx->mf_187FC, MTXMODE_APPLY); + Matrix_InsertZRotation_f(DEGF_TO_RADF(globalCtx->state.frames * 20.0f), MTXMODE_APPLY); + + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, D_0407AB58); + } + } + + CLOSE_DISPS(globalCtx->state.gfxCtx); + } +} + +// draw, giants and moon +void DemoKankyo_DrawMoonAndGiant(Actor* thisx, GlobalContext* globalCtx2) { + DemoKankyo* this = THIS; + GlobalContext* globalCtx = globalCtx2; + s16 i; + f32 alphaScale; + + if (this->isSafeToDrawGiants != false) { + Vec3f worldPos; + Vec3f screenPos; + s32 pad; + GraphicsContext* gfxCtx = globalCtx->state.gfxCtx; + + OPEN_DISPS(gfxCtx); + func_8012C2DC(gfxCtx); + + for (i = 0; i < globalCtx->envCtx.unk_F2[3]; i++) { + worldPos.x = this->particles[i].posBase.x + this->particles[i].posOffset.x; + worldPos.y = this->particles[i].posBase.y + this->particles[i].posOffset.y; + worldPos.z = this->particles[i].posBase.z + this->particles[i].posOffset.z; + + func_80169474(globalCtx, &worldPos, &screenPos); // unnamed Play_ function, func_800C016C from OoT + + // checking if particle is on screen + if (screenPos.x >= 0.0f && screenPos.x < SCREEN_WIDTH && screenPos.y >= 0.0f && + screenPos.y < SCREEN_HEIGHT) { + Matrix_InsertTranslation(worldPos.x, worldPos.y, worldPos.z, MTXMODE_NEW); + alphaScale = this->particles[i].alpha / 50.0f; + if (alphaScale > 1.0f) { + alphaScale = 1.0f; + } + Matrix_Scale(this->particles[i].scale * alphaScale, this->particles[i].scale * alphaScale, + this->particles[i].scale * alphaScale, MTXMODE_APPLY); + alphaScale = Math_Vec3f_DistXYZ(&worldPos, &globalCtx->view.eye) / 300.0f; + alphaScale = (alphaScale > 1.0f) ? 0.0f : (1.0f - alphaScale) > 1.0f ? 1.0f : 1.0f - alphaScale; + + if (this->actor.params == DEMO_KANKYO_TYPE_GIANTS) { + this->particles[i].alpha = 255.0f * alphaScale; + } else { + this->particles[i].alpha = 160.0f * alphaScale; + } + + gDPPipeSync(POLY_XLU_DISP++); + + switch (i & 1) { // half/half slightly different shades of yellow/tan + case 0: + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 230, 230, 220, this->particles[i].alpha); + gDPSetEnvColor(POLY_XLU_DISP++, 230, 230, 30, this->particles[i].alpha); + break; + + case 1: + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 200, 190, this->particles[i].alpha); + gDPSetEnvColor(POLY_XLU_DISP++, 200, 200, 30, this->particles[i].alpha); + break; + } + + gSPDisplayList(POLY_XLU_DISP++, &D_04023348); + + Matrix_InsertMatrix(&globalCtx->mf_187FC, MTXMODE_APPLY); + + Matrix_InsertZRotation_f(DEGF_TO_RADF(globalCtx->state.frames * 20.0f), MTXMODE_APPLY); + + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + if (this->actor.params == DEMO_KANKYO_TYPE_GIANTS) { + gSPDisplayList(POLY_XLU_DISP++, D_06001000); + } else { + gSPDisplayList(POLY_XLU_DISP++, D_04023428); + } + } + } + + CLOSE_DISPS(gfxCtx); + } +} + +void DemoKankyo_Draw(Actor* thisx, GlobalContext* globalCtx) { + DemoKankyo* this = THIS; + + switch (this->actor.params) { + case DEMO_KANKYO_TYPE_LOSTWOODS: + DemoKakyo_DrawLostWoodsSparkle(&this->actor, globalCtx); + break; + + case DEMO_KANKYO_TYPE_GIANTS: + case DEMO_KANKYO_TYPE_MOON: + DemoKankyo_DrawMoonAndGiant(&this->actor, globalCtx); + break; + } +} diff --git a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.h b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.h index 9dfa597b47..d70616ed67 100644 --- a/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.h +++ b/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.h @@ -7,13 +7,47 @@ struct DemoKankyo; typedef void (*DemoKankyoActionFunc)(struct DemoKankyo*, GlobalContext*); +typedef struct { + /* 0x000 */ u8 state; + /* 0x004 */ Vec3f posOffset; + /* 0x010 */ Vec3f posOffsetPrev; + /* 0x01C */ Vec3f posBase; + /* 0x028 */ Vec3f speedClock; // cycles in radians + /* 0x034 */ f32 speed; + /* 0x038 */ f32 speedTarget; + /* 0x03C */ u16 alphaClock; + /* 0x03E */ u16 LostWoodsSkyFishSpeedXZClock; + /* 0x040 */ u8 alpha; + /* 0x044 */ f32 scale; // size of the particle + /* 0x048 */ u16 LostWoodsSkyFishSpeedXZ; // the x-z speed (angular velocity) the lost woods skyfish oscillates around player. pos or neg 200-400 + /* 0x04A */ u16 LostWoodsSkyFishPosOffsetMax; // The x-z range the lost woods skyfish oscillates around player. random value between 15-65 + /* 0x04C */ f32 LostWoodsSkyFishSpeedY; // the y speed (angular velocity) the lost woods skyfish oscillates around player. + /* 0x050 */ u16 pad50; // unused, always assigned to 0, nothing else in this actor uses it +} DemoKankyoParticle; // size = 0x54 + +#define DEMOKANKYO_PARTICLE_COUNT 64 + typedef struct DemoKankyo { /* 0x0000 */ Actor actor; - /* 0x0144 */ char unk_144[0x1500]; + /* 0x0144 */ DemoKankyoParticle particles[DEMOKANKYO_PARTICLE_COUNT]; /* 0x1644 */ DemoKankyoActionFunc actionFunc; - /* 0x1648 */ char unk_1648[0x8]; + /* 0x1648 */ s32 objectId; + /* 0x164C */ u8 isSafeToDrawGiants; } DemoKankyo; // size = 0x1650 +typedef enum { + /* 0 */ DEMO_KANKYO_TYPE_LOSTWOODS, + /* 1 */ DEMO_KANKYO_TYPE_GIANTS, + /* 2 */ DEMO_KANKYO_TYPE_MOON, +} DemoKankyoType; + +typedef enum { + /* 0 */ DEMO_KANKYO_STATE_INIT, + /* 1 */ DEMO_KANKYO_STATE_SINGLE, + /* 2 */ DEMO_KANKYO_STATE_SKYFISH, + /* 3 */ DEMO_KANKYO_STATE_DISABLED, +} DemoKankyoStateType; + extern const ActorInit Demo_Kankyo_InitVars; #endif // Z_DEMO_KANKYO_H