From e20f06e02cac320fe4da2e9c0ab53ce57b64ce85 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 15 Nov 2024 21:33:29 -0700 Subject: [PATCH 01/43] Created empty d_a_py_lk actor --- src/ZeldaWindWaker/d_a.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 0ccfd9dde..eb1f20435 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4954,6 +4954,21 @@ class d_a_npc_ls1 extends fopNpc_npc_c { return true; } } +class d_a_py_lk extends fopAc_ac_c { + public static PROCESS_NAME = fpc__ProcessName.d_a_py_lk; + + protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { + return cPhs__Status.Next; + } + + override execute(globals: dGlobals, deltaTimeFrames: number): void { + + } + + override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { + + } +} interface constructor extends fpc_bs__Constructor { PROCESS_NAME: dProcName_e; @@ -4987,4 +5002,5 @@ export function d_a__RegisterConstructors(globals: fGlobals): void { R(d_a_obj_flame); R(d_a_ff); R(d_a_npc_ls1); + R(d_a_py_lk); } From 8901b71c791d2b4016b64dacf7a6685ac6b13f28 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 15 Nov 2024 21:33:57 -0700 Subject: [PATCH 02/43] Load d_a_py_lk on demo start (most of them expect it to be loaded)) --- src/ZeldaWindWaker/Main.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index beadef508..22b016975 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -31,7 +31,6 @@ import { LegacyActor__RegisterFallbackConstructor } from './LegacyActor.js'; import { dDlst_2DStatic_c, d_a__RegisterConstructors } from './d_a.js'; import { d_a_sea } from './d_a_sea.js'; import { dBgS } from './d_bg.js'; -import { EDemoCamFlags, EDemoMode, dDemo_manager_c } from './d_demo.js'; import { dDlst_list_Set, dDlst_list_c } from './d_drawlist.js'; import { dKankyo_create, dKy__RegisterConstructors, dKy_setLight, dScnKy_env_light_c } from './d_kankyo.js'; import { dKyw__RegisterConstructors } from './d_kankyo_wether.js'; @@ -40,8 +39,9 @@ import { dProcName_e } from './d_procname.js'; import { ResType, dRes_control_c } from './d_resorce.js'; import { dStage_dt_c_roomLoader, dStage_dt_c_roomReLoader, dStage_dt_c_stageInitLoader, dStage_dt_c_stageLoader, dStage_roomControl_c, dStage_roomStatus_c, dStage_stageDt_c } from './d_stage.js'; import { WoodPacket } from './d_wood.js'; -import { fopAcM_create, fopAc_ac_c } from './f_op_actor.js'; +import { fopAcM_create, fopAcM_searchFromName, fopAc_ac_c } from './f_op_actor.js'; import { cPhs__Status, fGlobals, fopDw_Draw, fopScn, fpcCt_Handler, fpcLy_SetCurrentLayer, fpcM_Management, fpcPf__Register, fpcSCtRq_Request, fpc_pc__ProfileList } from './framework.js'; +import { dDemo_manager_c, EDemoCamFlags, EDemoMode } from './d_demo.js'; type SymbolData = { Filename: string, SymbolName: string, Data: ArrayBufferSlice }; type SymbolMapData = { SymbolData: SymbolData[] }; @@ -999,6 +999,11 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { globals.scnPlay.demo.remove(); // TODO: Don't render until the camera has been placed for this demo. The cuts are jarring. + + // noclip modification: Most cutscenes expect the Link actor to be loaded + if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { + fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, null, globals.mStayNo, null, null, 0xFF, -1); + } // noclip modification: This normally happens on room load. Do it here instead so that we don't waste time // loading .arcs for cutscenes that aren't going to be played From 7fd466f4db12a24ac2334c4149926d0186468886 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 15 Nov 2024 22:32:33 -0700 Subject: [PATCH 03/43] Basic body rendering --- src/ZeldaWindWaker/d_a.ts | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index eb1f20435..93add062d 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4956,17 +4956,49 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } class d_a_py_lk extends fopAc_ac_c { public static PROCESS_NAME = fpc__ProcessName.d_a_py_lk; + private static ARC_NAME = "Link"; + private static LINK_BDL_CL = 0x18; + private static LINK_BDL_HANDS = 0x1D; + + private modelBody: J3DModelInstance; + private modelHands: J3DModelInstance; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { + this.playerInit(globals); return cPhs__Status.Next; } override execute(globals: dGlobals, deltaTimeFrames: number): void { - + dDemo_setDemoData(globals, deltaTimeFrames, this, 0xFFFF); + + // Line 3647 + this.modelBody.calcAnim(); + + // setWorldMatrix() + MtxTrans(this.pos, false, this.modelBody.modelMatrix); + mDoMtx_ZXYrotM(this.modelBody.modelMatrix, this.rot); } override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - + settingTevStruct(globals, LightType.Player, this.pos, this.tevStr); + setLightTevColorType(globals, this.modelBody, this.tevStr, viewerInput.camera); + + mDoExt_modelEntryDL(globals, this.modelBody, renderInstManager, viewerInput); + } + + private playerInit(globals: dGlobals) { + // createHeap() + this.modelBody = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); + this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); + + this.cullMtx = this.modelBody.modelMatrix; + } + + private initModel(globals: dGlobals, fileIdx: number): J3DModelInstance { + const modelData = globals.resCtrl.getObjectRes(ResType.Model, d_a_py_lk.ARC_NAME, fileIdx); + const model = new J3DModelInstance(modelData); + assert(!!model); + return model; } } From 99f751fce5dad553f4726152f6758907180a9b60 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sat, 16 Nov 2024 11:20:13 -0700 Subject: [PATCH 04/43] Eyes now render in front of hair, but with correct scene depth This is based off of the logic from Jasper's E4_WWZAT_b2 branch. Currently, we must manually set the translucency to false for the first eye alpha mask. When it's true (as it is in the BMD) noclip will always render it after the rest of the opaque body. I suspect something is not quite lining up with the game regarding how we're handling translucency sorting... --- src/ZeldaWindWaker/d_a.ts | 51 +++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 93add062d..b14978199 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -12,7 +12,7 @@ import { Endianness } from "../endian.js"; import { compareDepthValues } from "../gfx/helpers/ReversedDepthHelpers.js"; import { GfxClipSpaceNearZ, GfxCompareMode, GfxDevice } from "../gfx/platform/GfxPlatform.js"; import { GfxRenderCache } from "../gfx/render/GfxRenderCache.js"; -import { GfxRenderInst, GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; +import { GfxRendererLayer, GfxRenderInst, GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { GXMaterialBuilder } from "../gx/GXMaterialBuilder.js"; import * as GX from '../gx/gx_enum.js'; import { TevDefaultSwapTables } from "../gx/gx_material.js"; @@ -4960,38 +4960,69 @@ class d_a_py_lk extends fopAc_ac_c { private static LINK_BDL_CL = 0x18; private static LINK_BDL_HANDS = 0x1D; - private modelBody: J3DModelInstance; + private model: J3DModelInstance; private modelHands: J3DModelInstance; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.playerInit(globals); + + // noclip modification: The game manually draws the eye/eyebrow filter before the body. Let's do that with sorting. + this.model.setSortKeyLayer(GfxRendererLayer.OPAQUE + 5, false); + this.setupDam('eyeL'); + this.setupDam('eyeR'); + this.setupDam('mayuL'); + this.setupDam('mayuR'); + return cPhs__Status.Next; } override execute(globals: dGlobals, deltaTimeFrames: number): void { dDemo_setDemoData(globals, deltaTimeFrames, this, 0xFFFF); - // Line 3647 - this.modelBody.calcAnim(); + this.model.calcAnim(); // setWorldMatrix() - MtxTrans(this.pos, false, this.modelBody.modelMatrix); - mDoMtx_ZXYrotM(this.modelBody.modelMatrix, this.rot); + MtxTrans(this.pos, false, this.model.modelMatrix); + mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); } override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { settingTevStruct(globals, LightType.Player, this.pos, this.tevStr); - setLightTevColorType(globals, this.modelBody, this.tevStr, viewerInput.camera); + setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); - mDoExt_modelEntryDL(globals, this.modelBody, renderInstManager, viewerInput); + mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } private playerInit(globals: dGlobals) { // createHeap() - this.modelBody = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); + this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); - this.cullMtx = this.modelBody.modelMatrix; + globals.renderer.extraTextures.fillExtraTextures(this.model); + + this.cullMtx = this.model.modelMatrix; + } + + private setupDam(pref: string): void { + const matInstA = this.model.materialInstances.find((m) => m.name === `${pref}damA`)!; + const matInstB = this.model.materialInstances.find((m) => m.name === `${pref}damB`)!; + + // Render an alpha mask in the shape of the eyes. Needs to render before Link so that it can depth test against + // the scene but not against his hair. The eyes will then draw with depth testing enabled, but will mask against + // this alpha tex. + matInstA.setSortKeyLayer(GfxRendererLayer.BACKGROUND + 4, false); + matInstA.setColorWriteEnabled(false); + matInstA.setAlphaWriteEnabled(true); + + // @TODO: This material is marked as translucent in the original BMD. Noclip draws translucent shapes after all + // opaque shapes, meaning that this renders AFTER Link, which defeats the purpose of modifying the sort + // layer. Is there something wrong with noclip's translucency handling? + matInstA.materialData.material.translucent = false; + + // Clear the alpha mask written by the *damA materials so it doesn't interfere with other translucent objects + matInstB.setSortKeyLayer(GfxRendererLayer.OPAQUE + 6, false); + matInstB.setColorWriteEnabled(false); + matInstB.setAlphaWriteEnabled(true); } private initModel(globals: dGlobals, fileIdx: number): J3DModelInstance { From 946f03a63e21ab0ae6a561570883fc628e8b7f01 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 09:43:04 -0700 Subject: [PATCH 05/43] Link uses custom setDemoData function Basic translation and rotation is working --- src/ZeldaWindWaker/d_a.ts | 55 +++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index b14978199..d82e4da6f 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -33,7 +33,7 @@ import { mDoExt_McaMorf, mDoExt_bckAnm, mDoExt_brkAnm, mDoExt_btkAnm, mDoExt_btp import { MtxPosition, MtxTrans, calc_mtx, mDoMtx_XYZrotM, mDoMtx_XrotM, mDoMtx_YrotM, mDoMtx_YrotS, mDoMtx_ZXYrotM, mDoMtx_ZrotM, mDoMtx_ZrotS, quatM } from "./m_do_mtx.js"; import { dGlobals } from "./Main.js"; import { dDlst_alphaModel__Type } from "./d_drawlist.js"; -import { dDemo_setDemoData } from "./d_demo.js"; +import { dDemo_setDemoData, EDemoActorFlags } from "./d_demo.js"; import { fopAc_ac_c, fopAcIt_JudgeByID, fopAcM_create, fopAcM_prm_class } from "./f_op_actor.js"; import { dProcName_e } from "./d_procname.js"; @@ -4955,13 +4955,14 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } } class d_a_py_lk extends fopAc_ac_c { - public static PROCESS_NAME = fpc__ProcessName.d_a_py_lk; + public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; private static LINK_BDL_CL = 0x18; private static LINK_BDL_HANDS = 0x1D; private model: J3DModelInstance; private modelHands: J3DModelInstance; + private demoMode: number; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.playerInit(globals); @@ -4977,7 +4978,7 @@ class d_a_py_lk extends fopAc_ac_c { } override execute(globals: dGlobals, deltaTimeFrames: number): void { - dDemo_setDemoData(globals, deltaTimeFrames, this, 0xFFFF); + this.setDemoData(globals); this.model.calcAnim(); @@ -4987,7 +4988,8 @@ class d_a_py_lk extends fopAc_ac_c { } override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { - settingTevStruct(globals, LightType.Player, this.pos, this.tevStr); + // @TODO: This should use LightType.Player, but it's not yet implemented + settingTevStruct(globals, LightType.Actor, this.pos, this.tevStr); setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); @@ -4996,7 +4998,7 @@ class d_a_py_lk extends fopAc_ac_c { private playerInit(globals: dGlobals) { // createHeap() this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); - this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); + // this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); globals.renderer.extraTextures.fillExtraTextures(this.model); @@ -5031,6 +5033,49 @@ class d_a_py_lk extends fopAc_ac_c { assert(!!model); return model; } + + private setDemoData(globals: dGlobals) { + const demoActor = globals.scnPlay.demo.getSystem().getActor(this.demoActorID); + if (!demoActor) + return false; + + const enable = demoActor.checkEnable(0xFF); + if (enable & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } + if (enable & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } + if (enable & EDemoActorFlags.HasAnim) { this.demoMode = demoActor.nextBckId; } + + if (enable & EDemoActorFlags.HasShape) { + // TODO: 0 is casual clothes, 1 is hero clothes + } + + // Line 3387 + switch(this.demoMode) { + case 4: + case 0x2C: + debugger; + // Snap to target position + // Maybe equip an item + break; + + case 0x2B: + debugger; + break; + + case 2: + case 3: + // Transition to target position (2 is slower) + debugger; + break; + + case 5: + case 0x18: + // Rotate only + debugger; + break; + } + + return true; + } } interface constructor extends fpc_bs__Constructor { From 74237abba2d24ef61131767a2bbf166a7d36a948 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Sun, 17 Nov 2024 11:10:15 -0700 Subject: [PATCH 06/43] JStudio: Add JSGDebugGetAnimationName and hook it up to d_a_py_lk For easier debugging of which animations get played during demos --- src/Common/JSYSTEM/JStudio.ts | 8 ++- src/ZeldaWindWaker/d_a.ts | 110 +++++++++++++++++++++++++++++++--- src/ZeldaWindWaker/d_demo.ts | 7 +++ 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/Common/JSYSTEM/JStudio.ts b/src/Common/JSYSTEM/JStudio.ts index e591c1634..52095a80a 100644 --- a/src/Common/JSYSTEM/JStudio.ts +++ b/src/Common/JSYSTEM/JStudio.ts @@ -544,6 +544,8 @@ export abstract class TActor extends JStage.TObject { public JSGGetTextureAnimationFrame(): number { return 0.0; } public JSGSetTextureAnimationFrame(x: number): void { } public JSGGetTextureAnimationFrameMax(): number { return 0.0; } + + public JSGDebugGetAnimationName(x: number): string | null { return null; } } class TActorAdaptor extends TAdaptor { @@ -699,7 +701,11 @@ class TActorAdaptor extends TAdaptor { public adaptor_do_ANIMATION(data: ParagraphData): void { assert(data.dataOp == EDataOp.ObjectIdx); - this.log(`SetAnimation: ${(data.value) & 0xFFFF} (${(data.value) >> 4 & 0x01})`); + const animName = this.object.JSGDebugGetAnimationName(data.value); + if( animName ) + this.log(`SetAnimation: ${animName}`); + else + this.log(`SetAnimation: ${(data.value) & 0xFFFF} (${(data.value) >> 4 & 0x01})`); this.object.JSGSetAnimation(data.value); } diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index d82e4da6f..6dc6c5aab 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4954,6 +4954,86 @@ class d_a_npc_ls1 extends fopNpc_npc_c { return true; } } + +enum LinkDemoMode { + None = 0x00, + Unk1 = 0x01, + ChaseSlow = 0x02, + ChaseFast = 0x03, + Move = 0x04, + WaitTurn = 0x05, + // UNK6 = 0x06, + Damage = 0x07, + // NULL = 0x08, + // NULL = 0x09, + OpenTreasure = 0x0A, + GetItem = 0x0B, + Unequip = 0x0C, + Holdup = 0x0D, + NULL = 0x0E, + LookAround = 0x0F, + // NULL = 0x10, + // NULL = 0x11, + // NULL = 0x12, + Salute = 0x13, + LookAround2 = 0x14, + TalismanPickup = 0x15, + TalismanWait = 0x16, + // NULL = 0x17, + Surprised = 0x18, + TurnBack = 0x19, + LookUp = 0x1A, + // NULL = 0x1B, + QuakeWait = 0x1C, + Dance = 0x1D, + Caught = 0x1E, + // NULL = 0x1F, + PushPullWait = 0x20, + PushMove = 0x21, + // NULL = 0x22, + DoorOpen = 0x23, + Nod = 0x24, + Present = 0x25, + WindChange = 0x26, + ShipPaddle = 0x27, + StandItemPut = 0x28, + // NULL = 0x29, + // NULL = 0x2A, + // NULL = 0x2B, + // NULL = 0x2C, + TactPlayOriginal = 0x2D, + PowerUp = 0x2E, + VorcanoFail = 0x2F, + BossWarp = 0x30, + SlightSurprised = 0x31, + Smile = 0x32, + // NULL = 0x33, + AgbUse = 0x34, + LookTurn = 0x35, + LetterOpen = 0x36, + LetterRead = 0x37, + // daPy_lk_c::procGrabPut = 0x38, + RedeadStop = 0x39, + RedeadCatch = 0x3A, + GetDance = 0x3B, + BottleOpenFairy = 0x3C, + // NULL = 0x3D, + WarpShort = 0x3E, + OpenSalvageTreasure = 0x3F, + // NULL = 0x40, + FoodSet = 0x41, + SurprisedWait = 0x42, + PowerUpWait = 0x43, + ShipBow = 0x44, + ShipSit = 0x45, + LastCombo = 0x46, + ShipGetOff = 0x47, + HandUp = 0x48, + FoodThrow = 0x49, + IceSlip = 0x4A, + + Data = 0x200, +}; class d_a_py_lk extends fopAc_ac_c { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; @@ -4972,13 +5052,16 @@ class d_a_py_lk extends fopAc_ac_c { this.setupDam('eyeL'); this.setupDam('eyeR'); this.setupDam('mayuL'); - this.setupDam('mayuR'); + this.setupDam('mayuR'); return cPhs__Status.Next; } override execute(globals: dGlobals, deltaTimeFrames: number): void { this.setDemoData(globals); + if (this.demoMode != 5) { + // this.changeDemoProc(); + } this.model.calcAnim(); @@ -5008,7 +5091,7 @@ class d_a_py_lk extends fopAc_ac_c { private setupDam(pref: string): void { const matInstA = this.model.materialInstances.find((m) => m.name === `${pref}damA`)!; const matInstB = this.model.materialInstances.find((m) => m.name === `${pref}damB`)!; - + // Render an alpha mask in the shape of the eyes. Needs to render before Link so that it can depth test against // the scene but not against his hair. The eyes will then draw with depth testing enabled, but will mask against // this alpha tex. @@ -5039,34 +5122,43 @@ class d_a_py_lk extends fopAc_ac_c { if (!demoActor) return false; + demoActor.actor = this; + demoActor.model = this.model; + demoActor.debugGetAnimName = (idx: number) => LinkDemoMode[idx].toString(); + const enable = demoActor.checkEnable(0xFF); if (enable & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } if (enable & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } - if (enable & EDemoActorFlags.HasAnim) { this.demoMode = demoActor.nextBckId; } + + // The demo mode determines which 'Proc' action function will be called. It maps into the DemoProc*FuncTables. + // These functions can start anims (by indexing into AnmDataTable), play sounds, etc. + if (enable & EDemoActorFlags.HasAnim) { + this.demoMode = demoActor.nextBckId; + } if (enable & EDemoActorFlags.HasShape) { // TODO: 0 is casual clothes, 1 is hero clothes } - // Line 3387 - switch(this.demoMode) { + // Limit actor modifications based on the current mode. E.g. Mode 0x18 only allows rotation + switch (this.demoMode) { case 4: case 0x2C: debugger; // Snap to target position // Maybe equip an item break; - + case 0x2B: debugger; break; - + case 2: case 3: - // Transition to target position (2 is slower) + // Transition to target position/rotation (2 is slower) debugger; break; - + case 5: case 0x18: // Rotate only diff --git a/src/ZeldaWindWaker/d_demo.ts b/src/ZeldaWindWaker/d_demo.ts index db5738549..8b06298fc 100644 --- a/src/ZeldaWindWaker/d_demo.ts +++ b/src/ZeldaWindWaker/d_demo.ts @@ -187,6 +187,8 @@ export class dDemo_actor_c extends TActor { public btkId: number; public brkId: number; + debugGetAnimName?: (idx: number) => string; + constructor(public actor: fopAc_ac_c) { super(); } public checkEnable(mask: number) { @@ -287,6 +289,11 @@ export class dDemo_actor_c extends TActor { this.texAnimFrame = x; this.flags |= EDemoActorFlags.HasTexFrame; } + + override JSGDebugGetAnimationName(x: number): string | null { + if( this.debugGetAnimName ) { return this.debugGetAnimName(x); } + else return null; + } } class dDemo_system_c implements TSystem { From 09665db48f32bc07ef51523afa026eec00cebe1c Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Mon, 18 Nov 2024 19:57:59 -0700 Subject: [PATCH 07/43] Basic link animation support - The animation data table is extracted from the original object file - This maps animation IDs to a BCK/BTP IDs --- src/ZeldaWindWaker/Main.ts | 2 +- src/ZeldaWindWaker/d_a.ts | 372 ++++++++++++++++++++-- src/ZeldaWindWaker/tools/zww_extractor.ts | 3 + 3 files changed, 349 insertions(+), 28 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 22b016975..1dea46e7b 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -858,7 +858,7 @@ class SceneDesc { modelCache.fetchObjectData(`Always`); modelCache.fetchStageData(`Stage`); - modelCache.fetchFileData(`extra.crg1_arc`, 9); + modelCache.fetchFileData(`extra.crg1_arc`, 10); modelCache.fetchFileData(`f_pc_profiles.crg1_arc`); const particleArchives = [ diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 6dc6c5aab..0bca2fc80 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4955,6 +4955,243 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } } +enum LkAnim { + WAITS = 0x00, + WALK = 0x01, + DASH = 0x02, + DASHKAZE = 0x03, + WALKHBOOTS = 0x04, + WALKHBOOTSKAZE = 0x05, + WALKSLOPE = 0x06, + ATNLS = 0x07, + ATNRS = 0x08, + ATNWLS = 0x09, + ATNWRS = 0x0A, + ATNDLS = 0x0B, + ATNDRS = 0x0C, + JMPEDS = 0x0D, + ATNWB = 0x0E, + ATNDB = 0x0F, + ATNJL = 0x10, + ATNJR = 0x11, + ATNJLLAND = 0x12, + ATNJRLAND = 0x13, + ROT = 0x14, + CUTREL = 0x15, + DIFENCE = 0x16, + DIFENCEA = 0x17, + CUTTURNP = 0x18, + CUTTURNPWFB = 0x19, + CUTTURNPWLR = 0x1A, + TALKA = 0x1B, + WAITB = 0x1C, + WAITATOB = 0x1D, + CUTA = 0x1E, + CUTF = 0x1F, + CUTR = 0x20, + CUTL = 0x21, + CUTEA = 0x22, + CUTEB = 0x23, + EXCA1 = 0x24, + EXCB1 = 0x25, + CUTBOKO = 0x26, + CUTRER = 0x27, + CUTTURN = 0x28, + CUTTURNC = 0x29, + CUTTURNB = 0x2A, + JATTACK = 0x2B, + JATTCKP = 0x2C, + JATTCKCUT = 0x2D, + JATTCKCUTHAM = 0x2E, + JATTACKLAND = 0x2F, + LANDDAMA = 0x30, + LANDDAMAST = 0x31, + ROLLF = 0x32, + ROLLFMIS = 0x33, + SLIP = 0x34, + SLIDEF = 0x35, + SLIDEFLAND = 0x36, + SLIDEB = 0x37, + SLIDEBLAND = 0x38, + ATNGAL = 0x39, + ATNGAR = 0x3A, + ATNGAHAM = 0x3B, + JMPST = 0x3C, + ROLLB = 0x3D, + ROLLBLAND = 0x3E, + CROUCH = 0x3F, + LIE = 0x40, + LIEFORWARD = 0x41, + WALL = 0x42, + WALLDW = 0x43, + WALLWL = 0x44, + WALLWR = 0x45, + WALLWLDW = 0x46, + WALLWRDW = 0x47, + WALLPL = 0x48, + WALLPR = 0x49, + WALLPLDW = 0x4A, + WALLPRDW = 0x4B, + VJMP = 0x4C, + VJMPCHA = 0x4D, + VJMPCHB = 0x4E, + VJMPCL = 0x4F, + HANGING = 0x50, + HANGUP = 0x51, + HANGMOVEL = 0x52, + HANGMOVER = 0x53, + DAM = 0x54, + DAML = 0x55, + DAMR = 0x56, + DAMF = 0x57, + DAMB = 0x58, + DAMFL = 0x59, + DAMFR = 0x5A, + DAMFF = 0x5B, + DAMFB = 0x5C, + DAMFLUP = 0x5D, + DAMFRUP = 0x5E, + DAMFFUP = 0x5F, + DAMFBUP = 0x60, + LAVADAM = 0x61, + DIELONG = 0x62, + SHIPDIE = 0x63, + SWIMDIE = 0x64, + GRABP = 0x65, + GRABUP = 0x66, + GRABNG = 0x67, + GRABWAIT = 0x68, + GRABWAITB = 0x69, + GRABTHROW = 0x6A, + GRABRE = 0x6B, + MJMP = 0x6C, + MJMPC = 0x6D, + MROLLL = 0x6E, + MROLLR = 0x6F, + MROLLLC = 0x70, + MROLLRC = 0x71, + MSTEPOVER = 0x72, + MSTEPOVERA = 0x73, + MSTEPOVERLAND = 0x74, + ROPECATCH = 0x75, + ROPESWINGF = 0x76, + ROPESWINGB = 0x77, + ROPEWAIT = 0x78, + ROPECLIMB = 0x79, + ROPEDOWN = 0x7A, + ROPETHROWCATCH = 0x7B, + BOOMCATCH = 0x7C, + VOYAGE1 = 0x7D, + WAITPUSHPULL = 0x7E, + WALKPUSH = 0x7F, + WALKPULL = 0x80, + SWIMP = 0x81, + SWIMWAIT = 0x82, + SWIMING = 0x83, + LADDERUPST = 0x84, + LADDERUPEDR = 0x85, + LADDERUPEDL = 0x86, + LADDERDWST = 0x87, + LADDERDWEDR = 0x88, + LADDERDWEDL = 0x89, + LADDERRTOL = 0x8A, + LADDERLTOR = 0x8B, + FCLIMBSLIDELUP = 0x8C, + FCLIMBSLIDERUP = 0x8D, + BOXOPENLINK = 0x8E, + BOXOPENSHORTLINK = 0x8F, + ITEMGET = 0x90, + HOLDUP = 0x91, + WALLHOLDUP = 0x92, + WALLHOLDUPDW = 0x93, + COMEOUT = 0x94, + WALKBARREL = 0x95, + SALTATION = 0x96, + WHO = 0x97, + PICKUP = 0x98, + WAITPICKUP = 0x99, + SURPRISED = 0x9A, + TURNBACK = 0x9B, + LOOKUP = 0x9C, + WAITQ = 0x9D, + GLAD = 0x9E, + SHIP_JUMP1 = 0x9F, + SHIP_JUMP2 = 0xA0, + USEFANA = 0xA1, + USEFANB = 0xA2, + USEFANB2 = 0xA3, + MOGAKU1 = 0xA4, + FM_BATA = 0xA5, + HOOKSHOTJMP = 0xA6, + VOMITJMP = 0xA7, + WAITTAKT = 0xA8, + SLIPICE = 0xA9, + HAMSWINGA = 0xAA, + HAMSWINGBPRE = 0xAB, + HAMSWINGBHIT = 0xAC, + HAMSWINGBEND = 0xAD, + SETBOOTS = 0xAE, + DOOROPENALINK = 0xAF, + DOOROPENBLINK = 0xB0, + SEYYES = 0xB1, + PRESENTATIONA = 0xB2, + WINDL = 0xB3, + WINDR = 0xB4, + PRESENTATIONB = 0xB5, + BINDRINKPRE = 0xB6, + BINDRINKING = 0xB7, + BINDRINKAFTER = 0xB8, + BINOPENPRE = 0xB9, + BINOPENA = 0xBA, + BINOPENB = 0xBB, + BINSWINGS = 0xBC, + BINSWINGU = 0xBD, + BINGET = 0xBE, + SURPRISEDB = 0xBF, + RISE = 0xC0, + USETCEIVER = 0xC1, + YOBU = 0xC2, + NENRIKI = 0xC3, + ESAMAKI = 0xC4, + SETHYOINOMI = 0xC5, + GETLETTER = 0xC6, + WAITLETTER = 0xC7, + LINK_FREEZ = 0xC8, + LINK_MOGAKI = 0xC9, + TAKTDGE = 0xCA, + DAMBIRI = 0xCB, + SALVLR = 0xCC, + SALVRWAIT = 0xCD, + SALVLWAIT = 0xCE, + SALVRBAD = 0xCF, + SALVLBAD = 0xD0, + SALVRGOOD = 0xD1, + SALVLGOOD = 0xD2, + MSTEPOVER_JMPED = 0xD3, + BOXOPENSLINK = 0xD4, + SEARESET = 0xD5, + WARPIN = 0xD6, + WARPOUT = 0xD7, + SURPRISEDWAIT = 0xD8, + PRESENTATIONAWAIT = 0xD9, + POWUPWAIT = 0xDA, + POWUP = 0xDB, + KOSHIKAKE = 0xDC, + COMBO_LINK = 0xDD, + CUTKESA = 0xDE, + WARPOUTFIRST = 0xDF, + WAITAUCTION = 0xE0, + FREEA = 0xE1, + FREEB = 0xE2, + FREED = 0xE3, + TAKTKAZE = 0xE4, + TAKTSIPPU = 0xE5, + TAKTCHUYA = 0xE6, + TAKTFUJIN = 0xE7, + TAKTAYATSURI = 0xE8, + TAKTCHISIN = 0xE9, +}; + enum LinkDemoMode { None = 0x00, Unk1 = 0x01, @@ -4962,24 +5199,24 @@ enum LinkDemoMode { ChaseFast = 0x03, Move = 0x04, WaitTurn = 0x05, - // UNK6 = 0x06, + UNK6 = 0x06, Damage = 0x07, - // NULL = 0x08, - // NULL = 0x09, + Unk8 = 0x08, + Unk9 = 0x09, OpenTreasure = 0x0A, GetItem = 0x0B, Unequip = 0x0C, Holdup = 0x0D, NULL = 0x0E, LookAround = 0x0F, - // NULL = 0x10, - // NULL = 0x11, - // NULL = 0x12, + Unk10 = 0x10, + Unk11 = 0x11, + Unk12 = 0x12, Salute = 0x13, LookAround2 = 0x14, TalismanPickup = 0x15, TalismanWait = 0x16, - // NULL = 0x17, + Unk17 = 0x17, Surprised = 0x18, TurnBack = 0x19, LookUp = 0x1A, @@ -4997,10 +5234,10 @@ enum LinkDemoMode { WindChange = 0x26, ShipPaddle = 0x27, StandItemPut = 0x28, - // NULL = 0x29, - // NULL = 0x2A, - // NULL = 0x2B, - // NULL = 0x2C, + Unk29 = 0x29, + Unk2A = 0x2A, + Unk2B = 0x2B, + Unk2C = 0x2C, TactPlayOriginal = 0x2D, PowerUp = 0x2E, VorcanoFail = 0x2F, @@ -5020,7 +5257,7 @@ enum LinkDemoMode { // NULL = 0x3D, WarpShort = 0x3E, OpenSalvageTreasure = 0x3F, - // NULL = 0x40, + Unk40 = 0x40, FoodSet = 0x41, SurprisedWait = 0x42, PowerUpWait = 0x43, @@ -5031,20 +5268,36 @@ enum LinkDemoMode { HandUp = 0x48, FoodThrow = 0x49, IceSlip = 0x4A, + MAX = 0x4B, - Data = 0x200, + NewAnm0 = 0x200, }; + +interface LkAnimData { + underBckIdx: number; + upperBckIdx: number; + leftHandIdx: number; + rightHandIdx: number; + texAnmIdx: number; +} class d_a_py_lk extends fopAc_ac_c { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; private static LINK_BDL_CL = 0x18; private static LINK_BDL_HANDS = 0x1D; + private demoProcInitFuncTable = new Map boolean>(); + private model: J3DModelInstance; private modelHands: J3DModelInstance; - private demoMode: number; + private demoMode: number = LinkDemoMode.None; + + private anmDataTable: LkAnimData[] = []; + private anm = new mDoExt_bckAnm(); protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { + this.loadAnmTable(globals); + this.playerInit(globals); // noclip modification: The game manually draws the eye/eyebrow filter before the body. Let's do that with sorting. @@ -5054,15 +5307,19 @@ class d_a_py_lk extends fopAc_ac_c { this.setupDam('mayuL'); this.setupDam('mayuR'); + // noclip modification: + this.setSingleMoveAnime(globals, LkAnim.WAITS, 0.6, 0.0, 0xc, 6.0); + return cPhs__Status.Next; } override execute(globals: dGlobals, deltaTimeFrames: number): void { this.setDemoData(globals); if (this.demoMode != 5) { - // this.changeDemoProc(); + this.changeDemoProc(globals); } + this.anm.play(deltaTimeFrames); this.model.calcAnim(); // setWorldMatrix() @@ -5075,6 +5332,7 @@ class d_a_py_lk extends fopAc_ac_c { settingTevStruct(globals, LightType.Actor, this.pos, this.tevStr); setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); + this.anm.entry(this.model); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } @@ -5083,8 +5341,6 @@ class d_a_py_lk extends fopAc_ac_c { this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); // this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); - globals.renderer.extraTextures.fillExtraTextures(this.model); - this.cullMtx = this.model.modelMatrix; } @@ -5124,7 +5380,7 @@ class d_a_py_lk extends fopAc_ac_c { demoActor.actor = this; demoActor.model = this.model; - demoActor.debugGetAnimName = (idx: number) => LinkDemoMode[idx].toString(); + demoActor.debugGetAnimName = (idx: number) => LinkDemoMode[idx].toString(); const enable = demoActor.checkEnable(0xFF); if (enable & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } @@ -5132,8 +5388,8 @@ class d_a_py_lk extends fopAc_ac_c { // The demo mode determines which 'Proc' action function will be called. It maps into the DemoProc*FuncTables. // These functions can start anims (by indexing into AnmDataTable), play sounds, etc. - if (enable & EDemoActorFlags.HasAnim) { - this.demoMode = demoActor.nextBckId; + if (enable & EDemoActorFlags.HasAnim) { + this.demoMode = demoActor.nextBckId; } if (enable & EDemoActorFlags.HasShape) { @@ -5142,25 +5398,25 @@ class d_a_py_lk extends fopAc_ac_c { // Limit actor modifications based on the current mode. E.g. Mode 0x18 only allows rotation switch (this.demoMode) { - case 4: - case 0x2C: + case LinkDemoMode.Move: + case LinkDemoMode.Unk2C: debugger; // Snap to target position // Maybe equip an item break; - case 0x2B: + case LinkDemoMode.Unk2B: debugger; break; - case 2: - case 3: + case LinkDemoMode.ChaseSlow: + case LinkDemoMode.ChaseFast: // Transition to target position/rotation (2 is slower) debugger; break; - case 5: - case 0x18: + case LinkDemoMode.WaitTurn: + case LinkDemoMode.Surprised: // Rotate only debugger; break; @@ -5168,6 +5424,68 @@ class d_a_py_lk extends fopAc_ac_c { return true; } + + private changeDemoProc(globals: dGlobals): boolean { + assert(this.demoMode < LinkDemoMode.MAX || this.demoMode == LinkDemoMode.NewAnm0) + + const pred = true; + + if (pred) { + switch(this.demoMode) { + case LinkDemoMode.None: return false; + + // case LinkDemoMode.NewAnm0: break; // TODO: dProcTool_init(); + case LinkDemoMode.Move: + // TODO: setBlendMoveAnime + this.procWait_init(globals); + break; + + default: + const initFunc = this.demoProcInitFuncTable.get(this.demoMode); + if( initFunc ) { + initFunc(); + } else { + console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); + debugger; + } + break; + } + return true; + } + + return false; + } + + private getAnmData(anmIdx: number): LkAnimData { + // @TODO: Different table if sword is drawn + return this.anmDataTable[anmIdx]; + } + + private setSingleMoveAnime(globals: dGlobals, anmIdx: number, rate: number, start: number, end: number, param_5: number) { + const anmData = this.getAnmData(anmIdx); + + const bck = globals.resCtrl.getObjectRes(ResType.Bck, "LkAnm", anmData.upperBckIdx); + this.anm.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); + } + + private procWait_init(globals: dGlobals) { + this.setSingleMoveAnime(globals, LkAnim.WAITATOB, 0.6, 0.0, 0xc, 6.0); + } + + private loadAnmTable(globals: dGlobals) { + const anmDataView = globals.findExtraSymbolData(`d_a_player_main.o`, `mAnmDataTable__9daPy_lk_c`).createDataView(); + let offset = 0; + while( offset < anmDataView.byteLength) { + this.anmDataTable.push({ + underBckIdx: anmDataView.getUint16(offset + 0), + upperBckIdx: anmDataView.getUint16(offset + 2), + leftHandIdx: anmDataView.getUint8(offset + 4), + rightHandIdx: anmDataView.getUint8(offset + 5), + texAnmIdx: anmDataView.getUint16(offset + 6), + }) + offset += 8; + } + } } interface constructor extends fpc_bs__Constructor { diff --git a/src/ZeldaWindWaker/tools/zww_extractor.ts b/src/ZeldaWindWaker/tools/zww_extractor.ts index fe3ae3984..3935e91cb 100644 --- a/src/ZeldaWindWaker/tools/zww_extractor.ts +++ b/src/ZeldaWindWaker/tools/zww_extractor.ts @@ -341,6 +341,9 @@ function extractExtra(binaries: Binary[]) { extractSymbol(datas, framework, `d_drawlist.o`, `l_frontZMat`); extractSymbol(datas, framework, `d_drawlist.o`, `l_frontNoZSubMat`); + // main.dol : d_a_player_main.o + extractSymbol(datas, framework, `d_a_player_main.o`, `mAnmDataTable__9daPy_lk_c`); + const crg1 = { SymbolData: datas, }; From d341d7c75bd1360092969fb50334f582f75272ee Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 12:17:43 -0700 Subject: [PATCH 08/43] Link responds to animation mode 512 in demos This covers most cases where Link is standing still and playing a simple animation. For instance, sleeping, scratching his head, etc. --- src/ZeldaWindWaker/d_a.ts | 59 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 0bca2fc80..a5cf45b25 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5287,6 +5287,7 @@ class d_a_py_lk extends fopAc_ac_c { private static LINK_BDL_HANDS = 0x1D; private demoProcInitFuncTable = new Map boolean>(); + private proc: (globals: dGlobals) => void; private model: J3DModelInstance; private modelHands: J3DModelInstance; @@ -5294,6 +5295,7 @@ class d_a_py_lk extends fopAc_ac_c { private anmDataTable: LkAnimData[] = []; private anm = new mDoExt_bckAnm(); + private anmResID: number; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); @@ -5319,6 +5321,8 @@ class d_a_py_lk extends fopAc_ac_c { this.changeDemoProc(globals); } + if( this.proc ) this.proc(globals); + this.anm.play(deltaTimeFrames); this.model.calcAnim(); @@ -5434,7 +5438,10 @@ class d_a_py_lk extends fopAc_ac_c { switch(this.demoMode) { case LinkDemoMode.None: return false; - // case LinkDemoMode.NewAnm0: break; // TODO: dProcTool_init(); + case LinkDemoMode.NewAnm0: + this.proc = this.procTool; + break; + case LinkDemoMode.Move: // TODO: setBlendMoveAnime this.procWait_init(globals); @@ -5468,6 +5475,56 @@ class d_a_py_lk extends fopAc_ac_c { this.anm.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); } + private procTool(globals: dGlobals) { + const demoActor = globals.scnPlay.demo.getSystem().getActor(this.demoActorID); + if (!demoActor) + return; + + let anmFrame = 0.0; + let anmResId = 0xFFFF; + + if (demoActor.flags & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } + if (demoActor.flags & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } + if (demoActor.flags & EDemoActorFlags.HasFrame) { anmFrame = demoActor.animFrame; } + + if (demoActor.flags & EDemoActorFlags.HasData) { + const status = demoActor.stbData.getUint8(0); + switch( demoActor.stbDataId ) { + case 1: + case 3: + case 5: + const count = demoActor.stbData.getUint8(1); + assert(count == 3) + anmResId = demoActor.stbData.getUint16(2); + const btpId = demoActor.stbData.getUint16(4); // @TODO: Handle texture animations + const btkUnkId = demoActor.stbData.getUint16(6); // @TODO: What is this for? + break; + + case 0: + case 2: + case 4: + anmResId = demoActor.stbData.getUint16(1); + break; + + default: + debugger; + } + } + + if( anmResId == 0xFFFF || this.anmResID == anmResId) { + if (demoActor.flags & EDemoActorFlags.HasFrame) { + this.anm.entry(this.model, anmFrame); + demoActor.animFrameMax = this.anm.frameCtrl.endFrame; + } + } else { + // TODO: Load from LkD00 arc + const bck = globals.resCtrl.getObjectIDRes(ResType.Bck, 'LkD00', anmResId); + this.anm.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration ); + this.anm.entry(this.model, anmFrame); + this.anmResID = anmResId; + } + } + private procWait_init(globals: dGlobals) { this.setSingleMoveAnime(globals, LkAnim.WAITATOB, 0.6, 0.0, 0xc, 6.0); } From 89435be93773db46b4cc0860af381084a6ba2d4e Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 12:59:03 -0700 Subject: [PATCH 09/43] Add basic texture animation support Link now has facial animations in the Awaken demo --- src/ZeldaWindWaker/d_a.ts | 46 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index a5cf45b25..e68ec3fbf 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5294,8 +5294,9 @@ class d_a_py_lk extends fopAc_ac_c { private demoMode: number = LinkDemoMode.None; private anmDataTable: LkAnimData[] = []; - private anm = new mDoExt_bckAnm(); - private anmResID: number; + private anmBck = new mDoExt_bckAnm(); + private anmBtp = new mDoExt_btpAnm(); + private anmBckId: number; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); @@ -5323,7 +5324,8 @@ class d_a_py_lk extends fopAc_ac_c { if( this.proc ) this.proc(globals); - this.anm.play(deltaTimeFrames); + this.anmBck.play(deltaTimeFrames); + this.anmBtp.play(deltaTimeFrames); this.model.calcAnim(); // setWorldMatrix() @@ -5336,7 +5338,8 @@ class d_a_py_lk extends fopAc_ac_c { settingTevStruct(globals, LightType.Actor, this.pos, this.tevStr); setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); - this.anm.entry(this.model); + this.anmBck.entry(this.model); + if( this.anmBtp.anm ) this.anmBtp.entry(this.model); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } @@ -5472,7 +5475,7 @@ class d_a_py_lk extends fopAc_ac_c { const anmData = this.getAnmData(anmIdx); const bck = globals.resCtrl.getObjectRes(ResType.Bck, "LkAnm", anmData.upperBckIdx); - this.anm.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); + this.anmBck.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); } private procTool(globals: dGlobals) { @@ -5481,7 +5484,8 @@ class d_a_py_lk extends fopAc_ac_c { return; let anmFrame = 0.0; - let anmResId = 0xFFFF; + let anmBckId = 0xFFFF; + let anmBtpId = 0xFFFF; if (demoActor.flags & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } if (demoActor.flags & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } @@ -5495,15 +5499,15 @@ class d_a_py_lk extends fopAc_ac_c { case 5: const count = demoActor.stbData.getUint8(1); assert(count == 3) - anmResId = demoActor.stbData.getUint16(2); - const btpId = demoActor.stbData.getUint16(4); // @TODO: Handle texture animations - const btkUnkId = demoActor.stbData.getUint16(6); // @TODO: What is this for? + anmBckId = demoActor.stbData.getUint16(2); + anmBtpId = demoActor.stbData.getUint16(4); + const btkScrollId = demoActor.stbData.getUint16(6); // TODO: Scrolling texture resource break; case 0: case 2: case 4: - anmResId = demoActor.stbData.getUint16(1); + anmBckId = demoActor.stbData.getUint16(1); break; default: @@ -5511,17 +5515,23 @@ class d_a_py_lk extends fopAc_ac_c { } } - if( anmResId == 0xFFFF || this.anmResID == anmResId) { + if( anmBckId == 0xFFFF || this.anmBckId == anmBckId) { if (demoActor.flags & EDemoActorFlags.HasFrame) { - this.anm.entry(this.model, anmFrame); - demoActor.animFrameMax = this.anm.frameCtrl.endFrame; + this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; + this.anmBtp.frameCtrl.currentTimeInFrames = anmFrame; + demoActor.animFrameMax = this.anmBck.frameCtrl.endFrame; } } else { - // TODO: Load from LkD00 arc - const bck = globals.resCtrl.getObjectIDRes(ResType.Bck, 'LkD00', anmResId); - this.anm.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration ); - this.anm.entry(this.model, anmFrame); - this.anmResID = anmResId; + // TODO: How should LkD00 arc be loaded? + const bck = globals.resCtrl.getObjectIDRes(ResType.Bck, 'LkD00', anmBckId); + this.anmBck.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration ); + this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; + this.anmBckId = anmBckId; + + if(anmBtpId != 0xFFFF) { + const btp = globals.resCtrl.getObjectIDRes(ResType.Btp, 'LkD00', anmBtpId); + this.anmBtp.init(this.model.modelData, btp, true, btp.loopMode, 1.0, 0, btp.duration); + } } } From 9e6fd31f5ad8bf7e2ced344340425f71cd0ac7f4 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 14:09:38 -0700 Subject: [PATCH 10/43] Fix Link's eyes rendering in front of everything A leftover debug typo --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index e68ec3fbf..6571ecfcc 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5358,7 +5358,7 @@ class d_a_py_lk extends fopAc_ac_c { // Render an alpha mask in the shape of the eyes. Needs to render before Link so that it can depth test against // the scene but not against his hair. The eyes will then draw with depth testing enabled, but will mask against // this alpha tex. - matInstA.setSortKeyLayer(GfxRendererLayer.BACKGROUND + 4, false); + matInstA.setSortKeyLayer(GfxRendererLayer.OPAQUE + 4, false); matInstA.setColorWriteEnabled(false); matInstA.setAlphaWriteEnabled(true); From 6c898c33090068d9727e837939dda08d23d64254 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 15:54:06 -0700 Subject: [PATCH 11/43] Set Link's clothes using the demo actor ShapeID --- src/ZeldaWindWaker/d_a.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 6571ecfcc..fe06b59c7 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -10,7 +10,7 @@ import { GlobalSaveManager } from "../SaveManager.js"; import { TDDraw, TSDraw } from "../SuperMarioGalaxy/DDraw.js"; import { Endianness } from "../endian.js"; import { compareDepthValues } from "../gfx/helpers/ReversedDepthHelpers.js"; -import { GfxClipSpaceNearZ, GfxCompareMode, GfxDevice } from "../gfx/platform/GfxPlatform.js"; +import { GfxClipSpaceNearZ, GfxCompareMode, GfxDevice, GfxTexture } from "../gfx/platform/GfxPlatform.js"; import { GfxRenderCache } from "../gfx/render/GfxRenderCache.js"; import { GfxRendererLayer, GfxRenderInst, GfxRenderInstManager } from "../gfx/render/GfxRenderInstManager.js"; import { GXMaterialBuilder } from "../gx/GXMaterialBuilder.js"; @@ -36,6 +36,7 @@ import { dDlst_alphaModel__Type } from "./d_drawlist.js"; import { dDemo_setDemoData, EDemoActorFlags } from "./d_demo.js"; import { fopAc_ac_c, fopAcIt_JudgeByID, fopAcM_create, fopAcM_prm_class } from "./f_op_actor.js"; import { dProcName_e } from "./d_procname.js"; +import { TextureMapping } from "../TextureHolder.js"; // Framework'd actors @@ -5285,6 +5286,8 @@ class d_a_py_lk extends fopAc_ac_c { private static ARC_NAME = "Link"; private static LINK_BDL_CL = 0x18; private static LINK_BDL_HANDS = 0x1D; + private static LINK_BTI_LINKTEXBCI4 = 0x71; + private static LINK_CLOTHES_TEX_IDX = 0x22; private demoProcInitFuncTable = new Map boolean>(); private proc: (globals: dGlobals) => void; @@ -5293,6 +5296,10 @@ class d_a_py_lk extends fopAc_ac_c { private modelHands: J3DModelInstance; private demoMode: number = LinkDemoMode.None; + private texMappingClothes: TextureMapping; + private texMappingCasualClothes: TextureMapping = new TextureMapping(); + private texMappingHeroClothes: TextureMapping = new TextureMapping(); + private anmDataTable: LkAnimData[] = []; private anmBck = new mDoExt_bckAnm(); private anmBtp = new mDoExt_btpAnm(); @@ -5348,6 +5355,15 @@ class d_a_py_lk extends fopAc_ac_c { this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); // this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); + // Fetch the casual clothes and the hero texture. They'll be be selected by the ShapeID set by a demo. + const casualTexData = globals.resCtrl.getObjectRes(ResType.Bti, d_a_py_lk.ARC_NAME, d_a_py_lk.LINK_BTI_LINKTEXBCI4); + casualTexData.fillTextureMapping(this.texMappingCasualClothes); + + // Find the texture mapping for link's clothes in the model. There are two, the first has alpha enabled and is never + // used with the casual clothes. We want the second. + this.texMappingClothes = this.model.materialInstanceState.textureMappings[d_a_py_lk.LINK_CLOTHES_TEX_IDX]; + this.texMappingHeroClothes.copy(this.texMappingClothes); + this.cullMtx = this.model.modelMatrix; } @@ -5400,7 +5416,11 @@ class d_a_py_lk extends fopAc_ac_c { } if (enable & EDemoActorFlags.HasShape) { - // TODO: 0 is casual clothes, 1 is hero clothes + // ShapeID 1 is casual clothes, 0 is hero clothes + if( demoActor.shapeId == 1 ) + this.texMappingClothes.copy(this.texMappingCasualClothes); + else + this.texMappingClothes.copy(this.texMappingHeroClothes); } // Limit actor modifications based on the current mode. E.g. Mode 0x18 only allows rotation From 0524b2b82b467842c3d4b323e7294ac9757835b6 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 17:48:38 -0700 Subject: [PATCH 12/43] Link can wear casual clothes! The cutscene is able to set this by using ShapeID --- src/ZeldaWindWaker/d_a.ts | 64 +++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index fe06b59c7..fbc6017b6 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -1155,7 +1155,7 @@ class d_a_obj_lpalm extends fopAc_ac_c { this.animDir[i] = cLib_addCalcAngleRad2(this.animDir[i], cM_s2rad(animDirTarget), cM_s2rad(0x04), cM_s2rad(0x20)); // Rock back and forth. - this.animWave[i] += cM_s2rad((windPow * 0x800) + cM_rndFX(0x80)) *deltaTimeFrames; + this.animWave[i] += cM_s2rad((windPow * 0x800) + cM_rndFX(0x80)) * deltaTimeFrames; const wave = Math.sin(this.animWave[i]); vec3.set(scratchVec3a, wave, 0, wave); @@ -2876,7 +2876,7 @@ class d_a_majuu_flag extends fopAc_ac_c { private majuu_flag_move(globals: dGlobals, deltaTimeFrames: number): void { this.wave += this.waveSpeed * deltaTimeFrames; - const windSpeed = lerp(this.windSpeed1, this.windSpeed2, Math.sin(cM_s2rad(this.wave)) * 0.5 + 0.5); + const windSpeed = lerp(this.windSpeed1, this.windSpeed2, Math.sin(cM_s2rad(this.wave)) * 0.5 + 0.5); const windpow = dKyw_get_wind_pow(globals.g_env_light); vec3.set(scratchVec3a, 0, 0, windSpeed * windpow * 2.0); mDoMtx_ZrotS(calc_mtx, -this.rot[2]); @@ -5288,14 +5288,17 @@ class d_a_py_lk extends fopAc_ac_c { private static LINK_BDL_HANDS = 0x1D; private static LINK_BTI_LINKTEXBCI4 = 0x71; private static LINK_CLOTHES_TEX_IDX = 0x22; + private static LINK_BDL_KATSURA = 0x20; private demoProcInitFuncTable = new Map boolean>(); - private proc: (globals: dGlobals) => void; + private proc: (globals: dGlobals) => void; private model: J3DModelInstance; - private modelHands: J3DModelInstance; + private modelHands: J3DModelInstance + private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. private demoMode: number = LinkDemoMode.None; + private isWearingCasualClothes = false; private texMappingClothes: TextureMapping; private texMappingCasualClothes: TextureMapping = new TextureMapping(); private texMappingHeroClothes: TextureMapping = new TextureMapping(); @@ -5307,7 +5310,7 @@ class d_a_py_lk extends fopAc_ac_c { protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); - + this.playerInit(globals); // noclip modification: The game manually draws the eye/eyebrow filter before the body. Let's do that with sorting. @@ -5329,12 +5332,16 @@ class d_a_py_lk extends fopAc_ac_c { this.changeDemoProc(globals); } - if( this.proc ) this.proc(globals); + if (this.proc) this.proc(globals); this.anmBck.play(deltaTimeFrames); this.anmBtp.play(deltaTimeFrames); + this.model.calcAnim(); + mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); + this.modelKatsura.calcAnim(); + // setWorldMatrix() MtxTrans(this.pos, false, this.model.modelMatrix); mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); @@ -5343,16 +5350,27 @@ class d_a_py_lk extends fopAc_ac_c { override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { // @TODO: This should use LightType.Player, but it's not yet implemented settingTevStruct(globals, LightType.Actor, this.pos, this.tevStr); - setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); + + if (this.isWearingCasualClothes) { + this.model.setShapeVisible(5, false); // Hat + this.model.setShapeVisible(22, false); // Sword scabbard + this.model.setShapeVisible(23, false); // Belt buckle + + setLightTevColorType(globals, this.modelKatsura, this.tevStr, viewerInput.camera); + mDoExt_modelEntryDL(globals, this.modelKatsura, renderInstManager, viewerInput); + } this.anmBck.entry(this.model); - if( this.anmBtp.anm ) this.anmBtp.entry(this.model); + if (this.anmBtp.anm) this.anmBtp.entry(this.model); + + setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } private playerInit(globals: dGlobals) { // createHeap() this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); + this.modelKatsura = this.initModel(globals, d_a_py_lk.LINK_BDL_KATSURA); // this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); // Fetch the casual clothes and the hero texture. They'll be be selected by the ShapeID set by a demo. @@ -5416,10 +5434,10 @@ class d_a_py_lk extends fopAc_ac_c { } if (enable & EDemoActorFlags.HasShape) { - // ShapeID 1 is casual clothes, 0 is hero clothes - if( demoActor.shapeId == 1 ) + this.isWearingCasualClothes = (demoActor.shapeId == 1); + if (this.isWearingCasualClothes) this.texMappingClothes.copy(this.texMappingCasualClothes); - else + else this.texMappingClothes.copy(this.texMappingHeroClothes); } @@ -5458,21 +5476,21 @@ class d_a_py_lk extends fopAc_ac_c { const pred = true; if (pred) { - switch(this.demoMode) { + switch (this.demoMode) { case LinkDemoMode.None: return false; - case LinkDemoMode.NewAnm0: + case LinkDemoMode.NewAnm0: this.proc = this.procTool; break; case LinkDemoMode.Move: // TODO: setBlendMoveAnime - this.procWait_init(globals); + this.procWait_init(globals); break; default: const initFunc = this.demoProcInitFuncTable.get(this.demoMode); - if( initFunc ) { + if (initFunc) { initFunc(); } else { console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); @@ -5513,7 +5531,7 @@ class d_a_py_lk extends fopAc_ac_c { if (demoActor.flags & EDemoActorFlags.HasData) { const status = demoActor.stbData.getUint8(0); - switch( demoActor.stbDataId ) { + switch (demoActor.stbDataId) { case 1: case 3: case 5: @@ -5530,12 +5548,12 @@ class d_a_py_lk extends fopAc_ac_c { anmBckId = demoActor.stbData.getUint16(1); break; - default: + default: debugger; - } + } } - if( anmBckId == 0xFFFF || this.anmBckId == anmBckId) { + if (anmBckId == 0xFFFF || this.anmBckId == anmBckId) { if (demoActor.flags & EDemoActorFlags.HasFrame) { this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; this.anmBtp.frameCtrl.currentTimeInFrames = anmFrame; @@ -5544,11 +5562,11 @@ class d_a_py_lk extends fopAc_ac_c { } else { // TODO: How should LkD00 arc be loaded? const bck = globals.resCtrl.getObjectIDRes(ResType.Bck, 'LkD00', anmBckId); - this.anmBck.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration ); + this.anmBck.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration); this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; this.anmBckId = anmBckId; - if(anmBtpId != 0xFFFF) { + if (anmBtpId != 0xFFFF) { const btp = globals.resCtrl.getObjectIDRes(ResType.Btp, 'LkD00', anmBtpId); this.anmBtp.init(this.model.modelData, btp, true, btp.loopMode, 1.0, 0, btp.duration); } @@ -5562,7 +5580,7 @@ class d_a_py_lk extends fopAc_ac_c { private loadAnmTable(globals: dGlobals) { const anmDataView = globals.findExtraSymbolData(`d_a_player_main.o`, `mAnmDataTable__9daPy_lk_c`).createDataView(); let offset = 0; - while( offset < anmDataView.byteLength) { + while (offset < anmDataView.byteLength) { this.anmDataTable.push({ underBckIdx: anmDataView.getUint16(offset + 0), upperBckIdx: anmDataView.getUint16(offset + 2), @@ -5571,7 +5589,7 @@ class d_a_py_lk extends fopAc_ac_c { texAnmIdx: anmDataView.getUint16(offset + 6), }) offset += 8; - } + } } } From 68739c29c77884118e976c5e9b9d28c91f781243 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 21 Nov 2024 20:13:29 -0700 Subject: [PATCH 13/43] Fix links eye masking textures not animating The eyes would animate, but the masks would not, clipping some of the eyebrows in some cases (and removing the alpha blending) --- src/ZeldaWindWaker/d_a.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index fbc6017b6..4fcd47b56 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5386,6 +5386,7 @@ class d_a_py_lk extends fopAc_ac_c { } private setupDam(pref: string): void { + const matInst = this.model.materialInstances.find((m) => m.name === `${pref}`)!; const matInstA = this.model.materialInstances.find((m) => m.name === `${pref}damA`)!; const matInstB = this.model.materialInstances.find((m) => m.name === `${pref}damB`)!; @@ -5405,6 +5406,10 @@ class d_a_py_lk extends fopAc_ac_c { matInstB.setSortKeyLayer(GfxRendererLayer.OPAQUE + 6, false); matInstB.setColorWriteEnabled(false); matInstB.setAlphaWriteEnabled(true); + + // Ensure that any texture animations applied to `eyeL` or `eyeR` also apply to these two damA/B masks + matInstA.texNoCalc = matInst.texNoCalc; + matInstB.texNoCalc = matInst.texNoCalc; } private initModel(globals: dGlobals, fileIdx: number): J3DModelInstance { From fa0c98936cf6b18a648786dbc4e6f79c8f3f644d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 22 Nov 2024 08:43:27 -0700 Subject: [PATCH 14/43] Snap link to the ground --- src/ZeldaWindWaker/d_a.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 4fcd47b56..1742828ab 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5296,7 +5296,9 @@ class d_a_py_lk extends fopAc_ac_c { private model: J3DModelInstance; private modelHands: J3DModelInstance private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. + private demoMode: number = LinkDemoMode.None; + private gdnChk = new dBgS_GndChk() private isWearingCasualClothes = false; private texMappingClothes: TextureMapping; @@ -5334,6 +5336,11 @@ class d_a_py_lk extends fopAc_ac_c { if (this.proc) this.proc(globals); + // Clamp Link's position to the ground. // @TODO: Acch + this.gdnChk.Reset(); + vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); + this.pos[1] = globals.scnPlay.bgS.GroundCross(this.gdnChk); + this.anmBck.play(deltaTimeFrames); this.anmBtp.play(deltaTimeFrames); From 2c5f46be7b95a18e07d7e37252324e069fef6960 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 27 Nov 2024 20:38:07 -0700 Subject: [PATCH 15/43] Load the Link arcs and create his actor when creating a demo scene Almost all the demos expect the actor to be available --- src/ZeldaWindWaker/Main.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 1dea46e7b..76c773b58 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -999,12 +999,13 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { globals.scnPlay.demo.remove(); // TODO: Don't render until the camera has been placed for this demo. The cuts are jarring. - - // noclip modification: Most cutscenes expect the Link actor to be loaded - if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { - fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, null, globals.mStayNo, null, null, 0xFF, -1); - } + // Most cutscenes expect the Link actor to be loaded + this.globals.modelCache.fetchObjectData('Link'); + this.globals.modelCache.fetchObjectData('LkD00'); + this.globals.modelCache.fetchObjectData('LkD01'); + this.globals.modelCache.fetchObjectData('LkAnm'); + // noclip modification: This normally happens on room load. Do it here instead so that we don't waste time // loading .arcs for cutscenes that aren't going to be played const lbnk = globals.roomCtrl.status[this.roomList[0]].data.lbnk; @@ -1019,11 +1020,15 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { // @TODO: Better error handling. This does not prevent a debugger break. console.log(`Failed to load stage demo file: ${globals.roomCtrl.demoArcName}`, e); }) - - await globals.modelCache.waitForLoad(); } } + await globals.modelCache.waitForLoad(); + + if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { + fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, [-25414,3501,219], globals.mStayNo, null, null, 0xFF, -1); + } + // noclip modification: ensure all the actors are created before we load the cutscene await new Promise(resolve => { (function waitForActors(){ if (globals.frameworkGlobals.ctQueue.length == 0) return resolve(null); From 055da95302bce0372a7b1b3580bc158d04b0138a Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 11:53:01 -0700 Subject: [PATCH 16/43] Update LinkDemoMode enum with better values --- src/ZeldaWindWaker/d_a.ts | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 1742828ab..a8911fafe 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5195,23 +5195,23 @@ enum LkAnim { enum LinkDemoMode { None = 0x00, - Unk1 = 0x01, - ChaseSlow = 0x02, - ChaseFast = 0x03, - Move = 0x04, + Wait = 0x01, + Walk = 0x02, + Dash = 0x03, + SetPosRotEquip = 0x04, WaitTurn = 0x05, - UNK6 = 0x06, + PartnerInteractA = 0x06, Damage = 0x07, - Unk8 = 0x08, - Unk9 = 0x09, + PartnerInteractB = 0x08, + LargeDamage = 0x09, OpenTreasure = 0x0A, GetItem = 0x0B, Unequip = 0x0C, Holdup = 0x0D, NULL = 0x0E, LookAround = 0x0F, - Unk10 = 0x10, - Unk11 = 0x11, + BackJump = 0x10, + Fall = 0x11, Unk12 = 0x12, Salute = 0x13, LookAround2 = 0x14, @@ -5221,14 +5221,14 @@ enum LinkDemoMode { Surprised = 0x18, TurnBack = 0x19, LookUp = 0x1A, - // NULL = 0x1B, + LargeDamageUp = 0x1B, QuakeWait = 0x1C, Dance = 0x1D, Caught = 0x1E, - // NULL = 0x1F, + Throw = 0x1F, PushPullWait = 0x20, PushMove = 0x21, - // NULL = 0x22, + TactWait = 0x22, DoorOpen = 0x23, Nod = 0x24, Present = 0x25, @@ -5237,28 +5237,28 @@ enum LinkDemoMode { StandItemPut = 0x28, Unk29 = 0x29, Unk2A = 0x2A, - Unk2B = 0x2B, - Unk2C = 0x2C, + SetRot = 0x2B, + SetPosRot = 0x2C, TactPlayOriginal = 0x2D, PowerUp = 0x2E, VorcanoFail = 0x2F, BossWarp = 0x30, SlightSurprised = 0x31, Smile = 0x32, - // NULL = 0x33, + PartnerCarry = 0x33, AgbUse = 0x34, LookTurn = 0x35, LetterOpen = 0x36, LetterRead = 0x37, - // daPy_lk_c::procGrabPut = 0x38, + GrabPut = 0x38, RedeadStop = 0x39, RedeadCatch = 0x3A, GetDance = 0x3B, BottleOpenFairy = 0x3C, - // NULL = 0x3D, + BottleOpen = 0x3D, WarpShort = 0x3E, OpenSalvageTreasure = 0x3F, - Unk40 = 0x40, + SlowFall = 0x40, FoodSet = 0x41, SurprisedWait = 0x42, PowerUpWait = 0x43, @@ -5455,19 +5455,19 @@ class d_a_py_lk extends fopAc_ac_c { // Limit actor modifications based on the current mode. E.g. Mode 0x18 only allows rotation switch (this.demoMode) { - case LinkDemoMode.Move: - case LinkDemoMode.Unk2C: + case LinkDemoMode.SetPosRotEquip: + case LinkDemoMode.SetPosRot: debugger; // Snap to target position // Maybe equip an item break; - case LinkDemoMode.Unk2B: + case LinkDemoMode.SetRot: debugger; break; - case LinkDemoMode.ChaseSlow: - case LinkDemoMode.ChaseFast: + case LinkDemoMode.Walk: + case LinkDemoMode.Dash: // Transition to target position/rotation (2 is slower) debugger; break; @@ -5495,7 +5495,7 @@ class d_a_py_lk extends fopAc_ac_c { this.proc = this.procTool; break; - case LinkDemoMode.Move: + case LinkDemoMode.SetPosRotEquip: // TODO: setBlendMoveAnime this.procWait_init(globals); break; From 2966d97b7e5aadf8432fad0835d0993e88e3f1da Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 12:03:15 -0700 Subject: [PATCH 17/43] Root motion from translations is now applied to position --- src/ZeldaWindWaker/d_a.ts | 94 +++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index a8911fafe..38c55b122 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -2,7 +2,7 @@ import { ReadonlyMat4, ReadonlyVec3, mat4, quat, vec2, vec3 } from "gl-matrix"; import { TransparentBlack, colorCopy, colorFromRGBA8, colorNewCopy, colorNewFromRGBA8 } from "../Color.js"; import { J3DModelData, J3DModelInstance, buildEnvMtx } from "../Common/JSYSTEM/J3D/J3DGraphBase.js"; -import { LoopMode, TRK1, TTK1 } from "../Common/JSYSTEM/J3D/J3DLoader.js"; +import { JointTransformInfo, LoopMode, TRK1, TTK1 } from "../Common/JSYSTEM/J3D/J3DLoader.js"; import { JPABaseEmitter, JPASetRMtxSTVecFromMtx } from "../Common/JSYSTEM/JPA.js"; import { BTIData } from "../Common/JSYSTEM/JUTTexture.js"; import { Vec3One, Vec3UnitY, Vec3UnitZ, Vec3Zero, clamp, computeMatrixWithoutTranslation, computeModelMatrixR, computeModelMatrixS, lerp, saturate, scaleMatrix, transformVec3Mat4w0, transformVec3Mat4w1 } from "../MathHelpers.js"; @@ -37,6 +37,7 @@ import { dDemo_setDemoData, EDemoActorFlags } from "./d_demo.js"; import { fopAc_ac_c, fopAcIt_JudgeByID, fopAcM_create, fopAcM_prm_class } from "./f_op_actor.js"; import { dProcName_e } from "./d_procname.js"; import { TextureMapping } from "../TextureHolder.js"; +import { calcANK1JointAnimationTransform } from "../Common/JSYSTEM/J3D/J3DGraphAnimator.js"; // Framework'd actors @@ -5289,6 +5290,7 @@ class d_a_py_lk extends fopAc_ac_c { private static LINK_BTI_LINKTEXBCI4 = 0x71; private static LINK_CLOTHES_TEX_IDX = 0x22; private static LINK_BDL_KATSURA = 0x20; + private static MaxNormalSpeed = 17.0; // Extracted from daPy_HIO_move_c1 private demoProcInitFuncTable = new Map boolean>(); private proc: (globals: dGlobals) => void; @@ -5309,6 +5311,7 @@ class d_a_py_lk extends fopAc_ac_c { private anmBck = new mDoExt_bckAnm(); private anmBtp = new mDoExt_btpAnm(); private anmBckId: number; + private anmTranslation = vec3.create(); protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); @@ -5336,6 +5339,8 @@ class d_a_py_lk extends fopAc_ac_c { if (this.proc) this.proc(globals); + this.posMove(deltaTimeFrames); + // Clamp Link's position to the ground. // @TODO: Acch this.gdnChk.Reset(); vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); @@ -5435,9 +5440,12 @@ class d_a_py_lk extends fopAc_ac_c { demoActor.model = this.model; demoActor.debugGetAnimName = (idx: number) => LinkDemoMode[idx].toString(); + let targetPos: ReadonlyVec3 = this.pos; + let targetRot: number = this.rot[1]; + const enable = demoActor.checkEnable(0xFF); - if (enable & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } - if (enable & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } + if (enable & EDemoActorFlags.HasPos) { targetPos = demoActor.translation; } + if (enable & EDemoActorFlags.HasRot) { targetRot = demoActor.rotation[1]; } // The demo mode determines which 'Proc' action function will be called. It maps into the DemoProc*FuncTables. // These functions can start anims (by indexing into AnmDataTable), play sounds, etc. @@ -5457,20 +5465,52 @@ class d_a_py_lk extends fopAc_ac_c { switch (this.demoMode) { case LinkDemoMode.SetPosRotEquip: case LinkDemoMode.SetPosRot: - debugger; - // Snap to target position - // Maybe equip an item + vec3.copy(this.pos, targetPos); + this.rot[1] = targetRot; break; - case LinkDemoMode.SetRot: + case LinkDemoMode.SetRot: { debugger; + const moveVec = vec3.sub(scratchVec3a, targetPos, this.pos); + const oldRot = this.rot[1]; + const newRot = cM_atan2s(moveVec[0], moveVec[2]); + this.rot[1] = newRot; + // this->mRotUnk = this->mRotUnk - (sNewRot - sOldRot); break; + } case LinkDemoMode.Walk: - case LinkDemoMode.Dash: + case LinkDemoMode.Dash: { + const moveVec = vec3.sub(scratchVec3a, targetPos, this.pos); + const newRot = cM_atan2s(moveVec[0], moveVec[2]); + this.rot[1] = newRot; + this.setSingleMoveAnime(globals, (this.demoMode == LinkDemoMode.Walk) ? LkAnim.WALK : LkAnim.DASH) + // Transition to target position/rotation (2 is slower) - debugger; + // const dist = vec3.sub(scratchVec3a, targetPos, this.pos); + // local_6c = local_90.x; + // local_68 = local_90.y; + // local_64 = local_90.z; + // if (abs(this->mVelocity) / mMaxNormalSpeed < 0.5) { + // demo_mode = 2; + // } + // local_a8.x = local_90.x; + // local_a8.y = 0.0; + // local_a8.z = local_90.z; + // fVar6 = VECSquareMag(&local_a8); + // if ((fVar6 < 100.0) || (fVar6 < 2500.0 && (abs(this->mVelocity) < 0.001))) { + // demo_mode = 1; + // this->mVelocity = 0.0; + // } + // else if (((demo_mode == 2) && (fVar6 < 400.0)) || (fVar6 < 2500.0)) { + // mDemo.setStick(0.0); + // } + // iVar8 = cM_atan2s(local_6c,local_64); + // mDemo.setMoveAngle(iVar8); + // Snap to target position + // Maybe equip an item break; + } case LinkDemoMode.WaitTurn: case LinkDemoMode.Surprised: @@ -5505,8 +5545,8 @@ class d_a_py_lk extends fopAc_ac_c { if (initFunc) { initFunc(); } else { - console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); - debugger; + // console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); + // debugger; } break; } @@ -5516,18 +5556,46 @@ class d_a_py_lk extends fopAc_ac_c { return false; } + private posMove(deltaTimeFrames: number) { + if( this.anmBck ) { + // Apply the root motion from the current animation (swaying) + const rootTransform = new JointTransformInfo(); + calcANK1JointAnimationTransform(rootTransform, this.anmBck.anm.jointAnimationEntries[0], this.anmBck.frameCtrl.getFrame(), this.anmBck.frameCtrl.applyLoopMode(this.anmBck.frameCtrl.getFrame() + 1)); + + const prevTranslation = vec3.copy(scratchVec3a, this.anmTranslation); + vec3.scale(this.anmTranslation, rootTransform.translation, deltaTimeFrames); + + // Determine the speed from the movement of the feet + + const frameTranslation = this.anmTranslation;//vec3.sub(scratchVec3b, prevTranslation, this.anmTranslation); + console.log(frameTranslation[0], frameTranslation[1], frameTranslation[2]); + + const sinTheta = Math.sin(cM_s2rad(this.rot[1])); + const cosTheta = Math.cos(cM_s2rad(this.rot[1])); + const worldTransX = frameTranslation[2] * sinTheta + frameTranslation[0] * cosTheta; + const worldTransZ = frameTranslation[2] * cosTheta - frameTranslation[0] * sinTheta; + + this.pos[0] += worldTransX; + this.pos[2] += worldTransZ; + } + } + private getAnmData(anmIdx: number): LkAnimData { // @TODO: Different table if sword is drawn return this.anmDataTable[anmIdx]; } - private setSingleMoveAnime(globals: dGlobals, anmIdx: number, rate: number, start: number, end: number, param_5: number) { + private setSingleMoveAnime(globals: dGlobals, anmIdx: number, rate?: number, start?: number, end?: number, morf: number = 0.0) { const anmData = this.getAnmData(anmIdx); const bck = globals.resCtrl.getObjectRes(ResType.Bck, "LkAnm", anmData.upperBckIdx); - this.anmBck.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); + + if(this.anmBck.anm != bck) { + this.anmBck.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); + } } + // Process used while a demo is telling Link to play a direct animation private procTool(globals: dGlobals) { const demoActor = globals.scnPlay.demo.getSystem().getActor(this.demoActorID); if (!demoActor) From ceabbcee1912b08bb6a5fa85e7750e5ef5430bac Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 14:12:13 -0700 Subject: [PATCH 18/43] Link's root position moves when an anim moves his feet. He can walk! --- src/ZeldaWindWaker/d_a.ts | 95 ++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 38c55b122..758b7ab02 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -42,9 +42,13 @@ import { calcANK1JointAnimationTransform } from "../Common/JSYSTEM/J3D/J3DGraphA // Framework'd actors const scratchMat4a = mat4.create(); +const scratchMat4b = mat4.create(); +const scratchMat4c = mat4.create(); const scratchVec3a = vec3.create(); const scratchVec3b = vec3.create(); const scratchVec3c = vec3.create(); +const scratchVec3d = vec3.create(); +const scratchVec3e = vec3.create(); class d_a_grass extends fopAc_ac_c { public static PROCESS_NAME = dProcName_e.d_a_grass; @@ -5282,15 +5286,20 @@ interface LkAnimData { rightHandIdx: number; texAnmIdx: number; } + +interface LkFootData { + toePos: vec3, + heelPos: vec3, +} class d_a_py_lk extends fopAc_ac_c { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; private static LINK_BDL_CL = 0x18; - private static LINK_BDL_HANDS = 0x1D; private static LINK_BTI_LINKTEXBCI4 = 0x71; private static LINK_CLOTHES_TEX_IDX = 0x22; private static LINK_BDL_KATSURA = 0x20; - private static MaxNormalSpeed = 17.0; // Extracted from daPy_HIO_move_c1 + private static TOE_POS = vec3.fromValues(6.0, 3.25, 0.0); + private static HEEL_POS = vec3.fromValues(-6.0, 3.25, 0.0); private demoProcInitFuncTable = new Map boolean>(); private proc: (globals: dGlobals) => void; @@ -5298,7 +5307,7 @@ class d_a_py_lk extends fopAc_ac_c { private model: J3DModelInstance; private modelHands: J3DModelInstance private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. - + private demoMode: number = LinkDemoMode.None; private gdnChk = new dBgS_GndChk() @@ -5311,6 +5320,11 @@ class d_a_py_lk extends fopAc_ac_c { private anmBck = new mDoExt_bckAnm(); private anmBtp = new mDoExt_btpAnm(); private anmBckId: number; + + private vel = vec3.create(); // TODO: This should be part of fopAc_ac_c + + private frontFoot: number = 2; + private footData: LkFootData[] = nArray(2, i => ({ toePos: vec3.create(), heelPos: vec3.create() })); private anmTranslation = vec3.create(); protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { @@ -5339,12 +5353,11 @@ class d_a_py_lk extends fopAc_ac_c { if (this.proc) this.proc(globals); - this.posMove(deltaTimeFrames); - // Clamp Link's position to the ground. // @TODO: Acch this.gdnChk.Reset(); vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); - this.pos[1] = globals.scnPlay.bgS.GroundCross(this.gdnChk); + this.pos[1] = globals.scnPlay.bgS.GroundCross(this.gdnChk); + if (this.pos[1] === -Infinity) this.pos[1] = 0; this.anmBck.play(deltaTimeFrames); this.anmBtp.play(deltaTimeFrames); @@ -5354,6 +5367,8 @@ class d_a_py_lk extends fopAc_ac_c { mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); this.modelKatsura.calcAnim(); + this.posMove(); + // setWorldMatrix() MtxTrans(this.pos, false, this.model.modelMatrix); mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); @@ -5455,7 +5470,7 @@ class d_a_py_lk extends fopAc_ac_c { if (enable & EDemoActorFlags.HasShape) { this.isWearingCasualClothes = (demoActor.shapeId == 1); - if (this.isWearingCasualClothes) + if (this.isWearingCasualClothes) this.texMappingClothes.copy(this.texMappingCasualClothes); else this.texMappingClothes.copy(this.texMappingHeroClothes); @@ -5556,27 +5571,73 @@ class d_a_py_lk extends fopAc_ac_c { return false; } - private posMove(deltaTimeFrames: number) { - if( this.anmBck ) { + private posMove() { + if (this.anmBck) { // Apply the root motion from the current animation (swaying) - const rootTransform = new JointTransformInfo(); + const rootTransform = new JointTransformInfo(); calcANK1JointAnimationTransform(rootTransform, this.anmBck.anm.jointAnimationEntries[0], this.anmBck.frameCtrl.getFrame(), this.anmBck.frameCtrl.applyLoopMode(this.anmBck.frameCtrl.getFrame() + 1)); const prevTranslation = vec3.copy(scratchVec3a, this.anmTranslation); - vec3.scale(this.anmTranslation, rootTransform.translation, deltaTimeFrames); - - // Determine the speed from the movement of the feet + vec3.scale(this.anmTranslation, rootTransform.translation, 1.0); - const frameTranslation = this.anmTranslation;//vec3.sub(scratchVec3b, prevTranslation, this.anmTranslation); - console.log(frameTranslation[0], frameTranslation[1], frameTranslation[2]); + const frameTranslation = vec3.sub(scratchVec3b, prevTranslation, this.anmTranslation); const sinTheta = Math.sin(cM_s2rad(this.rot[1])); const cosTheta = Math.cos(cM_s2rad(this.rot[1])); const worldTransX = frameTranslation[2] * sinTheta + frameTranslation[0] * cosTheta; - const worldTransZ = frameTranslation[2] * cosTheta - frameTranslation[0] * sinTheta; + const worldTransZ = frameTranslation[2] * cosTheta - frameTranslation[0] * sinTheta; this.pos[0] += worldTransX; this.pos[2] += worldTransZ; + + // Apply motion based on the movement of the feet + this.posMoveFromFootPos(); + } + } + + private posMoveFromFootPos() { + const footLJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Lfoot_jnt') + const footRJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Rfoot_jnt') + const waistJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'waist_jnt') + + // Compute local -> model transforms for foot and waist joints + const invModelMtx = mat4.invert(calc_mtx, this.model.modelMatrix); + const footLMtx = mat4.mul(scratchMat4a, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[footLJointIdx]); + const footRMtx = mat4.mul(scratchMat4b, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[footRJointIdx]); + const waistMtx = mat4.mul(scratchMat4c, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[waistJointIdx]); + + // Compute model space positions of the feet + const toePos = []; + const heelPos = []; + toePos[0] = vec3.transformMat4(scratchVec3a, d_a_py_lk.TOE_POS, footRMtx); + toePos[1] = vec3.transformMat4(scratchVec3b, d_a_py_lk.TOE_POS, footLMtx); + heelPos[0] = vec3.transformMat4(scratchVec3c, d_a_py_lk.HEEL_POS, footRMtx); + heelPos[1] = vec3.transformMat4(scratchVec3d, d_a_py_lk.HEEL_POS, footLMtx); + + // Compare the model space positions of the feet to determine which is in front + const footZPos = [] + for (let i = 0; i < 2; i++) { + const footCenter = vec3.scale(scratchVec3e, vec3.add(scratchVec3e, toePos[i], heelPos[i]), 0.5); + footZPos[i] = footCenter[2]; + } + if (footZPos[1] > footZPos[0]) { this.frontFoot = 1 } + else { this.frontFoot = 0; } + + // Compute the horizontal distance moved by the front foot since last frame + const moveVec = vec3.sub(scratchVec3e, toePos[this.frontFoot], this.footData[this.frontFoot].toePos); + moveVec[1] = 0; + const moveVel = vec3.length(moveVec); + + // @TODO: Adjust speed when on slopes + + // Update actor vel and position + this.vel[0] = moveVel * Math.sin(cM_s2rad(this.rot[1])); + this.vel[1] = moveVel * Math.cos(cM_s2rad(this.rot[1])); + vec3.add(this.pos, this.pos, this.vel); + + for (let i = 0; i < 2; i++) { + vec3.copy(this.footData[i].toePos, toePos[i]); + vec3.copy(this.footData[i].heelPos, heelPos[i]); } } @@ -5619,7 +5680,7 @@ class d_a_py_lk extends fopAc_ac_c { assert(count == 3) anmBckId = demoActor.stbData.getUint16(2); anmBtpId = demoActor.stbData.getUint16(4); - const btkScrollId = demoActor.stbData.getUint16(6); // TODO: Scrolling texture resource + const btkScrollId = demoActor.stbData.getUint16(6); // TODO: Scrolling texture resource. Could this control pupil size? break; case 0: From 62ac2ca427bf2bf4a21b17aaca69364ed1a91b5d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 14:44:40 -0700 Subject: [PATCH 19/43] Adjust movement speed based on ground angle --- src/ZeldaWindWaker/d_a.ts | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 758b7ab02..503e7ec74 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5367,7 +5367,7 @@ class d_a_py_lk extends fopAc_ac_c { mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); this.modelKatsura.calcAnim(); - this.posMove(); + this.posMove(globals); // setWorldMatrix() MtxTrans(this.pos, false, this.model.modelMatrix); @@ -5571,7 +5571,7 @@ class d_a_py_lk extends fopAc_ac_c { return false; } - private posMove() { + private posMove(globals: dGlobals) { if (this.anmBck) { // Apply the root motion from the current animation (swaying) const rootTransform = new JointTransformInfo(); @@ -5591,11 +5591,11 @@ class d_a_py_lk extends fopAc_ac_c { this.pos[2] += worldTransZ; // Apply motion based on the movement of the feet - this.posMoveFromFootPos(); + this.posMoveFromFootPos(globals); } } - private posMoveFromFootPos() { + private posMoveFromFootPos(globals: dGlobals) { const footLJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Lfoot_jnt') const footRJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Rfoot_jnt') const waistJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'waist_jnt') @@ -5626,9 +5626,19 @@ class d_a_py_lk extends fopAc_ac_c { // Compute the horizontal distance moved by the front foot since last frame const moveVec = vec3.sub(scratchVec3e, toePos[this.frontFoot], this.footData[this.frontFoot].toePos); moveVec[1] = 0; - const moveVel = vec3.length(moveVec); + let moveVel = vec3.length(moveVec); - // @TODO: Adjust speed when on slopes + // Adjust speed when on slopes + let groundAngle = 0; + if( this.gdnChk.polyInfo.bgIdx >= 0 && this.gdnChk.polyInfo.triIdx >= 0) { // @TODO: Should be in cBgS::ChkPolySafe() + groundAngle = this.getGroundAngle(globals, this.rot[1]); + } + moveVel *= Math.cos(cM_s2rad(groundAngle)); + + // ... Reduce velocity even more for ascending slopes + if (groundAngle < 0) { + moveVel = moveVel * 0.85; + } // Update actor vel and position this.vel[0] = moveVel * Math.sin(cM_s2rad(this.rot[1])); @@ -5641,6 +5651,22 @@ class d_a_py_lk extends fopAc_ac_c { } } + /** + * Get the angle of the ground based when facing a specific direction + * @param dir the s16 angle which the actor is facing + */ + private getGroundAngle(globals: dGlobals, dir: number) { + const gndPlane = globals.scnPlay.bgS.GetTriPla(this.gdnChk.polyInfo.bgIdx, this.gdnChk.polyInfo.triIdx); + const norm = gndPlane.n; + + if (gndPlane && norm[1] >= 0.5) { + const slopeDir = cM_atan2s(norm[0], norm[2]); + const slopeGrade = Math.sqrt(norm[0] * norm[0] + norm[2] * norm[2]); + return cM_atan2s(slopeGrade * Math.cos(cM_s2rad(slopeDir - dir)), norm[1]); + } + return 0; + } + private getAnmData(anmIdx: number): LkAnimData { // @TODO: Different table if sword is drawn return this.anmDataTable[anmIdx]; From ad31e568f93fb6cc12a9a7c7166a7b4b5257afdc Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 15:34:28 -0700 Subject: [PATCH 20/43] Reorder logic in execute() to match the game. Fix initial state of posMoveFromFootPos() --- src/ZeldaWindWaker/d_a.ts | 49 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 503e7ec74..053a552f0 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5321,6 +5321,7 @@ class d_a_py_lk extends fopAc_ac_c { private anmBtp = new mDoExt_btpAnm(); private anmBckId: number; + private rawPos = vec3.create(); // The position before it is manipulated by anim root/foot motion private vel = vec3.create(); // TODO: This should be part of fopAc_ac_c private frontFoot: number = 2; @@ -5346,32 +5347,44 @@ class d_a_py_lk extends fopAc_ac_c { } override execute(globals: dGlobals, deltaTimeFrames: number): void { + // Collect ground info. TODO: Acch + this.gdnChk.Reset(); + vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); + const groundHeight = globals.scnPlay.bgS.GroundCross(this.gdnChk); + + // Update the current proc based on demo data this.setDemoData(globals); if (this.demoMode != 5) { this.changeDemoProc(globals); } - if (this.proc) this.proc(globals); - - // Clamp Link's position to the ground. // @TODO: Acch - this.gdnChk.Reset(); - vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); - this.pos[1] = globals.scnPlay.bgS.GroundCross(this.gdnChk); - if (this.pos[1] === -Infinity) this.pos[1] = 0; - + // Step our animations forward this.anmBck.play(deltaTimeFrames); this.anmBtp.play(deltaTimeFrames); - this.model.calcAnim(); - - mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); - this.modelKatsura.calcAnim(); + // Run the current custom update process (Walk, Idle, Swim, etc) + if (this.proc) this.proc(globals); + // Apply root motion from the animation, and adjust position based on foot movement + const rawPos = vec3.copy(this.rawPos, this.pos); this.posMove(globals); + // If we're pulling position directly from the demo, clamp to ground and ignore animation root motion + if (this.proc == this.procTool) { + vec3.copy(this.pos, rawPos); + if (groundHeight != -Infinity) { + this.pos[1] = groundHeight; + } + } + // setWorldMatrix() MtxTrans(this.pos, false, this.model.modelMatrix); mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); + + // Update joints based on the currently playing animation + this.model.calcAnim(); + mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); + this.modelKatsura.calcAnim(); } override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { @@ -5409,6 +5422,8 @@ class d_a_py_lk extends fopAc_ac_c { this.texMappingClothes = this.model.materialInstanceState.textureMappings[d_a_py_lk.LINK_CLOTHES_TEX_IDX]; this.texMappingHeroClothes.copy(this.texMappingClothes); + MtxTrans(this.pos, false, this.model.modelMatrix); + mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); this.cullMtx = this.model.modelMatrix; } @@ -5596,6 +5611,16 @@ class d_a_py_lk extends fopAc_ac_c { } private posMoveFromFootPos(globals: dGlobals) { + if (this.frontFoot == 2) { + vec3.zero(this.vel); + vec3.set(this.footData[0].toePos, -14.05, 0.0, 5.02); + vec3.set(this.footData[0].heelPos, -10.85, 0.0, -6.52); + vec3.set(this.footData[1].toePos, 14.05, 0.0, 5.02); + vec3.set(this.footData[1].heelPos, 10.85, 0.0, -6.52); + this.frontFoot = 0; + return; + } + const footLJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Lfoot_jnt') const footRJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Rfoot_jnt') const waistJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'waist_jnt') From 7be84c2c7f8b556029027dcaf0ac81abc0d64049 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 16:56:38 -0700 Subject: [PATCH 21/43] Fix up ground clamping behavior Link now snaps to the ground when walking/dashing. When in the tool demo mode (position data coming straight from the STB file), ground snapping is handled as expected based on the animation mode. --- src/ZeldaWindWaker/d_a.ts | 50 ++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 053a552f0..c5ae8d65f 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5309,6 +5309,7 @@ class d_a_py_lk extends fopAc_ac_c { private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. private demoMode: number = LinkDemoMode.None; + private demoClampToGround = true; private gdnChk = new dBgS_GndChk() private isWearingCasualClothes = false; @@ -5347,11 +5348,6 @@ class d_a_py_lk extends fopAc_ac_c { } override execute(globals: dGlobals, deltaTimeFrames: number): void { - // Collect ground info. TODO: Acch - this.gdnChk.Reset(); - vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 125.0); - const groundHeight = globals.scnPlay.bgS.GroundCross(this.gdnChk); - // Update the current proc based on demo data this.setDemoData(globals); if (this.demoMode != 5) { @@ -5369,10 +5365,16 @@ class d_a_py_lk extends fopAc_ac_c { const rawPos = vec3.copy(this.rawPos, this.pos); this.posMove(globals); - // If we're pulling position directly from the demo, clamp to ground and ignore animation root motion + // Evaluate for collisions, clamp to ground + this.gdnChk.Reset(); + vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 30.1); + const groundHeight = globals.scnPlay.bgS.GroundCross(this.gdnChk); + this.autoGroundHit(); + + // If we're pulling position directly from the JStudio tool, ignore collisions and animation root motion if (this.proc == this.procTool) { vec3.copy(this.pos, rawPos); - if (groundHeight != -Infinity) { + if (this.demoClampToGround && groundHeight != -Infinity) { this.pos[1] = groundHeight; } } @@ -5403,6 +5405,8 @@ class d_a_py_lk extends fopAc_ac_c { this.anmBck.entry(this.model); if (this.anmBtp.anm) this.anmBtp.entry(this.model); + console.log(mat4.getTranslation(scratchVec3a, this.model.modelMatrix)[1]); + setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } @@ -5586,6 +5590,28 @@ class d_a_py_lk extends fopAc_ac_c { return false; } + private autoGroundHit() { + const groundHeight = this.gdnChk.retY; + if(groundHeight == -Infinity) { + return; + } + + const groundDiff = this.pos[1] - groundHeight; + + // Our feet are near the ground, clamp to ground + if(groundDiff > 0.0) { + if(groundDiff <= 30.1) { + this.pos[1] = groundHeight; + this.vel[1] = 0.0; + return; + } + } + + // TODO: Our feet are below the ground, use last frame's height + this.pos[1] = groundHeight; + this.vel[1] = 0.0; + } + private posMove(globals: dGlobals) { if (this.anmBck) { // Apply the root motion from the current animation (swaying) @@ -5713,6 +5739,8 @@ class d_a_py_lk extends fopAc_ac_c { if (!demoActor) return; + this.demoClampToGround = false; + let anmFrame = 0.0; let anmBckId = 0xFFFF; let anmBtpId = 0xFFFF; @@ -5724,8 +5752,10 @@ class d_a_py_lk extends fopAc_ac_c { if (demoActor.flags & EDemoActorFlags.HasData) { const status = demoActor.stbData.getUint8(0); switch (demoActor.stbDataId) { - case 1: case 3: + this.demoClampToGround = true; + // Fall through + case 1: case 5: const count = demoActor.stbData.getUint8(1); assert(count == 3) @@ -5734,8 +5764,10 @@ class d_a_py_lk extends fopAc_ac_c { const btkScrollId = demoActor.stbData.getUint16(6); // TODO: Scrolling texture resource. Could this control pupil size? break; - case 0: case 2: + this.demoClampToGround = true; + // Fall through + case 0: case 4: anmBckId = demoActor.stbData.getUint16(1); break; From 9367b0d7babcfc93bf46b2da7a47ce9e775770e0 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 16:57:33 -0700 Subject: [PATCH 22/43] Add "Stolen Sister" to list of available demo scenes --- src/ZeldaWindWaker/Main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 76c773b58..3f8680a44 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1058,7 +1058,6 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { // It has been reconstructed by cross-referencing each Room's lbnk section (which points to a Demo*.arc file for each layer), // the .stb files contained in each of those Objects/Demo*.arc files, and the FileName attribute from the event action. const demoDescs = [ - new DemoDesc("sea", "Stolen Sister", [44], "stolensister.stb", 9, [0.0, 0.0, 20000.0], 0, 0, 0), new DemoDesc("sea", "Departure", [44], "departure.stb", 10, [-200000.0, 0.0, 320000.0], 0.0, 204, 0), new DemoDesc("sea", "Pirate Zelda Fly", [44], "kaizoku_zelda_fly.stb", 0, [-200000.0, 0.0, 320000.0], 180.0, 0, 0), new DemoDesc("sea", "Zola Awakens", [13], "awake_zola.stb", 8, [200000.0, 0.0, -200000.0], 0, 227, 0), @@ -1138,6 +1137,7 @@ const sceneDescs = [ "Cutscenes", new DemoDesc("sea_T", "Title Screen", [44], "title.stb", 0, [-220000.0, 0.0, 320000.0], 180.0, 0, 0), new DemoDesc("sea", "Awaken", [44], "awake.stb", 0, [-220000.0, 0.0, 320000.0], 0.0, 0, 0), + new DemoDesc("sea", "Stolen Sister", [44], "stolensister.stb", 9, [0.0, 0.0, 20000.0], 0, 0, 0), "Outset Island", new SceneDesc("sea_T", "Title Screen", [44]), From cac942c9b5a3a2a6863245b78be00e66ad948d05 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 17:10:54 -0700 Subject: [PATCH 23/43] Cleanup --- src/ZeldaWindWaker/d_a.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index c5ae8d65f..36b4682ea 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5305,7 +5305,6 @@ class d_a_py_lk extends fopAc_ac_c { private proc: (globals: dGlobals) => void; private model: J3DModelInstance; - private modelHands: J3DModelInstance private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. private demoMode: number = LinkDemoMode.None; @@ -5405,8 +5404,6 @@ class d_a_py_lk extends fopAc_ac_c { this.anmBck.entry(this.model); if (this.anmBtp.anm) this.anmBtp.entry(this.model); - console.log(mat4.getTranslation(scratchVec3a, this.model.modelMatrix)[1]); - setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); } @@ -5415,7 +5412,6 @@ class d_a_py_lk extends fopAc_ac_c { // createHeap() this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); this.modelKatsura = this.initModel(globals, d_a_py_lk.LINK_BDL_KATSURA); - // this.modelHands = this.initModel(globals, d_a_py_lk.LINK_BDL_HANDS); // Fetch the casual clothes and the hero texture. They'll be be selected by the ShapeID set by a demo. const casualTexData = globals.resCtrl.getObjectRes(ResType.Bti, d_a_py_lk.ARC_NAME, d_a_py_lk.LINK_BTI_LINKTEXBCI4); @@ -5519,30 +5515,6 @@ class d_a_py_lk extends fopAc_ac_c { const newRot = cM_atan2s(moveVec[0], moveVec[2]); this.rot[1] = newRot; this.setSingleMoveAnime(globals, (this.demoMode == LinkDemoMode.Walk) ? LkAnim.WALK : LkAnim.DASH) - - // Transition to target position/rotation (2 is slower) - // const dist = vec3.sub(scratchVec3a, targetPos, this.pos); - // local_6c = local_90.x; - // local_68 = local_90.y; - // local_64 = local_90.z; - // if (abs(this->mVelocity) / mMaxNormalSpeed < 0.5) { - // demo_mode = 2; - // } - // local_a8.x = local_90.x; - // local_a8.y = 0.0; - // local_a8.z = local_90.z; - // fVar6 = VECSquareMag(&local_a8); - // if ((fVar6 < 100.0) || (fVar6 < 2500.0 && (abs(this->mVelocity) < 0.001))) { - // demo_mode = 1; - // this->mVelocity = 0.0; - // } - // else if (((demo_mode == 2) && (fVar6 < 400.0)) || (fVar6 < 2500.0)) { - // mDemo.setStick(0.0); - // } - // iVar8 = cM_atan2s(local_6c,local_64); - // mDemo.setMoveAngle(iVar8); - // Snap to target position - // Maybe equip an item break; } @@ -5570,7 +5542,6 @@ class d_a_py_lk extends fopAc_ac_c { break; case LinkDemoMode.SetPosRotEquip: - // TODO: setBlendMoveAnime this.procWait_init(globals); break; From 979f33bcabae36013de643619ed031b39ac954c7 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 17:18:42 -0700 Subject: [PATCH 24/43] Remove unused enum values so they don't add a billion lines --- src/ZeldaWindWaker/d_a.ts | 313 +------------------------------------- 1 file changed, 5 insertions(+), 308 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 36b4682ea..918d9ab8a 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4965,237 +4965,9 @@ enum LkAnim { WAITS = 0x00, WALK = 0x01, DASH = 0x02, - DASHKAZE = 0x03, - WALKHBOOTS = 0x04, - WALKHBOOTSKAZE = 0x05, - WALKSLOPE = 0x06, - ATNLS = 0x07, - ATNRS = 0x08, - ATNWLS = 0x09, - ATNWRS = 0x0A, - ATNDLS = 0x0B, - ATNDRS = 0x0C, - JMPEDS = 0x0D, - ATNWB = 0x0E, - ATNDB = 0x0F, - ATNJL = 0x10, - ATNJR = 0x11, - ATNJLLAND = 0x12, - ATNJRLAND = 0x13, - ROT = 0x14, - CUTREL = 0x15, - DIFENCE = 0x16, - DIFENCEA = 0x17, - CUTTURNP = 0x18, - CUTTURNPWFB = 0x19, - CUTTURNPWLR = 0x1A, - TALKA = 0x1B, WAITB = 0x1C, WAITATOB = 0x1D, - CUTA = 0x1E, - CUTF = 0x1F, - CUTR = 0x20, - CUTL = 0x21, - CUTEA = 0x22, - CUTEB = 0x23, - EXCA1 = 0x24, - EXCB1 = 0x25, - CUTBOKO = 0x26, - CUTRER = 0x27, - CUTTURN = 0x28, - CUTTURNC = 0x29, - CUTTURNB = 0x2A, - JATTACK = 0x2B, - JATTCKP = 0x2C, - JATTCKCUT = 0x2D, - JATTCKCUTHAM = 0x2E, - JATTACKLAND = 0x2F, - LANDDAMA = 0x30, - LANDDAMAST = 0x31, - ROLLF = 0x32, - ROLLFMIS = 0x33, - SLIP = 0x34, - SLIDEF = 0x35, - SLIDEFLAND = 0x36, - SLIDEB = 0x37, - SLIDEBLAND = 0x38, - ATNGAL = 0x39, - ATNGAR = 0x3A, - ATNGAHAM = 0x3B, - JMPST = 0x3C, - ROLLB = 0x3D, - ROLLBLAND = 0x3E, - CROUCH = 0x3F, - LIE = 0x40, - LIEFORWARD = 0x41, - WALL = 0x42, - WALLDW = 0x43, - WALLWL = 0x44, - WALLWR = 0x45, - WALLWLDW = 0x46, - WALLWRDW = 0x47, - WALLPL = 0x48, - WALLPR = 0x49, - WALLPLDW = 0x4A, - WALLPRDW = 0x4B, - VJMP = 0x4C, - VJMPCHA = 0x4D, - VJMPCHB = 0x4E, - VJMPCL = 0x4F, - HANGING = 0x50, - HANGUP = 0x51, - HANGMOVEL = 0x52, - HANGMOVER = 0x53, - DAM = 0x54, - DAML = 0x55, - DAMR = 0x56, - DAMF = 0x57, - DAMB = 0x58, - DAMFL = 0x59, - DAMFR = 0x5A, - DAMFF = 0x5B, - DAMFB = 0x5C, - DAMFLUP = 0x5D, - DAMFRUP = 0x5E, - DAMFFUP = 0x5F, - DAMFBUP = 0x60, - LAVADAM = 0x61, - DIELONG = 0x62, - SHIPDIE = 0x63, - SWIMDIE = 0x64, - GRABP = 0x65, - GRABUP = 0x66, - GRABNG = 0x67, - GRABWAIT = 0x68, - GRABWAITB = 0x69, - GRABTHROW = 0x6A, - GRABRE = 0x6B, - MJMP = 0x6C, - MJMPC = 0x6D, - MROLLL = 0x6E, - MROLLR = 0x6F, - MROLLLC = 0x70, - MROLLRC = 0x71, - MSTEPOVER = 0x72, - MSTEPOVERA = 0x73, - MSTEPOVERLAND = 0x74, - ROPECATCH = 0x75, - ROPESWINGF = 0x76, - ROPESWINGB = 0x77, - ROPEWAIT = 0x78, - ROPECLIMB = 0x79, - ROPEDOWN = 0x7A, - ROPETHROWCATCH = 0x7B, - BOOMCATCH = 0x7C, - VOYAGE1 = 0x7D, - WAITPUSHPULL = 0x7E, - WALKPUSH = 0x7F, - WALKPULL = 0x80, - SWIMP = 0x81, - SWIMWAIT = 0x82, - SWIMING = 0x83, - LADDERUPST = 0x84, - LADDERUPEDR = 0x85, - LADDERUPEDL = 0x86, - LADDERDWST = 0x87, - LADDERDWEDR = 0x88, - LADDERDWEDL = 0x89, - LADDERRTOL = 0x8A, - LADDERLTOR = 0x8B, - FCLIMBSLIDELUP = 0x8C, - FCLIMBSLIDERUP = 0x8D, - BOXOPENLINK = 0x8E, - BOXOPENSHORTLINK = 0x8F, - ITEMGET = 0x90, - HOLDUP = 0x91, - WALLHOLDUP = 0x92, - WALLHOLDUPDW = 0x93, - COMEOUT = 0x94, - WALKBARREL = 0x95, - SALTATION = 0x96, - WHO = 0x97, - PICKUP = 0x98, - WAITPICKUP = 0x99, - SURPRISED = 0x9A, - TURNBACK = 0x9B, - LOOKUP = 0x9C, WAITQ = 0x9D, - GLAD = 0x9E, - SHIP_JUMP1 = 0x9F, - SHIP_JUMP2 = 0xA0, - USEFANA = 0xA1, - USEFANB = 0xA2, - USEFANB2 = 0xA3, - MOGAKU1 = 0xA4, - FM_BATA = 0xA5, - HOOKSHOTJMP = 0xA6, - VOMITJMP = 0xA7, - WAITTAKT = 0xA8, - SLIPICE = 0xA9, - HAMSWINGA = 0xAA, - HAMSWINGBPRE = 0xAB, - HAMSWINGBHIT = 0xAC, - HAMSWINGBEND = 0xAD, - SETBOOTS = 0xAE, - DOOROPENALINK = 0xAF, - DOOROPENBLINK = 0xB0, - SEYYES = 0xB1, - PRESENTATIONA = 0xB2, - WINDL = 0xB3, - WINDR = 0xB4, - PRESENTATIONB = 0xB5, - BINDRINKPRE = 0xB6, - BINDRINKING = 0xB7, - BINDRINKAFTER = 0xB8, - BINOPENPRE = 0xB9, - BINOPENA = 0xBA, - BINOPENB = 0xBB, - BINSWINGS = 0xBC, - BINSWINGU = 0xBD, - BINGET = 0xBE, - SURPRISEDB = 0xBF, - RISE = 0xC0, - USETCEIVER = 0xC1, - YOBU = 0xC2, - NENRIKI = 0xC3, - ESAMAKI = 0xC4, - SETHYOINOMI = 0xC5, - GETLETTER = 0xC6, - WAITLETTER = 0xC7, - LINK_FREEZ = 0xC8, - LINK_MOGAKI = 0xC9, - TAKTDGE = 0xCA, - DAMBIRI = 0xCB, - SALVLR = 0xCC, - SALVRWAIT = 0xCD, - SALVLWAIT = 0xCE, - SALVRBAD = 0xCF, - SALVLBAD = 0xD0, - SALVRGOOD = 0xD1, - SALVLGOOD = 0xD2, - MSTEPOVER_JMPED = 0xD3, - BOXOPENSLINK = 0xD4, - SEARESET = 0xD5, - WARPIN = 0xD6, - WARPOUT = 0xD7, - SURPRISEDWAIT = 0xD8, - PRESENTATIONAWAIT = 0xD9, - POWUPWAIT = 0xDA, - POWUP = 0xDB, - KOSHIKAKE = 0xDC, - COMBO_LINK = 0xDD, - CUTKESA = 0xDE, - WARPOUTFIRST = 0xDF, - WAITAUCTION = 0xE0, - FREEA = 0xE1, - FREEB = 0xE2, - FREED = 0xE3, - TAKTKAZE = 0xE4, - TAKTSIPPU = 0xE5, - TAKTCHUYA = 0xE6, - TAKTFUJIN = 0xE7, - TAKTAYATSURI = 0xE8, - TAKTCHISIN = 0xE9, }; enum LinkDemoMode { @@ -5205,78 +4977,11 @@ enum LinkDemoMode { Dash = 0x03, SetPosRotEquip = 0x04, WaitTurn = 0x05, - PartnerInteractA = 0x06, - Damage = 0x07, - PartnerInteractB = 0x08, - LargeDamage = 0x09, - OpenTreasure = 0x0A, - GetItem = 0x0B, - Unequip = 0x0C, - Holdup = 0x0D, - NULL = 0x0E, - LookAround = 0x0F, - BackJump = 0x10, - Fall = 0x11, - Unk12 = 0x12, - Salute = 0x13, - LookAround2 = 0x14, - TalismanPickup = 0x15, - TalismanWait = 0x16, - Unk17 = 0x17, - Surprised = 0x18, - TurnBack = 0x19, - LookUp = 0x1A, - LargeDamageUp = 0x1B, - QuakeWait = 0x1C, - Dance = 0x1D, - Caught = 0x1E, - Throw = 0x1F, - PushPullWait = 0x20, - PushMove = 0x21, - TactWait = 0x22, - DoorOpen = 0x23, - Nod = 0x24, - Present = 0x25, - WindChange = 0x26, - ShipPaddle = 0x27, - StandItemPut = 0x28, - Unk29 = 0x29, - Unk2A = 0x2A, SetRot = 0x2B, SetPosRot = 0x2C, - TactPlayOriginal = 0x2D, - PowerUp = 0x2E, - VorcanoFail = 0x2F, - BossWarp = 0x30, - SlightSurprised = 0x31, - Smile = 0x32, - PartnerCarry = 0x33, - AgbUse = 0x34, - LookTurn = 0x35, - LetterOpen = 0x36, - LetterRead = 0x37, - GrabPut = 0x38, - RedeadStop = 0x39, - RedeadCatch = 0x3A, - GetDance = 0x3B, - BottleOpenFairy = 0x3C, - BottleOpen = 0x3D, - WarpShort = 0x3E, - OpenSalvageTreasure = 0x3F, - SlowFall = 0x40, - FoodSet = 0x41, - SurprisedWait = 0x42, - PowerUpWait = 0x43, - ShipBow = 0x44, - ShipSit = 0x45, - LastCombo = 0x46, - ShipGetOff = 0x47, - HandUp = 0x48, - FoodThrow = 0x49, - IceSlip = 0x4A, MAX = 0x4B, - - NewAnm0 = 0x200, + + Tool = 0x200, }; interface LkAnimData { @@ -5502,10 +5207,8 @@ class d_a_py_lk extends fopAc_ac_c { case LinkDemoMode.SetRot: { debugger; const moveVec = vec3.sub(scratchVec3a, targetPos, this.pos); - const oldRot = this.rot[1]; const newRot = cM_atan2s(moveVec[0], moveVec[2]); this.rot[1] = newRot; - // this->mRotUnk = this->mRotUnk - (sNewRot - sOldRot); break; } @@ -5517,19 +5220,13 @@ class d_a_py_lk extends fopAc_ac_c { this.setSingleMoveAnime(globals, (this.demoMode == LinkDemoMode.Walk) ? LkAnim.WALK : LkAnim.DASH) break; } - - case LinkDemoMode.WaitTurn: - case LinkDemoMode.Surprised: - // Rotate only - debugger; - break; } return true; } private changeDemoProc(globals: dGlobals): boolean { - assert(this.demoMode < LinkDemoMode.MAX || this.demoMode == LinkDemoMode.NewAnm0) + assert(this.demoMode < LinkDemoMode.MAX || this.demoMode == LinkDemoMode.Tool) const pred = true; @@ -5537,7 +5234,7 @@ class d_a_py_lk extends fopAc_ac_c { switch (this.demoMode) { case LinkDemoMode.None: return false; - case LinkDemoMode.NewAnm0: + case LinkDemoMode.Tool: this.proc = this.procTool; break; @@ -5769,7 +5466,7 @@ class d_a_py_lk extends fopAc_ac_c { } private procWait_init(globals: dGlobals) { - this.setSingleMoveAnime(globals, LkAnim.WAITATOB, 0.6, 0.0, 0xc, 6.0); + this.setSingleMoveAnime(globals, LkAnim.WAITS); } private loadAnmTable(globals: dGlobals) { From bb1f71ee8836e9de49f2c89f0129c1160375edc1 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 17:24:42 -0700 Subject: [PATCH 25/43] Use proper parameters for default Link wait animation --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 918d9ab8a..e11b2c7dd 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5046,7 +5046,7 @@ class d_a_py_lk extends fopAc_ac_c { this.setupDam('mayuR'); // noclip modification: - this.setSingleMoveAnime(globals, LkAnim.WAITS, 0.6, 0.0, 0xc, 6.0); + this.setSingleMoveAnime(globals, LkAnim.WAITS); return cPhs__Status.Next; } From 1eee2e167bdb1f2ff4336d80e903336c48013db9 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 17:32:30 -0700 Subject: [PATCH 26/43] Fix up Title Screen to use Link's casual clothes --- src/ZeldaWindWaker/d_a.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index e11b2c7dd..b930d6ccc 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5127,6 +5127,10 @@ class d_a_py_lk extends fopAc_ac_c { this.texMappingClothes = this.model.materialInstanceState.textureMappings[d_a_py_lk.LINK_CLOTHES_TEX_IDX]; this.texMappingHeroClothes.copy(this.texMappingClothes); + // Set the default state based on EventBit 0x2A80, except we can't, so just hardcode to use casual clothes on the title screen + this.isWearingCasualClothes = (globals.stageName == 'sea_T' ); // dComIfGs_isEventBit(0x2A80) + if(this.isWearingCasualClothes) { this.texMappingClothes.copy(this.texMappingCasualClothes); } + MtxTrans(this.pos, false, this.model.modelMatrix); mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); this.cullMtx = this.model.modelMatrix; From 6196aebc7aa536ef6580caa34b90d33e0ce6ff01 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Thu, 28 Nov 2024 23:36:54 -0700 Subject: [PATCH 27/43] Fix first rendered frame of a new tool animation using the old anim. The model Ank1 wasn't being set until animBck.entry() was called in Draw. It needs to happen before model.calc() is called. --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index b930d6ccc..a1d577338 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5088,6 +5088,7 @@ class d_a_py_lk extends fopAc_ac_c { mDoMtx_ZXYrotM(this.model.modelMatrix, this.rot); // Update joints based on the currently playing animation + this.anmBck.entry(this.model); this.model.calcAnim(); mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); this.modelKatsura.calcAnim(); @@ -5106,7 +5107,6 @@ class d_a_py_lk extends fopAc_ac_c { mDoExt_modelEntryDL(globals, this.modelKatsura, renderInstManager, viewerInput); } - this.anmBck.entry(this.model); if (this.anmBtp.anm) this.anmBtp.entry(this.model); setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); From 50b08456a8adc3699b120317f0b2135695c702db Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 29 Nov 2024 13:49:05 -0700 Subject: [PATCH 28/43] Link handles btk (uv) demo animations. His pupils now contract when surpised. --- src/ZeldaWindWaker/d_a.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index a1d577338..2fabe339d 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5024,6 +5024,7 @@ class d_a_py_lk extends fopAc_ac_c { private anmDataTable: LkAnimData[] = []; private anmBck = new mDoExt_bckAnm(); private anmBtp = new mDoExt_btpAnm(); + private anmBtk = new mDoExt_btkAnm(); private anmBckId: number; private rawPos = vec3.create(); // The position before it is manipulated by anim root/foot motion @@ -5061,6 +5062,7 @@ class d_a_py_lk extends fopAc_ac_c { // Step our animations forward this.anmBck.play(deltaTimeFrames); this.anmBtp.play(deltaTimeFrames); + this.anmBtk.play(deltaTimeFrames); // Run the current custom update process (Walk, Idle, Swim, etc) if (this.proc) this.proc(globals); @@ -5108,6 +5110,7 @@ class d_a_py_lk extends fopAc_ac_c { } if (this.anmBtp.anm) this.anmBtp.entry(this.model); + if (this.anmBtk.anm) this.anmBtk.entry(this.model); setLightTevColorType(globals, this.model, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.model, renderInstManager, viewerInput); @@ -5416,6 +5419,7 @@ class d_a_py_lk extends fopAc_ac_c { let anmFrame = 0.0; let anmBckId = 0xFFFF; let anmBtpId = 0xFFFF; + let anmBtkId = 0xFFFF; if (demoActor.flags & EDemoActorFlags.HasPos) { vec3.copy(this.pos, demoActor.translation); } if (demoActor.flags & EDemoActorFlags.HasRot) { this.rot[1] = demoActor.rotation[1]; } @@ -5433,7 +5437,7 @@ class d_a_py_lk extends fopAc_ac_c { assert(count == 3) anmBckId = demoActor.stbData.getUint16(2); anmBtpId = demoActor.stbData.getUint16(4); - const btkScrollId = demoActor.stbData.getUint16(6); // TODO: Scrolling texture resource. Could this control pupil size? + anmBtkId = demoActor.stbData.getUint16(6); break; case 2: @@ -5466,6 +5470,11 @@ class d_a_py_lk extends fopAc_ac_c { const btp = globals.resCtrl.getObjectIDRes(ResType.Btp, 'LkD00', anmBtpId); this.anmBtp.init(this.model.modelData, btp, true, btp.loopMode, 1.0, 0, btp.duration); } + + if (anmBtkId != 0xFFFF) { + const btk = globals.resCtrl.getObjectIDRes(ResType.Btk, 'LkD00', anmBtkId); + this.anmBtk.init(this.model.modelData, btk, true, btk.loopMode, 1.0, 0, btk.duration); + } } } From b3182da32968e57ebe51742ea76bf59a5654173f Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 29 Nov 2024 16:14:21 -0700 Subject: [PATCH 29/43] Link can equip his sword. Equips based on demo events. --- src/ZeldaWindWaker/d_a.ts | 124 +++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 3 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 2fabe339d..df44225c6 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4996,6 +4996,27 @@ interface LkFootData { toePos: vec3, heelPos: vec3, } + +const enum ItemNo { + HerosSword = 0x38, + MasterSwordPowerless = 0x39, + MasterSwordHalfPower = 0x3A, + MasterSwordFullPower = 0x3E, + InvalidItem = 0xFF, +} + +const enum LkEquipItem { + None = 0x100, + Sword = 0x103, +} + +const enum LkHandStyle { + Idle = 0, + HoldSword = 3, + HoldWindWaker = 5, + HoldShield = 8, +} + class d_a_py_lk extends fopAc_ac_c { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; @@ -5003,6 +5024,8 @@ class d_a_py_lk extends fopAc_ac_c { private static LINK_BTI_LINKTEXBCI4 = 0x71; private static LINK_CLOTHES_TEX_IDX = 0x22; private static LINK_BDL_KATSURA = 0x20; + private static LINK_BDL_SWA = 0x25; // Hero's sword blade + private static LINK_BDL_SWGRIPA=0x26 // Hero's sword hilt private static TOE_POS = vec3.fromValues(6.0, 3.25, 0.0); private static HEEL_POS = vec3.fromValues(-6.0, 3.25, 0.0); @@ -5010,6 +5033,7 @@ class d_a_py_lk extends fopAc_ac_c { private proc: (globals: dGlobals) => void; private model: J3DModelInstance; + private modelSwordHilt: J3DModelInstance; private modelKatsura: J3DModelInstance; // Wig. To replace the hat when wearing casual clothes. private demoMode: number = LinkDemoMode.None; @@ -5022,9 +5046,9 @@ class d_a_py_lk extends fopAc_ac_c { private texMappingHeroClothes: TextureMapping = new TextureMapping(); private anmDataTable: LkAnimData[] = []; - private anmBck = new mDoExt_bckAnm(); - private anmBtp = new mDoExt_btpAnm(); - private anmBtk = new mDoExt_btkAnm(); + private anmBck = new mDoExt_bckAnm(); // Joint animation + private anmBtp = new mDoExt_btpAnm(); // Texture flipbook animation (e.g. facial expressions) + private anmBtk = new mDoExt_btkAnm(); // UV animation (e.g. eyes get small when surprised) private anmBckId: number; private rawPos = vec3.create(); // The position before it is manipulated by anim root/foot motion @@ -5034,6 +5058,11 @@ class d_a_py_lk extends fopAc_ac_c { private footData: LkFootData[] = nArray(2, i => ({ toePos: vec3.create(), heelPos: vec3.create() })); private anmTranslation = vec3.create(); + private handStyleLeft: LkHandStyle; + private handStyleRight: LkHandStyle; + private equippedItem: LkEquipItem; + private equippedItemModel: J3DModelInstance | null = null; + protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); @@ -5094,6 +5123,9 @@ class d_a_py_lk extends fopAc_ac_c { this.model.calcAnim(); mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); this.modelKatsura.calcAnim(); + + // Update item transform and animations + this.setItemModel(); } override draw(globals: dGlobals, renderInstManager: GfxRenderInstManager, viewerInput: ViewerRenderInput): void { @@ -5109,6 +5141,22 @@ class d_a_py_lk extends fopAc_ac_c { mDoExt_modelEntryDL(globals, this.modelKatsura, renderInstManager, viewerInput); } + if (this.equippedItem == LkEquipItem.Sword) { + setLightTevColorType(globals, this.equippedItemModel!, this.tevStr, viewerInput.camera); + mDoExt_modelEntryDL(globals, this.equippedItemModel!, renderInstManager, viewerInput); + + setLightTevColorType(globals, this.modelSwordHilt, this.tevStr, viewerInput.camera); + mDoExt_modelEntryDL(globals, this.modelSwordHilt, renderInstManager, viewerInput); + } + + // TODO: + // if (!checkNormalSwordEquip() && dStage_stagInfo_GetSTType(dComIfGp_getStageStagInfo()) != dStageType_FF1_e || + // checkCaughtShapeHide() || checkDemoShieldNoDraw()) { + // mpCLModelData->getJointNodePointer(0x0D)->getMesh()->getShape()->hide(); // cl_podA joint + // } else { + // mpCLModelData->getJointNodePointer(0x0D)->getMesh()->getShape()->show(); // cl_podA joint + // } + if (this.anmBtp.anm) this.anmBtp.entry(this.model); if (this.anmBtk.anm) this.anmBtk.entry(this.model); @@ -5120,6 +5168,7 @@ class d_a_py_lk extends fopAc_ac_c { // createHeap() this.model = this.initModel(globals, d_a_py_lk.LINK_BDL_CL); this.modelKatsura = this.initModel(globals, d_a_py_lk.LINK_BDL_KATSURA); + this.modelSwordHilt = this.initModel(globals, d_a_py_lk.LINK_BDL_SWGRIPA); // Fetch the casual clothes and the hero texture. They'll be be selected by the ShapeID set by a demo. const casualTexData = globals.resCtrl.getObjectRes(ResType.Bti, d_a_py_lk.ARC_NAME, d_a_py_lk.LINK_BTI_LINKTEXBCI4); @@ -5427,6 +5476,9 @@ class d_a_py_lk extends fopAc_ac_c { if (demoActor.flags & EDemoActorFlags.HasData) { const status = demoActor.stbData.getUint8(0); + let handIdxRight; + let handIdxLeft; + switch (demoActor.stbDataId) { case 3: this.demoClampToGround = true; @@ -5438,6 +5490,9 @@ class d_a_py_lk extends fopAc_ac_c { anmBckId = demoActor.stbData.getUint16(2); anmBtpId = demoActor.stbData.getUint16(4); anmBtkId = demoActor.stbData.getUint16(6); + + handIdxRight = demoActor.stbData.getUint8(9); + handIdxLeft = demoActor.stbData.getUint8(10); break; case 2: @@ -5451,6 +5506,42 @@ class d_a_py_lk extends fopAc_ac_c { default: debugger; } + + // Set the hand model and/or equipped item based on the demo data + let item = ItemNo.InvalidItem; + if(handIdxLeft == 0xC8) { item = ItemNo.HerosSword; } + else if(handIdxLeft == 0xC9) { item = ItemNo.MasterSwordPowerless; } + else if(handIdxLeft == 0xCA) { item = ItemNo.MasterSwordHalfPower; } + else if(handIdxLeft == 0xCB) { item = ItemNo.MasterSwordFullPower; } + + if(item == ItemNo.InvalidItem) { + if(handIdxLeft == 0xCC) { + this.handStyleLeft = LkHandStyle.HoldWindWaker; + // Set the Wind Waker as the equipped item + } else if (this.equippedItem != LkEquipItem.None) { + this.deleteEquipItem(); + this.handStyleLeft = handIdxLeft as LkHandStyle; + } + } else { + this.handStyleLeft = LkHandStyle.HoldSword; + if (this.equippedItem != LkEquipItem.Sword) { + // d_com_inf_game::dComIfGs_setSelectEquip(0, item); + this.deleteEquipItem(); + this.setSwordModel(globals); + } + } + + if(handIdxRight == 0xC8 || handIdxRight == 0xC9) { + this.handStyleRight = LkHandStyle.HoldShield; + if (handIdxRight == 0xC8) { /* equip HerosShield */ } + else { /* equip MirrorShield */ } + } else { + if(handIdxRight != 0) { + this.handStyleRight = (handIdxRight as LkHandStyle) + 6; + } else { + this.handStyleRight = LkHandStyle.Idle; + } + } } if (anmBckId == 0xFFFF || this.anmBckId == anmBckId) { @@ -5478,6 +5569,33 @@ class d_a_py_lk extends fopAc_ac_c { } } + private setSwordModel(globals: dGlobals) { + this.equippedItem = LkEquipItem.Sword; + this.equippedItemModel = this.initModel(globals, d_a_py_lk.LINK_BDL_SWA); + } + + private deleteEquipItem() { + this.equippedItem = LkEquipItem.None; + this.equippedItemModel = null; + } + + private setItemModel() { + if(!this.equippedItemModel) { + return; + } + + const handLJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[0x08]; + const handRJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[0x0D]; + + mat4.copy(this.equippedItemModel.modelMatrix, handLJointMtx); + this.equippedItemModel?.calcAnim(); + + if(this.equippedItem == LkEquipItem.Sword) { + mat4.copy(this.modelSwordHilt.modelMatrix, handLJointMtx); + this.modelSwordHilt.calcAnim(); + } + } + private procWait_init(globals: dGlobals) { this.setSingleMoveAnime(globals, LkAnim.WAITS); } From 49bd7cb181f5072e609c3715f0f6661af712a618 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 19:23:12 -0800 Subject: [PATCH 30/43] Spawn link at the origin, rather than an arbitrary position --- src/ZeldaWindWaker/Main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 3f8680a44..26621cd0f 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -1026,7 +1026,7 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { await globals.modelCache.waitForLoad(); if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { - fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, [-25414,3501,219], globals.mStayNo, null, null, 0xFF, -1); + fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, null, globals.mStayNo, null, null, 0xFF, -1); } // noclip modification: ensure all the actors are created before we load the cutscene From b02dc54a11b2f042676734cc6eb7cbf11b4c2931 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 19:25:52 -0800 Subject: [PATCH 31/43] Fix typo 'gdnChk` --- src/ZeldaWindWaker/d_a.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index df44225c6..f86f51c3f 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5038,7 +5038,7 @@ class d_a_py_lk extends fopAc_ac_c { private demoMode: number = LinkDemoMode.None; private demoClampToGround = true; - private gdnChk = new dBgS_GndChk() + private gndChk = new dBgS_GndChk() private isWearingCasualClothes = false; private texMappingClothes: TextureMapping; @@ -5101,9 +5101,9 @@ class d_a_py_lk extends fopAc_ac_c { this.posMove(globals); // Evaluate for collisions, clamp to ground - this.gdnChk.Reset(); - vec3.scaleAndAdd(this.gdnChk.pos, this.pos, Vec3UnitY, 30.1); - const groundHeight = globals.scnPlay.bgS.GroundCross(this.gdnChk); + this.gndChk.Reset(); + vec3.scaleAndAdd(this.gndChk.pos, this.pos, Vec3UnitY, 30.1); + const groundHeight = globals.scnPlay.bgS.GroundCross(this.gndChk); this.autoGroundHit(); // If we're pulling position directly from the JStudio tool, ignore collisions and animation root motion @@ -5315,7 +5315,7 @@ class d_a_py_lk extends fopAc_ac_c { } private autoGroundHit() { - const groundHeight = this.gdnChk.retY; + const groundHeight = this.gndChk.retY; if(groundHeight == -Infinity) { return; } @@ -5405,7 +5405,7 @@ class d_a_py_lk extends fopAc_ac_c { // Adjust speed when on slopes let groundAngle = 0; - if( this.gdnChk.polyInfo.bgIdx >= 0 && this.gdnChk.polyInfo.triIdx >= 0) { // @TODO: Should be in cBgS::ChkPolySafe() + if( this.gndChk.polyInfo.bgIdx >= 0 && this.gndChk.polyInfo.triIdx >= 0) { // @TODO: Should be in cBgS::ChkPolySafe() groundAngle = this.getGroundAngle(globals, this.rot[1]); } moveVel *= Math.cos(cM_s2rad(groundAngle)); @@ -5431,7 +5431,7 @@ class d_a_py_lk extends fopAc_ac_c { * @param dir the s16 angle which the actor is facing */ private getGroundAngle(globals: dGlobals, dir: number) { - const gndPlane = globals.scnPlay.bgS.GetTriPla(this.gdnChk.polyInfo.bgIdx, this.gdnChk.polyInfo.triIdx); + const gndPlane = globals.scnPlay.bgS.GetTriPla(this.gndChk.polyInfo.bgIdx, this.gndChk.polyInfo.triIdx); const norm = gndPlane.n; if (gndPlane && norm[1] >= 0.5) { From 704e242d22ec08a842dc05b564ed92f646f3728a Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 19:28:46 -0800 Subject: [PATCH 32/43] Add small todo --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index f86f51c3f..655bf166d 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5058,7 +5058,7 @@ class d_a_py_lk extends fopAc_ac_c { private footData: LkFootData[] = nArray(2, i => ({ toePos: vec3.create(), heelPos: vec3.create() })); private anmTranslation = vec3.create(); - private handStyleLeft: LkHandStyle; + private handStyleLeft: LkHandStyle; // @TODO: Handle non-standard hand rendering private handStyleRight: LkHandStyle; private equippedItem: LkEquipItem; private equippedItemModel: J3DModelInstance | null = null; From aa4572bfcbe4c64e502d90ba01da570d2e677499 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 19:38:55 -0800 Subject: [PATCH 33/43] Update comments around eye translucency settings --- src/ZeldaWindWaker/d_a.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 655bf166d..0a5d60997 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5058,7 +5058,7 @@ class d_a_py_lk extends fopAc_ac_c { private footData: LkFootData[] = nArray(2, i => ({ toePos: vec3.create(), heelPos: vec3.create() })); private anmTranslation = vec3.create(); - private handStyleLeft: LkHandStyle; // @TODO: Handle non-standard hand rendering + private handStyleLeft: LkHandStyle; // @TODO: Handle non-standard hand rendering. See setDrawHandModel(). private handStyleRight: LkHandStyle; private equippedItem: LkEquipItem; private equippedItemModel: J3DModelInstance | null = null; @@ -5200,9 +5200,10 @@ class d_a_py_lk extends fopAc_ac_c { matInstA.setColorWriteEnabled(false); matInstA.setAlphaWriteEnabled(true); - // @TODO: This material is marked as translucent in the original BMD. Noclip draws translucent shapes after all - // opaque shapes, meaning that this renders AFTER Link, which defeats the purpose of modifying the sort - // layer. Is there something wrong with noclip's translucency handling? + // @NOTE: This material is marked as translucent in the original BMD. It is manually drawn after Link's head but + // before his body. Since we don't actually need any translucent behavior, mark it as opaque so that it can + // be drawn before his body. + // @TODO: We need to have a separate sort key for head vs body, as the eyes will currently render on top of Link's arms. matInstA.materialData.material.translucent = false; // Clear the alpha mask written by the *damA materials so it doesn't interfere with other translucent objects From 7c5964b0b626d95674dea2eddcb47f99ce7cc491 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 20:02:42 -0800 Subject: [PATCH 34/43] Use modeProcInit/modeProcExec() helpers --- src/ZeldaWindWaker/d_a.ts | 52 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 0a5d60997..e8cadb237 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5017,7 +5017,8 @@ const enum LkHandStyle { HoldShield = 8, } -class d_a_py_lk extends fopAc_ac_c { +const enum d_a_py_lk_mode { wait, tool } +class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; private static LINK_BDL_CL = 0x18; @@ -5029,8 +5030,7 @@ class d_a_py_lk extends fopAc_ac_c { private static TOE_POS = vec3.fromValues(6.0, 3.25, 0.0); private static HEEL_POS = vec3.fromValues(-6.0, 3.25, 0.0); - private demoProcInitFuncTable = new Map boolean>(); - private proc: (globals: dGlobals) => void; + public curMode = d_a_py_lk_mode.wait; private model: J3DModelInstance; private modelSwordHilt: J3DModelInstance; @@ -5062,6 +5062,11 @@ class d_a_py_lk extends fopAc_ac_c { private handStyleRight: LkHandStyle; private equippedItem: LkEquipItem; private equippedItemModel: J3DModelInstance | null = null; + + private mode_tbl = [ + this.procWaitInit, this.procWait, + this.procToolInit, this.procTool, + ]; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { this.loadAnmTable(globals); @@ -5094,7 +5099,7 @@ class d_a_py_lk extends fopAc_ac_c { this.anmBtk.play(deltaTimeFrames); // Run the current custom update process (Walk, Idle, Swim, etc) - if (this.proc) this.proc(globals); + modeProcExec(globals, this, this.mode_tbl, deltaTimeFrames); // Apply root motion from the animation, and adjust position based on foot movement const rawPos = vec3.copy(this.rawPos, this.pos); @@ -5107,7 +5112,7 @@ class d_a_py_lk extends fopAc_ac_c { this.autoGroundHit(); // If we're pulling position directly from the JStudio tool, ignore collisions and animation root motion - if (this.proc == this.procTool) { + if (this.curMode == d_a_py_lk_mode.tool) { vec3.copy(this.pos, rawPos); if (this.demoClampToGround && groundHeight != -Infinity) { this.pos[1] = groundHeight; @@ -5290,23 +5295,12 @@ class d_a_py_lk extends fopAc_ac_c { if (pred) { switch (this.demoMode) { case LinkDemoMode.None: return false; - - case LinkDemoMode.Tool: - this.proc = this.procTool; - break; - - case LinkDemoMode.SetPosRotEquip: - this.procWait_init(globals); - break; + case LinkDemoMode.Tool: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.tool); break; + case LinkDemoMode.SetPosRotEquip: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.wait); break; default: - const initFunc = this.demoProcInitFuncTable.get(this.demoMode); - if (initFunc) { - initFunc(); - } else { - // console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); - // debugger; - } + // console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); + // debugger; break; } return true; @@ -5459,6 +5453,10 @@ class d_a_py_lk extends fopAc_ac_c { } // Process used while a demo is telling Link to play a direct animation + private procToolInit() { + + } + private procTool(globals: dGlobals) { const demoActor = globals.scnPlay.demo.getSystem().getActor(this.demoActorID); if (!demoActor) @@ -5570,6 +5568,16 @@ class d_a_py_lk extends fopAc_ac_c { } } + private procWaitInit(globals: dGlobals ) { + if( this.curMode != d_a_py_lk_mode.wait ) { + this.setSingleMoveAnime(globals, LkAnim.WAITS); + } + } + + private procWait() { + + } + private setSwordModel(globals: dGlobals) { this.equippedItem = LkEquipItem.Sword; this.equippedItemModel = this.initModel(globals, d_a_py_lk.LINK_BDL_SWA); @@ -5597,10 +5605,6 @@ class d_a_py_lk extends fopAc_ac_c { } } - private procWait_init(globals: dGlobals) { - this.setSingleMoveAnime(globals, LkAnim.WAITS); - } - private loadAnmTable(globals: dGlobals) { const anmDataView = globals.findExtraSymbolData(`d_a_player_main.o`, `mAnmDataTable__9daPy_lk_c`).createDataView(); let offset = 0; From 6a45f7fc4dbceebfd7a545bdd26f7ef4ba39eb24 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Wed, 4 Dec 2024 20:18:58 -0800 Subject: [PATCH 35/43] Warn on unsupported demo modes (only once) --- src/ZeldaWindWaker/d_a.ts | 41 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index e8cadb237..8567ee40b 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5017,7 +5017,7 @@ const enum LkHandStyle { HoldShield = 8, } -const enum d_a_py_lk_mode { wait, tool } +const enum d_a_py_lk_mode { unk, wait, tool } class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { public static PROCESS_NAME = dProcName_e.d_a_py_lk; private static ARC_NAME = "Link"; @@ -5031,6 +5031,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { private static HEEL_POS = vec3.fromValues(-6.0, 3.25, 0.0); public curMode = d_a_py_lk_mode.wait; + public prevMode = d_a_py_lk_mode.wait; private model: J3DModelInstance; private modelSwordHilt: J3DModelInstance; @@ -5064,6 +5065,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { private equippedItemModel: J3DModelInstance | null = null; private mode_tbl = [ + this.procUnkInit, this.procUnk, this.procWaitInit, this.procWait, this.procToolInit, this.procTool, ]; @@ -5290,23 +5292,21 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { private changeDemoProc(globals: dGlobals): boolean { assert(this.demoMode < LinkDemoMode.MAX || this.demoMode == LinkDemoMode.Tool) - const pred = true; - - if (pred) { - switch (this.demoMode) { - case LinkDemoMode.None: return false; - case LinkDemoMode.Tool: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.tool); break; - case LinkDemoMode.SetPosRotEquip: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.wait); break; - - default: - // console.warn('Not yet implemented demoMode', LinkDemoMode[this.demoMode]); - // debugger; - break; - } - return true; + switch (this.demoMode) { + case LinkDemoMode.None: return false; + case LinkDemoMode.Tool: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.tool); break; + case LinkDemoMode.SetPosRotEquip: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.wait); break; + + default: + if( this.prevMode != d_a_py_lk_mode.unk ) { + console.warn('Unsupported demo mode:', this.demoMode ); + modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.unk); + } + break; } - return false; + this.prevMode = this.curMode; + return true; } private autoGroundHit() { @@ -5568,8 +5568,15 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } } + private procUnkInit(globals: dGlobals ) { + } + + private procUnk(globals: dGlobals ) { + + } + private procWaitInit(globals: dGlobals ) { - if( this.curMode != d_a_py_lk_mode.wait ) { + if(this.prevMode != d_a_py_lk_mode.wait ) { this.setSingleMoveAnime(globals, LkAnim.WAITS); } } From eda91f452f9d0f59e107e7d3ea4548c43b9e5e20 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 19:59:53 -0800 Subject: [PATCH 36/43] Move Link arc loading to the d_a_py_lk actor --- src/ZeldaWindWaker/Main.ts | 9 ++------- src/ZeldaWindWaker/d_a.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ZeldaWindWaker/Main.ts b/src/ZeldaWindWaker/Main.ts index 26621cd0f..fc9aecd3e 100644 --- a/src/ZeldaWindWaker/Main.ts +++ b/src/ZeldaWindWaker/Main.ts @@ -999,12 +999,6 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { globals.scnPlay.demo.remove(); // TODO: Don't render until the camera has been placed for this demo. The cuts are jarring. - - // Most cutscenes expect the Link actor to be loaded - this.globals.modelCache.fetchObjectData('Link'); - this.globals.modelCache.fetchObjectData('LkD00'); - this.globals.modelCache.fetchObjectData('LkD01'); - this.globals.modelCache.fetchObjectData('LkAnm'); // noclip modification: This normally happens on room load. Do it here instead so that we don't waste time // loading .arcs for cutscenes that aren't going to be played @@ -1024,7 +1018,8 @@ class DemoDesc extends SceneDesc implements Viewer.SceneDesc { } await globals.modelCache.waitForLoad(); - + + // Most cutscenes expect the Link actor to be loaded if(!fopAcM_searchFromName(globals, 'Link', 0, 0)) { fopAcM_create(globals.frameworkGlobals, dProcName_e.d_a_py_lk, 0, null, globals.mStayNo, null, null, 0xFF, -1); } diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 8567ee40b..e8abd33da 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5071,6 +5071,16 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { ]; protected override subload(globals: dGlobals, prm: fopAcM_prm_class | null): cPhs__Status { + const statusA = dComIfG_resLoad(globals, 'Link'); + const statusB = dComIfG_resLoad(globals, 'LkD00'); + const statusC = dComIfG_resLoad(globals, 'LkD01'); + const statusD = dComIfG_resLoad(globals, 'LkAnm'); + + if (statusA !== cPhs__Status.Complete) return statusA; + if (statusB !== cPhs__Status.Complete) return statusB; + if (statusC !== cPhs__Status.Complete) return statusC; + if (statusD !== cPhs__Status.Complete) return statusD; + this.loadAnmTable(globals); this.playerInit(globals); From fca5769d75ea1a05bf169e074f6390d86d108e53 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 20:01:29 -0800 Subject: [PATCH 37/43] Use `Math.hypot()` --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index e8abd33da..7097f898a 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5441,7 +5441,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { if (gndPlane && norm[1] >= 0.5) { const slopeDir = cM_atan2s(norm[0], norm[2]); - const slopeGrade = Math.sqrt(norm[0] * norm[0] + norm[2] * norm[2]); + const slopeGrade = Math.hypot(norm[0], norm[2]); return cM_atan2s(slopeGrade * Math.cos(cM_s2rad(slopeDir - dir)), norm[1]); } return 0; From 6ef6536673c97c58849bc32f14f098d365d4f95d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 20:04:55 -0800 Subject: [PATCH 38/43] Use `frameCtrl.SetFrame/GetFrame()` --- src/ZeldaWindWaker/d_a.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 7097f898a..e4265cbce 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4918,10 +4918,10 @@ class d_a_npc_ls1 extends fopNpc_npc_c { // play_btk_anm(this); this.animStopped = this.morf.play(deltaTimeFrames); - if (this.morf.frameCtrl.currentTimeInFrames < this.animTime) { + if (this.morf.frameCtrl.getFrame() < this.animTime) { this.animStopped = true; } - this.animTime = this.morf.frameCtrl.currentTimeInFrames; + this.animTime = this.morf.frameCtrl.getFrame(); } private setMtx(param: boolean) { @@ -5555,15 +5555,15 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { if (anmBckId == 0xFFFF || this.anmBckId == anmBckId) { if (demoActor.flags & EDemoActorFlags.HasFrame) { - this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; - this.anmBtp.frameCtrl.currentTimeInFrames = anmFrame; + this.anmBck.frameCtrl.setFrame(anmFrame); + this.anmBtp.frameCtrl.setFrame(anmFrame); demoActor.animFrameMax = this.anmBck.frameCtrl.endFrame; } } else { // TODO: How should LkD00 arc be loaded? const bck = globals.resCtrl.getObjectIDRes(ResType.Bck, 'LkD00', anmBckId); this.anmBck.init(this.model.modelData, bck, true, bck.loopMode, 1.0, 0, bck.duration); - this.anmBck.frameCtrl.currentTimeInFrames = anmFrame; + this.anmBck.frameCtrl.setFrame(anmFrame); this.anmBckId = anmBckId; if (anmBtpId != 0xFFFF) { From fc409ab4971d129de3472f6aad7bd79a83906b99 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 20:06:50 -0800 Subject: [PATCH 39/43] Use LinkDemoMode enum instead of int --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index e4265cbce..4d035ffc9 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5101,7 +5101,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { override execute(globals: dGlobals, deltaTimeFrames: number): void { // Update the current proc based on demo data this.setDemoData(globals); - if (this.demoMode != 5) { + if (this.demoMode != LinkDemoMode.WaitTurn) { this.changeDemoProc(globals); } From 517bd9fe60ec305fcd247ee02431ce38f7b1b6eb Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 20:10:44 -0800 Subject: [PATCH 40/43] Fix velocity y/z swap --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 4d035ffc9..20439815b 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5422,7 +5422,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { // Update actor vel and position this.vel[0] = moveVel * Math.sin(cM_s2rad(this.rot[1])); - this.vel[1] = moveVel * Math.cos(cM_s2rad(this.rot[1])); + this.vel[2] = moveVel * Math.cos(cM_s2rad(this.rot[1])); vec3.add(this.pos, this.pos, this.vel); for (let i = 0; i < 2; i++) { From 3fcbcdbb341012eaf188166fbd4832982f7b3409 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 20:13:30 -0800 Subject: [PATCH 41/43] Use const enum if not using enum stringification --- src/ZeldaWindWaker/d_a.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 20439815b..68a114592 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4961,7 +4961,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { } } -enum LkAnim { +const enum LkAnim { WAITS = 0x00, WALK = 0x01, DASH = 0x02, From 312128d4c2d94d1a58266f6e5e8ecc6426450a63 Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 21:34:27 -0800 Subject: [PATCH 42/43] Start an LkJoint enum for commonly accessed joints --- src/ZeldaWindWaker/d_a.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 68a114592..4c482cbd5 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -5017,6 +5017,15 @@ const enum LkHandStyle { HoldShield = 8, } +const enum LkJoint { + HandL = 0x08, + HandR = 0x0D, + Head = 0x0F, + Waist = 0x1E, + FootL = 0x22, + FootR = 0x27, +} + const enum d_a_py_lk_mode { unk, wait, tool } class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { public static PROCESS_NAME = dProcName_e.d_a_py_lk; @@ -5138,7 +5147,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { // Update joints based on the currently playing animation this.anmBck.entry(this.model); this.model.calcAnim(); - mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[0x0F]); + mat4.copy(this.modelKatsura.modelMatrix, this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.Head]); this.modelKatsura.calcAnim(); // Update item transform and animations @@ -5376,15 +5385,11 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { return; } - const footLJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Lfoot_jnt') - const footRJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'Rfoot_jnt') - const waistJointIdx = this.model.modelData.bmd.jnt1.joints.findIndex(j => j.name == 'waist_jnt') - // Compute local -> model transforms for foot and waist joints const invModelMtx = mat4.invert(calc_mtx, this.model.modelMatrix); - const footLMtx = mat4.mul(scratchMat4a, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[footLJointIdx]); - const footRMtx = mat4.mul(scratchMat4b, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[footRJointIdx]); - const waistMtx = mat4.mul(scratchMat4c, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[waistJointIdx]); + const footLMtx = mat4.mul(scratchMat4a, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.FootL]); + const footRMtx = mat4.mul(scratchMat4b, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.FootR]); + const waistMtx = mat4.mul(scratchMat4c, invModelMtx, this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.Waist]); // Compute model space positions of the feet const toePos = []; @@ -5610,8 +5615,8 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { return; } - const handLJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[0x08]; - const handRJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[0x0D]; + const handLJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.HandL]; + const handRJointMtx = this.model.shapeInstanceState.jointToWorldMatrixArray[LkJoint.HandR]; mat4.copy(this.equippedItemModel.modelMatrix, handLJointMtx); this.equippedItemModel?.calcAnim(); From b09545e6f28c2de94c8c2d1e1f1313165a845b3d Mon Sep 17 00:00:00 2001 From: Mike Lester Date: Fri, 6 Dec 2024 21:39:17 -0800 Subject: [PATCH 43/43] Use `===` and `!==` in the Link and Aryll actors --- src/ZeldaWindWaker/d_a.ts | 76 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/ZeldaWindWaker/d_a.ts b/src/ZeldaWindWaker/d_a.ts index 4c482cbd5..8af2a8329 100644 --- a/src/ZeldaWindWaker/d_a.ts +++ b/src/ZeldaWindWaker/d_a.ts @@ -4846,7 +4846,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { case 4: this.type = 4; break; } - return this.type != 0xFF; + return this.type !== 0xFF; } private createInit(globals: dGlobals) { @@ -4873,8 +4873,8 @@ class d_a_npc_ls1 extends fopNpc_npc_c { this.morf = new mDoExt_McaMorf(modelData, null, null, null, LoopMode.Once, 1.0, 0, -1); - const jointIdxHandL = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'handL'); - const jointIdxHandR = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'handR'); + const jointIdxHandL = modelData.bmd.jnt1.joints.findIndex(j => j.name === 'handL'); + const jointIdxHandR = modelData.bmd.jnt1.joints.findIndex(j => j.name === 'handR'); this.jointMtxHandL = this.morf.model.shapeInstanceState.jointToWorldMatrixArray[jointIdxHandL]; this.jointMtxHandR = this.morf.model.shapeInstanceState.jointToWorldMatrixArray[jointIdxHandR]; } @@ -4883,12 +4883,12 @@ class d_a_npc_ls1 extends fopNpc_npc_c { const modelData = globals.resCtrl.getObjectIDRes(ResType.Model, this.arcName, 0xc); this.handModel = new J3DModelInstance(modelData); - const handJointIdxL = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'ls_handL'); - const handJointIdxR = modelData.bmd.jnt1.joints.findIndex(j => j.name == 'ls_handR'); + const handJointIdxL = modelData.bmd.jnt1.joints.findIndex(j => j.name === 'ls_handL'); + const handJointIdxR = modelData.bmd.jnt1.joints.findIndex(j => j.name === 'ls_handR'); this.handModel.jointMatrixCalcCallback = (dst: mat4, modelData: J3DModelData, i: number): void => { - if (i == handJointIdxL) { mat4.copy(dst, this.jointMtxHandL); } - else if (i == handJointIdxR) { mat4.copy(dst, this.jointMtxHandR); } + if (i === handJointIdxL) { mat4.copy(dst, this.jointMtxHandL); } + else if (i === handJointIdxR) { mat4.copy(dst, this.jointMtxHandR); } } } @@ -4904,7 +4904,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { const params = d_a_npc_ls1.animParamsTable[animIdx]; - if (params.anmIdx > -1 && this.animIdx != params.anmIdx) { + if (params.anmIdx > -1 && this.animIdx !== params.anmIdx) { const bckID = d_a_npc_ls1.bckIdxTable[params.anmIdx]; dNpc_setAnmIDRes(globals, this.morf, params.loopMode, params.morf, params.playSpeed, bckID, this.arcName); this.animIdx = params.anmIdx; @@ -4935,7 +4935,7 @@ class d_a_npc_ls1 extends fopNpc_npc_c { if (this.itemModel) { mat4.copy(calc_mtx, this.jointMtxHandR); - if (this.itemPosType == 0) { + if (this.itemPosType === 0) { MtxTrans([5.5, -3.0, -2.0], true); } else { @@ -5110,7 +5110,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { override execute(globals: dGlobals, deltaTimeFrames: number): void { // Update the current proc based on demo data this.setDemoData(globals); - if (this.demoMode != LinkDemoMode.WaitTurn) { + if (this.demoMode !== LinkDemoMode.WaitTurn) { this.changeDemoProc(globals); } @@ -5133,9 +5133,9 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { this.autoGroundHit(); // If we're pulling position directly from the JStudio tool, ignore collisions and animation root motion - if (this.curMode == d_a_py_lk_mode.tool) { + if (this.curMode === d_a_py_lk_mode.tool) { vec3.copy(this.pos, rawPos); - if (this.demoClampToGround && groundHeight != -Infinity) { + if (this.demoClampToGround && groundHeight !== -Infinity) { this.pos[1] = groundHeight; } } @@ -5167,7 +5167,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { mDoExt_modelEntryDL(globals, this.modelKatsura, renderInstManager, viewerInput); } - if (this.equippedItem == LkEquipItem.Sword) { + if (this.equippedItem === LkEquipItem.Sword) { setLightTevColorType(globals, this.equippedItemModel!, this.tevStr, viewerInput.camera); mDoExt_modelEntryDL(globals, this.equippedItemModel!, renderInstManager, viewerInput); @@ -5206,7 +5206,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { this.texMappingHeroClothes.copy(this.texMappingClothes); // Set the default state based on EventBit 0x2A80, except we can't, so just hardcode to use casual clothes on the title screen - this.isWearingCasualClothes = (globals.stageName == 'sea_T' ); // dComIfGs_isEventBit(0x2A80) + this.isWearingCasualClothes = (globals.stageName === 'sea_T' ); // dComIfGs_isEventBit(0x2A80) if(this.isWearingCasualClothes) { this.texMappingClothes.copy(this.texMappingCasualClothes); } MtxTrans(this.pos, false, this.model.modelMatrix); @@ -5272,7 +5272,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } if (enable & EDemoActorFlags.HasShape) { - this.isWearingCasualClothes = (demoActor.shapeId == 1); + this.isWearingCasualClothes = (demoActor.shapeId === 1); if (this.isWearingCasualClothes) this.texMappingClothes.copy(this.texMappingCasualClothes); else @@ -5300,7 +5300,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { const moveVec = vec3.sub(scratchVec3a, targetPos, this.pos); const newRot = cM_atan2s(moveVec[0], moveVec[2]); this.rot[1] = newRot; - this.setSingleMoveAnime(globals, (this.demoMode == LinkDemoMode.Walk) ? LkAnim.WALK : LkAnim.DASH) + this.setSingleMoveAnime(globals, (this.demoMode === LinkDemoMode.Walk) ? LkAnim.WALK : LkAnim.DASH) break; } } @@ -5309,7 +5309,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } private changeDemoProc(globals: dGlobals): boolean { - assert(this.demoMode < LinkDemoMode.MAX || this.demoMode == LinkDemoMode.Tool) + assert(this.demoMode < LinkDemoMode.MAX || this.demoMode === LinkDemoMode.Tool) switch (this.demoMode) { case LinkDemoMode.None: return false; @@ -5317,7 +5317,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { case LinkDemoMode.SetPosRotEquip: modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.wait); break; default: - if( this.prevMode != d_a_py_lk_mode.unk ) { + if( this.prevMode !== d_a_py_lk_mode.unk ) { console.warn('Unsupported demo mode:', this.demoMode ); modeProcInit(globals, this, this.mode_tbl, d_a_py_lk_mode.unk); } @@ -5330,7 +5330,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { private autoGroundHit() { const groundHeight = this.gndChk.retY; - if(groundHeight == -Infinity) { + if(groundHeight === -Infinity) { return; } @@ -5375,7 +5375,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } private posMoveFromFootPos(globals: dGlobals) { - if (this.frontFoot == 2) { + if (this.frontFoot === 2) { vec3.zero(this.vel); vec3.set(this.footData[0].toePos, -14.05, 0.0, 5.02); vec3.set(this.footData[0].heelPos, -10.85, 0.0, -6.52); @@ -5462,7 +5462,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { const bck = globals.resCtrl.getObjectRes(ResType.Bck, "LkAnm", anmData.upperBckIdx); - if(this.anmBck.anm != bck) { + if(this.anmBck.anm !== bck) { this.anmBck.init(this.model.modelData, bck, true, LoopMode.Repeat, rate, start, end); } } @@ -5500,7 +5500,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { case 1: case 5: const count = demoActor.stbData.getUint8(1); - assert(count == 3) + assert(count === 3) anmBckId = demoActor.stbData.getUint16(2); anmBtpId = demoActor.stbData.getUint16(4); anmBtkId = demoActor.stbData.getUint16(6); @@ -5523,34 +5523,34 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { // Set the hand model and/or equipped item based on the demo data let item = ItemNo.InvalidItem; - if(handIdxLeft == 0xC8) { item = ItemNo.HerosSword; } - else if(handIdxLeft == 0xC9) { item = ItemNo.MasterSwordPowerless; } - else if(handIdxLeft == 0xCA) { item = ItemNo.MasterSwordHalfPower; } - else if(handIdxLeft == 0xCB) { item = ItemNo.MasterSwordFullPower; } + if(handIdxLeft === 0xC8) { item = ItemNo.HerosSword; } + else if(handIdxLeft === 0xC9) { item = ItemNo.MasterSwordPowerless; } + else if(handIdxLeft === 0xCA) { item = ItemNo.MasterSwordHalfPower; } + else if(handIdxLeft === 0xCB) { item = ItemNo.MasterSwordFullPower; } - if(item == ItemNo.InvalidItem) { - if(handIdxLeft == 0xCC) { + if(item === ItemNo.InvalidItem) { + if(handIdxLeft === 0xCC) { this.handStyleLeft = LkHandStyle.HoldWindWaker; // Set the Wind Waker as the equipped item - } else if (this.equippedItem != LkEquipItem.None) { + } else if (this.equippedItem !== LkEquipItem.None) { this.deleteEquipItem(); this.handStyleLeft = handIdxLeft as LkHandStyle; } } else { this.handStyleLeft = LkHandStyle.HoldSword; - if (this.equippedItem != LkEquipItem.Sword) { + if (this.equippedItem !== LkEquipItem.Sword) { // d_com_inf_game::dComIfGs_setSelectEquip(0, item); this.deleteEquipItem(); this.setSwordModel(globals); } } - if(handIdxRight == 0xC8 || handIdxRight == 0xC9) { + if(handIdxRight === 0xC8 || handIdxRight === 0xC9) { this.handStyleRight = LkHandStyle.HoldShield; - if (handIdxRight == 0xC8) { /* equip HerosShield */ } + if (handIdxRight === 0xC8) { /* equip HerosShield */ } else { /* equip MirrorShield */ } } else { - if(handIdxRight != 0) { + if(handIdxRight !== 0) { this.handStyleRight = (handIdxRight as LkHandStyle) + 6; } else { this.handStyleRight = LkHandStyle.Idle; @@ -5558,7 +5558,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } } - if (anmBckId == 0xFFFF || this.anmBckId == anmBckId) { + if (anmBckId === 0xFFFF || this.anmBckId === anmBckId) { if (demoActor.flags & EDemoActorFlags.HasFrame) { this.anmBck.frameCtrl.setFrame(anmFrame); this.anmBtp.frameCtrl.setFrame(anmFrame); @@ -5571,12 +5571,12 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { this.anmBck.frameCtrl.setFrame(anmFrame); this.anmBckId = anmBckId; - if (anmBtpId != 0xFFFF) { + if (anmBtpId !== 0xFFFF) { const btp = globals.resCtrl.getObjectIDRes(ResType.Btp, 'LkD00', anmBtpId); this.anmBtp.init(this.model.modelData, btp, true, btp.loopMode, 1.0, 0, btp.duration); } - if (anmBtkId != 0xFFFF) { + if (anmBtkId !== 0xFFFF) { const btk = globals.resCtrl.getObjectIDRes(ResType.Btk, 'LkD00', anmBtkId); this.anmBtk.init(this.model.modelData, btk, true, btk.loopMode, 1.0, 0, btk.duration); } @@ -5591,7 +5591,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { } private procWaitInit(globals: dGlobals ) { - if(this.prevMode != d_a_py_lk_mode.wait ) { + if(this.prevMode !== d_a_py_lk_mode.wait ) { this.setSingleMoveAnime(globals, LkAnim.WAITS); } } @@ -5621,7 +5621,7 @@ class d_a_py_lk extends fopAc_ac_c implements ModeFuncExec { mat4.copy(this.equippedItemModel.modelMatrix, handLJointMtx); this.equippedItemModel?.calcAnim(); - if(this.equippedItem == LkEquipItem.Sword) { + if(this.equippedItem === LkEquipItem.Sword) { mat4.copy(this.modelSwordHilt.modelMatrix, handLJointMtx); this.modelSwordHilt.calcAnim(); }