Merge pull request #13 from aMannus/christmas-tree

Update to develop-macready and fix end title camera for christmas ornament hunt
This commit is contained in:
Caladius 2023-11-27 18:07:27 -05:00 committed by GitHub
commit 3106947d50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 344 additions and 61 deletions

View file

@ -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") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
project(Ship VERSION 8.0.1 LANGUAGES C CXX) project(Ship VERSION 8.0.2 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Bravo" CACHE STRING "") set(PROJECT_BUILD_NAME "MacReady Charlie" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)

@ -1 +1 @@
Subproject commit 9509806ae3ca6e35882fb976de70c5bde471b8f5 Subproject commit f717dd265aff2eff359de26915d8ad4e498ffdaf

View file

@ -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 colors were darker than the gDPSetPrimColor. You will see many more examples of this below in the `ApplyOrResetCustomGfxPatches` method
*/ */
static std::map<std::string, CosmeticOption> cosmeticOptions = { static std::map<std::string, CosmeticOption> cosmeticOptions = {
COSMETIC_OPTION("Link_KokiriTunic", "Kokiri Tunic", GROUP_LINK, ImVec4( 30, 105, 27, 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(100, 20, 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( 0, 60, 100, 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_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_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), COSMETIC_OPTION("Link_Boots", "Boots", GROUP_LINK, ImVec4( 93, 44, 18, 255), false, true, true),
@ -325,7 +325,7 @@ static std::map<std::string, CosmeticOption> cosmeticOptions = {
COSMETIC_OPTION("NPC_Dog1", "Dog 1", GROUP_NPC, ImVec4(255, 255, 200, 255), false, true, true), 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_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_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_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_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), COSMETIC_OPTION("NPC_IronKnuckles", "Iron Knuckles", GROUP_NPC, ImVec4(245, 255, 205, 255), false, true, false),

View file

@ -380,13 +380,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
effect->category = kEffectCatBoots; effect->category = kEffectCatBoots;
effect->timeRemaining = 30000; effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_IRON; effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
break; break;
case kEffectForceHoverBoots: case kEffectForceHoverBoots:
effect->category = kEffectCatBoots; effect->category = kEffectCatBoots;
effect->timeRemaining = 30000; effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = PLAYER_BOOTS_HOVER; effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
break; break;
case kEffectSlipperyFloor: case kEffectSlipperyFloor:
effect->category = kEffectCatSlipperyFloor; effect->category = kEffectCatSlipperyFloor;

View file

@ -194,6 +194,8 @@ public:
DEFINE_HOOK(OnSetGameLanguage, void()); DEFINE_HOOK(OnSetGameLanguage, void());
DEFINE_HOOK(OnAssetAltChange, void());
// Helpers // Helpers
static bool IsSaveLoaded(); static bool IsSaveLoaded();
static bool IsGameplayPaused(); static bool IsGameplayPaused();

View file

@ -181,3 +181,9 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode) {
void GameInteractor_ExecuteOnSetGameLanguage() { void GameInteractor_ExecuteOnSetGameLanguage() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>(); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
} }
// MARK: - System
void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnAssetAltChange>(fn);
}

View file

@ -56,6 +56,10 @@ void GameInteractor_ExecuteOnUpdateFileNameSelection(int16_t charCode);
// MARK: - Game // MARK: - Game
void GameInteractor_ExecuteOnSetGameLanguage(); void GameInteractor_ExecuteOnSetGameLanguage();
// MARK: - System
void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void));
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -255,7 +255,7 @@ OTRGlobals::OTRGlobals() {
std::string patchesPath = LUS::Context::LocateFileAcrossAppDirs("mods", appShortName); std::string patchesPath = LUS::Context::LocateFileAcrossAppDirs("mods", appShortName);
if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) { if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) {
if (std::filesystem::is_directory(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")) { if (StringHelper::IEquals(p.path().extension().string(), ".otr")) {
OTRFiles.push_back(p.path().generic_string()); OTRFiles.push_back(p.path().generic_string());
} }
@ -1048,10 +1048,6 @@ extern "C" void InitOTR() {
tm *tm_now = localtime(&now); tm *tm_now = localtime(&now);
CVarRegisterInteger("gLetItSnow", 1); 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); srand(now);
#ifdef ENABLE_CROWD_CONTROL #ifdef ENABLE_CROWD_CONTROL
@ -1217,6 +1213,7 @@ extern "C" void Graph_StartFrame() {
case KbScancode::LUS_KB_TAB: { case KbScancode::LUS_KB_TAB: {
// Toggle HD Assets // Toggle HD Assets
CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0)); CVarSetInteger("gAltAssets", !CVarGetInteger("gAltAssets", 0));
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnAssetAltChange>();
ShouldClearTextureCacheAtEndOfFrame = true; ShouldClearTextureCacheAtEndOfFrame = true;
break; break;
} }
@ -1416,6 +1413,14 @@ extern "C" void ResourceMgr_DirtyDirectory(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->DirtyDirectory(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... // OTRTODO: There is probably a more elegant way to go about this...
// Kenix: This is definitely leaking memory when it's called. // Kenix: This is definitely leaking memory when it's called.
extern "C" char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize) { extern "C" char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize) {
@ -1442,6 +1447,27 @@ extern "C" uint8_t ResourceMgr_FileExists(const char* filePath) {
return ExtensionCache.contains(path); 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) { extern "C" void ResourceMgr_LoadFile(const char* resName) {
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(resName); LUS::Context::GetInstance()->GetResourceManager()->LoadResource(resName);
} }
@ -1481,6 +1507,11 @@ extern "C" char* ResourceMgr_LoadFileFromDisk(const char* filePath) {
return data; return data;
} }
extern "C" uint8_t ResourceMgr_TexIsRaw(const char* texPath) {
auto res = std::static_pointer_cast<LUS::Texture>(GetResourceByNameHandlingMQ(texPath));
return res->Flags & TEX_FLAG_LOAD_AS_RAW;
}
extern "C" uint8_t ResourceMgr_ResourceIsBackground(char* texPath) { extern "C" uint8_t ResourceMgr_ResourceIsBackground(char* texPath) {
auto res = GetResourceByNameHandlingMQ(texPath); auto res = GetResourceByNameHandlingMQ(texPath);
return res->GetInitData()->Type == LUS::ResourceType::SOH_Background; return res->GetInitData()->Type == LUS::ResourceType::SOH_Background;
@ -1566,6 +1597,11 @@ extern "C" void ResourceMgr_PushCurrentDirectory(char* path)
extern "C" Gfx* ResourceMgr_LoadGfxByName(const 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<LUS::DisplayList>(GetResourceByNameHandlingMQ(path)); auto res = std::static_pointer_cast<LUS::DisplayList>(GetResourceByNameHandlingMQ(path));
return (Gfx*)&res->Instructions[0]; return (Gfx*)&res->Instructions[0];
} }

View file

@ -86,11 +86,15 @@ uint32_t ResourceMgr_GetGameVersion(int index);
uint32_t ResourceMgr_GetGamePlatform(int index); uint32_t ResourceMgr_GetGamePlatform(int index);
uint32_t ResourceMgr_GetGameRegion(int index); uint32_t ResourceMgr_GetGameRegion(int index);
void ResourceMgr_LoadDirectory(const char* resName); void ResourceMgr_LoadDirectory(const char* resName);
void ResourceMgr_UnloadResource(const char* resName);
char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize); char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize);
uint8_t ResourceMgr_FileExists(const char* resName); 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); char* GetResourceDataByNameHandlingMQ(const char* path);
void ResourceMgr_LoadFile(const char* resName); void ResourceMgr_LoadFile(const char* resName);
char* ResourceMgr_LoadFileFromDisk(const char* filePath); char* ResourceMgr_LoadFileFromDisk(const char* filePath);
uint8_t ResourceMgr_TexIsRaw(const char* texPath);
uint8_t ResourceMgr_ResourceIsBackground(char* texPath); uint8_t ResourceMgr_ResourceIsBackground(char* texPath);
char* ResourceMgr_LoadJPEG(char* data, size_t dataSize); char* ResourceMgr_LoadJPEG(char* data, size_t dataSize);
uint16_t ResourceMgr_LoadTexWidthByName(char* texPath); uint16_t ResourceMgr_LoadTexWidthByName(char* texPath);

View file

@ -907,6 +907,7 @@ void DrawEnhancementsMenu() {
if (ImGui::BeginMenu("Mods")) { if (ImGui::BeginMenu("Mods")) {
if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) { if (UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", "gAltAssets", false, false)) {
ShouldClearTextureCacheAtEndOfFrame = true; ShouldClearTextureCacheAtEndOfFrame = true;
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnAssetAltChange>();
} }
UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not."); 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); UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", "gDisableBombBillboarding", true, false);

View file

@ -260,7 +260,7 @@ void Audio_osWritebackDCache(void* mem, s32 size)
s32 osAiSetFrequency(u32 freq) s32 osAiSetFrequency(u32 freq)
{ {
return 1;
} }
s32 osEPiStartDma(OSPiHandle* handle, OSIoMesg* mb, s32 direction) s32 osEPiStartDma(OSPiHandle* handle, OSIoMesg* mb, s32 direction)

View file

@ -1526,13 +1526,13 @@ void Inventory_SwapAgeEquipment(void) {
} else { } else {
// When becoming child, set swordless flag if player doesn't have kokiri sword // When becoming child, set swordless flag if player doesn't have kokiri sword
// Only in rando to keep swordless link bugs in vanilla // 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); Flags_SetInfTable(INFTABLE_SWORDLESS);
} }
// When using enhancements, set swordless flag if player doesn't have kokiri sword or hasn't equipped a sword yet. // 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 // 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); Flags_SetInfTable(INFTABLE_SWORDLESS);
gSaveContext.childEquips.buttonItems[0] = ITEM_NONE; gSaveContext.childEquips.buttonItems[0] = ITEM_NONE;
} }
@ -1568,7 +1568,7 @@ void Inventory_SwapAgeEquipment(void) {
gSaveContext.equips.equipment = gSaveContext.childEquips.equipment; gSaveContext.equips.equipment = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); 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 // 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); gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);
} }
} else if (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE) { } 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). (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 When becoming child, set swordless flag if player doesn't have kokiri sword
Only in rando to keep swordless link bugs in vanilla*/ 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); Flags_SetInfTable(INFTABLE_SWORDLESS);
} }

View file

@ -967,10 +967,18 @@ void* sMouthTextures[] = {
}; };
#endif #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[] = { Color_RGB8 sTunicColors[] = {
{ 30, 105, 27 }, { 255, 0, 0 },
{ 100, 20, 0 }, { 255, 0, 0 },
{ 0, 60, 100 }, { 255, 0, 0 },
}; };
Color_RGB8 sGauntletColors[] = { Color_RGB8 sGauntletColors[] = {

View file

@ -277,6 +277,11 @@ void func_8009638C(Gfx** displayList, void* source, void* tlut, u16 width, u16 h
bg->b.imagePal = 0; bg->b.imagePal = 0;
bg->b.imageFlip = CVarGetInteger("gMirroredWorld", 0) ? G_BG_FLAG_FLIPS : 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)) { if (ResourceMgr_ResourceIsBackground((char*) source)) {
char* blob = (char*) ResourceGetDataByName((char *) source); char* blob = (char*) ResourceGetDataByName((char *) source);
swapAndConvertJPEG(blob); swapAndConvertJPEG(blob);

View file

@ -417,7 +417,12 @@ void func_800AEFC8(SkyboxContext* skyboxCtx, s16 skyboxId) {
s32 j; s32 j;
s32 phi_s3 = 0; 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) { 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, 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); D_8012AEBC[i].unk_8, D_8012AEBC[i].unk_C, D_8012AEBC[i].unk_10, i, j);

View file

@ -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[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[3]); gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[3]);
if (skyboxId != SKYBOX_BAZAAR) { //! @bug All shops only provide 2 faces for their sky box. Mask shop is missing from the condition
if (skyboxId <= SKYBOX_HOUSE_KAKARIKO || skyboxId > SKYBOX_BOMBCHU_SHOP) { // 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++); gDPPipeSync(POLY_OPA_DISP++);
gDPLoadTLUT_pal256(POLY_OPA_DISP++, skyboxCtx->palettes[2]); gDPLoadTLUT_pal256(POLY_OPA_DISP++, skyboxCtx->palettes[2]);
gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[4]); gSPDisplayList(POLY_OPA_DISP++, skyboxCtx->dListBuf[4]);

View file

@ -6,6 +6,14 @@
#include "soh/frame_interpolation.h" #include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h" #include "soh/Enhancements/boss-rush/BossRush.h"
#include "soh_assets.h" #include "soh_assets.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include <stdlib.h> // malloc
#include <string.h> // 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) #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -60,6 +68,13 @@ static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 0 } }; static u8 sMaskTex8x32[8 * 32] = { { 0 } };
static u8 sMaskTexLava[32 * 64] = { { 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[] = { static InitChainEntry sInitChain[] = {
ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE), ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE),
ICHAIN_S8(naviEnemyId, 0x0C, ICHAIN_CONTINUE), ICHAIN_S8(naviEnemyId, 0x0C, ICHAIN_CONTINUE),
@ -67,6 +82,70 @@ static InitChainEntry sInitChain[] = {
ICHAIN_F32(targetArrowOffset, 8200.0f, ICHAIN_STOP), 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) { void func_808C12C4(u8* arg1, s16 arg2) {
if (arg2[arg1] != 0) { if (arg2[arg1] != 0) {
sMaskTex8x16[arg2 / 2] = 1; sMaskTex8x16[arg2 / 2] = 1;
@ -87,12 +166,51 @@ void func_808C12C4(u8* arg1, s16 arg2) {
} }
} }
void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) { // Same as func_808C1554 but works with u32 values for RGBA32 raw textures
arg0 = GetResourceDataByNameHandlingMQ(arg0); void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
floorTex = ResourceGetDataByName(floorTex); u16 width = ResourceGetTexWidthByName(arg0);
s32 size = ResourceGetTexHeightByName(arg0) * width;
u16* temp_s3 = SEGMENTED_TO_VIRTUAL(arg0); u32* temp_s3 = sLavaWavyTexRaw;
u16* temp_s1 = SEGMENTED_TO_VIRTUAL(floorTex); 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 i;
s16 i2; s16 i2;
u16 sp54[2048]; u16 sp54[2048];
@ -113,6 +231,9 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
temp_s3[i + temp2] = sp54[i + i2]; 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) { void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) {
@ -188,27 +309,21 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->items); Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->items);
if (Flags_GetClear(play, play->roomCtx.curRoom.num)) { // KD is dead if (Flags_GetClear(play, play->roomCtx.curRoom.num)) { // KD is dead
u16* LavaFloorTex = ResourceGetDataByName(gDodongosCavernBossLavaFloorTex); // SOH [General]
u16* LavaFloorRockTex = ResourceGetDataByName(sLavaFloorRockTex); // Applying the "cooled off" lava rock CPU modified texture for re-visiting the scene
temp_s1_3 = SEGMENTED_TO_VIRTUAL(LavaFloorTex); // is now handled by BossDodongo_RegisterBlendedLavaTextureUpdate below
temp_s2 = SEGMENTED_TO_VIRTUAL(LavaFloorRockTex);
Actor_Kill(&this->actor); Actor_Kill(&this->actor);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f, Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f,
-3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD); -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_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); 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; 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++) { for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0; sMaskTex8x16[i] = 0;
} }
@ -224,6 +339,12 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) { for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) {
sMaskTex32x16[i] = 0; 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_015890, sMaskTex8x16, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_017210, sMaskTex8x32, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_017210, sMaskTex8x32, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015D90, sMaskTex16x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015D90, sMaskTex16x16, NULL);
@ -235,10 +356,14 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016990, sMaskTex32x16, NULL);
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL); Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_016E10, sMaskTex32x16, NULL);
// OTRTODO: This is causing OOB memory reads with HD assets BossDodongo_RegisterBlendedLavaTextureUpdate();
// commenting this out means the lava will stay lava even after beating king d
// // Register alt listener to update the blended lava for the replacement texture based on alt path
// Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaFloorRockTex); if (!hasRegisteredBlendedHook) {
GameInteractor_RegisterOnAssetAltChange(BossDodongo_RegisterBlendedLavaTextureUpdate);
hasRegisteredBlendedHook = 1;
}
// #endregion
} }
void BossDodongo_Destroy(Actor* thisx, PlayState* play) { void BossDodongo_Destroy(Actor* thisx, PlayState* play) {
@ -1015,21 +1140,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. // The lava bubbles with a wavy effect as a CPU modified texture
//func_808C1554(gDodongosCavernBossLavaFloorTex, sLavaFloorLavaTex, this->unk_19E, this->unk_224); // 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) { if (this->unk_1C6 != 0) {
u16* ptr1 = ResourceGetDataByName(sLavaFloorLavaTex); // Similar to above, the cooling off effect is a CPU modified texture effect
u16* ptr2 = ResourceGetDataByName(sLavaFloorRockTex); // Apply corresponding to the texture being raw or not
s16 i2; 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++) { // Get the scale based on the original texture size
s16 new_var = this->unk_1C2 & 0x7FF; u16 widthScale = width / 32;
u16 heightScale = height / 64;
u32 size = width * height;
sMaskTexLava[new_var] = 1; for (i2 = 0; i2 < 20; i2++) {
this->unk_1C2 += 37; 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); Math_SmoothStepToF(&this->unk_224, 0.0f, 1.0f, 0.01f, 0.0f);
} }

View file

@ -16,6 +16,7 @@ void EnChristmasTree_Draw(Actor* thisx, PlayState* play);
void EnChristmasTree_Wait(EnChristmasTree* this, PlayState* play); void EnChristmasTree_Wait(EnChristmasTree* this, PlayState* play);
void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play); void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play);
void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play); void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play);
void EnChristmasTree_HandleEndTitle(EnChristmasTree* this, PlayState* play);
static ColliderCylinderInit sCylinderInit = { static ColliderCylinderInit sCylinderInit = {
{ {
@ -91,8 +92,6 @@ void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play) {
void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) { void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) {
Player* player = GET_PLAYER(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); Actor_Spawn(&play->actorCtx, play, ACTOR_END_TITLE, 0, 0, 0, 0, 0, 0, 2, false);
player->stateFlags1 = PLAYER_STATE1_INPUT_DISABLED; player->stateFlags1 = PLAYER_STATE1_INPUT_DISABLED;
@ -101,9 +100,41 @@ void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) {
Play_PerformSave(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) { void EnChristmasTree_Update(Actor* thisx, PlayState* play) {

View file

@ -1377,6 +1377,12 @@ Gfx* EnKo_SetEnvColor(GraphicsContext* gfxCtx, u8 r, u8 g, u8 b, u8 a) {
void EnKo_Draw(Actor* thisx, PlayState* play) { void EnKo_Draw(Actor* thisx, PlayState* play) {
EnKo* this = (EnKo*)thisx; EnKo* this = (EnKo*)thisx;
Color_RGBA8 tunicColor = sModelInfo[ENKO_TYPE].tunicColor; 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; Color_RGBA8 bootsColor = sModelInfo[ENKO_TYPE].bootsColor;
if (CVarGetInteger("gCosmetics.NPC_Kokiri.Changed", 0)) { if (CVarGetInteger("gCosmetics.NPC_Kokiri.Changed", 0)) {

View file

@ -10101,7 +10101,8 @@ void func_80847BA0(PlayState* play, Player* this) {
D_808535F0 = func_80041DB8(&play->colCtx, this->actor.wallPoly, this->actor.wallBgId); 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. /* 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 * 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 * still be valid once climbing begins by doing a second raycast with a small