From d02bae05950bedbb973cd35cfdc27636dbee9e74 Mon Sep 17 00:00:00 2001 From: Sonic Dreamcaster Date: Tue, 6 May 2025 10:31:07 -0300 Subject: [PATCH] N64 Recorded Cutscene Timings for Corneria, Meteo, Macbeth and Sector Y Bosses --- include/fox_record.h | 22 +++++++++++++++++ src/engine/fox_demo.c | 24 ++++++++++++++++++ src/engine/fox_display.c | 15 +++++++++-- src/overlays/ovl_i1/fox_co.c | 48 +++++++++++++++++++++++++++++------- src/overlays/ovl_i2/fox_me.c | 22 +++++++++++++++++ src/overlays/ovl_i5/fox_ma.c | 32 ++++++++++++++++++++++++ src/overlays/ovl_i6/fox_sy.c | 17 +++++++++++++ src/port/Engine.cpp | 2 +- 8 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 include/fox_record.h diff --git a/include/fox_record.h b/include/fox_record.h new file mode 100644 index 00000000..1fbfd3ff --- /dev/null +++ b/include/fox_record.h @@ -0,0 +1,22 @@ +/** + * Used to reproduce recordings made from real N64 hardware + * to accurately reproduce Cutscenes at the correct speed. + * These recordings adjust gVisPerFrame during runtime to produce + * the same behaviour as the original game. + */ +#ifndef N64_RECORD_H +#define N64_RECORD_H + +#include "global.h" + +typedef struct Record { + u8 vis; + u16 frame; +} Record; + +extern u8 gCarrierCutsceneRecord[200]; + +void UpdateVisPerFrameFromRecording(u8* record, s32 maxFrames); +void UpdateVisPerFrameFromRecording2(Record* record, s32 maxFrames); + +#endif diff --git a/src/engine/fox_demo.c b/src/engine/fox_demo.c index 40506dbb..c3315a63 100644 --- a/src/engine/fox_demo.c +++ b/src/engine/fox_demo.c @@ -19,6 +19,28 @@ #include "assets/ast_katina.h" #include "assets/ast_allies.h" #include "port/hooks/Events.h" +#include "fox_co.h" +#include "fox_record.h" + +void UpdateVisPerFrameFromRecording(u8* record, s32 maxFrames) { + if (gCsFrameCount < maxFrames) { + gVIsPerFrame = record[gCsFrameCount]; + } +} + +void UpdateVisPerFrameFromRecording2(Record* record, s32 maxFrames) { + int i; + + if (gCsFrameCount > record[maxFrames - 1].frame) { + return; + } + + for (i = 0; i < maxFrames; i++) { + if (gCsFrameCount == record[i].frame) { + gVIsPerFrame = record[i].vis; + } + } +} void func_demo_80048AC0(TeamId teamId) { s32 teamShield; @@ -925,6 +947,8 @@ void Cutscene_CoComplete2(Player* player) { Math_SmoothStepToF(&player->camRoll, 0.0f, 0.1f, 5.0f, 0.01f); + UpdateVisPerFrameFromRecording(gCarrierCutsceneRecord, ARRAY_COUNT(gCarrierCutsceneRecord)); + switch (player->csState) { case 10: D_ctx_80177A48[2] = 0.0f; diff --git a/src/engine/fox_display.c b/src/engine/fox_display.c index d636ad62..0d6e2bad 100644 --- a/src/engine/fox_display.c +++ b/src/engine/fox_display.c @@ -148,7 +148,8 @@ void Display_DrawHelpAlert(void) { RCP_SetupDL(&gMasterDisp, SETUPDL_76_OPTIONAL); gDPSetPrimColor(gMasterDisp++, 0x00, 0x00, 255, 255, 0, 255); if (sp78 < 0.0f) { - Graphics_DisplaySmallText(OTRGetRectDimensionFromLeftEdgeOverride(38.0f), 106, 1.0f, 1.0f, "HELP!!"); + Graphics_DisplaySmallText(OTRGetRectDimensionFromLeftEdgeOverride(38.0f), 106, 1.0f, 1.0f, + "HELP!!"); } else { Graphics_DisplaySmallText(OTRGetRectDimensionFromRightEdgeOverride(248), 106, 1.0f, 1.0f, "HELP!!"); } @@ -1138,7 +1139,7 @@ void Display_ArwingLaserCharge(Player* player) { // @port: Tag the transform. FrameInterpolation_RecordOpenChild("ArwingMuzzleFlash", 0); - + Matrix_Translate(gGfxMatrix, sp94.x, sp94.y, sp94.z, MTXF_NEW); Matrix_Scale(gGfxMatrix, gMuzzleFlashScale[player->num], gMuzzleFlashScale[player->num], 1.0f, MTXF_APPLY); @@ -2049,6 +2050,16 @@ void Display_Update(void) { if (gInputPress->stick_y < 0) Graphics_DisplaySmallText(110, 220, 1.0f, 1.0f, "NEG:"); #endif +// For debugging cutscene timings +#if 0 + RCP_SetupDL(&gMasterDisp, SETUPDL_83); + gDPSetPrimColor(gMasterDisp++, 0, 0, 255, 255, 0, 255); + Graphics_DisplaySmallText(10 + 210, 190, 1.0f, 1.0f, "CSFMS:"); + Graphics_DisplaySmallNumber(60 + 210, 190, (int) gCsFrameCount); + Graphics_DisplaySmallText(10 + 210, 200, 1.0f, 1.0f, "PLTIM:"); + Graphics_DisplaySmallNumber(60 + 210, 200, (int) gPlayer->csTimer); +#endif + // @port: @event: Call DisplayPostUpdateEvent CALL_EVENT(DisplayPostUpdateEvent); } diff --git a/src/overlays/ovl_i1/fox_co.c b/src/overlays/ovl_i1/fox_co.c index 0e225f4e..456889c8 100644 --- a/src/overlays/ovl_i1/fox_co.c +++ b/src/overlays/ovl_i1/fox_co.c @@ -10,6 +10,34 @@ #include "fox_co.h" #include "port/hooks/Events.h" +// Carrier destroy cutscene timings recorded from a real N64 +u8 gCarrierCutsceneRecord[200] = { + 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x04, 0x04, 0x04, 0x05, 0x05, 0x04, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 +}; + +// Granga destroy cutscene timings recorded from a real N64 +u8 gGrangaCutsceneRecord[161] = { + 0x02, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, 0x02 +}; + u8 sFightCarrier; f32 sCoGrangaWork[68]; @@ -182,9 +210,9 @@ void Corneria_CoGranga_HandleDamage(CoGranga* this) { this->state = GRANGA_EXPLODE; + this->timer_050 = 100; // original value // @port: Adjust timing to compensate the lack of lag. - // this->timer_050 = 100; - this->timer_050 = 138; + // this->timer_050 = 138; // @port SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM, 80); SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_FANFARE, 80); @@ -2236,19 +2264,19 @@ void Corneria_CoCarrier_Update(CoCarrier* this) { Math_SmoothStepToF(&this->vel.y, 0.0f, 0.1f, 2.0f, 0.00001f); Math_SmoothStepToF(&this->vel.z, 0.0f, 0.1f, 2.0f, 0.00001f); + this->obj.rot.z -= 2.0f; // original value + this->gravity = 1.0f; // original value // @port: Adjust gravity and rot to compensate the lack of lag. - // this->obj.rot.z -= 2.0f - // this->gravity = 1.0f; - this->obj.rot.z -= 2.0f - 0.86f; - this->gravity = 1.0f - 0.43f; + // this->obj.rot.z -= 2.0f - 0.86f; + // this->gravity = 1.0f - 0.43f; if (this->obj.pos.y < (gGroundHeight + 150.0f)) { gCameraShake = 100; func_effect_80081A8C(this->obj.pos.x, this->obj.pos.y, this->obj.pos.z, 40.0f, 12); - + + this->timer_050 = 20; // original value // @port: Adjust timings to compensate the lack of lag. - // this->timer_050 = 20; - this->timer_050 = 40; + // this->timer_050 = 40; this->vel.y = -10.0f; this->gravity = 0.0f; this->fwork[17] = 20.0f; @@ -3456,6 +3484,8 @@ void Corneria_LevelComplete1(Player* player) { f32 temp_fa1; f32 temp_deg; + UpdateVisPerFrameFromRecording(gGrangaCutsceneRecord, ARRAY_COUNT(gGrangaCutsceneRecord)); + player->arwing.upperRightFlapYrot = player->arwing.upperLeftFlapYrot = player->arwing.bottomRightFlapYrot = player->arwing.bottomLeftFlapYrot = 0.0f; diff --git a/src/overlays/ovl_i2/fox_me.c b/src/overlays/ovl_i2/fox_me.c index 678dbfe0..4655908d 100644 --- a/src/overlays/ovl_i2/fox_me.c +++ b/src/overlays/ovl_i2/fox_me.c @@ -6,6 +6,26 @@ #include "global.h" #include "assets/ast_meteo.h" +#include "fox_record.h" + +// MeCrusher destroy cutscene timings recorded from a real N64 +u8 gMeCrusherCutsceneRecord[] = { + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x02 +}; Vec3f D_i2_80195430[] = { { 122.0, -5.0, -1200.0 }, { 122.0, -103.0, -727.0 }, { 142.0, -323.0, -848.0 }, { 362.0, -59.0, -435.0 }, @@ -2426,6 +2446,8 @@ void Meteo_LevelComplete(Player* player) { Math_SmoothStepToAngle(&player->aerobaticPitch, 0.0f, 0.1f, 20.0f, 0.0f); Math_SmoothStepToF(&player->boostSpeed, 0.0f, 0.1f, 3.0f, 0.0f); + UpdateVisPerFrameFromRecording(gMeCrusherCutsceneRecord, ARRAY_COUNT(gMeCrusherCutsceneRecord)); + switch (player->csState) { case 0: Audio_StopSfxByBankAndSource(1, player->sfxSource); diff --git a/src/overlays/ovl_i5/fox_ma.c b/src/overlays/ovl_i5/fox_ma.c index 414d0090..6825f828 100644 --- a/src/overlays/ovl_i5/fox_ma.c +++ b/src/overlays/ovl_i5/fox_ma.c @@ -10,6 +10,7 @@ #include "assets/ast_landmaster.h" #include "assets/ast_enmy_planet.h" // #include "prevent_bss_reordering2.h" +#include "fox_record.h" typedef struct { /* 0x00 */ f32 unk_00; @@ -59,6 +60,29 @@ Vec3f D_i5_801BE688[2]; Vec3f D_i5_801BE6A0[12]; s32 D_i5_801BE734[4]; +// Train cutscene timings recorded from a real N64 +Record sf64_virecord_macbeth_records[] = { + // Train breaking barriers + { 0x02, 0x000000 }, + { 0x03, 0x000002 }, + { 0x02, 0x00001F }, + { 0x03, 0x000190 }, + { 0x02, 0x0001A2 }, + { 0x03, 0x0001B1 }, + { 0x04, 0x0001B3 }, + { 0x03, 0x0001BC }, + { 0x02, 0x0001FD }, + // { 0x03, 0x00022F }, + // { 0x02, 0x000245 }, + // { 0x03, 0x00024B }, + // Explosions + { 0x02, 0x00024D }, + { 0x03, 0x0002CA }, + { 0x04, 0x000335 }, + { 0x05, 0x000351 }, + { 0x02, 0x0003AE }, +}; + UnkStruct_D_i5_801B8E50 D_i5_801B8E50[156] = { { 5174.4f, -2141.0f, 0.0f, 350.0f, OBJ_SCENERY_MA_TRAIN_TRACK_3 }, { 3401.4f, -1828.0f, 0.0f, 350.0f, OBJ_SCENERY_MA_TRAIN_TRACK_3 }, @@ -6472,6 +6496,12 @@ f32 D_i5_801BA854[8] = { 1.5f, -1.0f, 0.7f, 0.0f, 0.9f, 0.7f, -1.0f, 1.5f }; f32 D_i5_801BA874[8] = { 200.0f, 300.0f, 400.0f, 0.0f, 500.0f, 100.0f, 120.0f, 100.0f }; f32 D_i5_801BA894[8] = { 200.0f, 250.0f, 220.0f, 0.0f, 200.0f, 230.0f, 220.0f, 350.0f }; + + + + + + void Macbeth_LevelComplete2(Player* player) { s32 i; s32 j; @@ -6479,6 +6509,8 @@ void Macbeth_LevelComplete2(Player* player) { Vec3f spD8; f32 zeroVar = 0.0f; + UpdateVisPerFrameFromRecording2(sf64_virecord_macbeth_records, ARRAY_COUNT(sf64_virecord_macbeth_records)); + switch (player->csState) { case 0: gCsFrameCount = 0; diff --git a/src/overlays/ovl_i6/fox_sy.c b/src/overlays/ovl_i6/fox_sy.c index dd7b13fa..7eb32b89 100644 --- a/src/overlays/ovl_i6/fox_sy.c +++ b/src/overlays/ovl_i6/fox_sy.c @@ -6,6 +6,7 @@ #include "global.h" #include "assets/ast_sector_y.h" +#include "fox_record.h" #define SHOGUN_SHIP (0) @@ -29,6 +30,19 @@ void SectorY_801A0510(ActorCutscene*, s32); void SectorY_ActorDebris_Setup(Actor*, f32, f32, f32, f32, f32, f32, s32); void SectorY_ActorDebris_Spawn(f32, f32, f32, f32, f32, f32, s32); +// SyRobot destroy cutscene timings recorded from a real N64 +u8 gSyRobotCutsceneRecord[158] = { + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02 +}; + f32 D_i6_801A8440[3]; void SectorY_80197B30(ActorCutscene* this, s32 timer) { @@ -475,6 +489,7 @@ void SectorY_80198F5C(SyShogun* this) { this->vel.y = 0.0f; this->vel.x = 0.0f; + // first and second robot explode if ((gPlayer[0].state == PLAYERSTATE_ACTIVE) || (gPlayer[0].state == PLAYERSTATE_U_TURN)) { this->timer_058 = 100; gPlayer[0].state = PLAYERSTATE_STANDBY; @@ -2090,6 +2105,8 @@ void SectorY_LevelComplete(Player* player) { SyShogun* boss = &gBosses[0]; f32 temp_ft1; + UpdateVisPerFrameFromRecording(gSyRobotCutsceneRecord, ARRAY_COUNT(gSyRobotCutsceneRecord)); + switch (player->csState) { case 0: gCsFrameCount = 0; diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index d8d34711..519a00d0 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -376,7 +376,7 @@ void GameEngine::HandleAudioThread() { // gVIsPerFrame = 2; #define AUDIO_FRAMES_PER_UPDATE (gVIsPerFrame > 0 ? gVIsPerFrame : 1) -#define MAX_AUDIO_FRAMES_PER_UPDATE 3 // Compile-time constant with max value of gVIsPerFrame +#define MAX_AUDIO_FRAMES_PER_UPDATE 5 // Compile-time constant with max value of gVIsPerFrame std::unique_lock Lock(audio.mutex); int samples_left = AudioPlayerBuffered();