diff --git a/CMakeLists.txt b/CMakeLists.txt index 171e542b..8e51ac34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) # Set the project version and language -project(Starship VERSION 0.1.0 LANGUAGES C CXX ASM) +project(Starship VERSION 2.0.1 LANGUAGES C CXX ASM) include(FetchContent) set(NATO_PHONETIC_ALPHABET @@ -17,8 +17,8 @@ math(EXPR PATCH_INDEX "${PROJECT_VERSION_PATCH}") # Use the patch number to select the correct word list(GET NATO_PHONETIC_ALPHABET ${PATCH_INDEX} PROJECT_PATCH_WORD) -set(PROJECT_BUILD_NAME "Centauri ${PROJECT_PATCH_WORD}" CACHE STRING "" FORCE) -set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "" FORCE) +set(PROJECT_BUILD_NAME "Barnard ${PROJECT_PATCH_WORD}" CACHE STRING "" FORCE) +set(PROJECT_TEAM "SonicDcer & Lywx" CACHE STRING "" FORCE) if(APPLE) enable_language(OBJCXX) @@ -30,10 +30,6 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment ve # Set the C++ standard and enable the MSVC parallel build option set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_C_STANDARD 11 CACHE STRING "The C standard to use") -set(PROJECT_TEAM "Lywx & YoshiCrystal") -set(PROJECT_VERSION_MAJOR 2) -set(PROJECT_VERSION_MINOR 0) -set(PROJECT_VERSION_PATCH 0) #add_compile_options(-fsanitize=address) #add_link_options(-fsanitize=address) @@ -574,6 +570,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") else() if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") set(CPU_OPTION -msse2 -mfpmath=sse) + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + set(CPU_OPTION -mfpu=neon) endif() target_compile_options(${PROJECT_NAME} PRIVATE diff --git a/README.md b/README.md index b23a60d3..a7076a6b 100644 --- a/README.md +++ b/README.md @@ -18,27 +18,33 @@ If you're having any trouble after reading through this `README`, feel free ask Starship does not include any copyrighted assets. You are required to provide a supported copy of the game. ### 1. Verify your ROM dump -The supported ROM is the USA 1.1 Rev A version. You can verify you have dumped a supported copy of the game by using the SHA-1 File Checksum Online at https://www.romhacking.net/hash/. The hash for a US 1.1 ROM is SHA-1: 09F0D105F476B00EFA5303A3EBC42E60A7753B7A. +The supported ROMs are US 1.0 and US 1.1 Rev A versions. You can verify you have dumped a supported copy of the game by using the SHA-1 File Checksum Online at https://www.romhacking.net/hash/. + +* The SHA-1 hash for a US 1.0 ROM is D8B1088520F7C5F81433292A9258C1184AFA1457. +* The SHA-1 hash for a US 1.1 ROM is 09F0D105F476B00EFA5303A3EBC42E60A7753B7A. + +Starship also supports voice language replacement use from both EU (Lylat) and JP (Japanese) when used in conjunction with an US ROM. + +Note: JP and EU versions of the game are not supported for the base asset O2R creation, a US ROM must be used for it. ### 2. Verify your ROM is in .z64 format Your ROM needs to be in .z64 format. If it's in .n64 format, use the following to convert it to a .z64: https://hack64.net/tools/swapper.php ### 2. Download Starship from [Releases](https://github.com/HarbourMasters/Starship/releases) -### 3. Generating the O2R from the ROM +### 3. Generating the OTR from the ROM and Play! #### Windows * Extract every file from the zip into a folder of your choosing. -* Copy your ROM to the root of the folder you extracted the zip to. -* Run "generate_o2r.bat" +* Run starship.exe and select your US 1.0 or US 1.1 ROM. + +#### Linux +* Extract every file from the zip into a folder of your choosing. +* Execute starship.appimage. You may have to chmod +x the appimage via terminal. +* Select your US 1.0 or US 1.1 ROM. #### MacOS * Extract every file from the zip into a folder of your choosing. -* Copy your ROM to the root of the folder you extracted the zip to. -* Run "generate_o2r.sh" - -### 4. Play! -* Launch `Starship.exe` -Congratulations, you are now sailing with Starship! Have fun! +* Run starship and select your US 1.0 or US 1.1 ROM. # Configuration diff --git a/libultraship b/libultraship index a8cddd2f..45c4f8d6 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit a8cddd2f8991c9d50783db4de855c98a392f4ac9 +Subproject commit 45c4f8d6c19c6176f5e0918917c655ea09ecc212 diff --git a/src/audio/audio_playback.c b/src/audio/audio_playback.c index be9dfbcd..976d9f81 100644 --- a/src/audio/audio_playback.c +++ b/src/audio/audio_playback.c @@ -144,17 +144,13 @@ void Audio_InitNoteSub(Note* note, NoteAttributes* noteAttr) { panVolumeCenter = 1.0f; } else if (stereo.s.is_sfx) { // SFX - float pan_angle = (float)(pan + 64) / 128 * 2 * M_PI; + float pan_angle = ((float) pan) / 128 * 2 * M_PI; // Speaker angles in radians - const float front_left = -0.5236; - const float front_right = 0.5236; - const float rear_left = -1.92; - const float rear_right = 1.92; - - // Normalize pan_angle to [0, 2π] - pan_angle = fmodf(pan_angle, 2 * M_PI); - if (pan_angle < 0) pan_angle += 2 * M_PI; + const float front_left = (CVarGetInteger("gPositionFrontLeft", 240) - 90) * (M_PI / 180.0f); + const float front_right = (CVarGetInteger("gPositionFrontRight", 300) - 90) * (M_PI / 180.0f); + const float rear_left = (CVarGetInteger("gPositionRearLeft", 160) - 90) * (M_PI / 180.0f); + const float rear_right = (CVarGetInteger("gPositionRearRight", 20) - 90) * (M_PI / 180.0f); // Calculate volumes using cosine panning law panVolumeLeft = fmaxf(0, cosf(pan_angle - front_left)); // Front Left @@ -164,8 +160,10 @@ void Audio_InitNoteSub(Note* note, NoteAttributes* noteAttr) { } else { // MUSIC panVolumeLeft = gStereoPanVolume[pan]; panVolumeRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan]; - panVolumeRearLeft = gStereoPanVolume[pan]; - panVolumeRearRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan]; + + f32 rearMusicVolume = CVarGetFloat("gVolumeRearMusic", 1.0f); + panVolumeRearLeft = gStereoPanVolume[pan] * rearMusicVolume; + panVolumeRearRight = gStereoPanVolume[ARRAY_COUNT(gStereoPanVolume) - 1 - pan] * rearMusicVolume; } } diff --git a/src/audio/audio_synthesis.c b/src/audio/audio_synthesis.c index 84d894c5..17917d53 100644 --- a/src/audio/audio_synthesis.c +++ b/src/audio/audio_synthesis.c @@ -1368,6 +1368,7 @@ Acmd* AudioSynth_ProcessEnvelope(Acmd* aList, NoteSubEu* noteSub, NoteSynthesisS synthState->curVolLfe = curVolLfe + (rampLfe * aiBufLenSmall); synthState->curVolRLeft = curVolRLeft + (rampRLeft * aiBufLenSmall); synthState->curVolRRight = curVolRRight + (rampRRight * aiBufLenSmall); + uint32_t cutoffFreqLfe = CVarGetInteger("gSubwooferThreshold", 80); if (noteSub->bitField0.usesHeadsetPanEffects) { int32_t num_audio_channels = 2; @@ -1378,24 +1379,24 @@ Acmd* AudioSynth_ProcessEnvelope(Acmd* aList, NoteSubEu* noteSub, NoteSynthesisS switch (delaySide) { case HAAS_EFFECT_DELAY_LEFT: aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7), - noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP << 16, num_audio_channels); + noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP << 16, num_audio_channels, cutoffFreqLfe); break; case HAAS_EFFECT_DELAY_RIGHT: aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7), - noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP, num_audio_channels); + noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, DMEM_HAAS_TEMP, num_audio_channels, cutoffFreqLfe); break; default: // HAAS_EFFECT_DELAY_NONE aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7), - noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, num_audio_channels); + noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, num_audio_channels, cutoffFreqLfe); break; } } else { aEnvSetup1(aList++, (sourceReverbVol & 0x7F), rampReverb, rampLeft, rampRight, rampCenter, rampLfe, rampRLeft, rampRRight); aEnvSetup2(aList++, curVolLeft, curVolRight, curVolCenter, curVolLfe, curVolRLeft, curVolRRight); aEnvMixer(aList++, dmemSrc, aiBufLen, ((sourceReverbVol & 0x80) >> 7), - noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, GetNumAudioChannels()); + noteSub->bitField0.stereoStrongRight, noteSub->bitField0.stereoStrongLeft, (DMEM_WET_LEFT_CH << 16) | DMEM_LEFT_CH, 0, GetNumAudioChannels(), cutoffFreqLfe); } return aList; diff --git a/src/audio/mixer.c b/src/audio/mixer.c index c2f9f3ea..d3c5a4d7 100644 --- a/src/audio/mixer.c +++ b/src/audio/mixer.c @@ -77,7 +77,6 @@ static __m128i m256i_clamp_to_m128i(m256i a) { #define BUF_S16(a) (int16_t*) BUF_U8(a) #define SAMPLE_RATE 32000 // Adjusted to match the actual sample rate of 32 kHz -#define CUTOFF_FREQ_LFE 80 // Cutoff frequency of 80 Hz static struct { uint16_t in; @@ -645,7 +644,8 @@ void aEnvSetup2Impl(uint16_t initial_vol_left, uint16_t initial_vol_right, int16 void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb, bool neg_left, bool neg_right, - uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels) + uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels, + uint32_t cutoff_freq_lfe) { // Note: max number of samples is 192 (192 * 2 = 384 bytes = 0x180) int max_num_samples = 192; @@ -675,7 +675,7 @@ void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb, if (num_channels == 6) { // Calculate the filter coefficient - float RC = 1.f / (2 * M_PI * CUTOFF_FREQ_LFE); + float RC = 1.f / (2 * M_PI * cutoff_freq_lfe); float dt = 1.f / SAMPLE_RATE; float alpha = dt / (RC + dt); diff --git a/src/audio/mixer.h b/src/audio/mixer.h index 64c2221c..52cafd98 100644 --- a/src/audio/mixer.h +++ b/src/audio/mixer.h @@ -47,7 +47,8 @@ void aEnvSetup1Impl(uint8_t initial_vol_wet, uint16_t rate_wet, uint16_t rate_le void aEnvSetup2Impl(uint16_t initial_vol_left, uint16_t initial_vol_right, int16_t initial_vol_center, int16_t initial_vol_lfe, int16_t initial_vol_rear_left, int16_t initial_vol_rear_right); void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb, bool neg_left, - bool neg_right, uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels); + bool neg_right, uint32_t wet_dry_addr, uint32_t haas_temp_addr, uint32_t num_channels, + uint32_t cutoff_freq_lfe); void aMixImpl(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr); void aS8DecImpl(uint8_t flags, ADPCM_STATE state); void aAddMixerImpl(uint16_t count, uint16_t in_addr, uint16_t out_addr); @@ -76,8 +77,8 @@ void aUnkCmd19Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_add aEnvSetup1Impl(initialVolReverb, rampReverb, rampLeft, rampRight, rampCenter, rampLfe, rampRLeft, rampRRight) #define aEnvSetup2(pkt, initialVolLeft, initialVolRight, initialVolCenter, initialVolLfe, initialVolRLeft, initialVolRRight) \ aEnvSetup2Impl(initialVolLeft, initialVolRight, initialVolCenter, initialVolLfe, initialVolRLeft, initialVolRRight) -#define aEnvMixer(pkt, inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels) \ - aEnvMixerImpl(inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels) +#define aEnvMixer(pkt, inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels, cutoffFreqLfe) \ + aEnvMixerImpl(inAddr, nSamples, swapReverb, negLeft, negRight, wetDryAddr, haasTempAddr, numChannels, cutoffFreqLfe) #define aMix(pkt, c, g, i, o) aMixImpl(c, g, i, o) #define aS8Dec(pkt, f, s) aS8DecImpl(f, s) #define aAddMixer(pkt, s, d, c) aAddMixerImpl(s, d, c) diff --git a/src/engine/fox_display.c b/src/engine/fox_display.c index 0d6e2bad..ffbdcebe 100644 --- a/src/engine/fox_display.c +++ b/src/engine/fox_display.c @@ -432,6 +432,10 @@ void Display_LandmasterThrusters(Player* player) { } Matrix_Push(&gGfxMatrix); + + // @port: Tag the transform. + FrameInterpolation_RecordOpenChild("Display_LandmasterThrusters_1", player->num); + Matrix_Translate(gGfxMatrix, 20.0f, 30.0f, -10.0f, MTXF_APPLY); if (!gVersusMode) { @@ -447,6 +451,10 @@ void Display_LandmasterThrusters(Player* player) { } else { gSPDisplayList(gMasterDisp++, D_versus_301B6E0); } + + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); + Matrix_Pop(&gGfxMatrix); } @@ -461,6 +469,10 @@ void Display_LandmasterThrusters(Player* player) { } Matrix_Push(&gGfxMatrix); + + // @port: Tag the transform. + FrameInterpolation_RecordOpenChild("Display_LandmasterThrusters_2", player->num); + Matrix_Translate(gGfxMatrix, -20.0f, 30.0f, -10.0f, MTXF_APPLY); if (!gVersusMode) { @@ -476,8 +488,13 @@ void Display_LandmasterThrusters(Player* player) { } else { gSPDisplayList(gMasterDisp++, D_versus_301B6E0); } + + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); + Matrix_Pop(&gGfxMatrix); } + Matrix_Pop(&gGfxMatrix); } diff --git a/src/engine/fox_effect.c b/src/engine/fox_effect.c index 010d8bab..3a7665a5 100644 --- a/src/engine/fox_effect.c +++ b/src/engine/fox_effect.c @@ -218,6 +218,9 @@ void Effect_Effect372_Draw(Effect372* this) { } void Effect_Effect382_Draw(Effect382* this) { + // @port Skip interpolation + FrameInterpolation_ShouldInterpolateFrame(false); + RCP_SetupDL_49(); gDPSetPrimColor(gMasterDisp++, 0, 0, 255, 255, 255, this->unk_44); gDPSetEnvColor(gMasterDisp++, 255, 255, 255, this->unk_44); @@ -225,6 +228,10 @@ void Effect_Effect382_Draw(Effect382* this) { Matrix_Translate(gGfxMatrix, 0.0f, 20.0f, 0.0f, MTXF_APPLY); Matrix_SetGfxMtx(&gMasterDisp); gSPDisplayList(gMasterDisp++, D_ZO_6024220); + + // @port renable interpolation + FrameInterpolation_ShouldInterpolateFrame(true); + RCP_SetupDL(&gMasterDisp, SETUPDL_64); } @@ -746,6 +753,9 @@ void Effect_Effect357_Draw(Effect357* this) { gSPFogPosition(gMasterDisp++, gFogNear, 1005); } + // @port: Tag the transform. + FrameInterpolation_RecordOpenChild("Effect357", this->unk_4C | (this->index << 16) & 0x00FF); + Graphics_SetScaleMtx(this->scale2); switch (gCurrentLevel) { @@ -887,6 +897,8 @@ void Effect_Effect357_Draw(Effect357* this) { } break; } + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); RCP_SetupDL(&gMasterDisp, SETUPDL_64); @@ -2276,11 +2288,18 @@ void Effect_Effect374_Draw(Effect374* this) { break; case 1: + // @port Skip interpolation + FrameInterpolation_ShouldInterpolateFrame(false); + Matrix_Scale(gGfxMatrix, this->scale1, this->scale2, 2.5f, MTXF_APPLY); Matrix_SetGfxMtx(&gMasterDisp); RCP_SetupDL_40(); gSPClearGeometryMode(gMasterDisp++, G_CULL_BACK); gSPDisplayList(gMasterDisp++, D_ENMY_PLANET_4008F70); + + // @port renable interpolation + FrameInterpolation_ShouldInterpolateFrame(true); + RCP_SetupDL(&gMasterDisp, SETUPDL_64); break; } @@ -3795,7 +3814,7 @@ void Effect_Effect395_Update(Effect395* this) { D_ctx_801779A8[0] = 50.0f; if (this->unk_46 == 10) { gFillScreenRed = gFillScreenGreen = gFillScreenBlue = 255; - if (CVarGetInteger("gDisableGorgonFlash", 0) == 0){ + if (CVarGetInteger("gDisableGorgonFlash", 0) == 0) { gFillScreenAlpha = gFillScreenAlphaTarget = 255; } gFillScreenAlphaTarget = 0; diff --git a/src/engine/fox_load.c b/src/engine/fox_load.c index f740a0e2..9ba73553 100644 --- a/src/engine/fox_load.c +++ b/src/engine/fox_load.c @@ -34,6 +34,7 @@ void Load_RomFile(void* vRomAddress, void* dest, ptrdiff_t size) { u8 Load_SceneFiles(Scene* scene) { #if 1 + // TODO: BUG! sCurrentScene and scene never change so this will never be true. bool hasSceneChanged = memcmp(&sCurrentScene, scene, sizeof(Scene)) != 0; sCurrentScene = *scene; return hasSceneChanged; diff --git a/src/engine/fox_std_lib.c b/src/engine/fox_std_lib.c index 47be8b2a..a8f8de36 100644 --- a/src/engine/fox_std_lib.c +++ b/src/engine/fox_std_lib.c @@ -42,6 +42,8 @@ void Lib_Texture_Scroll(u16* texture, s32 width, s32 height, u8 mode) { width = newWidth; height = newHeight; + scale = 1; // TODO: a higher scale causes performance issues for large textures ? + for(s32 i = 0; i < (s32) scale; i++){ switch (mode) { case 0: diff --git a/src/engine/fox_versus.c b/src/engine/fox_versus.c index fb56a60e..2bba4e29 100644 --- a/src/engine/fox_versus.c +++ b/src/engine/fox_versus.c @@ -97,7 +97,7 @@ void func_versus_800BC9DC(f32 xPos, f32 yPos, f32 scale, s32 yScale) { s32 D_800D4AB0[] = { 40, 64, 64 }; Lib_TextureRect_CI8(&gMasterDisp, D_800D4ABC[yScale], D_800D4AA4[yScale], D_800D4AB0[yScale], 40, xPos, yPos, scale, - scale); + scale); } void func_versus_800BCB44(f32 xPos, f32 yPos, f32 scale) { @@ -110,7 +110,7 @@ void func_versus_800BCC48(f32 xPos, f32 yPos, f32 xScale, f32 yScale, s32 arg4) s32 D_800D4AE8[] = { 104, 152, 168, 152 }; Lib_TextureRect_CI8(&gMasterDisp, D_800D4AD8[arg4], D_800D4AC8[arg4], D_800D4AE8[arg4], 25, xPos, yPos, xScale, - yScale); + yScale); } void func_versus_800BCE24(f32 xPos, f32 yPos, f32 xScale, f32 yScale) { @@ -168,8 +168,7 @@ void func_versus_800BD350(f32 xPos, f32 yPos) { } void func_versus_800BD3A8(f32 xPos, f32 yPos) { - Lib_TextureRect_CI4(&gMasterDisp, aVsHandicapFrameTex, aVsHandicapFrameTLUT, 80, 71, xPos, yPos, 1.0f, - 1.0f); + Lib_TextureRect_CI4(&gMasterDisp, aVsHandicapFrameTex, aVsHandicapFrameTLUT, 80, 71, xPos, yPos, 1.0f, 1.0f); } void func_versus_800BD4D4(f32 xPos, f32 yPos, s32 arg2) { @@ -1262,6 +1261,9 @@ s32 func_versus_800C1138(s32 max, s32 arg1) { void Versus_InitMatch(void) { s32 i; + // Until Load_SceneFiles gets fixed, this fixes most of the audio issues in versus + AUDIO_SET_SPEC_ALT(SFXCHAN_3, AUDIOSPEC_16); + for (i = 0, sVsPlayerCount = 0; i < 4; i++) { if (!gPlayerInactive[i]) { sVsPlayerCount++; diff --git a/src/overlays/ovl_ending/fox_end1.c b/src/overlays/ovl_ending/fox_end1.c index c07432a8..79223f92 100644 --- a/src/overlays/ovl_ending/fox_end1.c +++ b/src/overlays/ovl_ending/fox_end1.c @@ -48,6 +48,25 @@ bool D_ending_80198584; s32 D_ending_80198588; s32 D_ending_8019858C; +void Ending_Port_InitOverlay(void) { + D_ending_80192E70 = 0; // ending sequence frame counter + D_ending_80196D04 = 0; // ending text frame counter + memset(D_ending_80196D08, 0, sizeof(D_ending_80196D08)); + D_ending_80196F88 = 0; + D_ending_80196F8C = 0; + D_ending_80196F90 = 0; + D_ending_80196F94 = 0; + D_ending_80196F98 = 0; + D_ending_80196F9C = 0.0f; + memset(D_ending_80196FA0, 0, sizeof(D_ending_80196FA0)); + memset(D_ending_80197900, 0, sizeof(D_ending_80197900)); + memset(D_ending_80198260, 0, sizeof(D_ending_80198260)); + D_ending_80198580 = 0.0f; + D_ending_80198584 = false; + D_ending_80198588 = 0; + D_ending_8019858C = 0; +} + const char str1[] = "fogR= %d, fogG= %d, fogB= %d\n"; const char str2[] = "ligR= %d, ligG= %d, ligB= %d\n"; const char str3[] = "kanR= %d, kanG= %d, kanB= %d\n"; @@ -1028,8 +1047,19 @@ void Ending_Main(void) { gCsFrameCount++; gGameFrameCount++; + if (gSaveFile.save.data.padEE[0] == 1) { + gControllerLock = 0; + if (gControllerPress[0].button & START_BUTTON) { + D_ending_80196D00 = 7; + D_ending_80196D04 = 7200; + D_ending_80192E70 = 7200; + } + } else { + gControllerLock = 10000; + } + switch (D_ending_80196D00) { - case 0: + case 0: // Ending Init gRadioState = 0; gGameFrameCount = 0; gSceneSetup = 0; @@ -1037,6 +1067,12 @@ void Ending_Main(void) { gCsCamAtX = gCsCamAtY = 0.0f; gCsCamAtZ = -100.0f; D_ending_80196D00 = 1; + + // @port Bugfix: + // In the original game, these variables are set to zero when the overlay is reloaded. + // Since we don't use overlays, the absence of this initializer causes the ending not to play + // as it should after a the first playthrough. + Ending_Port_InitOverlay(); break; case 1: diff --git a/src/overlays/ovl_ending/fox_end2.c b/src/overlays/ovl_ending/fox_end2.c index bd32bfde..8ca480af 100644 --- a/src/overlays/ovl_ending/fox_end2.c +++ b/src/overlays/ovl_ending/fox_end2.c @@ -458,8 +458,24 @@ void Ending_8018EDB8(u32 arg0, AssetInfo* asset) { gDPLoadTextureBlock(gMasterDisp++, D_END_700EA38, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, 0, G_TX_WRAP | G_TX_NOMIRROR, G_TX_WRAP | G_TX_NOMIRROR, 5, 5, G_TX_NOLOD, G_TX_NOLOD); - gDPSetupTile(gMasterDisp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, arg0 * 14, 0, G_TX_NOMIRROR | G_TX_WRAP, - G_TX_NOMIRROR | G_TX_WRAP, 5, 5, G_TX_NOLOD, G_TX_NOLOD); + int interpolatedFrames = GameEngine_GetInterpolationFrameCount(); + + float scrollArg = arg0 * 14; + float inc = 14 / (float) interpolatedFrames; + + for (int i = 0; i < interpolatedFrames; i++) { + gDPSetInterpolation(gMasterDisp++, i); + + gDPSetupTile2(gMasterDisp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, scrollArg, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, 5, 5, G_TX_NOLOD, G_TX_NOLOD); + + gDPSetTileSizeInterp(gMasterDisp, G_TX_RENDERTILE, scrollArg, 0, 32 << 2, 0); + + gMasterDisp += 3; + + scrollArg += inc; + } + gSPDisplayList(gMasterDisp++, D_END_700E9E0); } @@ -820,6 +836,10 @@ void Ending_80191234(u32 arg0, AssetInfo* asset) { gBgColor = 0; gStarCount = 0; gControllerLock = 10; + + // @port: Ending seen at least once. + gSaveFile.save.data.padEE[0] = 1; + Save_Write(); } void Ending_80191294(u32 arg0, AssetInfo* asset) { @@ -1083,7 +1103,11 @@ void Ending_801924EC(u32 arg0) { } void Ending_801926D4(void) { - gControllerLock = 10000; + if (gSaveFile.save.data.padEE[0] == 1) { + gControllerLock = 0; + } else { + gControllerLock = 10000; + } Matrix_Push(&gGfxMatrix); diff --git a/src/overlays/ovl_i5/fox_ma.c b/src/overlays/ovl_i5/fox_ma.c index 6825f828..0db5a77f 100644 --- a/src/overlays/ovl_i5/fox_ma.c +++ b/src/overlays/ovl_i5/fox_ma.c @@ -5925,11 +5925,17 @@ void Macbeth_MaBombDrop_Draw(MaBombDrop* this) { break; case 1: + // @port Skip interpolation + FrameInterpolation_ShouldInterpolateFrame(false); + Matrix_Scale(gGfxMatrix, this->fwork[0], this->scale, 2.5f, MTXF_APPLY); Matrix_SetGfxMtx(&gMasterDisp); RCP_SetupDL_40(); gSPClearGeometryMode(gMasterDisp++, G_CULL_BACK); gSPDisplayList(gMasterDisp++, D_ENMY_PLANET_4008F70); + + // @port renable interpolation + FrameInterpolation_ShouldInterpolateFrame(true); RCP_SetupDL(&gMasterDisp, SETUPDL_64); break; } @@ -6496,12 +6502,6 @@ 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; diff --git a/src/overlays/ovl_menu/fox_option.c b/src/overlays/ovl_menu/fox_option.c index e44ede97..97fda39b 100644 --- a/src/overlays/ovl_menu/fox_option.c +++ b/src/overlays/ovl_menu/fox_option.c @@ -1959,7 +1959,7 @@ void Option_Data_Select(void) { } void Option_Data_Draw(void) { - s32 i; + s32 i = 0; s32 sp7C[2]; s32 mask[2]; static f32 D_menu_801AF084[2] = { 172.0f, 76.0f }; @@ -1974,8 +1974,7 @@ void Option_Data_Draw(void) { gDPSetPrimColor(gMasterDisp++, 0, 0, 255, 255, 255, 255); - Lib_TextureRect_IA8(&gMasterDisp, D_OPT_80084B0, 176, 13, D_menu_801AF094[0], D_menu_801AF0AC[0] + (4.0f * i), 1.0f, - 1.0f); + Lib_TextureRect_IA8(&gMasterDisp, D_OPT_80084B0, 176, 13, D_menu_801AF094[0], D_menu_801AF0AC[0], 1.0f, 1.0f); if (D_menu_801B91CC < 2) { Lib_TextureRect_IA8_MirX(&gMasterDisp, aArrowTex, 8, 8, D_menu_801AF084[D_menu_801B91C0], 140.0f, 1.0f, 1.0f); @@ -2250,7 +2249,7 @@ void Option_80197914(void) { // @port: Tag the transform. FrameInterpolation_RecordOpenChild("RANKING_BORDERS", i); - Matrix_Translate(gGfxMatrix, vec1->x, vec1->y, -500.0f, MTXF_APPLY); + Matrix_Translate(gGfxMatrix, vec1->x, vec1->y, -500.0f, MTXF_APPLY); // @port: Increase the scale by 2.5f to compensate for missing borders Matrix_Scale(gGfxMatrix, vec2->x * 4, vec2->y + 2.5f, 1.0f, MTXF_APPLY); @@ -3492,6 +3491,9 @@ void Option_DrawMenuCard(OptionCardFrame arg0) { Matrix_Push(&gGfxMatrix); + // @port: Tag the transform. + FrameInterpolation_RecordOpenChild("MenuCard", (u32) & arg0); + Matrix_Translate(gGfxMatrix, arg0.x, arg0.y, arg0.z, MTXF_APPLY); Matrix_Scale(gGfxMatrix, arg0.xScale, arg0.yScale, 1.0f, MTXF_APPLY); Matrix_RotateX(gGfxMatrix, M_DTOR * 90.0f, MTXF_APPLY); @@ -3502,6 +3504,9 @@ void Option_DrawMenuCard(OptionCardFrame arg0) { Matrix_Pop(&gGfxMatrix); + // @port Pop the transform id. + FrameInterpolation_RecordCloseChild(); + Lib_InitPerspective(&gMasterDisp); } diff --git a/src/overlays/ovl_menu/fox_title.c b/src/overlays/ovl_menu/fox_title.c index 5a521c15..216263fe 100644 --- a/src/overlays/ovl_menu/fox_title.c +++ b/src/overlays/ovl_menu/fox_title.c @@ -146,6 +146,8 @@ f32 D_menu_801B9078; f32 D_menu_801B907C; f32 D_menu_801B9080; f32 D_menu_801B9084; + +// @port: Timer for drawing the mirrored Great Fox Deck. s32 segataSanshiroTimer = 0; TitleAnimation sTeamAnim[4] = { diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index a2c16ca8..d2bee9ec 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -88,7 +88,7 @@ GameEngine::GameEngine() { if (std::filesystem::exists(main_path)) { archiveFiles.push_back(main_path); } else { - if (ShowYesNoBox("No O2R Files", "No O2R files found. Generate one now?") == IDYES) { + if (ShowYesNoBox("Starship - Asset Extraction", "Please provide a Starfox 64 ROM.\n\nSupported Versions:\nUS 1.0\nUS 1.1\n\nAssets will be extracted into an O2R file.") == IDYES) { if(!GenAssetFile()){ ShowMessage("Error", "An error occured, no O2R file was generated.\n\nExiting..."); exit(1); @@ -96,7 +96,7 @@ GameEngine::GameEngine() { archiveFiles.push_back(main_path); } - if (ShowYesNoBox("Extraction Complete", "ROM Extracted. Extract another?") == IDYES) { + if (ShowYesNoBox("Extraction Complete", "ROM Extracted. Extract another?\n\n Starship supports JP and EU ROMs for voice replacement.\n Voice replacement ROM assets can also be installed in:\n Settings->Language->Install JP/EU Audio") == IDYES) { if(!GenAssetFile()){ ShowMessage("Error", "An error occured, no O2R file was generated."); } @@ -291,7 +291,7 @@ bool GameEngine::GenAssetFile(bool exitOnFail) { } } - ShowMessage(("Found " + game.value()).c_str(), "The extraction process will now begin.\n\nThis may take a few minutes.", SDL_MESSAGEBOX_INFORMATION); + ShowMessage(("Starship - Extraction - Found " + game.value()).c_str(), "The extraction process will now begin.\n\nThis may take a few minutes.", SDL_MESSAGEBOX_INFORMATION); return extractor->GenerateOTR(); } @@ -522,7 +522,7 @@ void GameEngine::ProcessGfxCommands(Gfx* commands) { if (wnd != nullptr) { wnd->SetTargetFps(fps); - wnd->SetMaximumFrameLatency(CVarGetInteger("gRenderParallelization", 0) ? 2 : 1); + wnd->SetMaximumFrameLatency(CVarGetInteger("gRenderParallelization", 1) ? 2 : 1); } // When the gfx debugger is active, only run with the final mtx diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index 615a07d0..be909fc6 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -116,6 +116,117 @@ static const char* voiceLangs[] = { "Original", /*"Japanese",*/ "Lylat" }; +void DrawSpeakerPositionEditor() { + static ImVec2 lastCanvasPos; + ImGui::Text("Speaker Position Editor"); + ImVec2 canvasSize = ImVec2(200, 200); // Static canvas size + ImVec2 canvasPos = ImGui::GetCursorScreenPos(); + ImVec2 center = ImVec2(canvasPos.x + canvasSize.x / 2, canvasPos.y + canvasSize.y / 2); + + // Speaker positions + static ImVec2 speakerPositions[4]; + static bool initialized = false; + static float radius = 80.0f; + + // Reset positions if canvas position changed (window resized/moved) + if (!initialized || (lastCanvasPos.x != canvasPos.x || lastCanvasPos.y != canvasPos.y)) { + const char* cvarNames[4] = { "gPositionFrontLeft", "gPositionFrontRight", "gPositionRearLeft", "gPositionRearRight" }; + float angles[4] = { 240.f, 300.f, 160.f, 20.f }; // Default angles + + for (int i = 0; i < 4; i++) { + int savedAngle = CVarGetInteger(cvarNames[i], -1); + if (savedAngle != -1) { + angles[i] = static_cast(savedAngle); + } + + float rad = angles[i] * (M_PI / 180.0f); + speakerPositions[i] = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad)); + } + initialized = true; + lastCanvasPos = canvasPos; + } + + // Draw canvas + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(canvasPos, ImVec2(canvasPos.x + canvasSize.x, canvasPos.y + canvasSize.y), IM_COL32(26, 26, 26, 255)); + drawList->AddCircleFilled(center, 5.0f, IM_COL32(255, 255, 255, 255)); // Central person + + // Draw circle line for speaker positions + drawList->AddCircle(center, radius, IM_COL32(163, 163, 163, 255), 100); + + // Add markers at 0, 22.5, 45, etc. + for (float angle = 0; angle < 360; angle += 22.5f) { + float rad = angle * (M_PI / 180.0f); + ImVec2 markerStart = ImVec2(center.x + (radius - 5) * cosf(rad), center.y + (radius - 5) * sinf(rad)); + ImVec2 markerEnd = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad)); + drawList->AddLine(markerStart, markerEnd, IM_COL32(163, 163, 163, 255)); + } + + const char* speakerLabels[4] = { "L", "R", "RL", "RR" }; + const char* cvarNames[4] = { "gPositionFrontLeft", "gPositionFrontRight", "gPositionRearLeft", "gPositionRearRight" }; + + const float snapThreshold = 2.5f; // Degrees within which snapping occurs + + for (int i = 0; i < 4; i++) { + // Draw speaker as a darker blue circle + drawList->AddCircleFilled(speakerPositions[i], 10.0f, IM_COL32(34, 52, 78, 255)); // Dark blue color + drawList->AddText(ImVec2(speakerPositions[i].x - 6, speakerPositions[i].y - 6), IM_COL32(255, 255, 255, 255), speakerLabels[i]); + + // Handle dragging + ImGui::SetCursorScreenPos(ImVec2(speakerPositions[i].x - 10, speakerPositions[i].y - 10)); + ImGui::InvisibleButton(speakerLabels[i], ImVec2(20, 20)); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + ImVec2 mouseDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); + ImVec2 newPos = ImVec2(speakerPositions[i].x + mouseDelta.x, speakerPositions[i].y + mouseDelta.y); + + // Constrain position to the circle + ImVec2 direction = ImVec2(newPos.x - center.x, newPos.y - center.y); + float length = sqrtf(direction.x * direction.x + direction.y * direction.y); + ImVec2 constrainedPos = ImVec2(center.x + (direction.x / length) * radius, center.y + (direction.y / length) * radius); + + // Calculate angle of the constrained position + float angle = atan2f(constrainedPos.y - center.y, constrainedPos.x - center.x) * (180.0f / M_PI); + if (angle < 0) angle += 360.0f; + + // Snap to the nearest 22.5-degree marker if within the snap threshold + float snappedAngle = roundf(angle / 22.5f) * 22.5f; + if (fabsf(snappedAngle - angle) <= snapThreshold) { + float rad = snappedAngle * (M_PI / 180.0f); + constrainedPos = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad)); + } + + speakerPositions[i] = constrainedPos; + ImGui::ResetMouseDragDelta(); + + // Save the updated angle to CVar after dragging + float updatedAngle = atan2f(speakerPositions[i].y - center.y, speakerPositions[i].x - center.x) * (180.0f / M_PI); + if (updatedAngle < 0) updatedAngle += 360.0f; + CVarSetInteger(cvarNames[i], static_cast(updatedAngle)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); // Mark for saving + } + + // Calculate angle and save to CVar + float angle = atan2f(speakerPositions[i].y - center.y, speakerPositions[i].x - center.x) * (180.0f / M_PI); + if (angle < 0) angle += 360.0f; + CVarSetInteger(cvarNames[i], static_cast(angle)); + } + + // Reset cursor position for button placement + ImGui::SetCursorScreenPos(ImVec2(canvasPos.x, canvasPos.y + canvasSize.y + 10)); + if (ImGui::Button("Reset Positions")) { + float defaultAngles[4] = { 240.f, 300.f, 160.f, 20.f }; + for (int i = 0; i < 4; i++) { + float rad = defaultAngles[i] * (M_PI / 180.0f); + speakerPositions[i] = ImVec2(center.x + radius * cosf(rad), center.y + radius * sinf(rad)); + CVarSetInteger(cvarNames[i], static_cast(defaultAngles[i])); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Reset cursor position to ensure canvas size remains static + ImGui::SetCursorScreenPos(ImVec2(canvasPos.x, canvasPos.y + canvasSize.y + 10)); +} + void DrawSettingsMenu(){ if(UIWidgets::BeginMenu("Settings")){ if (UIWidgets::BeginMenu("Audio")) { @@ -173,6 +284,23 @@ void DrawSettingsMenu(){ } UIWidgets::PaddedEnhancementCheckbox("Surround 5.1 (Needs reload)", "gAudioChannelsSetting", 1, 0); + + if (CVarGetInteger("gAudioChannelsSetting", 0) == 1) { + // Subwoofer threshold + UIWidgets::CVarSliderInt("Subwoofer threshold (Hz)", "gSubwooferThreshold", 10u, 1000u, 80u, { + .tooltip = "The threshold for the subwoofer to be activated. Any sound under this frequency will be played on the subwoofer.", + .format = "%d", + }); + + // Rear music volume slider + UIWidgets::CVarSliderFloat("Rear music volume", "gVolumeRearMusic", 0.0f, 1.0f, 1.0f, { + .format = "%.0f%%", + .isPercentage = true, + }); + + // Configurable positioning of speakers + DrawSpeakerPositionEditor(); + } ImGui::EndMenu(); } @@ -192,10 +320,10 @@ void DrawSettingsMenu(){ }; } else { if (UIWidgets::Button("Install JP/EU Audio")) { - if (GameEngine::GenAssetFile()){ + if (GameEngine::GenAssetFile(false)){ GameEngine::ShowMessage("Success", "Audio assets installed. Changes will be applied on the next startup.", SDL_MESSAGEBOX_INFORMATION); - Ship::Context::GetInstance()->GetWindow()->Close(); } + Ship::Context::GetInstance()->GetWindow()->Close(); } } ImGui::EndMenu(); @@ -321,7 +449,7 @@ void DrawSettingsMenu(){ UIWidgets::Tooltip("Matches interpolation value to the refresh rate of your display."); if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - UIWidgets::PaddedEnhancementCheckbox("Render parallelization","gRenderParallelization", true, false); + UIWidgets::PaddedEnhancementCheckbox("Render parallelization","gRenderParallelization", true, false, {}, {}, {}, true); UIWidgets::Tooltip( "This setting allows the CPU to work on one frame while GPU works on the previous frame.\n" "Recommended if you can't reach the FPS you set, despite it being set below your refresh rate " diff --git a/tools/Torch b/tools/Torch index 9c460ea5..f75facb2 160000 --- a/tools/Torch +++ b/tools/Torch @@ -1 +1 @@ -Subproject commit 9c460ea54312e9cefabf155e3b83021c01862843 +Subproject commit f75facb20883570ed091e8ae733ec0539f606e57