mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-13 01:57:18 -07:00
Merge pull request #3986 from Archez/merge-macready-805
Merge MacReady 8.0.5 -> Develop
This commit is contained in:
commit
8b3cfdb84f
13 changed files with 212 additions and 343 deletions
|
@ -5,8 +5,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
|
||||||
|
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
|
||||||
|
|
||||||
project(Ship VERSION 8.0.4 LANGUAGES C CXX)
|
project(Ship VERSION 8.0.5 LANGUAGES C CXX)
|
||||||
set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "")
|
set(PROJECT_BUILD_NAME "MacReady Foxtrot" CACHE STRING "")
|
||||||
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
|
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
|
||||||
|
|
||||||
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
|
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
|
||||||
|
|
|
@ -7,68 +7,6 @@ export RESPATH="${SNAME%/MacOS*}/Resources"
|
||||||
export LIBPATH="${SNAME%/MacOS*}/Frameworks"
|
export LIBPATH="${SNAME%/MacOS*}/Frameworks"
|
||||||
export DYLD_FALLBACK_LIBRARY_PATH="$LIBPATH"
|
export DYLD_FALLBACK_LIBRARY_PATH="$LIBPATH"
|
||||||
|
|
||||||
remap_hashes ()
|
|
||||||
{
|
|
||||||
# Remap v64 and n64 hashes to their z64 hash equivalent
|
|
||||||
# ZAPD will handle converting the data into z64 format
|
|
||||||
case "$ROMHASH" in
|
|
||||||
a9059b56e761c9034fbe02fe4c24985aaa835dac) # v64
|
|
||||||
ROMHASH=cee6bc3c2a634b41728f2af8da54d9bf8cc14099
|
|
||||||
;;
|
|
||||||
24708102dc504d3f375a37f4ae4e149c167dc515) # n64
|
|
||||||
ROMHASH=cee6bc3c2a634b41728f2af8da54d9bf8cc14099
|
|
||||||
;;
|
|
||||||
580dd0bd1b6d2c51cc20a764eece84dba558964c) # v64
|
|
||||||
ROMHASH=0227d7c0074f2d0ac935631990da8ec5914597b4
|
|
||||||
;;
|
|
||||||
d6342c59007e57c1194661ec6880b2f078403f4e) # n64
|
|
||||||
ROMHASH=0227d7c0074f2d0ac935631990da8ec5914597b4
|
|
||||||
;;
|
|
||||||
d0bdc2eb320668b4ba6893b9aefe4040a73123ff) # v64
|
|
||||||
ROMHASH=328a1f1beba30ce5e178f031662019eb32c5f3b5
|
|
||||||
;;
|
|
||||||
4946ab250f6ac9b32d76b21f309ebb8ebc8103d2) # n64
|
|
||||||
ROMHASH=328a1f1beba30ce5e178f031662019eb32c5f3b5
|
|
||||||
;;
|
|
||||||
663c34f1b2c05a09e5beffe4d0dcd440f7d49dc7) # v64
|
|
||||||
ROMHASH=cfbb98d392e4a9d39da8285d10cbef3974c2f012
|
|
||||||
;;
|
|
||||||
24c73d378b0620a380ce5ef9f2b186c6c157a68b) # n64
|
|
||||||
ROMHASH=cfbb98d392e4a9d39da8285d10cbef3974c2f012
|
|
||||||
;;
|
|
||||||
8ebf2e29313f44f2d49e5b4191971d09919e8e48) # v64
|
|
||||||
ROMHASH=f46239439f59a2a594ef83cf68ef65043b1bffe2
|
|
||||||
;;
|
|
||||||
4264bf7b875737b8fae77d52322a5099d051fc11) # n64
|
|
||||||
ROMHASH=f46239439f59a2a594ef83cf68ef65043b1bffe2
|
|
||||||
;;
|
|
||||||
973bc6fe56010a8d646166a1182a81b4f13b8cf9) # v64
|
|
||||||
ROMHASH=50bebedad9e0f10746a52b07239e47fa6c284d03
|
|
||||||
;;
|
|
||||||
d327752c46edc70ff3668b9514083dbbee08927c) # v64
|
|
||||||
ROMHASH=50bebedad9e0f10746a52b07239e47fa6c284d03
|
|
||||||
;;
|
|
||||||
ecdeb1747560834e079c22243febea7f6f26ba3b) # v64
|
|
||||||
ROMHASH=079b855b943d6ad8bd1eb026c0ed169ecbdac7da
|
|
||||||
;;
|
|
||||||
f19f8662ec7abee29484a272a6fda53e39efe0f1) # n64
|
|
||||||
ROMHASH=079b855b943d6ad8bd1eb026c0ed169ecbdac7da
|
|
||||||
;;
|
|
||||||
ab519ce04a33818ce2c39b3c514a751d807a494a) # v64
|
|
||||||
ROMHASH=cfecfdc58d650e71a200c81f033de4e6d617a9f6
|
|
||||||
;;
|
|
||||||
c19a34f7646305e1755249fca2071e178bd7cd00) # n64
|
|
||||||
ROMHASH=cfecfdc58d650e71a200c81f033de4e6d617a9f6
|
|
||||||
;;
|
|
||||||
25e8ae79ea0839ca5c984473f7460d8040c36f9c) # v64
|
|
||||||
ROMHASH=517bd9714c73cb96c21e7c2ef640d7b55186102f
|
|
||||||
;;
|
|
||||||
166c02770d67fcc3954c443eb400a6a3573d3fc0) # n64
|
|
||||||
ROMHASH=517bd9714c73cb96c21e7c2ef640d7b55186102f
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ ! -e "$SHIP_HOME" ]; then mkdir "$SHIP_HOME"; fi
|
if [ ! -e "$SHIP_HOME" ]; then mkdir "$SHIP_HOME"; fi
|
||||||
|
|
||||||
if [ ! -e "$SHIP_HOME"/mods ]; then
|
if [ ! -e "$SHIP_HOME"/mods ]; then
|
||||||
|
@ -76,178 +14,6 @@ if [ ! -e "$SHIP_HOME"/mods ]; then
|
||||||
touch "$SHIP_HOME"/mods/custom_otr_files_go_here.txt
|
touch "$SHIP_HOME"/mods/custom_otr_files_go_here.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If either OTR doesn't exist kick off the OTR gen process
|
|
||||||
if [ ! -e "$SHIP_HOME"/oot.otr ] || [ ! -e "$SHIP_HOME"/oot-mq.otr ]; then
|
|
||||||
|
|
||||||
# If no ROMs exist kick off the file selection prompts
|
|
||||||
while [ ! -e "$SHIP_HOME"/*.*64 ] && [ ! -e "$SHIP_HOME"/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 }')"
|
|
||||||
|
|
||||||
remap_hashes
|
|
||||||
|
|
||||||
case "$ROMHASH" in
|
|
||||||
cee6bc3c2a634b41728f2af8da54d9bf8cc14099)
|
|
||||||
ROM_TYPE=0;;
|
|
||||||
0227d7c0074f2d0ac935631990da8ec5914597b4)
|
|
||||||
ROM_TYPE=0;;
|
|
||||||
328a1f1beba30ce5e178f031662019eb32c5f3b5)
|
|
||||||
ROM_TYPE=0;;
|
|
||||||
cfbb98d392e4a9d39da8285d10cbef3974c2f012)
|
|
||||||
ROM_TYPE=0;;
|
|
||||||
f46239439f59a2a594ef83cf68ef65043b1bffe2)
|
|
||||||
ROM_TYPE=1;;
|
|
||||||
50bebedad9e0f10746a52b07239e47fa6c284d03)
|
|
||||||
ROM_TYPE=1;;
|
|
||||||
079b855b943d6ad8bd1eb026c0ed169ecbdac7da)
|
|
||||||
ROM_TYPE=1;;
|
|
||||||
cfecfdc58d650e71a200c81f033de4e6d617a9f6)
|
|
||||||
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" "$SHIP_HOME"
|
|
||||||
|
|
||||||
# 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 $SHIP_HOME directory
|
|
||||||
|
|
||||||
# Prepare tmp dir
|
|
||||||
for ROMPATH in "$SHIP_HOME"/*.*64
|
|
||||||
do
|
|
||||||
ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)"
|
|
||||||
export ASSETDIR
|
|
||||||
cp -r "$RESPATH/assets" "$ASSETDIR"
|
|
||||||
mkdir -p "$ASSETDIR"/tmp
|
|
||||||
cp "$ROMPATH" "$ASSETDIR"/tmp/rom.z64
|
|
||||||
cd "$ASSETDIR" || return
|
|
||||||
|
|
||||||
# If an invalid rom was detected, let the user know
|
|
||||||
ROMHASH="$(shasum "$ASSETDIR"/tmp/rom.z64 | awk '{ print $1 }')"
|
|
||||||
|
|
||||||
remap_hashes
|
|
||||||
|
|
||||||
case "$ROMHASH" in
|
|
||||||
cee6bc3c2a634b41728f2af8da54d9bf8cc14099)
|
|
||||||
ROM=GC_NMQ_D
|
|
||||||
OTRNAME="oot.otr";;
|
|
||||||
0227d7c0074f2d0ac935631990da8ec5914597b4)
|
|
||||||
ROM=GC_NMQ_PAL_F
|
|
||||||
OTRNAME="oot.otr";;
|
|
||||||
328a1f1beba30ce5e178f031662019eb32c5f3b5)
|
|
||||||
ROM=N64_PAL_10
|
|
||||||
OTRNAME="oot.otr";;
|
|
||||||
cfbb98d392e4a9d39da8285d10cbef3974c2f012)
|
|
||||||
ROM=N64_PAL_11
|
|
||||||
OTRNAME="oot.otr";;
|
|
||||||
f46239439f59a2a594ef83cf68ef65043b1bffe2)
|
|
||||||
ROM=GC_MQ_PAL_F
|
|
||||||
OTRNAME="oot-mq.otr";;
|
|
||||||
50bebedad9e0f10746a52b07239e47fa6c284d03)
|
|
||||||
ROM=GC_MQ_D
|
|
||||||
OTRNAME="oot-mq.otr";;
|
|
||||||
079b855b943d6ad8bd1eb026c0ed169ecbdac7da)
|
|
||||||
ROM=GC_MQ_D
|
|
||||||
OTRNAME="oot-mq.otr";;
|
|
||||||
cfecfdc58d650e71a200c81f033de4e6d617a9f6)
|
|
||||||
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"
|
|
||||||
cd "$SNAME"
|
|
||||||
continue;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Only generate OTR if we don't have on of this type yet
|
|
||||||
if [ -e "$SHIP_HOME"/"$OTRNAME" ]; then
|
|
||||||
rm -r "$ASSETDIR"
|
|
||||||
cd "$SNAME"
|
|
||||||
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 --portVer "@CMAKE_PROJECT_VERSION@"
|
|
||||||
if [ -e "$ASSETDIR"/oot.otr ]; then
|
|
||||||
osascript -e 'display notification "OTR successfully generated" with title "Ship Of Harkinian"'
|
|
||||||
cp "$ASSETDIR"/oot.otr "$SHIP_HOME"/"$OTRNAME"
|
|
||||||
rm -r "$ASSETDIR"
|
|
||||||
cd "$SNAME"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ! -e "$SHIP_HOME"/oot*.otr ]; then
|
|
||||||
osascript -e 'display notification "OTR failed to generate" with title "Ship Of Harkinian"'
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$SNAME"
|
|
||||||
|
|
||||||
"$RESPATH"/soh-macos
|
"$RESPATH"/soh-macos
|
||||||
|
|
||||||
exit
|
exit
|
||||||
|
|
|
@ -1336,7 +1336,7 @@ void DrawLocation(RandomizerCheckObject rcObj) {
|
||||||
CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default);
|
CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default);
|
||||||
extraColor = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default);
|
extraColor = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default);
|
||||||
} else if (status == RCSHOW_SCUMMED) {
|
} else if (status == RCSHOW_SCUMMED) {
|
||||||
if (!showHidden && CVarGetInteger("gCheckTrackerKnownHide", 0)) {
|
if (!showHidden && CVarGetInteger("gCheckTrackerScummedHide", 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default) :
|
mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default) :
|
||||||
|
|
|
@ -394,6 +394,11 @@ void Entrance_SetSavewarpEntrance(void) {
|
||||||
gSaveContext.entranceIndex = ENTR_THIEVES_HIDEOUT_0; // Gerudo Fortress -> Thieve's Hideout spawn 0
|
gSaveContext.entranceIndex = ENTR_THIEVES_HIDEOUT_0; // Gerudo Fortress -> Thieve's Hideout spawn 0
|
||||||
} else if (scene == SCENE_LINKS_HOUSE) {
|
} else if (scene == SCENE_LINKS_HOUSE) {
|
||||||
gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_LINKS_HOUSE_0);
|
gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_LINKS_HOUSE_0);
|
||||||
|
} else if (CVarGetInteger("gRememberSaveLocation", 0) && scene != SCENE_FAIRYS_FOUNTAIN && scene != SCENE_GROTTOS &&
|
||||||
|
gSaveContext.entranceIndex != ENTR_LOAD_OPENING) {
|
||||||
|
// Use the saved entrance value with remember save location, except when in grottos/fairy fountains or if
|
||||||
|
// the entrance index is -1 (new save)
|
||||||
|
return;
|
||||||
} else if (LINK_IS_CHILD) {
|
} else if (LINK_IS_CHILD) {
|
||||||
gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_LINKS_HOUSE_0); // Child Overworld Spawn
|
gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_LINKS_HOUSE_0); // Child Overworld Spawn
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <variables.h>
|
#include <variables.h>
|
||||||
#include "soh/Enhancements/boss-rush/BossRush.h"
|
#include "soh/Enhancements/boss-rush/BossRush.h"
|
||||||
#include <libultraship/libultraship.h>
|
#include <libultraship/libultraship.h>
|
||||||
|
#include "SohGui.hpp"
|
||||||
|
|
||||||
#define NOGDI // avoid various windows defines that conflict with things in z64.h
|
#define NOGDI // avoid various windows defines that conflict with things in z64.h
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
@ -1025,51 +1026,67 @@ void SaveManager::SaveGlobal() {
|
||||||
|
|
||||||
void SaveManager::LoadFile(int fileNum) {
|
void SaveManager::LoadFile(int fileNum) {
|
||||||
SPDLOG_INFO("Load File - fileNum: {}", fileNum);
|
SPDLOG_INFO("Load File - fileNum: {}", fileNum);
|
||||||
assert(std::filesystem::exists(GetFileName(fileNum)));
|
std::filesystem::path fileName = GetFileName(fileNum);
|
||||||
|
assert(std::filesystem::exists(fileName));
|
||||||
InitFile(false);
|
InitFile(false);
|
||||||
|
|
||||||
std::ifstream input(GetFileName(fileNum));
|
std::ifstream input(fileName);
|
||||||
|
|
||||||
saveBlock = nlohmann::json::object();
|
try {
|
||||||
input >> saveBlock;
|
saveBlock = nlohmann::json::object();
|
||||||
if (!saveBlock.contains("version")) {
|
input >> saveBlock;
|
||||||
SPDLOG_ERROR("Save at " + GetFileName(fileNum).string() + " contains no version");
|
if (!saveBlock.contains("version")) {
|
||||||
assert(false);
|
SPDLOG_ERROR("Save at " + fileName.string() + " contains no version");
|
||||||
}
|
|
||||||
switch (saveBlock["version"].get<int>()) {
|
|
||||||
case 1:
|
|
||||||
for (auto& block : saveBlock["sections"].items()) {
|
|
||||||
int sectionVersion = block.value()["version"];
|
|
||||||
std::string sectionName = block.key();
|
|
||||||
if (!sectionLoadHandlers.contains(sectionName)) {
|
|
||||||
// Unloadable sections aren't necessarily errors, they are probably mods that were unloaded
|
|
||||||
// TODO report in a more noticeable manner
|
|
||||||
SPDLOG_WARN("Save " + GetFileName(fileNum).string() + " contains unloadable section " + sectionName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SectionLoadHandler& handler = sectionLoadHandlers[sectionName];
|
|
||||||
if (!handler.contains(sectionVersion)) {
|
|
||||||
// A section that has a loader without a handler for the specific version means that the user has a mod
|
|
||||||
// at an earlier version than the save has. In this case, the user probably wants to load the save.
|
|
||||||
// Report the error so that the user can rectify the error.
|
|
||||||
// TODO report in a more noticeable manner
|
|
||||||
SPDLOG_ERROR("Save " + GetFileName(fileNum).string() + " contains section " + sectionName +
|
|
||||||
" with an unloadable version " + std::to_string(sectionVersion));
|
|
||||||
assert(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
currentJsonContext = &block.value()["data"];
|
|
||||||
handler[sectionVersion]();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SPDLOG_ERROR("Unrecognized save version " + std::to_string(saveBlock["version"].get<int>()) + " in " +
|
|
||||||
GetFileName(fileNum).string());
|
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
}
|
||||||
|
switch (saveBlock["version"].get<int>()) {
|
||||||
|
case 1:
|
||||||
|
for (auto& block : saveBlock["sections"].items()) {
|
||||||
|
int sectionVersion = block.value()["version"];
|
||||||
|
std::string sectionName = block.key();
|
||||||
|
if (!sectionLoadHandlers.contains(sectionName)) {
|
||||||
|
// Unloadable sections aren't necessarily errors, they are probably mods that were unloaded
|
||||||
|
// TODO report in a more noticeable manner
|
||||||
|
SPDLOG_WARN("Save " + GetFileName(fileNum).string() + " contains unloadable section " +
|
||||||
|
sectionName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SectionLoadHandler& handler = sectionLoadHandlers[sectionName];
|
||||||
|
if (!handler.contains(sectionVersion)) {
|
||||||
|
// A section that has a loader without a handler for the specific version means that the user
|
||||||
|
// has a mod at an earlier version than the save has. In this case, the user probably wants to
|
||||||
|
// load the save. Report the error so that the user can rectify the error.
|
||||||
|
// TODO report in a more noticeable manner
|
||||||
|
SPDLOG_ERROR("Save " + GetFileName(fileNum).string() + " contains section " + sectionName +
|
||||||
|
" with an unloadable version " + std::to_string(sectionVersion));
|
||||||
|
assert(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentJsonContext = &block.value()["data"];
|
||||||
|
handler[sectionVersion]();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SPDLOG_ERROR("Unrecognized save version " + std::to_string(saveBlock["version"].get<int>()) + " in " +
|
||||||
|
GetFileName(fileNum).string());
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
InitMeta(fileNum);
|
||||||
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnLoadFile>(fileNum);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
input.close();
|
||||||
|
std::filesystem::path newFile(LUS::Context::GetPathRelativeToAppDirectory("Save") + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"));
|
||||||
|
#if defined(__SWITCH__) || defined(__WIIU__)
|
||||||
|
copy_file(fileName.c_str(), newFile.c_str());
|
||||||
|
#else
|
||||||
|
std::filesystem::copy_file(fileName, newFile);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::filesystem::remove(fileName);
|
||||||
|
SohGui::RegisterPopup("Error loading save file", "A problem occurred loading the save in slot " + std::to_string(fileNum + 1) + ".\nSave file corruption is suspected.\n" +
|
||||||
|
"The file has been renamed to prevent further issues.");
|
||||||
}
|
}
|
||||||
InitMeta(fileNum);
|
|
||||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnLoadFile>(fileNum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveManager::ThreadPoolWait() {
|
void SaveManager::ThreadPoolWait() {
|
||||||
|
|
|
@ -133,8 +133,8 @@ namespace SohGui {
|
||||||
std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
|
std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
|
||||||
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
|
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
|
||||||
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
|
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
|
||||||
|
|
||||||
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
|
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
|
||||||
|
std::shared_ptr<SohModalWindow> mModalWindow;
|
||||||
|
|
||||||
void SetupGuiElements() {
|
void SetupGuiElements() {
|
||||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||||
|
@ -201,9 +201,13 @@ namespace SohGui {
|
||||||
gui->AddGuiWindow(mRandomizerSettingsWindow);
|
gui->AddGuiWindow(mRandomizerSettingsWindow);
|
||||||
mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>("gAdvancedResolutionEditorEnabled", "Advanced Resolution Settings");
|
mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>("gAdvancedResolutionEditorEnabled", "Advanced Resolution Settings");
|
||||||
gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
|
gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
|
||||||
|
mModalWindow = std::make_shared<SohModalWindow>("gOpenWindows.modalWindowEnabled", "Modal Window");
|
||||||
|
gui->AddGuiWindow(mModalWindow);
|
||||||
|
mModalWindow->Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Destroy() {
|
void Destroy() {
|
||||||
|
mModalWindow = nullptr;
|
||||||
mAdvancedResolutionSettingsWindow = nullptr;
|
mAdvancedResolutionSettingsWindow = nullptr;
|
||||||
mRandomizerSettingsWindow = nullptr;
|
mRandomizerSettingsWindow = nullptr;
|
||||||
mItemTrackerWindow = nullptr;
|
mItemTrackerWindow = nullptr;
|
||||||
|
@ -227,4 +231,8 @@ namespace SohGui {
|
||||||
mInputViewer = nullptr;
|
mInputViewer = nullptr;
|
||||||
mInputViewerSettings = nullptr;
|
mInputViewerSettings = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) {
|
||||||
|
mModalWindow->RegisterPopup(title, message, button1, button2, button1callback, button2callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "Enhancements/randomizer/randomizer_entrance_tracker.h"
|
#include "Enhancements/randomizer/randomizer_entrance_tracker.h"
|
||||||
#include "Enhancements/randomizer/randomizer_item_tracker.h"
|
#include "Enhancements/randomizer/randomizer_item_tracker.h"
|
||||||
#include "Enhancements/randomizer/randomizer_settings_window.h"
|
#include "Enhancements/randomizer/randomizer_settings_window.h"
|
||||||
|
#include "SohModals.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -38,6 +39,7 @@ namespace SohGui {
|
||||||
void SetupGuiElements();
|
void SetupGuiElements();
|
||||||
void Draw();
|
void Draw();
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function<void()> button1callback = nullptr, std::function<void()> button2callback = nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* SohGui_hpp */
|
#endif /* SohGui_hpp */
|
||||||
|
|
|
@ -609,7 +609,7 @@ void DrawEnhancementsMenu() {
|
||||||
UIWidgets::Tooltip("Allows you to change the number of days it takes for Biggoron to forge the Biggoron Sword");
|
UIWidgets::Tooltip("Allows you to change the number of days it takes for Biggoron to forge the Biggoron Sword");
|
||||||
UIWidgets::PaddedEnhancementCheckbox("Remember Save Location", "gRememberSaveLocation", false, false);
|
UIWidgets::PaddedEnhancementCheckbox("Remember Save Location", "gRememberSaveLocation", false, false);
|
||||||
UIWidgets::Tooltip("When loading a save, places Link at the last entrance he went through.\n"
|
UIWidgets::Tooltip("When loading a save, places Link at the last entrance he went through.\n"
|
||||||
"This doesn't work if the save was made in a grotto.");
|
"This doesn't work if the save was made in grottos/fairy fountains or dungeons.");
|
||||||
UIWidgets::PaddedEnhancementCheckbox("No Forced Navi", "gNoForcedNavi", true, false);
|
UIWidgets::PaddedEnhancementCheckbox("No Forced Navi", "gNoForcedNavi", true, false);
|
||||||
UIWidgets::Tooltip("Prevent forced Navi conversations");
|
UIWidgets::Tooltip("Prevent forced Navi conversations");
|
||||||
UIWidgets::PaddedEnhancementCheckbox("Navi Timer Resets", "gEnhancements.ResetNaviTimer", true, false);
|
UIWidgets::PaddedEnhancementCheckbox("Navi Timer Resets", "gEnhancements.ResetNaviTimer", true, false);
|
||||||
|
|
54
soh/soh/SohModals.cpp
Normal file
54
soh/soh/SohModals.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#include "SohModals.h"
|
||||||
|
#include "ImGui/imgui.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <libultraship/bridge.h>
|
||||||
|
#include <libultraship/libultraship.h>
|
||||||
|
#include "UIWidgets.hpp"
|
||||||
|
#include "OTRGlobals.h"
|
||||||
|
#include "z64.h"
|
||||||
|
|
||||||
|
extern "C" PlayState* gPlayState;
|
||||||
|
struct SohModal {
|
||||||
|
std::string title_;
|
||||||
|
std::string message_;
|
||||||
|
std::string button1_;
|
||||||
|
std::string button2_;
|
||||||
|
std::function<void()> button1callback_;
|
||||||
|
std::function<void()> button2callback_;
|
||||||
|
};
|
||||||
|
std::vector<SohModal> modals;
|
||||||
|
|
||||||
|
void SohModalWindow::DrawElement() {
|
||||||
|
if (modals.size() > 0) {
|
||||||
|
SohModal curModal = modals.at(0);
|
||||||
|
if (!ImGui::IsPopupOpen(curModal.title_.c_str())) {
|
||||||
|
ImGui::OpenPopup(curModal.title_.c_str());
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal(curModal.title_.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
ImGui::Text(curModal.message_.c_str());
|
||||||
|
if (ImGui::Button(curModal.button1_.c_str())) {
|
||||||
|
if (curModal.button1callback_ != nullptr) {
|
||||||
|
curModal.button1callback_();
|
||||||
|
}
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
modals.erase(modals.begin());
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (curModal.button2_ != "") {
|
||||||
|
if (ImGui::Button(curModal.button2_.c_str())) {
|
||||||
|
if (curModal.button2callback_ != nullptr) {
|
||||||
|
curModal.button2callback_();
|
||||||
|
}
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
modals.erase(modals.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function<void()> button1callback, std::function<void()> button2callback) {
|
||||||
|
modals.push_back({ title, message, button1, button2, button1callback, button2callback });
|
||||||
|
}
|
15
soh/soh/SohModals.h
Normal file
15
soh/soh/SohModals.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libultraship/libultraship.h>
|
||||||
|
#include "window/gui/GuiMenuBar.h"
|
||||||
|
#include "window/gui/GuiElement.h"
|
||||||
|
|
||||||
|
class SohModalWindow : public LUS::GuiWindow {
|
||||||
|
public:
|
||||||
|
using LUS::GuiWindow::GuiWindow;
|
||||||
|
|
||||||
|
void InitElement() override {};
|
||||||
|
void DrawElement() override;
|
||||||
|
void UpdateElement() override {};
|
||||||
|
void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function<void()> button1callback = nullptr, std::function<void()> button2callback = nullptr);
|
||||||
|
};
|
|
@ -8,6 +8,7 @@
|
||||||
#include "textures/message_static/message_static.h"
|
#include "textures/message_static/message_static.h"
|
||||||
#include "textures/message_texture_static/message_texture_static.h"
|
#include "textures/message_texture_static/message_texture_static.h"
|
||||||
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
|
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
|
||||||
|
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||||
#include "soh/OTRGlobals.h"
|
#include "soh/OTRGlobals.h"
|
||||||
|
|
||||||
|
@ -3072,7 +3073,9 @@ void Message_Draw(PlayState* play) {
|
||||||
POLY_OPA_DISP = plusOne;
|
POLY_OPA_DISP = plusOne;
|
||||||
}
|
}
|
||||||
plusOne = Graph_GfxPlusOne(polyOpaP = POLY_OPA_DISP);
|
plusOne = Graph_GfxPlusOne(polyOpaP = POLY_OPA_DISP);
|
||||||
gSPDisplayList(OVERLAY_DISP++, plusOne);
|
if (!GameInteractor_NoUIActive()) {
|
||||||
|
gSPDisplayList(OVERLAY_DISP++, plusOne);
|
||||||
|
}
|
||||||
Message_DrawMain(play, &plusOne);
|
Message_DrawMain(play, &plusOne);
|
||||||
gSPEndDisplayList(plusOne++);
|
gSPEndDisplayList(plusOne++);
|
||||||
Graph_BranchDlist(polyOpaP, plusOne);
|
Graph_BranchDlist(polyOpaP, plusOne);
|
||||||
|
|
|
@ -74,65 +74,68 @@ void Sram_OpenSave() {
|
||||||
|
|
||||||
Save_LoadFile();
|
Save_LoadFile();
|
||||||
|
|
||||||
if (!CVarGetInteger("gRememberSaveLocation", 0) || gSaveContext.savedSceneNum == SCENE_FAIRYS_FOUNTAIN ||
|
switch (gSaveContext.savedSceneNum) {
|
||||||
gSaveContext.savedSceneNum == SCENE_GROTTOS) {
|
case SCENE_DEKU_TREE:
|
||||||
switch (gSaveContext.savedSceneNum) {
|
case SCENE_DODONGOS_CAVERN:
|
||||||
case SCENE_DEKU_TREE:
|
case SCENE_JABU_JABU:
|
||||||
case SCENE_DODONGOS_CAVERN:
|
case SCENE_FOREST_TEMPLE:
|
||||||
case SCENE_JABU_JABU:
|
case SCENE_FIRE_TEMPLE:
|
||||||
case SCENE_FOREST_TEMPLE:
|
case SCENE_WATER_TEMPLE:
|
||||||
case SCENE_FIRE_TEMPLE:
|
case SCENE_SPIRIT_TEMPLE:
|
||||||
case SCENE_WATER_TEMPLE:
|
case SCENE_SHADOW_TEMPLE:
|
||||||
case SCENE_SPIRIT_TEMPLE:
|
case SCENE_BOTTOM_OF_THE_WELL:
|
||||||
case SCENE_SHADOW_TEMPLE:
|
case SCENE_ICE_CAVERN:
|
||||||
case SCENE_BOTTOM_OF_THE_WELL:
|
case SCENE_GANONS_TOWER:
|
||||||
case SCENE_ICE_CAVERN:
|
case SCENE_GERUDO_TRAINING_GROUND:
|
||||||
case SCENE_GANONS_TOWER:
|
case SCENE_THIEVES_HIDEOUT:
|
||||||
case SCENE_GERUDO_TRAINING_GROUND:
|
case SCENE_INSIDE_GANONS_CASTLE:
|
||||||
case SCENE_THIEVES_HIDEOUT:
|
gSaveContext.entranceIndex = sDungeonEntrances[gSaveContext.savedSceneNum];
|
||||||
case SCENE_INSIDE_GANONS_CASTLE:
|
break;
|
||||||
gSaveContext.entranceIndex = sDungeonEntrances[gSaveContext.savedSceneNum];
|
case SCENE_DEKU_TREE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_DEKU_TREE_0;
|
||||||
case SCENE_DEKU_TREE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_DEKU_TREE_0;
|
case SCENE_DODONGOS_CAVERN_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_DODONGOS_CAVERN_0;
|
||||||
case SCENE_DODONGOS_CAVERN_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_DODONGOS_CAVERN_0;
|
case SCENE_JABU_JABU_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_JABU_JABU_0;
|
||||||
case SCENE_JABU_JABU_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_JABU_JABU_0;
|
case SCENE_FOREST_TEMPLE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_FOREST_TEMPLE_0;
|
||||||
case SCENE_FOREST_TEMPLE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_FOREST_TEMPLE_0;
|
case SCENE_FIRE_TEMPLE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_FIRE_TEMPLE_0;
|
||||||
case SCENE_FIRE_TEMPLE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_FIRE_TEMPLE_0;
|
case SCENE_WATER_TEMPLE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_WATER_TEMPLE_0;
|
||||||
case SCENE_WATER_TEMPLE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_WATER_TEMPLE_0;
|
case SCENE_SPIRIT_TEMPLE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_SPIRIT_TEMPLE_0;
|
||||||
case SCENE_SPIRIT_TEMPLE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_SPIRIT_TEMPLE_0;
|
case SCENE_SHADOW_TEMPLE_BOSS:
|
||||||
break;
|
gSaveContext.entranceIndex = ENTR_SHADOW_TEMPLE_0;
|
||||||
case SCENE_SHADOW_TEMPLE_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_SHADOW_TEMPLE_0;
|
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
||||||
break;
|
case SCENE_INSIDE_GANONS_CASTLE_COLLAPSE:
|
||||||
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
|
case SCENE_GANONDORF_BOSS:
|
||||||
case SCENE_INSIDE_GANONS_CASTLE_COLLAPSE:
|
case SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR:
|
||||||
case SCENE_GANONDORF_BOSS:
|
case SCENE_GANON_BOSS:
|
||||||
case SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR:
|
gSaveContext.entranceIndex = ENTR_GANONS_TOWER_0;
|
||||||
case SCENE_GANON_BOSS:
|
break;
|
||||||
gSaveContext.entranceIndex = ENTR_GANONS_TOWER_0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (gSaveContext.savedSceneNum != SCENE_LINKS_HOUSE) {
|
// Use the saved entrance value with remember save location, except when in grottos/fairy fountains
|
||||||
gSaveContext.entranceIndex = (LINK_AGE_IN_YEARS == YEARS_CHILD) ? ENTR_LINKS_HOUSE_0 : ENTR_TEMPLE_OF_TIME_7;
|
if (CVarGetInteger("gRememberSaveLocation", 0) && gSaveContext.savedSceneNum != SCENE_FAIRYS_FOUNTAIN &&
|
||||||
} else {
|
gSaveContext.savedSceneNum != SCENE_GROTTOS) {
|
||||||
gSaveContext.entranceIndex = ENTR_LINKS_HOUSE_0;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gSaveContext.savedSceneNum != SCENE_LINKS_HOUSE) {
|
||||||
|
gSaveContext.entranceIndex = (LINK_AGE_IN_YEARS == YEARS_CHILD) ? ENTR_LINKS_HOUSE_0 : ENTR_TEMPLE_OF_TIME_7;
|
||||||
|
} else {
|
||||||
|
gSaveContext.entranceIndex = ENTR_LINKS_HOUSE_0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex);
|
osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex);
|
||||||
|
|
|
@ -3036,11 +3036,7 @@ void FileChoose_LoadGame(GameState* thisx) {
|
||||||
Entrance_Init();
|
Entrance_Init();
|
||||||
|
|
||||||
// Handle randomized spawn positions after the save context has been setup from load
|
// Handle randomized spawn positions after the save context has been setup from load
|
||||||
// When remeber save location is on, set save warp if the save was in an a grotto, or
|
if (Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) {
|
||||||
// the entrance index is -1 from shuffle overwarld spawn
|
|
||||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES) && ((!CVarGetInteger("gRememberSaveLocation", 0) ||
|
|
||||||
gSaveContext.savedSceneNum == SCENE_FAIRYS_FOUNTAIN || gSaveContext.savedSceneNum == SCENE_GROTTOS) ||
|
|
||||||
(CVarGetInteger("gRememberSaveLocation", 0) && Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) && gSaveContext.entranceIndex == ENTR_LOAD_OPENING))) {
|
|
||||||
Entrance_SetSavewarpEntrance();
|
Entrance_SetSavewarpEntrance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue