Merge pull request #3399 from HarbourMasters/develop-macready

macready -> dev
This commit is contained in:
Garrett Cox 2023-11-14 22:24:48 -06:00 committed by GitHub
commit 2075213544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 247 additions and 142 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")
project(Ship VERSION 8.0.0 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Alfa" CACHE STRING "")
project(Ship VERSION 8.0.1 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Bravo" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)

@ -1 +1 @@
Subproject commit 0d8f5570a8e57f302ec6633d65615ee21ab39454
Subproject commit 04b85b95fab07a394b62dcd28a502a3040f08e0c

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

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)" },

View File

@ -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);

View File

@ -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) {

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);

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) {
Play_PerformSave(gPlayState);
performSave = false;
performDelayedSave = false;
}
}
}

View File

@ -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"},

View File

@ -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();
});

View File

@ -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"},

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];

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);

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);

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();

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);
@ -1061,6 +1063,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();
}

View File

@ -103,10 +103,9 @@ void SkeletonFactoryV0::ParseFileXML(tinyxml2::XMLElement* reader, std::shared_p
{
std::shared_ptr<Skeleton> skel = std::static_pointer_cast<Skeleton>(resource);
skel->type = SkeletonType::Flex; // Default to Flex for legacy reasons
if (reader->FindAttribute("Type")) {
std::string skeletonType = reader->Attribute("Type");
// std::string skeletonLimbType = reader->Attribute("LimbType");
int numLimbs = reader->IntAttribute("LimbCount");
int numDLs = reader->IntAttribute("DisplayListCount");
if (skeletonType == "Flex") {
skel->type = SkeletonType::Flex;
@ -115,26 +114,31 @@ void SkeletonFactoryV0::ParseFileXML(tinyxml2::XMLElement* reader, std::shared_p
} else if (skeletonType == "Normal") {
skel->type = SkeletonType::Normal;
}
}
skel->type = SkeletonType::Flex;
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") {
skel->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")
// Sskel->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();

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) {

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) {

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) &&

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),

View File

@ -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) {

View File

@ -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);