From d8a7a6c76498bdd03cda2d91050b9b0147b5f67c Mon Sep 17 00:00:00 2001 From: inspectredc <78732756+inspectredc@users.noreply.github.com> Date: Thu, 16 Nov 2023 01:38:21 +0000 Subject: [PATCH 01/11] Use Correct Player Boot Enums in CC (#3403) --- soh/soh/Enhancements/crowd-control/CrowdControl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp index a9f3b1f19..d2e8e03b1 100644 --- a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp @@ -380,13 +380,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = PLAYER_BOOTS_IRON; + effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON; break; case kEffectForceHoverBoots: effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = PLAYER_BOOTS_HOVER; + effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER; break; case kEffectSlipperyFloor: effect->category = kEffectCatSlipperyFloor; From 3234256b030f482911fef79731438aedd3e03b0c Mon Sep 17 00:00:00 2001 From: briaguya <70942617+briaguya-ai@users.noreply.github.com> Date: Wed, 15 Nov 2023 22:45:09 -0500 Subject: [PATCH 02/11] bump lus (#3405) --- libultraship | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libultraship b/libultraship index 9509806ae..f717dd265 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 9509806ae3ca6e35882fb976de70c5bde471b8f5 +Subproject commit f717dd265aff2eff359de26915d8ad4e498ffdaf From 0ddb0711ad1b46e434e1587003f1c749b9a222e7 Mon Sep 17 00:00:00 2001 From: briaguya <70942617+briaguya-ai@users.noreply.github.com> Date: Wed, 15 Nov 2023 23:22:09 -0500 Subject: [PATCH 03/11] Version bump to MacReady Charlie (#3406) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fccae7f36..53b6b0ac3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(Ship VERSION 8.0.1 LANGUAGES C CXX) -set(PROJECT_BUILD_NAME "MacReady Bravo" CACHE STRING "") +project(Ship VERSION 8.0.2 LANGUAGES C CXX) +set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) From f4e4545180c7fb643f8a2012a36c6346c495f510 Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Thu, 23 Nov 2023 09:07:30 -0500 Subject: [PATCH 04/11] Re-implement King Dodongo's Lava texture effects (#3434) * fix alt backgrounds not always loading * include gfx lookup with the original unload * Add hook for alt toggle * handle cpu modified texture for kd lava * malloc array instead of illegal initialize --- .../game-interactor/GameInteractor.h | 2 + .../game-interactor/GameInteractor_Hooks.cpp | 6 + .../game-interactor/GameInteractor_Hooks.h | 4 + soh/soh/OTRGlobals.cpp | 40 +++ soh/soh/OTRGlobals.h | 4 + soh/soh/SohMenuBar.cpp | 3 +- soh/src/code/z_room.c | 5 + .../actors/ovl_Boss_Dodongo/z_boss_dodongo.c | 228 +++++++++++++++--- 8 files changed, 261 insertions(+), 31 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index ae711c093..809ccda09 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -193,6 +193,8 @@ public: DEFINE_HOOK(OnSetGameLanguage, void()); + DEFINE_HOOK(OnAssetAltChange, void()); + // Helpers static bool IsSaveLoaded(); static bool IsGameplayPaused(); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 13df133a0..4bd5354a6 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -181,3 +181,9 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode) { void GameInteractor_ExecuteOnSetGameLanguage() { GameInteractor::Instance->ExecuteHooks(); } + +// MARK: - System + +void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) { + GameInteractor::Instance->RegisterGameHook(fn); +} diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 47114a300..7b7b226fa 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -56,6 +56,10 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode); // MARK: - Game void GameInteractor_ExecuteOnSetGameLanguage(); + +// MARK: - System +void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)); + #ifdef __cplusplus } #endif diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index ad57d1ca5..2eb72591d 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1216,6 +1216,7 @@ extern "C" void Graph_StartFrame() { case KbScancode::LUS_KB_TAB: { // Toggle HD Assets CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0)); + GameInteractor::Instance->ExecuteHooks(); ShouldClearTextureCacheAtEndOfFrame = true; break; } @@ -1415,6 +1416,14 @@ extern "C" void ResourceMgr_DirtyDirectory(const char* resName) { LUS::Context::GetInstance()->GetResourceManager()->DirtyDirectory(resName); } +extern "C" void ResourceMgr_UnloadResource(const char* resName) { + std::string path = resName; + if (path.substr(0, 7) == "__OTR__") { + path = path.substr(7); + } + auto res = LUS::Context::GetInstance()->GetResourceManager()->UnloadResource(path); +} + // OTRTODO: There is probably a more elegant way to go about this... // Kenix: This is definitely leaking memory when it's called. extern "C" char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize) { @@ -1441,6 +1450,27 @@ extern "C" uint8_t ResourceMgr_FileExists(const char* filePath) { return ExtensionCache.contains(path); } +extern "C" uint8_t ResourceMgr_FileAltExists(const char* filePath) { + std::string path = filePath; + if (path.substr(0, 7) == "__OTR__") { + path = path.substr(7); + } + + if (path.substr(0, 4) != "alt/") { + path = "alt/" + path; + } + + return ExtensionCache.contains(path); +} + +// Unloads a resource if an alternate version exists when alt assets are enabled +// The resource is only removed from the internal cache to prevent it from used in the next resource lookup +extern "C" void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName) { + if (CVarGetInteger("gAltAssets", 0) && ResourceMgr_FileAltExists((char*) resName)) { + ResourceMgr_UnloadResource((char*) resName); + } +} + extern "C" void ResourceMgr_LoadFile(const char* resName) { LUS::Context::GetInstance()->GetResourceManager()->LoadResource(resName); } @@ -1480,6 +1510,11 @@ extern "C" char* ResourceMgr_LoadFileFromDisk(const char* filePath) { return data; } +extern "C" uint8_t ResourceMgr_TexIsRaw(const char* texPath) { + auto res = std::static_pointer_cast(GetResourceByNameHandlingMQ(texPath)); + return res->Flags & TEX_FLAG_LOAD_AS_RAW; +} + extern "C" uint8_t ResourceMgr_ResourceIsBackground(char* texPath) { auto res = GetResourceByNameHandlingMQ(texPath); return res->GetInitData()->Type == LUS::ResourceType::SOH_Background; @@ -1565,6 +1600,11 @@ extern "C" void ResourceMgr_PushCurrentDirectory(char* path) extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path) { + // When an alt resource exists for the DL, we need to unload the original asset + // to clear the cache so the alt asset will be loaded instead + // OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed + ResourceMgr_UnloadOriginalWhenAltExists(path); + auto res = std::static_pointer_cast(GetResourceByNameHandlingMQ(path)); return (Gfx*)&res->Instructions[0]; } diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index e00cfecd5..9b42e6895 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -86,11 +86,15 @@ uint32_t ResourceMgr_GetGameVersion(int index); uint32_t ResourceMgr_GetGamePlatform(int index); uint32_t ResourceMgr_GetGameRegion(int index); void ResourceMgr_LoadDirectory(const char* resName); +void ResourceMgr_UnloadResource(const char* resName); char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize); uint8_t ResourceMgr_FileExists(const char* resName); +uint8_t ResourceMgr_FileAltExists(const char* resName); +void ResourceMgr_UnloadOriginalWhenAltExists(const char* resName); char* GetResourceDataByNameHandlingMQ(const char* path); void ResourceMgr_LoadFile(const char* resName); char* ResourceMgr_LoadFileFromDisk(const char* filePath); +uint8_t ResourceMgr_TexIsRaw(const char* texPath); uint8_t ResourceMgr_ResourceIsBackground(char* texPath); char* ResourceMgr_LoadJPEG(char* data, size_t dataSize); uint16_t ResourceMgr_LoadTexWidthByName(char* texPath); diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 8db6c7703..22746895f 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -907,6 +907,7 @@ void DrawEnhancementsMenu() { if (ImGui::BeginMenu("Mods")) { if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) { ShouldClearTextureCacheAtEndOfFrame = true; + GameInteractor::Instance->ExecuteHooks(); } UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not."); UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", "gDisableBombBillboarding", true, false); @@ -1626,4 +1627,4 @@ void SohMenuBar::DrawElement() { ImGui::EndMenuBar(); } } -} // namespace SohGui \ No newline at end of file +} // namespace SohGui diff --git a/soh/src/code/z_room.c b/soh/src/code/z_room.c index ccbc93d0d..772978f09 100644 --- a/soh/src/code/z_room.c +++ b/soh/src/code/z_room.c @@ -277,6 +277,11 @@ void func_8009638C(Gfx** displayList, void* source, void* tlut, u16 width, u16 h bg->b.imagePal = 0; bg->b.imageFlip = CVarGetInteger("gMirroredWorld", 0) ? G_BG_FLAG_FLIPS : 0; + // When an alt resource exists for the background, we need to unload the original asset + // to clear the cache so the alt asset will be loaded instead + // OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed + ResourceMgr_UnloadOriginalWhenAltExists((char*) source); + if (ResourceMgr_ResourceIsBackground((char*) source)) { char* blob = (char*) ResourceGetDataByName((char *) source); swapAndConvertJPEG(blob); diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index e90e36e80..be9778c6c 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -5,6 +5,14 @@ #include "scenes/dungeons/ddan_boss/ddan_boss_room_1.h" #include "soh/frame_interpolation.h" #include "soh/Enhancements/boss-rush/BossRush.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" + +#include // malloc +#include // memcpy + +// OTRTODO: Replace usage of this method when we can clear the cache +// for a single texture without the need of a DL opcode in the render code +void gfx_texture_cache_clear(); #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED) @@ -59,6 +67,13 @@ static u8 sMaskTex8x8[8 * 8] = { { 0 } }; static u8 sMaskTex8x32[8 * 32] = { { 0 } }; static u8 sMaskTexLava[32 * 64] = { { 0 } }; +static u32* sLavaFloorModifiedTexRaw = NULL; +static u32* sLavaWavyTexRaw = NULL; +static u16 sLavaFloorModifiedTex[4096]; +static u16 sLavaWavyTex[2048]; + +static u8 hasRegisteredBlendedHook = 0; + static InitChainEntry sInitChain[] = { ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE), ICHAIN_S8(naviEnemyId, 0x0C, ICHAIN_CONTINUE), @@ -66,6 +81,70 @@ static InitChainEntry sInitChain[] = { ICHAIN_F32(targetArrowOffset, 8200.0f, ICHAIN_STOP), }; +void BossDodongo_RegisterBlendedLavaTextureUpdate() { + // Not in scene so there is nothing to do + if (gPlayState == NULL || gPlayState->sceneNum != SCENE_DODONGOS_CAVERN_BOSS) { + return; + } + + // Free old textures + if (sLavaFloorModifiedTexRaw != NULL) { + free(sLavaFloorModifiedTexRaw); + sLavaFloorModifiedTexRaw = NULL; + } + if (sLavaWavyTexRaw != NULL) { + free(sLavaWavyTexRaw); + sLavaWavyTexRaw = NULL; + } + + // Unload original textures to bypass cache result for lookups + ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorLavaTex); + ResourceMgr_UnloadOriginalWhenAltExists(sLavaFloorRockTex); + ResourceMgr_UnloadOriginalWhenAltExists(gDodongosCavernBossLavaFloorTex); + + // When the texture is HD (raw) we need to work with u32 values for RGBA32 + // Otherwise the original asset is u16 for RGBA16 + if (ResourceMgr_TexIsRaw(sLavaFloorLavaTex)) { + u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex); + size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex); + size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex); + + sLavaFloorModifiedTexRaw = malloc(lavaSize); + sLavaWavyTexRaw = malloc(floorSize); + + memcpy(sLavaFloorModifiedTexRaw, lavaTex, lavaSize); + + // When KD is dead, just immediately copy the rock texture + if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) { + u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex); + size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex); + memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize); + } + + memcpy(sLavaWavyTexRaw, sLavaFloorModifiedTexRaw, floorSize); + + // Register the blended effect for the raw texture + Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTexRaw); + } else { + u16* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex); + memcpy(sLavaFloorModifiedTex, lavaTex, sizeof(sLavaFloorModifiedTex)); + + // When KD is dead, just immediately copy the rock texture + if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) { + u16* rockTex = ResourceGetDataByName(sLavaFloorRockTex); + size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex); + memcpy(sLavaFloorModifiedTex, rockTex, rockSize); + } + + // Register the blended effect for the non-raw texture + memcpy(sLavaWavyTex, sLavaFloorModifiedTex, sizeof(sLavaWavyTex)); + + Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex); + } + + gfx_texture_cache_clear(); +} + void func_808C12C4(u8* arg1, s16 arg2) { if (arg2[arg1] != 0) { sMaskTex8x16[arg2 / 2] = 1; @@ -86,12 +165,51 @@ void func_808C12C4(u8* arg1, s16 arg2) { } } -void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { - arg0 = GetResourceDataByNameHandlingMQ(arg0); - floorTex = ResourceGetDataByName(floorTex); +// Same as func_808C1554 but works with u32 values for RGBA32 raw textures +void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) { + u16 width = ResourceGetTexWidthByName(arg0); + s32 size = ResourceGetTexHeightByName(arg0) * width; - u16* temp_s3 = SEGMENTED_TO_VIRTUAL(arg0); - u16* temp_s1 = SEGMENTED_TO_VIRTUAL(floorTex); + u32* temp_s3 = sLavaWavyTexRaw; + u32* temp_s1 = sLavaFloorModifiedTexRaw; + s32 i; + s32 i2; + u32* sp54 = malloc(size * sizeof(u32)); // Match the size for lava floor tex + s32 temp; + s32 temp2; + + // Multiplier is used to try to scale the wavy effect to match the scale of the HD texture + // Applying sqrt(multiplier) to arg3 is to control how many pixels move left/right for the selected row + // Applying to arg2 and M_PI help to space out the wave effect + // It's not perfect but close enough + u16 multiplier = width / 32; + + for (i = 0; i < size; i += width) { + temp = sinf((((i / width) + (s32)(((arg2 * multiplier) * 50.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier)); + for (i2 = 0; i2 < width; i2++) { + sp54[i + ((temp + i2) & (width - 1))] = temp_s1[i + i2]; + } + } + for (i = 0; i < width; i++) { + temp = sinf(((i + (s32)(((arg2 * multiplier) * 80.0f) / 100.0f)) & (width - 1)) * (M_PI / (16 * multiplier))) * (arg3 * sqrt(multiplier)); + temp *= width; + for (i2 = 0; i2 < size; i2 += width) { + temp2 = (temp + i2) & (size - 1); + temp_s3[i + temp2] = sp54[i + i2]; + } + } + + free(sp54); + + // Need to clear the cache after updating sLavaWavyTexRaw + gfx_texture_cache_clear(); +} + +// Modified to support CPU modified texture with the resource system +// Used for the original non-raw asset working with u16 values +void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { + u16* temp_s3 = sLavaWavyTex; + u16* temp_s1 = sLavaFloorModifiedTex; s16 i; s16 i2; u16 sp54[2048]; @@ -112,6 +230,9 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { temp_s3[i + temp2] = sp54[i + i2]; } } + + // Need to clear the cache after updating sLavaWavyTex + gfx_texture_cache_clear(); } void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) { @@ -187,27 +308,21 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->items); if (Flags_GetClear(play, play->roomCtx.curRoom.num)) { // KD is dead - u16* LavaFloorTex = ResourceGetDataByName(gDodongosCavernBossLavaFloorTex); - u16* LavaFloorRockTex = ResourceGetDataByName(sLavaFloorRockTex); - temp_s1_3 = SEGMENTED_TO_VIRTUAL(LavaFloorTex); - temp_s2 = SEGMENTED_TO_VIRTUAL(LavaFloorRockTex); + // SOH [General] + // Applying the "cooled off" lava rock CPU modified texture for re-visiting the scene + // is now handled by BossDodongo_RegisterBlendedLavaTextureUpdate below + Actor_Kill(&this->actor); Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD); Actor_Spawn(&play->actorCtx, play, ACTOR_BG_BREAKWALL, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, 0x6000, true); Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, -690.0f, -1523.76f, -3304.0f, 0, 0, 0, 0, true); - - for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { - sMaskTexLava[i] = 1; - } - } else { - for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { - sMaskTexLava[i] = 0; - } } this->actor.flags &= ~ACTOR_FLAG_TARGETABLE; + // #region SOH [General] + // Init mask values for all blended textures for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) { sMaskTex8x16[i] = 0; } @@ -223,6 +338,12 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) { sMaskTex32x16[i] = 0; } + // Set all true for the lava as it will always replace the scene texture + for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) { + sMaskTexLava[i] = 1; + } + + // Register all blended textures Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_017210, sMaskTex8x32, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015D90, sMaskTex16x16, NULL); @@ -234,10 +355,14 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) { Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL); - // OTRTODO: This is causing OOB memory reads with HD assets - // commenting this out means the lava will stay lava even after beating king d - // - // Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaFloorRockTex); + BossDodongo_RegisterBlendedLavaTextureUpdate(); + + // Register alt listener to update the blended lava for the replacement texture based on alt path + if (!hasRegisteredBlendedHook) { + GameInteractor_RegisterOnAssetAltChange(BossDodongo_RegisterBlendedLavaTextureUpdate); + hasRegisteredBlendedHook = 1; + } + // #endregion } void BossDodongo_Destroy(Actor* thisx, PlayState* play) { @@ -1014,21 +1139,64 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) { } } - // TODO The lave floor bubbles with an effect that modifies the texture. This needs to be recreated shader-side. - //func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224); + // The lava bubbles with a wavy effect as a CPU modified texture + // This has been done by maintaining copied/modified texture values in the actor code + // The "cooling off" effect for the lava is pre-applied to the lava texture before applying + // the wavy effect. Since this is two effects and closely related to the actor, I've opted + // to handle them here rather than as a shader effect. + // + // Apply the corresponding wavy effect based on the texture being raw or not + if (ResourceMgr_TexIsRaw(gDodongosCavernBossLavaFloorTex)) { + func_808C1554_Raw(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224); + } else { + func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224); + } } + // Apply the "cooling off" effect for the lava if (this->unk_1C6 != 0) { - u16* ptr1 = ResourceGetDataByName(sLavaFloorLavaTex); - u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex); - s16 i2; + // Similar to above, the cooling off effect is a CPU modified texture effect + // Apply corresponding to the texture being raw or not + if (ResourceMgr_TexIsRaw(sLavaFloorRockTex)) { + u32* ptr1 = sLavaFloorModifiedTexRaw; + u32* ptr2 = ResourceGetDataByName(sLavaFloorRockTex); + u16 width = ResourceGetTexWidthByName(sLavaFloorRockTex); + u16 height = ResourceGetTexHeightByName(sLavaFloorRockTex); + s16 i2; - for (i2 = 0; i2 < 20; i2++) { - s16 new_var = this->unk_1C2 & 0x7FF; + // Get the scale based on the original texture size + u16 widthScale = width / 32; + u16 heightScale = height / 64; + u32 size = width * height; - sMaskTexLava[new_var] = 1; - this->unk_1C2 += 37; + for (i2 = 0; i2 < 20; i2++) { + s16 new_var = this->unk_1C2 & 0x7FF; + // Compute the index to a scaled position (scaling pseudo x,y as a 1D value) + s32 indexStart = ((new_var % 32) * widthScale) + ((new_var / 32) * width * heightScale); + + // From the starting index, apply extra pixels right/down based on the scale + for (size_t j = 0; j < heightScale; j++) { + for (size_t i3 = 0; i3 < widthScale; i3++) { + s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1); + ptr1[scaledIndex] = ptr2[scaledIndex]; + } + } + + this->unk_1C2 += 37; + } + } else { + u16* ptr1 = sLavaFloorModifiedTex; + u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex); + s16 i2; + + for (i2 = 0; i2 < 20; i2++) { + s16 new_var = this->unk_1C2 & 0x7FF; + + ptr1[new_var] = ptr2[new_var]; + this->unk_1C2 += 37; + } } + Math_SmoothStepToF(&this->unk_224, 0.0f, 1.0f, 0.01f, 0.0f); } From bdb201201e133b9a09f08158b60d5df5aeb66907 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Fri, 24 Nov 2023 08:38:13 -0600 Subject: [PATCH 05/11] Fix audio cutoff issues (#3428) --- soh/soh/stubs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/stubs.c b/soh/soh/stubs.c index 9afd93468..a162a7ac5 100644 --- a/soh/soh/stubs.c +++ b/soh/soh/stubs.c @@ -260,7 +260,7 @@ void Audio_osWritebackDCache(void* mem, s32 size) s32 osAiSetFrequency(u32 freq) { - + return 1; } s32 osEPiStartDma(OSPiHandle* handle, OSIoMesg* mb, s32 direction) From f2df029efa9c46d4169dd1b1fc10ad887ddebe65 Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Fri, 24 Nov 2023 09:38:45 -0500 Subject: [PATCH 06/11] fix: skybox crash for mask shop (#3427) --- soh/src/code/z_vr_box.c | 7 ++++++- soh/src/code/z_vr_box_draw.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/soh/src/code/z_vr_box.c b/soh/src/code/z_vr_box.c index b77c3d869..ff2622187 100644 --- a/soh/src/code/z_vr_box.c +++ b/soh/src/code/z_vr_box.c @@ -417,7 +417,12 @@ void func_800AEFC8(SkyboxContext* skyboxCtx, s16 skyboxId) { s32 j; s32 phi_s3 = 0; - if (skyboxId == SKYBOX_BAZAAR || (skyboxId > SKYBOX_HOUSE_KAKARIKO && skyboxId <= SKYBOX_BOMBCHU_SHOP)) { + //! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition + // meaning that the Mask shop will calculate 4 faces + // This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH. + // SOH General: We have added the Mask shop to this check so only the 2 expected faces are calculated. + if (skyboxId == SKYBOX_BAZAAR || skyboxId == SKYBOX_HAPPY_MASK_SHOP || + (skyboxId >= SKYBOX_KOKIRI_SHOP && skyboxId <= SKYBOX_BOMBCHU_SHOP)) { for (j = 0, i = 0; i < 2; i++, j += 2) { phi_s3 = func_800ADBB0(skyboxCtx, skyboxCtx->roomVtx, phi_s3, D_8012AEBC[i].unk_0, D_8012AEBC[i].unk_4, D_8012AEBC[i].unk_8, D_8012AEBC[i].unk_C, D_8012AEBC[i].unk_10, i, j); diff --git a/soh/src/code/z_vr_box_draw.c b/soh/src/code/z_vr_box_draw.c index 8eb97573e..5b4867121 100644 --- a/soh/src/code/z_vr_box_draw.c +++ b/soh/src/code/z_vr_box_draw.c @@ -59,8 +59,14 @@ void SkyboxDraw_Draw(SkyboxContext* skyboxCtx, GraphicsContext* gfxCtx, s16 skyb gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[2]); gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[3]); - if (skyboxId != SKYBOX_BAZAAR) { - if (skyboxId <= SKYBOX_HOUSE_KAKARIKO || skyboxId > SKYBOX_BOMBCHU_SHOP) { + //! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition + // meaning that the Mask shop will render the previously loaded sky box values, or uninitialized data. + // This effect is not noticed as the faces are behind the camera, but will cause a crash in SoH. + // SOH General: We have added the Mask shop to this check so only the 2 expected faces are rendered. + if (skyboxId != SKYBOX_BAZAAR && skyboxId != SKYBOX_HAPPY_MASK_SHOP) { + if (skyboxId < SKYBOX_KOKIRI_SHOP || skyboxId > SKYBOX_BOMBCHU_SHOP) { + // Skip remaining faces for most shop skyboxes + gDPPipeSync(POLY_OPA_DISP++); gDPLoadTLUT_pal256(POLY_OPA_DISP++, skyboxCtx->palettes[2]); gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[4]); From d50ad4779d016c3439885d4aa351c6b56dbb36bf Mon Sep 17 00:00:00 2001 From: Salt Date: Fri, 24 Nov 2023 08:40:30 -0600 Subject: [PATCH 07/11] Fix #3417 Symlinks to Directories in Mods Dir Aren't Traversed (#3418) --- soh/soh/OTRGlobals.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 2eb72591d..bd2e6208a 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -255,7 +255,7 @@ OTRGlobals::OTRGlobals() { std::string patchesPath = LUS::Context::LocateFileAcrossAppDirs("mods", appShortName); if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) { if (std::filesystem::is_directory(patchesPath)) { - for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath)) { + for (const auto& p : std::filesystem::recursive_directory_iterator(patchesPath, std::filesystem::directory_options::follow_directory_symlink)) { if (StringHelper::IEquals(p.path().extension().string(), ".otr")) { OTRFiles.push_back(p.path().generic_string()); } From 95f27ace2e918a5512f4d2621a8dad3200042e7c Mon Sep 17 00:00:00 2001 From: inspectredc <78732756+inspectredc@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:40:42 +0000 Subject: [PATCH 08/11] Fix kokiri sword unequipping when using switch age cheat/enhancements (#3415) * test fix for ks unequip * remove unnecessary brackets --- soh/src/code/z_parameter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index bbaa8d90b..20debfd79 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1526,13 +1526,13 @@ void Inventory_SwapAgeEquipment(void) { } else { // When becoming child, set swordless flag if player doesn't have kokiri sword // Only in rando to keep swordless link bugs in vanilla - if (IS_RANDO && (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) { + if (IS_RANDO && CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) { Flags_SetInfTable(INFTABLE_SWORDLESS); } // When using enhancements, set swordless flag if player doesn't have kokiri sword or hasn't equipped a sword yet. // Then set the child equips button items to item none to ensure kokiri sword is not equipped - if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && ((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) { + if ((CVarGetInteger("gSwitchAge", 0) || CVarGetInteger("gSwitchTimeline", 0)) && (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0 || Flags_GetInfTable(INFTABLE_SWORDLESS))) { Flags_SetInfTable(INFTABLE_SWORDLESS); gSaveContext.childEquips.buttonItems[0] = ITEM_NONE; } @@ -1568,7 +1568,7 @@ void Inventory_SwapAgeEquipment(void) { gSaveContext.equips.equipment = gSaveContext.childEquips.equipment; gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); // Equips kokiri sword in the inventory screen only if kokiri sword exists in inventory and a sword has been equipped already - if (!((EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) { + if (!(CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) && !Flags_GetInfTable(INFTABLE_SWORDLESS)) { gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4); } } else if (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE) { @@ -1598,7 +1598,7 @@ void Inventory_SwapAgeEquipment(void) { (only kokiri tunic/boots equipped, no sword, no C-button items, no D-Pad items). When becoming child, set swordless flag if player doesn't have kokiri sword Only in rando to keep swordless link bugs in vanilla*/ - if (EQUIP_INV_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4) & gSaveContext.inventory.equipment == 0) { + if (CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) == 0) { Flags_SetInfTable(INFTABLE_SWORDLESS); } From 3cf9d655a73ab76f7decd2bb4b2b18c1675d21d3 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sun, 26 Nov 2023 09:34:54 -0700 Subject: [PATCH 09/11] Disable Fix Vine Fall when Climb Everything is enabled (#3439) * Disable Fix Vine Fall when Climb Everything is enabled. * Remove option disabling. --- soh/src/overlays/actors/ovl_player_actor/z_player.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 497935a69..52dc2a3e4 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10101,7 +10101,8 @@ void func_80847BA0(PlayState* play, Player* this) { D_808535F0 = func_80041DB8(&play->colCtx, this->actor.wallPoly, this->actor.wallBgId); - if (CVarGetInteger("gFixVineFall", 0)) { + // conflicts arise from these two being enabled at once, and with ClimbEverything on, FixVineFall is redundant anyway + if (CVarGetInteger("gFixVineFall", 0) && !CVarGetInteger("gClimbEverything", 0)) { /* This fixes the "started climbing a wall and then immediately fell off" bug. * The main idea is if a climbing wall is detected, double-check that it will * still be valid once climbing begins by doing a second raycast with a small From 17df3733c9d20c7e998ecf695ca4e2a139ee022b Mon Sep 17 00:00:00 2001 From: aMannus Date: Mon, 27 Nov 2023 12:51:12 +0100 Subject: [PATCH 10/11] Fix Christmas Ornament final camera shot --- .../ovl_En_ChristmasTree/z_en_christmastree.c | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c index c18e2e098..66514fd4b 100644 --- a/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c +++ b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c @@ -16,6 +16,7 @@ void EnChristmasTree_Draw(Actor* thisx, PlayState* play); void EnChristmasTree_Wait(EnChristmasTree* this, PlayState* play); void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play); void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play); +void EnChristmasTree_HandleEndTitle(EnChristmasTree* this, PlayState* play); static ColliderCylinderInit sCylinderInit = { { @@ -91,8 +92,6 @@ void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play) { void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) { Player* player = GET_PLAYER(play); - GameInteractor_SetNoUIActive(1); - Actor_Spawn(&play->actorCtx, play, ACTOR_END_TITLE, 0, 0, 0, 0, 0, 0, 2, false); player->stateFlags1 = PLAYER_STATE1_INPUT_DISABLED; @@ -101,9 +100,41 @@ void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) { Play_PerformSave(play); - Camera_ChangeMode(Play_GetCamera(play, play->mainCamera.thisIdx), CAM_MODE_STILL); + this->actionFunc = EnChristmasTree_HandleEndTitle; +} - this->actionFunc = EnChristmasTree_Wait; +void EnChristmasTree_HandleEndTitle(EnChristmasTree* this, PlayState* play) { + Camera* camera = Play_GetCamera(play, play->mainCamera.thisIdx); + Player* player = GET_PLAYER(play); + Vec3f camAt; + Vec3f camEye; + + // Not forcing camera mode makes the camera jitter for a bit after setting position. + // Also forces letterbox bars. + Camera_ChangeMode(camera, CAM_MODE_STILL); + + // Christmas Tree's position + camAt.x = -734.0f; + camAt.y = 130.0f; + camAt.z = 420.0f; + + // Camera's position + camEye.x = -1237.0f; + camEye.y = 218.0f; + camEye.z = 408.0f; + + // Not setting fov manually makes camera zoom in after setting the above for a little bit. + camera->fov = 60.0f; + + // Set camera + Play_CameraSetAtEye(play, play->mainCamera.thisIdx, &camAt, &camEye); + + // Hide player so he's not visible in the final screen. Also move him so target arrow on tree dissapears. + player->actor.scale.x = player->actor.scale.y = player->actor.scale.z = 0.00001f; + player->actor.world.pos.y = -200.0f; + + // Hide HUD + Interface_ChangeAlpha(1); } void EnChristmasTree_Update(Actor* thisx, PlayState* play) { From acac97ed16e01b26e6d9103335af2bdc6d570d96 Mon Sep 17 00:00:00 2001 From: aMannus Date: Mon, 27 Nov 2023 21:52:43 +0100 Subject: [PATCH 11/11] Switch tunics and kokiri colors to be red by default --- soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp | 8 ++++---- soh/soh/OTRGlobals.cpp | 4 ---- soh/src/code/z_player_lib.c | 14 +++++++++++--- soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c | 6 ++++++ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 9c56ac32b..0c5ba8ed3 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -190,9 +190,9 @@ typedef struct { colors were darker than the gDPSetPrimColor. You will see many more examples of this below in the `ApplyOrResetCustomGfxPatches` method */ static std::map cosmeticOptions = { - COSMETIC_OPTION("Link_KokiriTunic", "Kokiri Tunic", GROUP_LINK, ImVec4( 30, 105, 27, 255), false, true, false), - COSMETIC_OPTION("Link_GoronTunic", "Goron Tunic", GROUP_LINK, ImVec4(100, 20, 0, 255), false, true, false), - COSMETIC_OPTION("Link_ZoraTunic", "Zora Tunic", GROUP_LINK, ImVec4( 0, 60, 100, 255), false, true, false), + COSMETIC_OPTION("Link_KokiriTunic", "Kokiri Tunic", GROUP_LINK, ImVec4(255, 0, 0, 255), false, true, false), + COSMETIC_OPTION("Link_GoronTunic", "Goron Tunic", GROUP_LINK, ImVec4(255, 0, 0, 255), false, true, false), + COSMETIC_OPTION("Link_ZoraTunic", "Zora Tunic", GROUP_LINK, ImVec4(255, 0, 0, 255), false, true, false), COSMETIC_OPTION("Link_Hair", "Hair", GROUP_LINK, ImVec4(255, 173, 27, 255), false, true, true), COSMETIC_OPTION("Link_Linen", "Linen", GROUP_LINK, ImVec4(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("Link_Boots", "Boots", GROUP_LINK, ImVec4( 93, 44, 18, 255), false, true, true), @@ -325,7 +325,7 @@ static std::map cosmeticOptions = { COSMETIC_OPTION("NPC_Dog1", "Dog 1", GROUP_NPC, ImVec4(255, 255, 200, 255), false, true, true), COSMETIC_OPTION("NPC_Dog2", "Dog 2", GROUP_NPC, ImVec4(150, 100, 50, 255), false, true, true), COSMETIC_OPTION("NPC_GoldenSkulltula", "Golden Skulltula", GROUP_NPC, ImVec4(255, 255, 255, 255), false, true, false), - COSMETIC_OPTION("NPC_Kokiri", "Kokiri", GROUP_NPC, ImVec4( 0, 130, 70, 255), false, true, false), + COSMETIC_OPTION("NPC_Kokiri", "Kokiri", GROUP_NPC, ImVec4(255, 0, 0, 255), false, true, false), COSMETIC_OPTION("NPC_Gerudo", "Gerudo", GROUP_NPC, ImVec4( 90, 0, 140, 255), false, true, false), COSMETIC_OPTION("NPC_MetalTrap", "Metal Trap", GROUP_NPC, ImVec4(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("NPC_IronKnuckles", "Iron Knuckles", GROUP_NPC, ImVec4(245, 255, 205, 255), false, true, false), diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 10c847fc2..b7703d0a5 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1048,10 +1048,6 @@ extern "C" void InitOTR() { tm *tm_now = localtime(&now); CVarRegisterInteger("gLetItSnow", 1); - CVarRegisterInteger("gCosmetics.Link_KokiriTunic.Changed", 1); - CVarRegisterColor("gCosmetics.Link_KokiriTunic.Value", Color_RGBA8{ 255, 0, 0, 255 }); - CVarRegisterInteger("gCosmetics.NPC_Kokiri.Changed", 1); - CVarRegisterColor("gCosmetics.NPC_Kokiri.Value", Color_RGBA8{ 255, 0, 0, 255 }); srand(now); #ifdef ENABLE_CROWD_CONTROL diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index a05652f69..9628b28f2 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -967,10 +967,18 @@ void* sMouthTextures[] = { }; #endif +// Original colors +//Color_RGB8 sTunicColors[] = { +// { 30, 105, 27 }, +// { 100, 20, 0 }, +// { 0, 60, 100 }, +//}; + +// Overwrite to red tunic as default for Holidays in Hyrule build Color_RGB8 sTunicColors[] = { - { 30, 105, 27 }, - { 100, 20, 0 }, - { 0, 60, 100 }, + { 255, 0, 0 }, + { 255, 0, 0 }, + { 255, 0, 0 }, }; Color_RGB8 sGauntletColors[] = { diff --git a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c index 25299f5b0..9acf7f857 100644 --- a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c +++ b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c @@ -1377,6 +1377,12 @@ Gfx* EnKo_SetEnvColor(GraphicsContext* gfxCtx, u8 r, u8 g, u8 b, u8 a) { void EnKo_Draw(Actor* thisx, PlayState* play) { EnKo* this = (EnKo*)thisx; Color_RGBA8 tunicColor = sModelInfo[ENKO_TYPE].tunicColor; + + // Overwrite to red tunic as default for Holidays in Hyrule build + tunicColor.r = 255; + tunicColor.g = 0; + tunicColor.b = 0; + Color_RGBA8 bootsColor = sModelInfo[ENKO_TYPE].bootsColor; if (CVarGetInteger("gCosmetics.NPC_Kokiri.Changed", 0)) {