/* * File: z_en_jsjutan.c * Overlay: ovl_En_Jsjutan * Description: Magic carpet man's carpet */ #include "z_en_jsjutan.h" #include "overlays/actors/ovl_En_Bom/z_en_bom.h" #define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_3) void EnJsjutan_Init(Actor* thisx, GlobalContext* globalCtx); void EnJsjutan_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnJsjutan_Update(Actor* thisx, GlobalContext* globalCtx); void EnJsjutan_Draw(Actor* thisx, GlobalContext* globalCtx); const ActorInit En_Jsjutan_InitVars = { ACTOR_EN_JSJUTAN, ACTORCAT_NPC, FLAGS, OBJECT_GAMEPLAY_KEEP, sizeof(EnJsjutan), (ActorFunc)EnJsjutan_Init, (ActorFunc)EnJsjutan_Destroy, (ActorFunc)EnJsjutan_Update, (ActorFunc)EnJsjutan_Draw, NULL, }; // Shadow texture. 32x64 I8. static u8 sShadowTex[0x800]; static Vec3s D_80A8EE10[0x90]; static s32 sUnused[2] = { 0, 0 }; #include "overlays/ovl_En_Jsjutan/ovl_En_Jsjutan.h" void EnJsjutan_Init(Actor* thisx, GlobalContext* globalCtx) { EnJsjutan* this = (EnJsjutan*)thisx; s32 pad; CollisionHeader* header = NULL; this->dyna.actor.flags &= ~ACTOR_FLAG_0; DynaPolyActor_Init(&this->dyna, DPM_UNK); CollisionHeader_GetVirtual(&sCol, &header); this->dyna.bgId = DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, thisx, header); Actor_SetScale(thisx, 0.02f); this->unk_164 = true; this->shadowAlpha = 100.0f; } void EnJsjutan_Destroy(Actor* thisx, GlobalContext* globalCtx) { EnJsjutan* this = (EnJsjutan*)thisx; DynaPoly_DeleteBgActor(globalCtx, &globalCtx->colCtx.dyna, this->dyna.bgId); } void func_80A89860(EnJsjutan* this, GlobalContext* globalCtx) { s16 i; Vtx* oddVtx; Vtx* evenVtx; Vec3f actorPos = this->dyna.actor.world.pos; oddVtx = ResourceMgr_LoadVtxByName(SEGMENTED_TO_VIRTUAL(gShadowOddVtx)); evenVtx = ResourceMgr_LoadVtxByName(SEGMENTED_TO_VIRTUAL(sShadowEvenVtx)); for (i = 0; i < ARRAY_COUNT(D_80A8EE10); i++, oddVtx++, evenVtx++) { D_80A8EE10[i].x = oddVtx->v.ob[0]; D_80A8EE10[i].z = oddVtx->v.ob[2]; if (this->dyna.actor.params == ENJSJUTAN_TYPE_01) { oddVtx->v.ob[1] = evenVtx->v.ob[1] = 0x585; } else { this->dyna.actor.world.pos.x = oddVtx->v.ob[0] * 0.02f + actorPos.x; this->dyna.actor.world.pos.z = oddVtx->v.ob[2] * 0.02f + actorPos.z; Actor_UpdateBgCheckInfo(globalCtx, &this->dyna.actor, 10.0f, 10.0f, 10.0f, 4); oddVtx->v.ob[1] = evenVtx->v.ob[1] = this->dyna.actor.floorHeight; this->dyna.actor.world.pos = actorPos; } } } void func_80A89A6C(EnJsjutan* this, GlobalContext* globalCtx) { u8 isPlayerOnTop = false; // sp127 s16 i; s16 j; Vtx* carpetVtx; Vtx* shadowVtx; Vtx* phi_s0_2; Vec3f sp108; Vec3f spFC; Actor* actorProfessor; Actor* actorBeanGuy; f32 dxVtx; f32 dyVtx; f32 dzVtx; f32 distVtx; // 0 if no actor in that index of diffToTracked u8 spE0[3]; // Tracks distance to other actors. // Index 0 is always the Magic Carpet Man. 1 and 2 could be bombs, or EnMk and EnMs if in credits. f32 spD4[3]; // diffToTracked X f32 spC8[3]; // diffToTracked Y f32 spBC[3]; // diffToTracked Z // Tracks distance to Link f32 spB8; // diffToPlayer X f32 spB4; // diffToPlayer Y f32 spB0; // diffToPlayer Z f32 weight; f32 spA8; // wave amplitude (?) f32 offset; f32 maxOffset; f32 maxAmp; f32 waveform; Player* player = GET_PLAYER(globalCtx); Actor* parent = this->dyna.actor.parent; Actor* actorExplosive = globalCtx->actorCtx.actorLists[ACTORCAT_EXPLOSIVE].head; u8 isInCreditsScene = false; // sp8B if (globalCtx->gameplayFrames % 2 != 0) { carpetVtx = SEGMENTED_TO_VIRTUAL(sCarpetOddVtx); shadowVtx = SEGMENTED_TO_VIRTUAL(gShadowOddVtx); } else { carpetVtx = SEGMENTED_TO_VIRTUAL(sCarpetEvenVtx); shadowVtx = SEGMENTED_TO_VIRTUAL(sShadowEvenVtx); } carpetVtx = ResourceMgr_LoadVtxByName(carpetVtx); shadowVtx = ResourceMgr_LoadVtxByName(shadowVtx); // Distance of player to carpet. spB8 = (player->actor.world.pos.x - this->dyna.actor.world.pos.x) * 50.0f; spB4 = (player->actor.world.pos.y - this->unk_168) * 50.0f; spB0 = (player->actor.world.pos.z - this->dyna.actor.world.pos.z) * 50.0f; phi_s0_2 = carpetVtx; if ((fabsf(spB8) < 5500.0f) && (fabsf(spB4) < 3000.0f) && (fabsf(spB0) < 5500.0f)) { isPlayerOnTop = true; } // Distance of Magic Carpet Salesman to carpet. spD4[0] = (parent->world.pos.x - this->dyna.actor.world.pos.x) * 50.0f; spC8[0] = ((parent->world.pos.y - 8.0f) - this->unk_168) * 50.0f; spBC[0] = (parent->world.pos.z - this->dyna.actor.world.pos.z) * 50.0f; spE0[0] = 1; for (i = 1; i < 3; i++) { spE0[i] = 0; } i = 1; // Credits scene. The magic carpet man is friends with the bean guy and the lakeside professor. if ((gSaveContext.entranceIndex == 0x157) && (gSaveContext.sceneSetupIndex == 8)) { isInCreditsScene = true; actorProfessor = globalCtx->actorCtx.actorLists[ACTORCAT_NPC].head; while (actorProfessor != NULL) { if (actorProfessor->id == ACTOR_EN_MK) { break; } actorProfessor = actorProfessor->next; } actorBeanGuy = globalCtx->actorCtx.actorLists[ACTORCAT_NPC].head; while (actorBeanGuy != NULL) { if (actorBeanGuy->id == ACTOR_EN_MS) { break; } actorBeanGuy = actorBeanGuy->next; } spD4[1] = 50.0f * (actorProfessor->world.pos.x - this->dyna.actor.world.pos.x); spC8[1] = 50.0f * (actorProfessor->world.pos.y - this->unk_168); spBC[1] = 50.0f * (actorProfessor->world.pos.z - this->dyna.actor.world.pos.z); spE0[1] = 1; spD4[2] = 50.0f * (actorBeanGuy->world.pos.x - this->dyna.actor.world.pos.x); spC8[2] = 50.0f * (actorBeanGuy->world.pos.y - this->unk_168); spBC[2] = 50.0f * (actorBeanGuy->world.pos.z - this->dyna.actor.world.pos.z); spE0[2] = 1; } else { // Player can place bombs in carpet and it will react to it. while (actorExplosive != NULL) { if (i < 3) { spD4[i] = (actorExplosive->world.pos.x - this->dyna.actor.world.pos.x) * 50.0f; spC8[i] = (actorExplosive->world.pos.y - this->unk_168) * 50.0f; spBC[i] = (actorExplosive->world.pos.z - this->dyna.actor.world.pos.z) * 50.0f; if ((fabsf(spD4[i]) < 5500.0f) && (fabsf(spC8[i]) < 3000.0f) && (fabsf(spBC[i]) < 5500.0f)) { if (actorExplosive->params == BOMB_EXPLOSION) { spE0[i] = 35; // Code never checks this, so it goes unused. Maybe it was planned to damage the // carpet with explosions (?) } else { spE0[i] = 1; } } i++; } actorExplosive = actorExplosive->next; } } // Fancy math to make a woobly and reactive carpet. for (i = 0; i < ARRAY_COUNT(D_80A8EE10); i++, carpetVtx++, shadowVtx++) { if (isPlayerOnTop) { // Linear distance from j-th wave to player, in XZ plane. dxVtx = carpetVtx->n.ob[0] - spB8; dzVtx = carpetVtx->n.ob[2] - spB0; distVtx = sqrtf(SQ(dxVtx) + SQ(dzVtx)); // Distance percentage. 0.0f to 1.0f. 2500.0f is the max distance to an actor that this wave will consider. weight = (2500.0f - distVtx) / 2500.0f; if (weight < 0.0f) { weight = 0.0f; } offset = (spB4 * weight) + ((this->unk_170 - (this->unk_170 * weight)) - 200.0f); distVtx -= 1500.0f; if (distVtx < 0.0f) { distVtx = 0.0f; } spA8 = 100.0f * distVtx * 0.01f; spA8 = CLAMP_MAX(spA8, 100.0f); } else { offset = this->unk_170 - 200.f; spA8 = 100.0f; } for (j = 0; j < 3; j++) { if (spE0[j] != 0) { dxVtx = carpetVtx->n.ob[0] - spD4[j]; dzVtx = carpetVtx->n.ob[2] - spBC[j]; // Linear distance from j-th wave to whatever actor is there, in XZ plane. distVtx = sqrtf(SQ(dxVtx) + SQ(dzVtx)); if ((j == 0) || isInCreditsScene) { weight = (3000.0f - distVtx) / 3000.0f; } else { weight = (2000.0f - distVtx) / 2000.0f; } if (weight < 0.0f) { weight = 0.0f; } // should be the following, but doesn't match that way. // maxoffset = (spC8[i] * weight) + ((this->unk_170 - (this->unk_170 * weight)) - 200.0f); maxOffset = (spC8[j] * weight); maxOffset += ((this->unk_170 - (this->unk_170 * weight)) - 200.0f); distVtx -= 1500.0f; if (distVtx < 0.0f) { distVtx = 0.0f; } maxAmp = 100.0f * distVtx * 0.01f; maxAmp = CLAMP_MAX(maxAmp, 100.0f); offset = CLAMP_MAX(offset, maxOffset); spA8 = CLAMP_MAX(spA8, maxAmp); } } /** * See https://en.wikipedia.org/wiki/Sine_wave#General_form * k: 10000 * x: j * w: 4000 * t: gameplayFrames * A: spA8 * D: phi_f28 */ waveform = spA8 * Math_SinS(globalCtx->gameplayFrames * 4000 + i * 10000); if (this->unk_174) { s16 phi_v1_4 = offset + waveform; s16 temp_a0_3 = (shadowVtx->n.ob[1] - this->unk_168) * 50.0f; if (phi_v1_4 < temp_a0_3) { phi_v1_4 = temp_a0_3; } carpetVtx->n.ob[1] = phi_v1_4; } else { carpetVtx->n.ob[1] = offset + waveform; carpetVtx->n.ob[0] = D_80A8EE10[i].x + (s16)(waveform * 0.5f); carpetVtx->n.ob[2] = D_80A8EE10[i].z + (s16)(waveform * 0.5f); shadowVtx->n.ob[0] = D_80A8EE10[i].x + (s16)waveform; shadowVtx->n.ob[2] = D_80A8EE10[i].z + (s16)waveform; } } if (!this->unk_174) { u16 dayTime; this->dyna.actor.velocity.y = 0.0f; this->dyna.actor.world.pos.y = this->unk_168; dayTime = gSaveContext.dayTime; if (dayTime >= 0x8000) { dayTime = 0xFFFF - dayTime; } this->shadowAlpha = (dayTime * 0.00275f) + 10.0f; // (1.0f / 364.0f) ? this->unk_170 = 1000.0f; } else { Math_ApproachF(&this->dyna.actor.world.pos.y, this->unk_168 - 1000.0f, 1.0f, this->dyna.actor.velocity.y); Math_ApproachF(&this->dyna.actor.velocity.y, 5.0f, 1.0f, 0.5f); Math_ApproachF(&this->shadowAlpha, 0.0f, 1.0f, 3.0f); Math_ApproachF(&this->unk_170, -5000.0f, 1.0f, 100.0f); } carpetVtx = phi_s0_2; sp108.x = 0.0f; sp108.y = 0.0f; sp108.z = 120.0f; // Fancy math to smooth each part of the wave considering its neighborhood. for (i = 0; i < ARRAY_COUNT(sCarpetOddVtx); i++, carpetVtx++) { f32 rotX; f32 rotZ; s32 pad; // Carpet size is 12x12. if ((i % 12) == 11) { // Last column. j = i - 1; dzVtx = carpetVtx->n.ob[2] - phi_s0_2[j].n.ob[2]; } else { j = i + 1; dzVtx = phi_s0_2[j].n.ob[2] - carpetVtx->n.ob[2]; } dyVtx = phi_s0_2[j].n.ob[1] - carpetVtx->n.ob[1]; rotX = Math_Atan2F(dzVtx, dyVtx); if (i >= 132) { // Last row. j = i - 12; dxVtx = carpetVtx->n.ob[0] - phi_s0_2[j].n.ob[0]; } else { j = i + 12; dxVtx = phi_s0_2[j].n.ob[0] - carpetVtx->n.ob[0]; } rotZ = Math_Atan2F(dxVtx, dyVtx); Matrix_RotateX(rotX, MTXMODE_NEW); Matrix_RotateZ(rotZ, MTXMODE_APPLY); Matrix_MultVec3f(&sp108, &spFC); carpetVtx->n.n[0] = spFC.x; carpetVtx->n.n[1] = spFC.y; carpetVtx->n.n[2] = spFC.z; } } void EnJsjutan_Update(Actor* thisx, GlobalContext* globalCtx2) { GlobalContext* globalCtx = globalCtx2; thisx->shape.rot.x = Math_SinS(globalCtx->gameplayFrames * 3000) * 300.0f; thisx->shape.rot.z = Math_CosS(globalCtx->gameplayFrames * 3500) * 300.0f; } void EnJsjutan_Draw(Actor* thisx, GlobalContext* globalCtx2) { EnJsjutan* this = (EnJsjutan*)thisx; GlobalContext* globalCtx = globalCtx2; s16 i; Actor* parent = thisx->parent; OPEN_DISPS(globalCtx->state.gfxCtx); if (thisx->params == ENJSJUTAN_TYPE_01) { thisx->world.pos.x = parent->world.pos.x; thisx->world.pos.y = parent->world.pos.y; thisx->world.pos.z = parent->world.pos.z; this->unk_168 = thisx->world.pos.y; if (!this->unk_175) { this->unk_175 = true; func_80A89860(this, globalCtx); } } else if (!this->unk_175) { this->unk_175 = true; thisx->world.pos.x = Math_SinS(parent->shape.rot.y) * 60.0f + parent->world.pos.x; thisx->world.pos.y = (parent->world.pos.y + 5.0f) - 10.0f; thisx->world.pos.z = Math_CosS(parent->shape.rot.y) * 60.0f + parent->world.pos.z; this->unk_168 = thisx->world.pos.y; func_80A89860(this, globalCtx); } func_80A89A6C(this, globalCtx); if (this->unk_164) { this->unk_164 = false; u8* carpTex = ResourceMgr_LoadTexByName(sCarpetTex); u8* shadTex = sShadowTex; for (i = 0; i < ARRAY_COUNT(sShadowTex); i++) { if (((u16*)carpTex)[i] != 0) { // Hack to bypass ZAPD exporting textures as u64. shadTex[i] = 0xFF; } else { shadTex[i] = 0; } } } func_80093D18(globalCtx->state.gfxCtx); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, (s16)this->shadowAlpha); Matrix_Translate(thisx->world.pos.x, 3.0f, thisx->world.pos.z, MTXMODE_NEW); Matrix_Scale(thisx->scale.x, 1.0f, thisx->scale.z, MTXMODE_APPLY); gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); // Draws the carpet's shadow texture. gSPSegment(POLY_OPA_DISP++, 0x0C, sShadowTex); gSPDisplayList(POLY_OPA_DISP++, sShadowMaterialDL); gDPPipeSync(POLY_OPA_DISP++); // Draws the carpet's shadow vertices. Swaps them between frames to get a smoother result. if (globalCtx->gameplayFrames % 2 != 0) { gSPSegment(POLY_OPA_DISP++, 0x0C, gShadowOddVtx); } else { gSPSegment(POLY_OPA_DISP++, 0x0C, sShadowEvenVtx); } gSPDisplayList(POLY_OPA_DISP++, sModelDL); func_80093D18(globalCtx->state.gfxCtx); Matrix_Translate(thisx->world.pos.x, this->unk_168 + 3.0f, thisx->world.pos.z, MTXMODE_NEW); Matrix_Scale(thisx->scale.x, thisx->scale.y, thisx->scale.z, MTXMODE_APPLY); gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); // Draws the carpet's texture. gSPDisplayList(POLY_OPA_DISP++, sCarpetMaterialDL); gDPPipeSync(POLY_OPA_DISP++); // Draws the carpet vertices. if (globalCtx->gameplayFrames % 2 != 0) { gSPSegment(POLY_OPA_DISP++, 0x0C, sCarpetOddVtx); } else { gSPSegment(POLY_OPA_DISP++, 0x0C, sCarpetEvenVtx); } gSPDisplayList(POLY_OPA_DISP++, sModelDL); CLOSE_DISPS(globalCtx->state.gfxCtx); }