Compare commits

...

18 Commits

Author SHA1 Message Date
briaguya 0ddb0711ad Version bump to MacReady Charlie (#3406) 2023-11-15 23:22:09 -05:00
briaguya 3234256b03 bump lus (#3405) 2023-11-15 22:45:09 -05:00
inspectredc d8a7a6c764 Use Correct Player Boot Enums in CC (#3403) 2023-11-15 20:38:21 -05:00
Garrett Cox 2dfbbc63e3 Version bump to MacReady Bravo (#3398) 2023-11-14 21:14:47 -05:00
Garrett Cox 044d32a46f Add gFixEyesOpenWhileSleeping (#3365) 2023-11-14 20:47:07 -05:00
Malkierian afe032ea21 [Feature/fix] Save to temp file first (#3376)
* Add temp file flow to `SaveManager::SaveFileThreaded`.
Add "Save finish" info log message.

* Fix WiiU/Switch
2023-11-14 20:46:50 -05:00
Josh Bodner fb45b66903 Fix magic being zeroed out when using fast file select (#3389)
* Move to frame counter init to a place that fast file select also touches

* Undo removing old fix

* Reset on gameover
2023-11-14 17:08:45 -05:00
Malkierian ba987c49e2 SaveManager cleanup (#3386)
* Move threadpool initialization and `OnExitGame` registration from `SaveManager::Init` to SM's constructor.
Comment on `Init` to mention it's not an initializer for `SaveManager`.
Added check for `SaveManager::SaveSection` to prevent firing a save worker if the game is already exited from a reset.

* Removed `IsSaveLoaded` check in favor of another `ThreadPoolWait()` at the start of `SaveManager::Init()`.
2023-11-14 16:46:38 -05:00
AltoXorg bd0672767a Use substr method to determine file extension (#3390)
See https://github.com/HarbourMasters/OTRExporter/pull/12
2023-11-14 16:37:54 -05:00
Adam Bird e66eb8756d Fix: Prevent patching custom models (#3367)
* fix prevent patching custom models

* prevent patching chests textures for custom chest models

* add tooltip for cosmetic editor about custom models

* chest texture handling for alt toggles
2023-11-14 16:37:03 -05:00
Garrett Cox bf31f2b330 Stop hardcoding skeleton type to flex (#3397) 2023-11-14 16:36:05 -05:00
Malkierian 4e9040d761 [Feature] Remove performDelayedSave functionality from Autosave (#3387)
* Removes delayed save functionality, making autosave work everywhere except Ganon and Chamber of Sages scenes.

* Change AutoSave comment to remove the scenarios we no longer block autosave in.

* handle temp B on saving outside of kaleido

---------

Co-authored-by: Adam Bird <archez39@me.com>
2023-11-14 16:35:19 -05:00
Malkierian 304016ddd2 [Feature Fix] Tunics stolen by like likes now removed from the item buttons (#3375)
* Extends `Assignable Boots and Tunics` functionality to check for and remove Goron and Zora tunics from item buttons when like likes steal them.

* Comment documentation.
2023-11-14 00:12:08 -05:00
briaguya fe9c0fa4f7 bump lus (#3394) 2023-11-14 00:10:56 -05:00
Malkierian 384403edb5 Rename all instances of Desert Wasteland to Haunted Wasteland. (#3372) 2023-11-13 23:45:52 -05:00
Malkierian 60687aff0d Move everything in RandomizerCheckTracker::LoadFile() except the block to load the "trackerData" section to a new OnLoadGame hook function to fix crashes on transferred saves. (#3368) 2023-11-13 23:45:41 -05:00
Ralphie Morell cf88b3d2bf Fix edge case of MS shuffle (#3364) 2023-11-13 23:45:15 -05:00
Malkierian 78ffb41cd2 Moved the check for !seqInfo.canBeUsedAsReplacement in InitializeShufflePool to exclude them before modifying either shuffle pool. (#3370) 2023-11-13 18:11:29 -05:00
24 changed files with 249 additions and 144 deletions
+2 -2
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")
project(Ship VERSION 8.0.0 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Alfa" 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)
+1 -1
View File
@@ -439,7 +439,7 @@ static std::unordered_map<u16, const char*> actorDescriptions = {
{ ACTOR_EN_DAIKU_KAKARIKO, "Carpenters (Kakariko)" },
{ ACTOR_BG_BOWL_WALL, "Bombchu Bowling Alley Wall" },
{ ACTOR_EN_WALL_TUBO, "Bombchu Bowling Alley Bullseyes" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Desert Wasteland)" },
{ ACTOR_EN_PO_DESERT, "Poe Guide (Haunted Wasteland)" },
{ ACTOR_EN_CROW, "Guay" },
{ ACTOR_DOOR_KILLER, "Fake Door" },
{ ACTOR_BG_SPOT11_OASIS, "Oasis (Desert Colossus)" },
@@ -400,8 +400,9 @@ void AudioCollection::InitializeShufflePool() {
if (shufflePoolInitialized) return;
for (auto& [seqId, seqInfo] : sequenceMap) {
if (!seqInfo.canBeUsedAsReplacement) continue;
const std::string cvarKey = "gAudioEditor.Excluded." + seqInfo.sfxKey;
if (CVarGetInteger(cvarKey.c_str(), 0) && !seqInfo.canBeUsedAsReplacement) {
if (CVarGetInteger(cvarKey.c_str(), 0)) {
excludedSequences.insert(&seqInfo);
} else {
includedSequences.insert(&seqInfo);
@@ -1767,6 +1767,10 @@ void CosmeticsEditorWindow::DrawElement() {
ImGui::SameLine();
UIWidgets::EnhancementCombobox("gCosmetics.DefaultColorScheme", colorSchemes, COLORSCHEME_N64);
UIWidgets::EnhancementCheckbox("Advanced Mode", "gCosmetics.AdvancedMode");
UIWidgets::InsertHelpHoverText(
"Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic option.\n\n"
"For example, if you have custom Link model, then the Link's Hair color option will most likely not apply."
);
if (CVarGetInteger("gCosmetics.AdvancedMode", 0)) {
if (ImGui::Button("Lock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) {
for (auto& [id, cosmeticOption] : cosmeticOptions) {
@@ -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;
+1
View File
@@ -207,6 +207,7 @@ static bool ResetHandler(std::shared_ptr<LUS::Console> Console, std::vector<std:
return 1;
}
gPlayState->gameplayFrames = 0;
SET_NEXT_GAMESTATE(&gPlayState->state, TitleSetup_Init, GameState);
gPlayState->state.running = false;
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnExitGame>(gSaveContext.fileNum);
+3 -23
View File
@@ -35,8 +35,6 @@ extern PlayState* gPlayState;
extern void Overlay_DisplayText(float duration, const char* text);
uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum);
}
bool performDelayedSave = false;
bool performSave = false;
// TODO: When there's more uses of something like this, create a new GI::RawAction?
void ReloadSceneTogglingLinkAge() {
@@ -258,14 +256,12 @@ void RegisterOcarinaTimeTravel() {
void AutoSave(GetItemEntry itemEntry) {
u8 item = itemEntry.itemId;
bool performSave = false;
// Don't autosave immediately after buying items from shops to prevent getting them for free!
// Don't autosave in the Chamber of Sages since resuming from that map breaks the game
// Don't autosave during the Ganon fight when picking up the Master Sword
// Don't autosave in the fishing pond to prevent getting rod on B outside of the pond
// Don't autosave in the bombchu bowling alley to prevent having chus on B outside of the minigame
// Don't autosave in grottos since resuming from grottos breaks the game.
if ((CVarGetInteger("gAutosave", AUTOSAVE_OFF) != AUTOSAVE_OFF) && (gPlayState != NULL) && (gSaveContext.pendingSale == ITEM_NONE) &&
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS)) {
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS) && (gPlayState->sceneNum != SCENE_CHAMBER_OF_THE_SAGES)) {
if (((CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS) || (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_ALL_ITEMS)) && (item != ITEM_NONE)) {
// Autosave for all items
performSave = true;
@@ -326,25 +322,9 @@ void AutoSave(GetItemEntry itemEntry) {
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = true;
}
if (gPlayState->sceneNum == SCENE_FAIRYS_FOUNTAIN || gPlayState->sceneNum == SCENE_GROTTOS ||
gPlayState->sceneNum == SCENE_CHAMBER_OF_THE_SAGES || gPlayState->sceneNum == SCENE_FISHING_POND ||
gPlayState->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) {
if (CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_MAJOR_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS ||
CVarGetInteger("gAutosave", AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
performSave = false;
return;
}
if (performSave) {
performSave = false;
performDelayedSave = true;
}
return;
}
if (performSave || performDelayedSave) {
if (performSave) {
Play_PerformSave(gPlayState);
performSave = false;
performDelayedSave = false;
}
}
}
@@ -783,7 +783,7 @@ std::map<RandomizerCheckArea, std::string> rcAreaNames = {
{ RCAREA_LAKE_HYLIA, "Lake Hylia"},
{ RCAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RCAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RCAREA_WASTELAND, "Desert Wasteland"},
{ RCAREA_WASTELAND, "Haunted Wasteland"},
{ RCAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RCAREA_MARKET, "Hyrule Market"},
{ RCAREA_HYRULE_CASTLE, "Hyrule Castle"},
@@ -452,6 +452,63 @@ bool HasItemBeenCollected(RandomizerCheck rc) {
return false;
}
void CheckTrackerLoadGame(int32_t fileNum) {
LoadSettings();
TrySetAreas();
for (auto [rc, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) {
RandomizerCheckTrackerData rcTrackerData = gSaveContext.checkTrackerData[rc];
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || rc == RC_LINKS_POCKET ||
!RandomizerCheckObjects::GetAllRCObjects().contains(rc))
continue;
RandomizerCheckObject realRcObj;
if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) {
realRcObj = RCO_RAORU;
} else {
realRcObj = rcObj;
}
if (!IsVisibleInCheckTracker(realRcObj)) continue;
checksByArea.find(realRcObj.rcArea)->second.push_back(realRcObj);
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
areaChecksGotten[realRcObj.rcArea]++;
}
if (areaChecksGotten[realRcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(realRcObj.rcArea)) {
areasSpoiled |= (1 << realRcObj.rcArea);
}
}
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE);
RandomizerCheckArea startingArea;
switch (startingAge) {
case RO_AGE_CHILD:
startingArea = RCAREA_KOKIRI_FOREST;
break;
case RO_AGE_ADULT:
startingArea = RCAREA_MARKET;
break;
default:
startingArea = RCAREA_KOKIRI_FOREST;
break;
}
RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" };
checksByArea.find(startingArea)->second.push_back(linksPocket);
areaChecksGotten[startingArea]++;
}
showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER ||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12));
LinksPocket();
SongFromImpa();
GiftFromSages();
initialized = true;
UpdateAllOrdering();
UpdateInventoryChecks();
}
void CheckTrackerDialogClosed() {
if (messageCloseCheck) {
messageCloseCheck = false;
@@ -679,9 +736,6 @@ void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
}
void LoadFile() {
Teardown();
LoadSettings();
TrySetAreas();
SaveManager::Instance->LoadArray("checks", RC_MAX, [](size_t i) {
SaveManager::Instance->LoadStruct("", [&]() {
SaveManager::Instance->LoadData("status", gSaveContext.checkTrackerData[i].status);
@@ -689,58 +743,7 @@ void LoadFile() {
SaveManager::Instance->LoadData("price", gSaveContext.checkTrackerData[i].price);
SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem);
});
RandomizerCheckTrackerData entry = gSaveContext.checkTrackerData[i];
RandomizerCheck rc = static_cast<RandomizerCheck>(i);
if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX ||
!RandomizerCheckObjects::GetAllRCObjects().contains(rc))
return;
RandomizerCheckObject entry2;
if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) {
entry2 = RCO_RAORU;
} else {
entry2 = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second;
}
if (!IsVisibleInCheckTracker(entry2)) return;
checksByArea.find(entry2.rcArea)->second.push_back(entry2);
if (entry.status == RCSHOW_SAVED || entry.skipped) {
areaChecksGotten[entry2.rcArea]++;
}
if (areaChecksGotten[entry2.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2.rcArea)) {
areasSpoiled |= (1 << entry2.rcArea);
}
});
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE);
RandomizerCheckArea startingArea;
switch (startingAge) {
case RO_AGE_CHILD:
startingArea = RCAREA_KOKIRI_FOREST;
break;
case RO_AGE_ADULT:
startingArea = RCAREA_MARKET;
break;
default:
startingArea = RCAREA_KOKIRI_FOREST;
break;
}
RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" };
checksByArea.find(startingArea)->second.push_back(linksPocket);
areaChecksGotten[startingArea]++;
}
showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER ||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12));
LinksPocket();
SongFromImpa();
GiftFromSages();
initialized = true;
UpdateAllOrdering();
UpdateInventoryChecks();
}
void Teardown() {
@@ -1533,6 +1536,7 @@ void CheckTrackerWindow::InitElement() {
SaveManager::Instance->AddInitFunction(InitTrackerData);
sectionId = SaveManager::Instance->AddSaveFunction("trackerData", 1, SaveFile, true, -1);
SaveManager::Instance->AddLoadFunction("trackerData", 1, LoadFile);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>(CheckTrackerLoadGame);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([](uint32_t fileNum) {
Teardown();
});
@@ -219,7 +219,7 @@ std::unordered_map<RandomizerTrickArea, std::string> rtAreaNames = {
{ RTAREA_LAKE_HYLIA, "Lake Hylia"},
{ RTAREA_GERUDO_VALLEY, "Gerudo Valley"},
{ RTAREA_GERUDO_FORTRESS, "Gerudo Fortress"},
{ RTAREA_WASTELAND, "Desert Wasteland"},
{ RTAREA_WASTELAND, "Haunted Wasteland"},
{ RTAREA_DESERT_COLOSSUS, "Desert Colossus"},
{ RTAREA_MARKET, "Hyrule Market"},
{ RTAREA_HYRULE_CASTLE, "Hyrule Castle"},
+15
View File
@@ -1569,6 +1569,11 @@ extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
return (Gfx*)&res->Instructions[0];
}
extern "C" uint8_t ResourceMgr_FileIsCustomByName(const char* path) {
auto res = std::static_pointer_cast<LUS::DisplayList>(GetResourceByNameHandlingMQ(path));
return res->GetInitData()->IsCustom;
}
typedef struct {
int index;
Gfx instruction;
@@ -1600,6 +1605,11 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa
// index /= 2;
// }
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* gfx = (Gfx*)&res->Instructions[index];
if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) {
@@ -1616,6 +1626,11 @@ extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const ch
auto res = std::static_pointer_cast<LUS::DisplayList>(
LUS::Context::GetInstance()->GetResourceManager()->LoadResource(path));
// Do not patch custom assets as they most likely do not have the same instructions as authentic assets
if (res->GetInitData()->IsCustom) {
return;
}
Gfx* destinationGfx = (Gfx*)&res->Instructions[destinationIndex];
Gfx sourceGfx = res->Instructions[sourceIndex];
+1
View File
@@ -101,6 +101,7 @@ AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path);
char* ResourceMgr_GetNameByCRC(uint64_t crc, char* alloc);
Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc);
Gfx* ResourceMgr_LoadGfxByName(const char* path);
uint8_t ResourceMgr_FileIsCustomByName(const char* path);
void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction);
void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path);
+63 -30
View File
@@ -47,6 +47,11 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) {
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".sav");
}
std::filesystem::path SaveManager::GetFileTempName(int fileNum) {
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
return sSavePath / ("file" + std::to_string(fileNum + 1) + ".temp");
}
SaveManager::SaveManager() {
coreSectionIDsByName["base"] = SECTION_ID_BASE;
coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER;
@@ -65,6 +70,10 @@ SaveManager::SaveManager() {
AddInitFunction(InitFileImpl);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
smThreadPool = std::make_shared<BS::thread_pool>(1);
for (SaveFileMetaInfo& info : fileMetaInfo) {
info.valid = false;
info.deaths = 0;
@@ -357,12 +366,14 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
});
}
// Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any class initialization stuff here
void SaveManager::Init() {
// Wait on saves that snuck through the Wait in OnExitGame
ThreadPoolWait();
const std::filesystem::path sSavePath(LUS::Context::GetPathRelativeToAppDirectory("Save"));
const std::filesystem::path sGlobalPath = sSavePath / std::string("global.sav");
auto sOldSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.sav");
auto sOldBackupSavePath = LUS::Context::GetPathRelativeToAppDirectory("oot_save.bak");
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([this](uint32_t fileNum) { ThreadPoolWait(); });
// If the save directory does not exist, create it
if (!std::filesystem::exists(sSavePath)) {
@@ -403,7 +414,6 @@ void SaveManager::Init() {
} else {
CreateDefaultGlobal();
}
smThreadPool = std::make_shared<BS::thread_pool>(1);
// Load files to initialize metadata
for (int fileNum = 0; fileNum < MaxFiles; fileNum++) {
@@ -869,6 +879,32 @@ void SaveManager::InitFileMaxed() {
gSaveContext.sceneFlags[5].swch = 0x40000000;
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
// Threaded SaveFile takes copy of gSaveContext for local unmodified storage
void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) {
@@ -910,19 +946,42 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se
svi.func(saveContext, sectionID, false);
}
std::filesystem::path fileName = GetFileName(fileNum);
std::filesystem::path tempFile = GetFileTempName(fileNum);
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
#if defined(__SWITCH__) || defined(__WIIU__)
FILE* w = fopen(GetFileName(fileNum).c_str(), "w");
FILE* w = fopen(tempFile.c_str(), "w");
std::string json_string = saveBlock.dump(4);
fwrite(json_string.c_str(), sizeof(char), json_string.length(), w);
fclose(w);
#else
std::ofstream output(GetFileName(fileNum));
std::ofstream output(tempFile);
output << std::setw(4) << saveBlock << std::endl;
output.close();
#endif
if (std::filesystem::exists(fileName)) {
std::filesystem::remove(fileName);
}
#if defined(__SWITCH__) || defined(__WIIU__)
copy_file(tempFile.c_str(), fileName.c_str());
#else
std::filesystem::copy_file(tempFile, fileName);
#endif
if (std::filesystem::exists(tempFile)) {
std::filesystem::remove(tempFile);
}
delete saveContext;
InitMeta(fileNum);
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveFile>(fileNum);
SPDLOG_INFO("Save File Finish - fileNum: {}", fileNum);
}
// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
@@ -2105,32 +2164,6 @@ void SaveManager::LoadStruct(const std::string& name, LoadStructFunc func) {
}
}
#if defined(__WIIU__) || defined(__SWITCH__)
// std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm
int copy_file(const char* src, const char* dst) {
alignas(0x40) uint8_t buf[4096];
FILE* r = fopen(src, "r");
if (!r) {
return -1;
}
FILE* w = fopen(dst, "w");
if (!w) {
return -2;
}
size_t res;
while ((res = fread(buf, 1, sizeof(buf), r)) > 0) {
if (fwrite(buf, 1, res, w) != res) {
break;
}
}
fclose(r);
fclose(w);
return res >= 0 ? 0 : res;
}
#endif
void SaveManager::CopyZeldaFile(int from, int to) {
assert(std::filesystem::exists(GetFileName(from)));
DeleteZeldaFile(to);
+1
View File
@@ -142,6 +142,7 @@ class SaveManager {
private:
std::filesystem::path GetFileName(int fileNum);
std::filesystem::path GetFileTempName(int fileNum);
nlohmann::json saveBlock;
void ConvertFromUnversioned();
+4
View File
@@ -532,6 +532,8 @@ void DrawEnhancementsMenu() {
" - Small keys: Small silver chest\n"
" - Boss keys: Vanilla size and texture\n"
" - Skulltula Tokens: Small skulltula chest\n"
"\n"
"NOTE: Textures will not apply if you are using a mod pack with a custom chest model."
);
if (CVarGetInteger("gChestSizeAndTextureMatchesContents", CSMC_DISABLED) != CSMC_DISABLED) {
UIWidgets::PaddedEnhancementCheckbox("Chests of Agony", "gChestSizeDependsStoneOfAgony", true, false);
@@ -1059,6 +1061,8 @@ void DrawEnhancementsMenu() {
UIWidgets::Tooltip("Fixes the bushes to drop items correctly rather than spawning undefined items.");
UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", "gFixVineFall", true, false);
UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges.");
UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", "gFixEyesOpenWhileSleeping", true, false);
UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping.");
ImGui::EndMenu();
}
+29 -25
View File
@@ -103,38 +103,42 @@ void SkeletonFactoryV0::ParseFileXML(tinyxml2::XMLElement* reader, std::shared_p
{
std::shared_ptr<Skeleton> skel = std::static_pointer_cast<Skeleton>(resource);
std::string skeletonType = reader->Attribute("Type");
// std::string skeletonLimbType = reader->Attribute("LimbType");
int numLimbs = reader->IntAttribute("LimbCount");
int numDLs = reader->IntAttribute("DisplayListCount");
skel->type = SkeletonType::Flex; // Default to Flex for legacy reasons
if (reader->FindAttribute("Type")) {
std::string skeletonType = reader->Attribute("Type");
if (skeletonType == "Flex") {
skel->type = SkeletonType::Flex;
} else if (skeletonType == "Curve") {
skel->type = SkeletonType::Curve;
} else if (skeletonType == "Normal") {
skel->type = SkeletonType::Normal;
if (skeletonType == "Flex") {
skel->type = SkeletonType::Flex;
} else if (skeletonType == "Curve") {
skel->type = SkeletonType::Curve;
} else if (skeletonType == "Normal") {
skel->type = SkeletonType::Normal;
}
}
skel->type = SkeletonType::Flex;
skel->limbType = LimbType::LOD;
skel->limbType = LimbType::LOD; // Default to LOD for legacy reasons
if (reader->FindAttribute("LimbType")) {
std::string skeletonLimbType = reader->Attribute("LimbType");
// if (skeletonLimbType == "Standard")
// skel->limbType = LimbType::Standard;
// else if (skeletonLimbType == "LOD")
// skel->limbType = LimbType::LOD;
// else if (skeletonLimbType == "Curve")
// skel->limbType = LimbType::Curve;
// else if (skeletonLimbType == "Skin")
// skel->limbType = LimbType::Skin;
// else if (skeletonLimbType == "Legacy")
// Sskel->limbType = LimbType::Legacy;
if (skeletonLimbType == "Standard") {
skel->limbType = LimbType::Standard;
} else if (skeletonLimbType == "LOD") {
skel->limbType = LimbType::LOD;
} else if (skeletonLimbType == "Curve") {
skel->limbType = LimbType::Curve;
} else if (skeletonLimbType == "Skin") {
skel->limbType = LimbType::Skin;
} else if (skeletonLimbType == "Legacy") {
skel->limbType = LimbType::Legacy;
}
}
skel->limbCount = reader->IntAttribute("LimbCount");
skel->dListCount = reader->IntAttribute("DisplayListCount");
auto child = reader->FirstChildElement();
skel->limbCount = numLimbs;
skel->dListCount = numDLs;
while (child != nullptr) {
std::string childName = child->Name();
+11
View File
@@ -204,6 +204,17 @@ u8 Inventory_DeleteEquipment(PlayState* play, s16 equipment) {
if (equipment == EQUIP_TYPE_TUNIC) {
gSaveContext.equips.equipment |= EQUIP_VALUE_TUNIC_KOKIRI << (EQUIP_TYPE_TUNIC * 4);
// non-vanilla: remove goron and zora tunics from item buttons if assignable tunics is on
if (CVarGetInteger("gAssignableTunicsAndBoots", 0) && equipValue != EQUIP_VALUE_TUNIC_KOKIRI) {
ItemID item = (equipValue == EQUIP_VALUE_TUNIC_GORON ? ITEM_TUNIC_GORON : ITEM_TUNIC_ZORA);
for (int i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) {
if (gSaveContext.equips.buttonItems[i] == item) {
gSaveContext.equips.buttonItems[i] = ITEM_NONE;
gSaveContext.equips.cButtonSlots[i - 1] = SLOT_NONE;
}
}
}
// end non-vanilla
}
if (equipment == EQUIP_TYPE_SWORD) {
+1
View File
@@ -1469,6 +1469,7 @@ void Inventory_SwapAgeEquipment(void) {
gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER;
} else {
gSaveContext.equips.buttonItems[0] = ITEM_NONE;
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
if (gSaveContext.inventory.items[SLOT_NUT] != ITEM_NONE) {
+27
View File
@@ -33,6 +33,7 @@ u64 D_801614D0[0xA00];
#endif
PlayState* gPlayState;
s16 firstInit = 0;
s16 gEnPartnerId;
@@ -490,6 +491,12 @@ void Play_Init(GameState* thisx) {
}
}
// Properly initialize the frame counter so it doesn't use garbage data
if (!firstInit) {
play->gameplayFrames = 0;
firstInit = 1;
}
// Invalid entrance, so immediately exit the game to opening title
if (gSaveContext.entranceIndex == -1) {
gSaveContext.entranceIndex = 0;
@@ -2329,8 +2336,28 @@ void Play_PerformSave(PlayState* play) {
if (play != NULL && gSaveContext.fileNum != 0xFF) {
Play_SaveSceneFlags(play);
gSaveContext.savedSceneNum = play->sceneNum;
// Track values from temp B
uint8_t prevB = gSaveContext.equips.buttonItems[0];
uint8_t prevStatus = gSaveContext.buttonStatus[0];
// Replicate the B button restore from minigames/epona that kaleido does
if (gSaveContext.equips.buttonItems[0] == ITEM_SLINGSHOT ||
gSaveContext.equips.buttonItems[0] == ITEM_BOW ||
gSaveContext.equips.buttonItems[0] == ITEM_BOMBCHU ||
gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE ||
(gSaveContext.equips.buttonItems[0] == ITEM_NONE && !Flags_GetInfTable(INFTABLE_SWORDLESS))) {
gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0];
Interface_RandoRestoreSwordless();
}
Save_SaveFile();
// Restore temp B values back
gSaveContext.equips.buttonItems[0] = prevB;
gSaveContext.buttonStatus[0] = prevStatus;
uint8_t triforceHuntCompleted =
IS_RANDO &&
gSaveContext.triforcePiecesCollected == Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) &&
+14 -2
View File
@@ -88,6 +88,7 @@ Gfx gKeyTreasureChestChestFrontDL[128] = {0};
Gfx gChristmasRedTreasureChestChestFrontDL[128] = {0};
Gfx gChristmasGreenTreasureChestChestFrontDL[128] = {0};
u8 hasCreatedRandoChestTextures = 0;
u8 hasCustomChestDLs = 0;
u8 hasChristmasChestTexturesAvailable = 0;
void EnBox_SetupAction(EnBox* this, EnBoxActionFunc actionFunc) {
@@ -690,7 +691,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
// Change texture
if (!isVanilla && (csmc == CSMC_BOTH || csmc == CSMC_TEXTURE)) {
if (!isVanilla && hasCreatedRandoChestTextures && !hasCustomChestDLs && (csmc == CSMC_BOTH || csmc == CSMC_TEXTURE)) {
switch (getItemCategory) {
case ITEM_CATEGORY_MAJOR:
this->boxBodyDL = gGoldTreasureChestChestFrontDL;
@@ -725,7 +726,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
}
if (CVarGetInteger("gLetItSnow", 0) && hasChristmasChestTexturesAvailable) {
if (CVarGetInteger("gLetItSnow", 0) && hasChristmasChestTexturesAvailable && hasCreatedRandoChestTextures && !hasCustomChestDLs) {
if (this->dyna.actor.scale.x == 0.01f) {
this->boxBodyDL = gChristmasRedTreasureChestChestFrontDL;
this->boxLidDL = gChristmasRedTreasureChestChestSideAndLidDL;
@@ -767,7 +768,18 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
void EnBox_CreateExtraChestTextures() {
// Don't patch textures for custom chest models, as they do not import textures the exact same way as vanilla chests
// OTRTODO: Make it so model packs can provide a unique DL per chest type, instead of us copying the brown chest and attempting to patch
if (ResourceMgr_FileIsCustomByName(gTreasureChestChestFrontDL) ||
ResourceMgr_FileIsCustomByName(gTreasureChestChestSideAndLidDL)) {
hasCustomChestDLs = 1;
return;
}
hasCustomChestDLs = 0;
if (hasCreatedRandoChestTextures) return;
Gfx gTreasureChestChestTextures[] = {
gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, gSkullTreasureChestFrontTex),
gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, gSkullTreasureChestSideAndTopTex),
@@ -15169,6 +15169,10 @@ void func_80852C50(PlayState* play, Player* this, CsCmdActorAction* arg2) {
sp24 = D_808547C4[this->unk_446];
func_80852B4C(play, this, linkCsAction, &D_80854E50[ABS(sp24)]);
if (CVarGetInteger("gFixEyesOpenWhileSleeping", 0) && (play->csCtx.linkAction->action == 28 || play->csCtx.linkAction->action == 29)) {
this->skelAnime.jointTable[22].x = 8;
}
}
void func_80852E14(Player* this, PlayState* play) {
@@ -4290,6 +4290,8 @@ void KaleidoScope_Update(PlayState* play)
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
Grotto_ForceGrottoReturn();
}
// Reset frame counter to prevent autosave on respawn
play->gameplayFrames = 0;
gSaveContext.nextTransitionType = 2;
gSaveContext.health = CVarGetInteger("gFullHealthSpawn", 0) ? gSaveContext.healthCapacity : 0x30;
Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA);