Dual OTR MQ and Vanilla Support (#1694)

* Changes OTR Extraction to have specific mq and nonmq paths.

Also updates the game to load resources according to whether or not
Master Quest or Vanilla is loaded.

* Removes unneeded code from the last commit.

* Fixes some weird formatting in ZRom.c

* Loads oot-mq.otr and patches oot.otr on top, if both are present.

If only one or the other are present, it becomes the only and main OTR.

* Adds ImGui Logic for whether or an MQ Checkbox.

Checkbox checked only specifies whether new saves should be MQ or not.
Checkbox is disabled or force-enabled according to which OTRs are loaded.
Also as a necessity includes tracking what game versions have been loaded
from the OTRs.

* Adds MQ settings logic for Randomizer's ImGui menu.

* Writes Master Quest dungeons to the spoiler log

* Loads MQ Dungeons from spoiler, persists in save, and loads when appropriate.

* Adds logic to prevent loading or creating incompatible rando saves.

* Fixdes some linux build issues and new rando save issues

* Makes appimage create both vanilla and mq otrs

If either rom is present, it makes the corresponding OTR. If both are present,
it will make both. If one OTR is present but both roms are present, it will
create the missing OTR.

* Makes it so a randomized save file will not be marked as MQ.

* Refactors to load all OTRs from MainPath or a specific list.

Also adds the ability to take a std::unordered_set of hashes to
validate each OTR's version file against.

* Fixes a syntax error

* Makes ExtractAssets output Vanilla and MQ OTRs if both roms are present

* Fixes asset generation bug.

* Partially working fix for dual OTR extract_assets

Currently the cmake ExtractAssets target will return with a 1 if you
only end up exporting one type of OTR isntead of both. Haven't found
a great way to only attempt to copy a file if it exists from within
cmake. It does actually correctly copy the OTR that is generated,
despite the error from copying the other one.

Pushing as is for now but will keep investigating.

* Adds oot-mq.otr to the gitignore.

* Makes ExtractAssets not fail on only one rom/OTR.

* Removes PatchesPath from the constructors requiring OTRFiles vector.

* Renames OOT_UNKNOWN to just UNKNOWN to remove OOT specific reference.

* Removes randomizing MQ Dungeons and re-disables MQ rando.

Doing this so the PR can get merged quicker with just the Dual OTR
support and won't need to wait on rando logic to be updated. That
will happen in another PR directly after the merge.

* Update mac startup script for dual otr

* Update soh/macosx/soh-macos.sh

* Update soh/macosx/soh-macos.sh

* Update soh/macosx/soh-macos.sh

* Implements new BinaryReader to fix Linux build issue.

BinaryReader itself comes from https://github.com/Moneyl/BinaryTools
I added a wrapper to adapt it to the ABI from ZAPD's Binary Reader and
add Endianness checking. I also had to copy a handful of other bits and
pieces from ZAPD to make it all function as expected.

* A few edits to the updatream BinaryReader to compile it on Linux.

* Adds the Endianness to the first byte of the version file.

* Fixes Jenkins

* Addresses some of Kenix's comments

* Renames `ReadNullTerminatedString` to `ReadCString`

* Refactors Archive::LoadFile into a private method with more arguments.

* Removes BitConverter and extends existing endianness.h instead.

* Fixes an endianness issue with the version file.

Co-authored-by: Garrett Cox <garrettjcox@gmail.com>
This commit is contained in:
Christopher Leggett 2022-10-16 23:07:35 -04:00 committed by GitHub
commit 7b08f98b8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 3149 additions and 485 deletions

View file

@ -188,6 +188,7 @@ typedef struct {
u8 temporaryWeapon;
u16 adultTradeItems;
u8 pendingIceTrapCount;
u8 mqDungeonCount;
} SaveContext; // size = 0x1428
typedef enum {

View file

@ -7,67 +7,150 @@ export RESPATH="${SNAME%/MacOS*}/Resources"
export LIBPATH="${SNAME%/MacOS*}/Frameworks"
export DYLD_FALLBACK_LIBRARY_PATH="$LIBPATH"
while [ ! -e "$DATA_SHARE/oot.otr" ]; do
ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)"
export ASSETDIR
cp -r "$RESPATH/assets" "$ASSETDIR"
mkdir -p "$ASSETDIR"/tmp
mkdir -p "$ASSETDIR"/Extract
DROPROM="$(osascript -ss - "$ASSETDIR" <<-EOF
set romFile to choose file of type {"b64","n64","v64","z64"} with prompt "Please select your ROM:"
set destinationFolder to POSIX file "$ASSETDIR"
tell application "Finder"
duplicate romFile to destinationFolder
end tell
EOF
)"
"$DROPROM"
for rom in "$ASSETDIR"/*.*64
if [ ! -e "$DATA_SHARE" ]; then mkdir "$DATA_SHARE"; fi
# If either OTR doesn't exist kick off the OTR gen process
if [ ! -e "$DATA_SHARE"/oot.otr ] || [ ! -e "$DATA_SHARE"/oot-mq.otr ]; then
# If no ROMs exist kick off the file selection prompts
while [ ! -e "$DATA_SHARE"/*.*64 ] && [ ! -e "$DATA_SHARE"/oot*.otr ]; do
SHOULD_PROMPT_FOR_ROM=1
while [ $SHOULD_PROMPT_FOR_ROM -eq 1 ]; do
SHOULD_PROMPT_FOR_ROM=0
# Use osascript to prompt the user to chose a file
DROPROM=`osascript <<-EOF
set romFile to choose file of type {"b64","n64","v64","z64"} with prompt "Please select your ROM:"
return POSIX path of romFile
EOF`
# If no rom was selected, the user cancelled, so exit
if [[ -z $DROPROM ]] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then
echo "No ROM selected. Exiting..."
exit 1
elif [[ -z $DROPROM ]]; then
break;
fi
# If an invalid rom was selected, let the user know and ask to try again
ROMHASH="$(shasum "$DROPROM" | awk '{ print $1 }')"
case "$ROMHASH" in
cee6bc3c2a634b41728f2af8da54d9bf8cc14099)
ROM_TYPE=0;;
0227d7c0074f2d0ac935631990da8ec5914597b4)
ROM_TYPE=0;;
50bebedad9e0f10746a52b07239e47fa6c284d03)
ROM_TYPE=1;;
079b855b943d6ad8bd1eb026c0ed169ecbdac7da)
ROM_TYPE=1;;
517bd9714c73cb96c21e7c2ef640d7b55186102f)
ROM_TYPE=1;;
*)
TRY_AGAIN_RESULT=`osascript <<-EOF
set alertText to "Incompatible ROM hash"
set alertMessage to "Incompatible ROM provided, would you like to try again?"
return display alert alertText \
message alertMessage \
as critical \
buttons {"Cancel", "Try Again"}
EOF`
if [[ "$TRY_AGAIN_RESULT" == "button returned:Try Again" ]]; then
SHOULD_PROMPT_FOR_ROM=1
continue;
else
echo "No ROM selected. Exiting..."
exit 1
fi
esac
cp "$DROPROM" "$DATA_SHARE"
# Ask user if they would also like to select the other variant (MQ/Vanilla)
if [ $ROM_TYPE -eq 0 ] && [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then
UPLOAD_ANOTHER_RESULT=`osascript <<-EOF
set alertText to "Success"
set alertMessage to "Would you also like to provide a Master Quest ROM?"
return display alert alertText \
message alertMessage \
buttons {"No", "Yes"}
EOF`
elif [[ -z "$UPLOAD_ANOTHER_RESULT" ]]; then
UPLOAD_ANOTHER_RESULT=`osascript <<-EOF
set alertText to "Success"
set alertMessage to "Would you also like to provide a Vanilla (Non Master Quest) ROM?"
return display alert alertText \
message alertMessage \
buttons {"No", "Yes"}
EOF`
fi
if [[ "$UPLOAD_ANOTHER_RESULT" == "button returned:Yes" ]]; then
UPLOAD_ANOTHER_RESULT="button returned:No"
SHOULD_PROMPT_FOR_ROM=1
continue;
fi
break
done
done
# At this point we should now have 1 or more valid roms in $DATA_SHARE directory
# Prepare tmp dir
for ROMPATH in "$DATA_SHARE"/*.*64
do
if [ ! -e "$rom" ]; then
echo "no ROM"
osascript -e 'display dialog "Select ROM to generate OTR" giving up after 5'
ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)"
export ASSETDIR
cp -r "$RESPATH/assets" "$ASSETDIR"
mkdir -p "$ASSETDIR"/tmp
mkdir -p "$ASSETDIR"/Extract
cp "$ROMPATH" "$ASSETDIR"/tmp/rom.z64
cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets/
cd "$ASSETDIR" || return
# If an invalid rom was detected, let the user know
ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')"
case "$ROMHASH" in
cee6bc3c2a634b41728f2af8da54d9bf8cc14099)
ROM=GC_NMQ_D
OTRNAME="oot.otr";;
0227d7c0074f2d0ac935631990da8ec5914597b4)
ROM=GC_NMQ_PAL_F
OTRNAME="oot.otr";;
50bebedad9e0f10746a52b07239e47fa6c284d03)
ROM=GC_MQ_D
OTRNAME="oot-mq.otr";;
079b855b943d6ad8bd1eb026c0ed169ecbdac7da)
ROM=GC_MQ_D
OTRNAME="oot-mq.otr";;
517bd9714c73cb96c21e7c2ef640d7b55186102f)
ROM=GC_MQ_D
OTRNAME="oot-mq.otr";;
*)
osascript -e 'display notification "One or more invalid ROM provided" with title "Ship Of Harkinian"'
rm -r "$ASSETDIR"
continue;
esac
# Only generate OTR if we don't have on of this type yet
if [ -e "$DATA_SHARE"/"$OTRNAME" ]; then
rm -r "$ASSETDIR"
continue;
fi
osascript -e 'display notification "Generating OTR..." with title "Ship Of Harkinian"'
assets/extractor/ZAPD.out ed -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR
if [ -e "$ASSETDIR"/oot.otr ]; then
osascript -e 'display notification "OTR successfully generated" with title "Ship Of Harkinian"'
cp "$ASSETDIR"/oot.otr "$DATA_SHARE"/"$OTRNAME"
rm -r "$ASSETDIR"
exit
fi
done
cp "$ASSETDIR"/*.*64 "$ASSETDIR"/tmp/rom.z64
cp -r "$ASSETDIR"/assets/game "$ASSETDIR"/Extract/assets/
cd "$ASSETDIR" || return
ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')"
case "$ROMHASH" in
cee6bc3c2a634b41728f2af8da54d9bf8cc14099)
export ROM=GC_NMQ_D;;
0227d7c0074f2d0ac935631990da8ec5914597b4)
export ROM=GC_NMQ_PAL_F;;
50bebedad9e0f10746a52b07239e47fa6c284d03)
export ROM=GC_MQ_D;;
079b855b943d6ad8bd1eb026c0ed169ecbdac7da)
export ROM=GC_MQ_D;;
*)
WRONGHASH="$(osascript -ss - "$ROMHASH" <<-EOF
display dialog "Incompatible ROM hash $ROMHASH" \
with title "Incompatible ROM hash" \
with icon caution \
giving up after 5
EOF
)"
"$WRONGHASH"
rm -r "$ASSETDIR"
exit;;
esac
echo "$ROM"
osascript -e 'display notification "Processing OTR..." with title "SOH: Generating OTR"'
assets/extractor/ZAPD.out ed -i assets/extractor/xmls/"${ROM}" -b tmp/rom.z64 -fl assets/extractor/filelists -o placeholder -osf placeholder -gsf 1 -rconf assets/extractor/Config_"${ROM}".xml -se OTR
if [ -e "$PWD"/oot.otr ]; then
osascript -e 'display notification "OTR Successfully Generated" with title "SOH: Generating OTR"'
if [ ! -e "$DATA_SHARE" ]; then mkdir "$DATA_SHARE"; fi
cp "$ASSETDIR"/oot.otr "$DATA_SHARE"
rm -r "$ASSETDIR"
fi
break
done
if [ ! -e "$DATA_SHARE"/oot*.otr ]; then
osascript -e 'display notification "OTR failed to generate" with title "Ship Of Harkinian"'
exit 1;
fi
fi
arch_name="$(uname -m)"
launch_arch="arm64"

View file

@ -27,10 +27,11 @@ void BootCommands_Init()
CVar_RegisterS32("gHudColors", 0); //0 = N64 / 1 = NGC / 2 = Custom
CVar_RegisterS32("gInvertYAxis", 1);
CVar_RegisterS32("gTrailDuration", 4); // 4 = Default trail duration
if (ResourceMgr_IsGameMasterQuest()) {
if (ResourceMgr_GameHasMasterQuest() && !ResourceMgr_GameHasOriginal()) {
CVar_SetS32("gMasterQuest", 1);
CVar_SetS32("gRandomizer", 0);
} else {
CVar_RegisterS32("gRandomizer", 0);
} else if (!ResourceMgr_GameHasMasterQuest()) {
CVar_SetS32("gMasterQuest", 0);
}
#if defined(__SWITCH__) || defined(__WIIU__)
CVar_RegisterS32("gControlNav", 1); // always enable controller nav on switch/wii u

View file

@ -2535,6 +2535,13 @@ namespace Settings {
} else {
GanonsTrialsCount.SetSelectedIndex(cvarSettings[RSK_TRIAL_COUNT]);
}
if (cvarSettings[RSK_RANDOM_MQ_DUNGEONS] == 2) {
MQDungeonCount.SetSelectedIndex(13);
} else if (cvarSettings[RSK_RANDOM_MQ_DUNGEONS] == 0) {
MQDungeonCount.SetSelectedIndex(0);
} else {
MQDungeonCount.SetSelectedIndex(cvarSettings[RSK_MQ_DUNGEON_COUNT]);
}
ShuffleRewards.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS]);
ShuffleSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_SONGS]);
Tokensanity.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_TOKENS]);

View file

@ -477,20 +477,13 @@ static void WriteEnabledGlitches(tinyxml2::XMLDocument& spoilerLog) {
// Writes the Master Quest dungeons to the spoiler log, if there are any.
static void WriteMasterQuestDungeons(tinyxml2::XMLDocument& spoilerLog) {
auto parentNode = spoilerLog.NewElement("master-quest-dungeons");
for (const auto* dungeon : Dungeon::dungeonList) {
if (dungeon->IsVanilla()) {
continue;
for (const auto* dungeon : Dungeon::dungeonList) {
std::string dungeonName;
if (dungeon->IsVanilla()) {
continue;
}
jsonData["masterQuestDungeons"].push_back(dungeon->GetName());
}
auto node = parentNode->InsertNewChildElement("dungeon");
node->SetAttribute("name", dungeon->GetName().c_str());
}
if (!parentNode->NoChildren()) {
spoilerLog.RootElement()->InsertEndChild(parentNode);
}
}
// Writes the required trials to the spoiler log, if there are any.
@ -741,7 +734,7 @@ const char* SpoilerLog_Write(int language) {
//if (Settings::Logic.Is(LOGIC_GLITCHED)) {
// WriteEnabledGlitches(spoilerLog);
//}
//WriteMasterQuestDungeons(spoilerLog);
WriteMasterQuestDungeons(spoilerLog);
WriteRequiredTrials();
WritePlaythrough();
//WriteWayOfTheHeroLocation(spoilerLog);

View file

@ -121,48 +121,62 @@ std::unordered_map<std::string, RandomizerInf> spoilerFileTrialToEnum = {
{ "l'épreuve de la Lumière", RAND_INF_TRIALS_DONE_LIGHT_TRIAL }
};
std::unordered_map<s16, s16> getItemIdToItemId = {
{ GI_BOW, ITEM_BOW },
{ GI_ARROW_FIRE, ITEM_ARROW_FIRE },
{ GI_DINS_FIRE, ITEM_DINS_FIRE },
{ GI_SLINGSHOT, ITEM_SLINGSHOT },
{ GI_OCARINA_FAIRY, ITEM_OCARINA_FAIRY },
{ GI_OCARINA_OOT, ITEM_OCARINA_TIME },
{ GI_HOOKSHOT, ITEM_HOOKSHOT },
{ GI_LONGSHOT, ITEM_LONGSHOT },
{ GI_ARROW_ICE, ITEM_ARROW_ICE },
{ GI_FARORES_WIND, ITEM_FARORES_WIND },
{ GI_BOOMERANG, ITEM_BOOMERANG },
{ GI_LENS, ITEM_LENS },
{ GI_HAMMER, ITEM_HAMMER },
{ GI_ARROW_LIGHT, ITEM_ARROW_LIGHT },
{ GI_NAYRUS_LOVE, ITEM_NAYRUS_LOVE },
{ GI_BOTTLE, ITEM_BOTTLE },
{ GI_POTION_RED, ITEM_POTION_RED },
{ GI_POTION_GREEN, ITEM_POTION_GREEN },
{ GI_POTION_BLUE, ITEM_POTION_BLUE },
{ GI_FAIRY, ITEM_FAIRY },
{ GI_FISH, ITEM_FISH },
{ GI_MILK_BOTTLE, ITEM_MILK_BOTTLE },
{ GI_LETTER_RUTO, ITEM_LETTER_RUTO },
{ GI_BLUE_FIRE, ITEM_BLUE_FIRE },
{ GI_BUGS, ITEM_BUG },
{ GI_BIG_POE, ITEM_BIG_POE },
{ GI_POE, ITEM_POE },
{ GI_WEIRD_EGG, ITEM_WEIRD_EGG },
{ GI_LETTER_ZELDA, ITEM_LETTER_ZELDA },
{ GI_POCKET_EGG, ITEM_POCKET_EGG },
{ GI_COJIRO, ITEM_COJIRO },
{ GI_ODD_MUSHROOM, ITEM_ODD_MUSHROOM },
{ GI_ODD_POTION, ITEM_ODD_POTION },
{ GI_SAW, ITEM_SAW },
{ GI_SWORD_BROKEN, ITEM_SWORD_BROKEN },
{ GI_PRESCRIPTION, ITEM_PRESCRIPTION },
{ GI_FROG, ITEM_FROG },
{ GI_EYEDROPS, ITEM_EYEDROPS },
{ GI_CLAIM_CHECK, ITEM_CLAIM_CHECK }
std::unordered_map<std::string, SceneID> spoilerFileDungeonToScene = {
{ "Deku Tree", SCENE_YDAN },
{ "Dodongo's Cavern", SCENE_DDAN },
{ "Jabu Jabu's Belly", SCENE_BDAN },
{ "Forest Temple", SCENE_BMORI1 },
{ "Fire Temple", SCENE_HIDAN },
{ "Water Temple", SCENE_MIZUSIN },
{ "Spirit Temple", SCENE_JYASINZOU },
{ "Shadow Temple", SCENE_HAKADAN },
{ "Bottom of the Well", SCENE_HAKADANCH },
{ "Ice Cavern", SCENE_ICE_DOUKUTO },
{ "Gerudo Training Grounds", SCENE_MEN },
{ "Ganon's Castle", SCENE_GANONTIKA }
};
std::unordered_map<s16, s16>
getItemIdToItemId = { { GI_BOW, ITEM_BOW },
{ GI_ARROW_FIRE, ITEM_ARROW_FIRE },
{ GI_DINS_FIRE, ITEM_DINS_FIRE },
{ GI_SLINGSHOT, ITEM_SLINGSHOT },
{ GI_OCARINA_FAIRY, ITEM_OCARINA_FAIRY },
{ GI_OCARINA_OOT, ITEM_OCARINA_TIME },
{ GI_HOOKSHOT, ITEM_HOOKSHOT },
{ GI_LONGSHOT, ITEM_LONGSHOT },
{ GI_ARROW_ICE, ITEM_ARROW_ICE },
{ GI_FARORES_WIND, ITEM_FARORES_WIND },
{ GI_BOOMERANG, ITEM_BOOMERANG },
{ GI_LENS, ITEM_LENS },
{ GI_HAMMER, ITEM_HAMMER },
{ GI_ARROW_LIGHT, ITEM_ARROW_LIGHT },
{ GI_NAYRUS_LOVE, ITEM_NAYRUS_LOVE },
{ GI_BOTTLE, ITEM_BOTTLE },
{ GI_POTION_RED, ITEM_POTION_RED },
{ GI_POTION_GREEN, ITEM_POTION_GREEN },
{ GI_POTION_BLUE, ITEM_POTION_BLUE },
{ GI_FAIRY, ITEM_FAIRY },
{ GI_FISH, ITEM_FISH },
{ GI_MILK_BOTTLE, ITEM_MILK_BOTTLE },
{ GI_LETTER_RUTO, ITEM_LETTER_RUTO },
{ GI_BLUE_FIRE, ITEM_BLUE_FIRE },
{ GI_BUGS, ITEM_BUG },
{ GI_BIG_POE, ITEM_BIG_POE },
{ GI_POE, ITEM_POE },
{ GI_WEIRD_EGG, ITEM_WEIRD_EGG },
{ GI_LETTER_ZELDA, ITEM_LETTER_ZELDA },
{ GI_POCKET_EGG, ITEM_POCKET_EGG },
{ GI_COJIRO, ITEM_COJIRO },
{ GI_ODD_MUSHROOM, ITEM_ODD_MUSHROOM },
{ GI_ODD_POTION, ITEM_ODD_POTION },
{ GI_SAW, ITEM_SAW },
{ GI_SWORD_BROKEN, ITEM_SWORD_BROKEN },
{ GI_PRESCRIPTION, ITEM_PRESCRIPTION },
{ GI_FROG, ITEM_FROG },
{ GI_EYEDROPS, ITEM_EYEDROPS },
{ GI_CLAIM_CHECK, ITEM_CLAIM_CHECK } };
std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEnum = {
{ "Open Settings:Forest", RSK_FOREST },
{ "Open Settings:Kakariko Gate", RSK_KAK_GATE },
@ -212,6 +226,7 @@ std::unordered_map<std::string, RandomizerSettingKey> SpoilerfileSettingNameToEn
{ "Timesaver Settings:Complete Mask Quest", RSK_COMPLETE_MASK_QUEST },
{ "Timesaver Settings:Skip Scarecrow's Song", RSK_SKIP_SCARECROWS_SONG },
{ "Timesaver Settings:Enable Glitch-Useful Cutscenes", RSK_ENABLE_GLITCH_CUTSCENES },
{ "World Settings:MQ Dungeon Count", RSK_MQ_DUNGEON_COUNT }
};
std::string sanitize(std::string stringValue) {
@ -491,6 +506,13 @@ void Randomizer::LoadRequiredTrials(const char* spoilerFileName) {
}
}
void Randomizer::LoadMasterQuestDungeons(const char* spoilerFileName) {
if (strcmp(spoilerFileName, "") != 0) {
ParseMasterQuestDungeonsFile(spoilerFileName);
}
gSaveContext.mqDungeonCount = this->masterQuestDungeons.size();
}
void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
if (!spoilerFileStream)
@ -631,6 +653,7 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
case RSK_SHUFFLE_ADULT_TRADE:
case RSK_SHUFFLE_MAGIC_BEANS:
case RSK_RANDOM_TRIALS:
case RSK_RANDOM_MQ_DUNGEONS:
case RSK_STARTING_DEKU_SHIELD:
case RSK_STARTING_KOKIRI_SWORD:
case RSK_COMPLETE_MASK_QUEST:
@ -806,6 +829,13 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) {
gSaveContext.randoSettings[index].value = 3;
}
break;
case RSK_MQ_DUNGEON_COUNT:
if (it.value() == "Random") {
gSaveContext.randoSettings[index].value = 13;
}
numericValueString = it.value();
gSaveContext.randoSettings[index].value = std::stoi(numericValueString);
break;
}
}
}
@ -987,6 +1017,27 @@ void Randomizer::ParseRequiredTrialsFile(const char* spoilerFileName) {
}
}
void Randomizer::ParseMasterQuestDungeonsFile(const char* spoilerFileName) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
if (!spoilerFileStream) {
return;
}
this->masterQuestDungeons.clear();
try {
json spoilerFileJson;
spoilerFileStream >> spoilerFileJson;
json mqDungeonsJson = spoilerFileJson["masterQuestDungeons"];
for (auto it = mqDungeonsJson.begin(); it != mqDungeonsJson.end(); it++) {
this->masterQuestDungeons.emplace(spoilerFileDungeonToScene[it.value()]);
}
} catch (const std::exception& e) {
return;
}
}
void Randomizer::ParseItemLocationsFile(const char* spoilerFileName, bool silent) {
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
if (!spoilerFileStream)
@ -3733,23 +3784,36 @@ void GenerateRandomizerImgui() {
// Link's Pocket has to have a dungeon reward if the other rewards are shuffled to end of dungeon.
cvarSettings[RSK_LINKS_POCKET] = CVar_GetS32("gRandomizeShuffleDungeonReward", 0) != 0 ?
CVar_GetS32("gRandomizeLinksPocket", 0) : 0;
// todo: this efficently when we build out cvar array support
std::set<RandomizerCheck> excludedLocations;
std::stringstream excludedLocationStringStream(CVar_GetString("gRandomizeExcludedLocations", ""));
std::string excludedLocationString;
while(getline(excludedLocationStringStream, excludedLocationString, ',')) {
excludedLocations.insert((RandomizerCheck)std::stoi(excludedLocationString));
if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) {
// If both OTRs are loaded.
cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = CVar_GetS32("gRandomizeMqDungeons", 0);
cvarSettings[RSK_MQ_DUNGEON_COUNT] = CVar_GetS32("gRandomizeMqDungeonCount", 0);
} else if (OTRGlobals::Instance->HasMasterQuest()) {
// If only Master Quest is loaded.
cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = 1;
cvarSettings[RSK_MQ_DUNGEON_COUNT] = 12;
} else {
// If only Original Quest is loaded.
cvarSettings[RSK_RANDOM_MQ_DUNGEONS] = 1;
cvarSettings[RSK_MQ_DUNGEON_COUNT] = 0;
}
RandoMain::GenerateRando(cvarSettings, excludedLocations);
// todo: this efficently when we build out cvar array support
std::set<RandomizerCheck> excludedLocations;
std::stringstream excludedLocationStringStream(CVar_GetString("gRandomizeExcludedLocations", ""));
std::string excludedLocationString;
while (getline(excludedLocationStringStream, excludedLocationString, ',')) {
excludedLocations.insert((RandomizerCheck)std::stoi(excludedLocationString));
}
CVar_SetS32("gRandoGenerating", 0);
CVar_Save();
CVar_Load();
RandoMain::GenerateRando(cvarSettings, excludedLocations);
generated = 1;
}
CVar_SetS32("gRandoGenerating", 0);
CVar_Save();
CVar_Load();
generated = 1;
}
void DrawRandoEditor(bool& open) {
if (generated) {
@ -3762,11 +3826,6 @@ void DrawRandoEditor(bool& open) {
return;
}
if (ResourceMgr_IsGameMasterQuest()) {
ImGui::Text("Master Quest Randomizer is not currently supported.");
return;
}
// Randomizer settings
// Logic Settings
const char* randoLogicRules[2] = { "Glitchless", "No logic" };
@ -3780,6 +3839,7 @@ void DrawRandoEditor(bool& open) {
const char* randoRainbowBridge[7] = { "Vanilla", "Always open", "Stones", "Medallions",
"Dungeon rewards", "Dungeons", "Tokens" };
const char* randoGanonsTrial[3] = { "Skip", "Set Number", "Random Number" };
const char* randoMqDungeons[3] = { "None", "Set Number", "Random Number" };
// World Settings
const char* randoStartingAge[3] = { "Child", "Adult", "Random" };
@ -3830,6 +3890,14 @@ void DrawRandoEditor(bool& open) {
return;
}
if (OTRGlobals::Instance->HasMasterQuest() && !OTRGlobals::Instance->HasOriginal()) {
ImGui::Text("Coming Soon! Randomizer is currently not compatible with Master Quest Dungeons.\nFor now, please "
"generate an "
"OTR using a non-Master Quest rom to play the Randomizer");
ImGui::End();
return;
}
bool disableEditingRandoSettings = CVar_GetS32("gRandoGenerating", 0) || CVar_GetS32("gOnFileSelectNameEntry", 0);
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, disableEditingRandoSettings);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * (disableEditingRandoSettings ? 0.5f : 1.0f));
@ -4071,6 +4139,29 @@ void DrawRandoEditor(bool& open) {
UIWidgets::PaddedSeparator();
//MQ Dungeons - Commented out until Logic can be updated to account for MQ Dungeons
// if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) {
// ImGui::PushItemWidth(-FLT_MIN);
// ImGui::Text("Master Quest Dungeons");
// UIWidgets::InsertHelpHoverText(
// "Sets the number of Master Quest Dungeons that are shuffled into the pool.\n"
// "\n"
// "None - All Dungeons will be their Vanilla versions.\n"
// "\n"
// "Set Number - Select a number of dungeons that will be their Master Quest versions"
// "using the slider below. Which dungeons are set to be the Master Quest variety will be random.\n"
// "\n"
// "Random Number - A Random number and set of dungeons will be their Master Quest varieties."
// );
// UIWidgets::EnhancementCombobox("gRandomizeMqDungeons", randoMqDungeons, 3, 1);
// ImGui::PopItemWidth();
// if (CVar_GetS32("gRandomizeMqDungeons", 1) == 1) {
// ImGui::Dummy(ImVec2(0.0f, 0.0f));
// UIWidgets::EnhancementSliderInt("Master Quest Dungeon Count: %d", "##RandoMqDungeonCount",
// "gRandomizeMqDungeonCount", 1, 12, "", 12, true);
// }
// }
ImGui::EndChild();
// COLUMN 3 - Shuffle Entrances

View file

@ -1,6 +1,7 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <string>
#include "../../../include/ultra64.h"
#include "../../../include/z64item.h"
@ -24,6 +25,7 @@ class Randomizer {
void ParseRandomizerSettingsFile(const char* spoilerFileName);
void ParseHintLocationsFile(const char* spoilerFileName);
void ParseRequiredTrialsFile(const char* spoilerFileName);
void ParseMasterQuestDungeonsFile(const char* spoilerFileName);
void ParseItemLocationsFile(const char* spoilerFileName, bool silent);
bool IsItemVanilla(RandomizerGet randoGet);
GetItemEntry GetItemEntryFromRGData(RandomizerGetData rgData, GetItemID ogItemId, bool checkObtainability = true);
@ -41,6 +43,7 @@ class Randomizer {
// Public for now to be accessed by SaveManager, will be made private again soon :tm:
std::unordered_map<RandomizerInf, bool> trialsRequired;
std::unordered_set<uint16_t> masterQuestDungeons;
std::unordered_map<RandomizerCheck, u16> merchantPrices;
static Sprite* GetSeedTexture(uint8_t index);
@ -52,6 +55,7 @@ class Randomizer {
void LoadMerchantMessages(const char* spoilerFileName);
void LoadItemLocations(const char* spoilerFileName, bool silent);
void LoadRequiredTrials(const char* spoilerFileName);
void LoadMasterQuestDungeons(const char* spoilerFileName);
bool IsTrialRequired(RandomizerInf trial);
u8 GetRandoSettingValue(RandomizerSettingKey randoSettingKey);
RandomizerCheck GetCheckFromActor(s16 actorId, s16 sceneNum, s16 actorParams);

View file

@ -1034,6 +1034,8 @@ typedef enum {
RSK_ENABLE_BOMBCHU_DROPS,
RSK_BOMBCHUS_IN_LOGIC,
RSK_LINKS_POCKET,
RSK_RANDOM_MQ_DUNGEONS,
RSK_MQ_DUNGEON_COUNT,
RSK_MAX
} RandomizerSettingKey;

View file

@ -26,6 +26,7 @@
#include "include/global.h"
#include "include/z64audio.h"
#include "soh/SaveManager.h"
#include "OTRGlobals.h"
#define EXPERIMENTAL() \
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 50, 50, 255)); \
@ -94,6 +95,35 @@ namespace GameMenuBar {
Audio_SetGameVolume(SEQ_SFX, CVar_GetFloat("gFanfareVolume", 1));
}
bool MasterQuestCheckboxDisabled() {
return !(OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal())
|| CVar_GetS32("gRandomizer", 0);
}
std::string MasterQuestDisabledTooltip() {
std::string tooltip = "";
if (CVar_GetS32("gRandomizer", 0)) {
tooltip = "This option is disabled because you have the Randomizer enabled.\nRandomizer has it's own "
"settings surrounding Master Quest dungeons\nthat you can set from the Randomizer Settings Menu.";
}
if (!OTRGlobals::Instance->HasOriginal()) {
tooltip = "This option is force-enabled because you have only loaded the\noot-mq.otr file. If you wish to "
"play the Original Quest,\nplease provide the oot.otr file.";
}
if (!OTRGlobals::Instance->HasMasterQuest()) {
tooltip = "This option is disabled because you have only loaded the\noot.otr file. If you wish to play "
"the Master Quest,\nplease proivde the oot-mq.otr file.";
}
return tooltip;
}
UIWidgets::CheckboxGraphics MasterQuestDisabledGraphic() {
if (!OTRGlobals::Instance->HasOriginal()) {
return UIWidgets::CheckboxGraphics::Checkmark;
}
return UIWidgets::CheckboxGraphics::Cross;
}
void applyEnhancementPresetDefault(void) {
// D-pad Support on Pause
CVar_SetS32("gDpadPause", 0);
@ -1254,6 +1284,10 @@ namespace GameMenuBar {
UIWidgets::Tooltip("Holding down B skips text");
UIWidgets::PaddedEnhancementCheckbox("Free Camera", "gFreeCamera", true, false);
UIWidgets::Tooltip("Enables camera control\nNote: You must remap C buttons off of the right stick in the controller config menu, and map the camera stick to the right stick.");
UIWidgets::PaddedEnhancementCheckbox("Master Quest", "gMasterQuest", true, false, MasterQuestCheckboxDisabled(), MasterQuestDisabledTooltip().c_str(), MasterQuestDisabledGraphic());
UIWidgets::Tooltip("Enables Master Quest.\n\nWhen checked, any non-rando save files you create will be "
"Master Quest save files. Master Quest save files will still have Master Quest dungeons "
"regardless of this setting and require oot-mq.otr to be present in order to play.");
#ifdef __SWITCH__
UIWidgets::Spacer(0);

View file

@ -77,14 +77,78 @@ CustomMessageManager* CustomMessageManager::Instance;
ItemTableManager* ItemTableManager::Instance;
OTRGlobals::OTRGlobals() {
context = Ship::Window::CreateInstance("Ship of Harkinian");
std::vector<std::string> OTRFiles;
std::string mqPath = Ship::Window::GetPathRelativeToAppDirectory("oot-mq.otr");
if (std::filesystem::exists(mqPath)) {
OTRFiles.push_back(mqPath);
}
std::string ootPath = Ship::Window::GetPathRelativeToAppDirectory("oot.otr");
if (std::filesystem::exists(ootPath)) {
OTRFiles.push_back(ootPath);
}
std::unordered_set<uint32_t> ValidHashes = {
OOT_PAL_MQ,
OOT_NTSC_JP_MQ,
OOT_NTSC_US_MQ,
OOT_PAL_GC_MQ_DBG,
OOT_NTSC_10,
OOT_NTSC_11,
OOT_NTSC_12,
OOT_PAL_10,
OOT_PAL_11,
OOT_NTSC_JP_GC_CE,
OOT_NTSC_JP_GC,
OOT_NTSC_US_GC,
OOT_PAL_GC,
OOT_PAL_GC_DBG1,
OOT_PAL_GC_DBG2
};
context = Ship::Window::CreateInstance("Ship of Harkinian", OTRFiles, ValidHashes);
gSaveStateMgr = std::make_shared<SaveStateMgr>();
gRandomizer = std::make_shared<Randomizer>();
hasMasterQuest = hasOriginal = false;
auto versions = context->GetResourceManager()->GetGameVersions();
for (uint32_t version : versions) {
switch (version) {
case OOT_PAL_MQ:
case OOT_NTSC_JP_MQ:
case OOT_NTSC_US_MQ:
case OOT_PAL_GC_MQ_DBG:
hasMasterQuest = true;
break;
case OOT_NTSC_10:
case OOT_NTSC_11:
case OOT_NTSC_12:
case OOT_PAL_10:
case OOT_PAL_11:
case OOT_NTSC_JP_GC_CE:
case OOT_NTSC_JP_GC:
case OOT_NTSC_US_GC:
case OOT_PAL_GC:
case OOT_PAL_GC_DBG1:
case OOT_PAL_GC_DBG2:
hasOriginal = true;
break;
default:
break;
}
}
}
OTRGlobals::~OTRGlobals() {
}
bool OTRGlobals::HasMasterQuest() {
return hasMasterQuest;
}
bool OTRGlobals::HasOriginal() {
return hasOriginal;
}
struct ExtensionEntry {
std::string path;
std::string ext;
@ -341,7 +405,10 @@ extern "C" void InitOTR() {
if (!t->bHasLoadError)
{
uint32_t gameVersion = LE32SWAP(*((uint32_t*)t->buffer.get()));
Ship::BinaryReader reader(t->buffer.get(), t->dwBufferSize);
Ship::Endianness endianness = (Ship::Endianness)reader.ReadUByte();
reader.SetEndianness(endianness);
uint32_t gameVersion = reader.ReadUInt32();
OTRGlobals::Instance->context->GetResourceManager()->SetGameVersion(gameVersion);
}
@ -549,32 +616,37 @@ extern "C" uint32_t ResourceMgr_GetGameVersion()
return OTRGlobals::Instance->context->GetResourceManager()->GetGameVersion();
}
extern "C" uint32_t ResourceMgr_IsGameMasterQuest() {
uint32_t version = OTRGlobals::Instance->context->GetResourceManager()->GetGameVersion();
switch (version) {
case OOT_PAL_MQ:
case OOT_NTSC_JP_MQ:
case OOT_NTSC_US_MQ:
case OOT_PAL_GC_MQ_DBG:
return 1;
case OOT_NTSC_10:
case OOT_NTSC_11:
case OOT_NTSC_12:
case OOT_PAL_10:
case OOT_PAL_11:
case OOT_NTSC_JP_GC_CE:
case OOT_NTSC_JP_GC:
case OOT_NTSC_US_GC:
case OOT_PAL_GC:
case OOT_PAL_GC_DBG1:
case OOT_PAL_GC_DBG2:
return 0;
default:
SPDLOG_WARN("Unknown rom detected. Defaulting to Non-mq {:x}", version);
return 0;
uint32_t IsGameMasterQuest() {
uint32_t value = 0;
if (OTRGlobals::Instance->HasMasterQuest()) {
if (!OTRGlobals::Instance->HasOriginal()) {
value = 1;
} else if (gSaveContext.isMasterQuest) {
value = 1;
} else {
value = 0;
if (gSaveContext.n64ddFlag) {
if (!OTRGlobals::Instance->gRandomizer->masterQuestDungeons.empty()) {
if (gGlobalCtx != NULL && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(gGlobalCtx->sceneNum)) {
value = 1;
}
}
}
}
}
return value;
}
extern "C" uint32_t ResourceMgr_GameHasMasterQuest() {
return OTRGlobals::Instance->HasMasterQuest();
}
extern "C" uint32_t ResourceMgr_GameHasOriginal() {
return OTRGlobals::Instance->HasOriginal();
}
extern "C" uint32_t ResourceMgr_IsGameMasterQuest() {
return IsGameMasterQuest();
}
extern "C" void ResourceMgr_CacheDirectory(const char* resName) {
@ -608,6 +680,17 @@ extern "C" void ResourceMgr_LoadFile(const char* resName) {
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(resName);
}
std::shared_ptr<Ship::Resource> ResourceMgr_LoadResource(const char* path) {
std::string Path = path;
if (ResourceMgr_IsGameMasterQuest()) {
size_t pos = 0;
if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) {
Path.replace(pos, 7, "/mq/");
}
}
return OTRGlobals::Instance->context->GetResourceManager()->LoadResource(Path.c_str());
}
extern "C" char* ResourceMgr_LoadFileRaw(const char* resName) {
return OTRGlobals::Instance->context->GetResourceManager()->LoadFile(resName)->buffer.get();
}
@ -673,14 +756,22 @@ extern "C" uint16_t ResourceMgr_LoadTexHeightByName(char* texPath);
extern "C" uint32_t ResourceMgr_LoadTexSizeByName(const char* texPath);
extern "C" char* ResourceMgr_LoadTexOrDListByName(const char* filePath) {
auto res = OTRGlobals::Instance->context->GetResourceManager()->LoadResource(filePath);
auto res = ResourceMgr_LoadResource(filePath);
if (res->resType == Ship::ResourceType::DisplayList)
return (char*)&((std::static_pointer_cast<Ship::DisplayList>(res))->instructions[0]);
else if (res->resType == Ship::ResourceType::Array)
return (char*)(std::static_pointer_cast<Ship::Array>(res))->vertices.data();
else
return ResourceMgr_LoadTexByName(filePath);
else {
std::string Path = filePath;
if (ResourceMgr_IsGameMasterQuest()) {
size_t pos = 0;
if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) {
Path.replace(pos, 7, "/mq/");
}
}
return ResourceMgr_LoadTexByName(Path.c_str());
}
}
extern "C" Sprite* GetSeedTexture(uint8_t index) {
@ -688,16 +779,14 @@ extern "C" Sprite* GetSeedTexture(uint8_t index) {
}
extern "C" char* ResourceMgr_LoadPlayerAnimByName(const char* animPath) {
auto anim = std::static_pointer_cast<Ship::PlayerAnimation>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(animPath));
auto anim = std::static_pointer_cast<Ship::PlayerAnimation>(ResourceMgr_LoadResource(animPath));
return (char*)&anim->limbRotData[0];
}
extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path)
{
auto res = std::static_pointer_cast<Ship::DisplayList>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(path));
return (Gfx*)&res->instructions[0];
}
@ -753,14 +842,13 @@ extern "C" void ResourceMgr_UnpatchGfxByName(const char* path, const char* patch
extern "C" char* ResourceMgr_LoadArrayByName(const char* path)
{
auto res = std::static_pointer_cast<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Array>(ResourceMgr_LoadResource(path));
return (char*)res->scalars.data();
}
extern "C" char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path) {
auto res =
std::static_pointer_cast<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Array>(ResourceMgr_LoadResource(path));
if (res->cachedGameAsset != nullptr)
return (char*)res->cachedGameAsset;
@ -782,7 +870,7 @@ extern "C" char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path) {
extern "C" CollisionHeader* ResourceMgr_LoadColByName(const char* path)
{
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(ResourceMgr_LoadResource(path));
if (colRes->cachedGameAsset != nullptr)
return (CollisionHeader*)colRes->cachedGameAsset;
@ -876,8 +964,8 @@ extern "C" CollisionHeader* ResourceMgr_LoadColByName(const char* path)
extern "C" Vtx* ResourceMgr_LoadVtxByName(const char* path)
{
auto res = std::static_pointer_cast<Ship::Array>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
return (Vtx*)res->vertices.data();
auto res = std::static_pointer_cast<Ship::Array>(ResourceMgr_LoadResource(path));
return (Vtx*)res->vertices.data();
}
extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path)
@ -971,8 +1059,7 @@ extern "C" SoundFontSample* ResourceMgr_LoadAudioSample(const char* path)
if (cSample != nullptr)
return cSample;
auto sample = std::static_pointer_cast<Ship::AudioSample>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto sample = std::static_pointer_cast<Ship::AudioSample>(ResourceMgr_LoadResource(path));
if (sample == nullptr)
return NULL;
@ -1018,8 +1105,7 @@ extern "C" SoundFontSample* ResourceMgr_LoadAudioSample(const char* path)
}
extern "C" SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path) {
auto soundFont =
std::static_pointer_cast<Ship::AudioSoundFont>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto soundFont = std::static_pointer_cast<Ship::AudioSoundFont>(ResourceMgr_LoadResource(path));
if (soundFont == nullptr)
return NULL;
@ -1162,8 +1248,7 @@ extern "C" int ResourceMgr_OTRSigCheck(char* imgData)
}
extern "C" AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path) {
auto res = std::static_pointer_cast<Ship::Animation>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Animation>(ResourceMgr_LoadResource(path));
if (res->cachedGameAsset != nullptr)
return (AnimationHeaderCommon*)res->cachedGameAsset;
@ -1231,7 +1316,7 @@ extern "C" AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path) {
}
extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) {
auto res = std::static_pointer_cast<Ship::Skeleton>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Skeleton>(ResourceMgr_LoadResource(path));
if (res->cachedGameAsset != nullptr)
return (SkeletonHeader*)res->cachedGameAsset;
@ -1264,8 +1349,7 @@ extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) {
for (size_t i = 0; i < res->limbTable.size(); i++) {
std::string limbStr = res->limbTable[i];
auto limb = std::static_pointer_cast<Ship::SkeletonLimb>(
OTRGlobals::Instance->context->GetResourceManager()->LoadResource(limbStr.c_str()));
auto limb = std::static_pointer_cast<Ship::SkeletonLimb>(ResourceMgr_LoadResource(limbStr.c_str()));
if (limb->limbType == Ship::LimbType::LOD) {
LodLimb* limbC = (LodLimb*)malloc(sizeof(LodLimb));
@ -1419,7 +1503,7 @@ extern "C" SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path) {
extern "C" s32* ResourceMgr_LoadCSByName(const char* path)
{
auto res = std::static_pointer_cast<Ship::Cutscene>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(path));
auto res = std::static_pointer_cast<Ship::Cutscene>(ResourceMgr_LoadResource(path));
return (s32*)res->commands.data();
}
@ -1670,6 +1754,10 @@ extern "C" void Randomizer_LoadRequiredTrials(const char* spoilerFileName) {
OTRGlobals::Instance->gRandomizer->LoadRequiredTrials(spoilerFileName);
}
extern "C" void Randomizer_LoadMasterQuestDungeons(const char* spoilerFileName) {
OTRGlobals::Instance->gRandomizer->LoadMasterQuestDungeons(spoilerFileName);
}
extern "C" void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent) {
OTRGlobals::Instance->gRandomizer->LoadItemLocations(spoilerFileName, silent);
}

View file

@ -25,13 +25,20 @@ public:
OTRGlobals();
~OTRGlobals();
bool HasMasterQuest();
bool HasOriginal();
private:
void CheckSaveFile(size_t sramSize) const;
bool hasMasterQuest;
bool hasOriginal;
};
uint32_t IsGameMasterQuest();
#endif
#ifndef __cplusplus
void InitOTR(void);
void InitOTR(void);
void DeinitOTR(void);
void VanillaItemTable_Init();
void OTRAudio_Init();
@ -43,8 +50,10 @@ void OTRGfxPrint(const char* str, void* printer, void (*printImpl)(void*, char))
void OTRGetPixelDepthPrepare(float x, float y);
uint16_t OTRGetPixelDepth(float x, float y);
int32_t OTRGetLastScancode();
uint32_t ResourceMgr_GetGameVersion();
uint32_t ResourceMgr_IsGameMasterQuest();
uint32_t ResourceMgr_GameHasMasterQuest();
uint32_t ResourceMgr_GameHasOriginal();
uint32_t ResourceMgr_GetGameVersion();
void ResourceMgr_CacheDirectory(const char* resName);
char** ResourceMgr_ListFiles(const char* searchMask, int* resultSize);
void ResourceMgr_LoadFile(const char* resName);
@ -106,6 +115,7 @@ ShopItemIdentity Randomizer_IdentifyShopItem(s32 sceneNum, u8 slotIndex);
void Randomizer_LoadHintLocations(const char* spoilerFileName);
void Randomizer_LoadMerchantMessages(const char* spoilerFileName);
void Randomizer_LoadRequiredTrials(const char* spoilerFileName);
void Randomizer_LoadMasterQuestDungeons(const char* spoilerFileName);
void Randomizer_LoadItemLocations(const char* spoilerFileName, bool silent);
bool Randomizer_IsTrialRequired(RandomizerInf trial);
GetItemEntry Randomizer_GetItemFromActor(s16 actorId, s16 sceneNum, s16 actorParams, GetItemID ogId);

View file

@ -50,7 +50,8 @@ SaveManager::SaveManager() {
}
info.randoSave = 0;
info.isMasterQuest = 0;
info.requiresMasterQuest = 0;
info.requiresOriginal = 0;
}
}
@ -194,6 +195,15 @@ void SaveManager::LoadRandomizerVersion2() {
randomizer->merchantPrices[rc] = price;
});
});
SaveManager::Instance->LoadData("masterQuestDungeonCount", gSaveContext.mqDungeonCount);
OTRGlobals::Instance->gRandomizer->masterQuestDungeons.clear();
SaveManager::Instance->LoadArray("masterQuestDungeons", randomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT), [&](size_t i) {
uint16_t scene;
SaveManager::Instance->LoadData("", scene);
randomizer->masterQuestDungeons.emplace(scene);
});
}
void SaveManager::SaveRandomizer() {
@ -245,6 +255,16 @@ void SaveManager::SaveRandomizer() {
SaveManager::Instance->SaveData("price", merchantPrices[i].second);
});
});
SaveManager::Instance->SaveData("masterQuestDungeonCount", gSaveContext.mqDungeonCount);
std::vector<uint16_t> masterQuestDungeons;
for (const auto scene : randomizer->masterQuestDungeons) {
masterQuestDungeons.push_back(scene);
}
SaveManager::Instance->SaveArray("masterQuestDungeons", masterQuestDungeons.size(), [&](size_t i) {
SaveManager::Instance->SaveData("", masterQuestDungeons[i]);
});
}
void SaveManager::Init() {
@ -318,7 +338,8 @@ void SaveManager::InitMeta(int fileNum) {
}
fileMetaInfo[fileNum].randoSave = gSaveContext.n64ddFlag;
fileMetaInfo[fileNum].isMasterQuest = gSaveContext.isMasterQuest;
fileMetaInfo[fileNum].requiresMasterQuest = gSaveContext.isMasterQuest || gSaveContext.mqDungeonCount > 0;
fileMetaInfo[fileNum].requiresOriginal = !gSaveContext.isMasterQuest || gSaveContext.mqDungeonCount < 12;
}
void SaveManager::InitFile(bool isDebug) {
@ -465,7 +486,7 @@ void SaveManager::InitFileNormal() {
gSaveContext.infTable[29] = 1;
gSaveContext.sceneFlags[5].swch = 0x40000000;
gSaveContext.isMasterQuest = ResourceMgr_IsGameMasterQuest();
gSaveContext.isMasterQuest = CVar_GetS32("gMasterQuest", 0) && !CVar_GetS32("gRandomizer", 0);
//RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT)
}
@ -1297,7 +1318,8 @@ void SaveManager::CopyZeldaFile(int from, int to) {
fileMetaInfo[to].defense = fileMetaInfo[from].defense;
fileMetaInfo[to].health = fileMetaInfo[from].health;
fileMetaInfo[to].randoSave = fileMetaInfo[from].randoSave;
fileMetaInfo[to].isMasterQuest = fileMetaInfo[from].isMasterQuest;
fileMetaInfo[to].requiresMasterQuest = fileMetaInfo[from].requiresMasterQuest;
fileMetaInfo[to].requiresOriginal = fileMetaInfo[from].requiresOriginal;
}
void SaveManager::DeleteZeldaFile(int fileNum) {

View file

@ -10,7 +10,8 @@ typedef struct {
u32 questItems;
s8 defense;
u16 health;
u32 isMasterQuest;
u32 requiresMasterQuest;
u32 requiresOriginal;
u8 seedHash[5];
u8 randoSave;
} SaveFileMetaInfo;

View file

@ -28,7 +28,13 @@ extern "C" void OTRGameplay_SpawnScene(GlobalContext* globalCtx, s32 sceneNum, s
//osSyncPrintf("\nSCENE SIZE %fK\n", (scene->sceneFile.vromEnd - scene->sceneFile.vromStart) / 1024.0f);
std::string scenePath = StringHelper::Sprintf("scenes/%s/%s", scene->sceneFile.fileName, scene->sceneFile.fileName);
std::string sceneVersion;
if (IsGameMasterQuest()) {
sceneVersion = "mq";
} else {
sceneVersion = "nonmq";
}
std::string scenePath = StringHelper::Sprintf("scenes/%s/%s/%s", sceneVersion.c_str(), scene->sceneFile.fileName, scene->sceneFile.fileName);
globalCtx->sceneSegment = (Ship::Scene*)OTRGameplay_LoadFile(globalCtx, scenePath.c_str());

View file

@ -16,6 +16,20 @@ extern "C" s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId);
extern "C" RomFile sNaviMsgFiles[];
s32 OTRScene_ExecuteCommands(GlobalContext* globalCtx, Ship::Scene* scene);
std::shared_ptr<Ship::File> ResourceMgr_LoadFile(const char* path) {
std::string Path = path;
if (IsGameMasterQuest()) {
size_t pos = 0;
if ((pos = Path.find("/nonmq/", 0)) != std::string::npos) {
Path.replace(pos, 7, "/mq/");
}
}
return OTRGlobals::Instance->context->GetResourceManager()->LoadFile(Path.c_str());
}
// Forward Declaration of function declared in OTRGlobals.cpp
std::shared_ptr<Ship::Resource> ResourceMgr_LoadResource(const char* path);
bool Scene_CommandSpawnList(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
{
Ship::SetStartPositionList* cmdStartPos = (Ship::SetStartPositionList*)cmd;
@ -103,7 +117,7 @@ bool Scene_CommandCollisionHeader(GlobalContext* globalCtx, Ship::SceneCommand*
{
Ship::SetCollisionHeader* cmdCol = (Ship::SetCollisionHeader*)cmd;
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdCol->filePath));
auto colRes = std::static_pointer_cast<Ship::CollisionHeader>(ResourceMgr_LoadResource(cmdCol->filePath.c_str()));
CollisionHeader* colHeader = nullptr;
@ -298,7 +312,8 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
if (otrMesh->meshes[i].opa != "")
{
auto opaFile = std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa));
auto opaFile =
std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[i].opa.c_str()));
dlist->opaDL = opaFile.get();
dlist->opa = (Gfx*)&dlist->opaDL->instructions[0];
@ -310,7 +325,8 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
if (otrMesh->meshes[i].xlu != "")
{
auto xluFile = std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu));
auto xluFile =
std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[i].xlu.c_str()));
dlist->xluDL = xluFile.get();
dlist->xlu = (Gfx*)&dlist->xluDL->instructions[0];
@ -330,12 +346,12 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
PolygonDlist* pType = (PolygonDlist*)malloc(sizeof(PolygonDlist));
if (otrMesh->meshes[0].imgOpa != "")
pType->opa = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgOpa))->instructions[0];
pType->opa = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[0].imgOpa.c_str()))->instructions[0];
else
pType->opa = 0;
if (otrMesh->meshes[0].imgXlu != "")
pType->xlu = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[0].imgXlu))->instructions[0];
pType->xlu = (Gfx*)&std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[0].imgXlu.c_str()))->instructions[0];
else
pType->xlu = 0;
@ -347,8 +363,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
{
globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.fmt = otrMesh->meshes[0].images[0].fmt;
globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.source =
(void*)(OTRGlobals::Instance->context->GetResourceManager()->LoadFile(
otrMesh->meshes[0].images[0].sourceBackground))
(void*)(ResourceMgr_LoadFile(otrMesh->meshes[0].images[0].sourceBackground.c_str()))
.get()
->buffer.get();
globalCtx->roomCtx.curRoom.meshHeader->polygon1.single.siz = otrMesh->meshes[0].images[0].siz;
@ -368,8 +383,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
{
globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].fmt = otrMesh->meshes[0].images[i].fmt;
globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].source =
(void*)(OTRGlobals::Instance->context->GetResourceManager()->LoadFile(
otrMesh->meshes[0].images[i].sourceBackground))
(void*)(ResourceMgr_LoadFile(otrMesh->meshes[0].images[i].sourceBackground.c_str()))
.get()
->buffer.get();
globalCtx->roomCtx.curRoom.meshHeader->polygon1.multi.list[i].siz = otrMesh->meshes[0].images[i].siz;
@ -395,7 +409,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
if (otrMesh->meshes[i].opa != "")
{
auto opaFile = std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].opa));
auto opaFile = std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[i].opa.c_str()));
dlist->opaDL = opaFile.get();
dlist->opa = (Gfx*)&dlist->opaDL->instructions[0];
@ -405,7 +419,7 @@ bool Scene_CommandMeshHeader(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
if (otrMesh->meshes[i].xlu != "")
{
auto xluFile = std::static_pointer_cast<Ship::DisplayList>(OTRGlobals::Instance->context->GetResourceManager()->LoadResource(otrMesh->meshes[i].xlu));
auto xluFile = std::static_pointer_cast<Ship::DisplayList>(ResourceMgr_LoadResource(otrMesh->meshes[i].xlu.c_str()));
dlist->xluDL = xluFile.get();
dlist->xlu = (Gfx*)&dlist->xluDL->instructions[0];
@ -515,7 +529,7 @@ bool Scene_CommandPathList(GlobalContext* globalCtx, Ship::SceneCommand* cmd)
{
Ship::SetPathways* cmdPath = (Ship::SetPathways*)cmd;
Ship::Path* path = (Ship::Path*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdPath->paths[0]).get();
Ship::Path* path = (Ship::Path*)ResourceMgr_LoadResource(cmdPath->paths[0].c_str()).get();
globalCtx->setupPathList = (Path*)malloc(path->paths.size() * sizeof(Path));
//for (int i = 0; i < cmdPath->paths.size(); i++)
@ -740,8 +754,7 @@ bool Scene_CommandAlternateHeaderList(GlobalContext* globalCtx, Ship::SceneComma
std::string desiredHeader = cmdHeaders->headers[gSaveContext.sceneSetupIndex - 1];
Ship::Scene* headerData = nullptr;
if (desiredHeader != "") {
headerData =
(Ship::Scene*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(desiredHeader).get();
headerData = (Ship::Scene*)ResourceMgr_LoadResource(desiredHeader.c_str()).get();
}
if (headerData != nullptr)
@ -759,9 +772,7 @@ bool Scene_CommandAlternateHeaderList(GlobalContext* globalCtx, Ship::SceneComma
std::string desiredHeader = cmdHeaders->headers[gSaveContext.sceneSetupIndex - 2];
Ship::Scene* headerData = nullptr;
if (desiredHeader != "") {
headerData = (Ship::Scene*)OTRGlobals::Instance->context->GetResourceManager()
->LoadResource(desiredHeader)
.get();
headerData = (Ship::Scene*)ResourceMgr_LoadResource(desiredHeader.c_str()).get();
}
// "Using adult day data there!"
@ -782,7 +793,7 @@ bool Scene_CommandCutsceneData(GlobalContext* globalCtx, Ship::SceneCommand* cmd
{
Ship::SetCutscenes* cmdCS = (Ship::SetCutscenes*)cmd;
Ship::Cutscene* csData = (Ship::Cutscene*)OTRGlobals::Instance->context->GetResourceManager()->LoadResource(cmdCS->cutscenePath).get();
Ship::Cutscene* csData = (Ship::Cutscene*)ResourceMgr_LoadResource(cmdCS->cutscenePath.c_str()).get();
globalCtx->csCtx.segment = csData->commands.data();
//osSyncPrintf("\ngame_play->demo_play.data=[%x]", globalCtx->csCtx.segment);
@ -921,7 +932,7 @@ extern "C" s32 OTRfunc_8009728C(GlobalContext* globalCtx, RoomContext* roomCtx,
//DmaMgr_SendRequest2(&roomCtx->dmaRequest, roomCtx->unk_34, globalCtx->roomList[roomNum].vromStart, size, 0,
//&roomCtx->loadQueue, NULL, __FILE__, __LINE__);
auto roomData = OTRGlobals::Instance->context->GetResourceManager()->LoadResource(globalCtx->roomList[roomNum].fileName);
auto roomData = ResourceMgr_LoadResource(globalCtx->roomList[roomNum].fileName);
roomCtx->status = 1;
roomCtx->roomToLoad = (Ship::Scene*)roomData.get();

View file

@ -297,8 +297,9 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) {
gSaveContext.playerName[offset] = Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->playerName[offset];
}
if (CVar_GetS32("gRandomizer", 0) != 0 &&
strcmp(CVar_GetString("gSpoilerLog", ""), "") != 0) {
if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0 &&
!((Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->requiresMasterQuest && !ResourceMgr_GameHasMasterQuest()) ||
(Save_GetSaveMetaInfo(fileChooseCtx->buttonIndex)->requiresMasterQuest && !ResourceMgr_GameHasOriginal()))) {
// Set N64DD Flags for save file
fileChooseCtx->n64ddFlags[fileChooseCtx->buttonIndex] = 1;
fileChooseCtx->n64ddFlag = 1;

View file

@ -32,7 +32,14 @@ static s16 sWindowContentColors[2][3] = {
};
static int FileChoose_IsSaveCompatible(const SaveFileMetaInfo* restrict meta) {
return meta->isMasterQuest == ResourceMgr_IsGameMasterQuest();
bool valid = true;
if (meta->requiresMasterQuest) {
valid = valid && ResourceMgr_GameHasMasterQuest();
}
if (meta->requiresOriginal) {
valid = valid && ResourceMgr_GameHasOriginal();
}
return valid;
}
void FileChoose_SetView(FileChooseContext* this, f32 eyeX, f32 eyeY, f32 eyeZ) {
@ -219,13 +226,15 @@ void DrawSeedHashSprites(FileChooseContext* this) {
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, this->fileButtonAlpha[this->buttonIndex]);
if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0) {
if (CVar_GetS32("gRandomizer", 0) && strnlen(CVar_GetString("gSpoilerLog", ""), 1) != 0 &&
!((gSaveContext.mqDungeonCount > 0 && !ResourceMgr_GameHasMasterQuest())
|| (gSaveContext.mqDungeonCount < 12 && !ResourceMgr_GameHasOriginal()))) {
u16 xStart = 64;
for (unsigned int i = 0; i < 5; i++) {
SpriteLoad(this, GetSeedTexture(gSaveContext.seedIcons[i]));
SpriteDraw(this, GetSeedTexture(gSaveContext.seedIcons[i]), xStart + (40 * i), 10, 24, 24);
}
}
}
}
gDPPipeSync(POLY_OPA_DISP++);
@ -235,6 +244,7 @@ void DrawSeedHashSprites(FileChooseContext* this) {
u8 generating;
bool fileSelectSpoilerFileLoaded;
bool shouldLoadSpoilerFile;
/**
* Update the cursor and wait for the player to select a button to change menus accordingly.
@ -270,7 +280,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
if ((CVar_GetS32("gNewFileDropped", 0) != 0) ||
(CVar_GetS32("gNewSeedGenerated", 0) != 0) ||
(!fileSelectSpoilerFileLoaded &&
(!fileSelectSpoilerFileLoaded && shouldLoadSpoilerFile &&
SpoilerFileExists(CVar_GetString("gSpoilerLog", "")))) {
if (CVar_GetS32("gNewFileDropped", 0) != 0) {
CVar_SetString("gSpoilerLog", CVar_GetString("gDroppedFile", "None"));
@ -288,6 +298,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
Randomizer_LoadSettings(fileLoc);
Randomizer_LoadHintLocations(fileLoc);
Randomizer_LoadRequiredTrials(fileLoc);
Randomizer_LoadMasterQuestDungeons(fileLoc);
Randomizer_LoadItemLocations(fileLoc, silent);
Randomizer_LoadMerchantMessages(fileLoc);
fileSelectSpoilerFileLoaded = true;
@ -1118,7 +1129,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
gSP1Quadrangle(POLY_OPA_DISP++, 8, 10, 11, 9, 0);
}
//Draw MQ label
if (Save_GetSaveMetaInfo(i)->isMasterQuest && Save_GetSaveMetaInfo(i)->valid) {
if (Save_GetSaveMetaInfo(i)->requiresMasterQuest && !Save_GetSaveMetaInfo(i)->randoSave && Save_GetSaveMetaInfo(i)->valid) {
if (CVar_GetS32("gHudColors", 1) == 2 && FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) {
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, CVar_GetRGB("gCCFileChoosePrim", Background_Color).r, CVar_GetRGB("gCCFileChoosePrim", Background_Color).g, CVar_GetRGB("gCCFileChoosePrim", Background_Color).b, this->nameAlpha[i]);
} else if (!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) {
@ -1150,7 +1161,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
G_TX_NOLOD);
gSP1Quadrangle(POLY_OPA_DISP++, 12, 14, 15, 13, 0);
if (Save_GetSaveMetaInfo(i)->randoSave || Save_GetSaveMetaInfo(i)->isMasterQuest) {
if (Save_GetSaveMetaInfo(i)->randoSave || Save_GetSaveMetaInfo(i)->requiresMasterQuest) {
gSP1Quadrangle(POLY_OPA_DISP++, 16, 18, 19, 17, 0);
}
}
@ -2091,6 +2102,7 @@ void FileChoose_Init(GameState* thisx) {
size_t size = (u32)_title_staticSegmentRomEnd - (u32)_title_staticSegmentRomStart;
s32 pad;
fileSelectSpoilerFileLoaded = false;
shouldLoadSpoilerFile = true;
CVar_SetS32("gOnFileSelectNameEntry", 0);
SREG(30) = 1;