Merge branch 'rando-next' of https://github.com/HarbourMasters/Shipwright into keysanity

This commit is contained in:
Garrett Cox 2022-08-07 15:41:44 -05:00
commit 8b59466ce5
27 changed files with 252 additions and 118 deletions

3
.gitignore vendored
View File

@ -441,4 +441,5 @@ compile_commands.json
CTestTestfile.cmake
_deps
*/extract_assets_cmake*
/build*
/build*
build.c

View File

@ -30,8 +30,8 @@ With the cmake build system you have two options for working on the project:
#### Visual Studio
To develop using Visual Studio you only need to use cmake to generate the solution file:
```powershell
# Generates Ship.sln at the root directory
& 'C:\Program Files\CMake\bin\cmake' -S . -G "Visual Studio 17 2022" -T v142 -A x64
# Generates Ship.sln at `build/x64`
& 'C:\Program Files\CMake\bin\cmake' -S . -B "build/x64" -G "Visual Studio 17 2022" -T v142 -A x64
```
#### Visual Studio Code or another editor
@ -53,7 +53,7 @@ cd "build/x64"
```
## Linux
1. Requires `gcc, x11, curl, python3, sdl2, libpng, glew, ninja, cmake`
1. Requires `gcc >= 10, x11, curl, python3, sdl2 >= 2.0.22, libpng, glew >= 2.2, ninja, cmake, lld`
**Important: For maximum performance make sure you have ninja build tools installed!**

View File

@ -140,6 +140,7 @@ add_custom_target(
COMMAND ${CMAKE_COMMAND} -E rm -f oot.otr
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter/extract_assets_cmake2.py
COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_COMMAND} -E copy oot.otr ${CMAKE_BINARY_DIR}/soh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/OTRExporter
COMMENT "Running asset extraction..."
DEPENDS ZAPD

View File

@ -46,7 +46,8 @@ RUN curl -sLO https://libsdl.org/release/SDL2-${SDL2VER}.tar.gz && \
cd SDL2-${SDL2VER} && \
./configure --build=x86_64-linux-gnu && \
make -j$(nproc) && make install && \
rm ../SDL2-${SDL2VER}.tar.gz
rm ../SDL2-${SDL2VER}.tar.gz && \
cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/
RUN \
ln -sf /proc/self/mounts /etc/mtab && \

View File

@ -456,6 +456,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|Clang|AppleClang")
"NDEBUG"
">"
"ENABLE_OPENGL;"
"SPDLOG_ACTIVE_LEVEL=0;"
)
endif()
################################################################################

View File

@ -239,8 +239,8 @@ static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_par
dxgi.current_height = (uint32_t)(l_param >> 16);
break;
case WM_DESTROY:
Ship::ExecuteHooks<Ship::ExitGame>();
exit(0);
PostQuitMessage(0);
break;
case WM_PAINT:
if (dxgi.in_paint) {
dxgi.recursive_paint_detected = true;
@ -378,6 +378,8 @@ static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Ship::ExecuteHooks<Ship::ExitGame>();
}
static void gfx_dxgi_get_dimensions(uint32_t *width, uint32_t *height) {

View File

@ -238,6 +238,8 @@ static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) {
Ship::Switch::Exit();
#endif
Ship::ExecuteHooks<Ship::ExitGame>();
SDL_Quit();
}
static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) {
@ -307,9 +309,8 @@ static void gfx_sdl_handle_events(void) {
CVar_Save();
break;
case SDL_QUIT:
Ship::ExecuteHooks<Ship::ExitGame>();
SDL_Quit(); // bandaid fix for linux window closing issue
exit(0);
is_running = false;
break;
}
}
}

View File

@ -1559,7 +1559,7 @@ set(ALL_FILES
################################################################################
# Target
################################################################################
add_executable(${PROJECT_NAME} ${ALL_FILES})
add_executable(${PROJECT_NAME} ${ALL_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/Resource.rc)
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
use_props(${PROJECT_NAME} "${CMAKE_CONFIGURATION_TYPES}" "${DEFAULT_CXX_PROPS}")

View File

@ -374,6 +374,7 @@ typedef enum {
FLAG_SCENE_TREASURE,
FLAG_SCENE_CLEAR,
FLAG_SCENE_COLLECTIBLE,
FLAG_COW_MILKED
} FlagType;
typedef struct {

View File

@ -165,6 +165,11 @@ void LocationTable_Init() {
//Zoras River
locationTable[ZR_OPEN_GROTTO_CHEST] = ItemLocation::Chest (0x3E, 0x09, "ZR Open Grotto Chest", ZR_OPEN_GROTTO_CHEST, RED_RUPEE, {Category::cZorasRiver, Category::cGrotto,}, SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_MAGIC_BEAN_SALESMAN] = ItemLocation::Base (0x54, 0x16, "ZR Magic Bean Salesman", ZR_MAGIC_BEAN_SALESMAN, MAGIC_BEAN, {Category::cZorasRiver,}, SpoilerCollectionCheck::MagicBeans(0x54, 0x01), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_ZELDAS_LULLABY] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs Zelda's Lullaby", ZR_FROGS_ZELDAS_LULLABY, PURPLE_RUPEE, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_EPONAS_SONG] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs Epona's Song", ZR_FROGS_EPONAS_SONG, PURPLE_RUPEE, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_SARIAS_SONG] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs Saria's Song", ZR_FROGS_SARIAS_SONG, PURPLE_RUPEE, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_SUNS_SONG] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs Sun's Song", ZR_FROGS_SUNS_SONG, PURPLE_RUPEE, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_SONG_OF_TIME] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs Song of Time", ZR_FROGS_SONG_OF_TIME, PURPLE_RUPEE, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_IN_THE_RAIN] = ItemLocation::Base (0x54, 0x3E, "ZR Frogs in the Rain", ZR_FROGS_IN_THE_RAIN, PIECE_OF_HEART, {Category::cZorasRiver,}, SpoilerCollectionCheck::EventChkInf(0xD6), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_FROGS_OCARINA_GAME] = ItemLocation::Base (0x54, 0x76, "ZR Frogs Ocarina Game", ZR_FROGS_OCARINA_GAME, PIECE_OF_HEART, {Category::cZorasRiver, Category::cMinigame,}, SpoilerCollectionCheck::EventChkInf(0xD0), SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
locationTable[ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH] = ItemLocation::Collectable(0x54, 0x04, "ZR Near Open Grotto Freestanding PoH", ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH, PIECE_OF_HEART, {Category::cZorasRiver,}, SpoilerCollectionCheckGroup::GROUP_ZORAS_RIVER);
@ -1302,6 +1307,11 @@ std::vector<uint32_t> overworldLocations = {
//Zoras River
ZR_OPEN_GROTTO_CHEST,
ZR_MAGIC_BEAN_SALESMAN,
ZR_FROGS_ZELDAS_LULLABY,
ZR_FROGS_EPONAS_SONG,
ZR_FROGS_SARIAS_SONG,
ZR_FROGS_SUNS_SONG,
ZR_FROGS_SONG_OF_TIME,
ZR_FROGS_IN_THE_RAIN,
ZR_FROGS_OCARINA_GAME,
ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH,

View File

@ -729,6 +729,16 @@ void GenerateItemPool() {
PlaceItemInLocation(WASTELAND_BOMBCHU_SALESMAN, BOMBCHU_10, false, true);
}
if (ShuffleFrogSongRupees) {
AddItemToMainPool(PURPLE_RUPEE, 5);
} else {
PlaceItemInLocation(ZR_FROGS_ZELDAS_LULLABY, PURPLE_RUPEE, false, true);
PlaceItemInLocation(ZR_FROGS_EPONAS_SONG, PURPLE_RUPEE, false, true);
PlaceItemInLocation(ZR_FROGS_SARIAS_SONG, PURPLE_RUPEE, false, true);
PlaceItemInLocation(ZR_FROGS_SUNS_SONG, PURPLE_RUPEE, false, true);
PlaceItemInLocation(ZR_FROGS_SONG_OF_TIME, PURPLE_RUPEE, false, true);
}
if (ShuffleAdultTradeQuest) {
AddItemToMainPool(POCKET_EGG);
AddItemToMainPool(COJIRO);

View File

@ -472,6 +472,11 @@ typedef enum {
//ZORA'S_RIVER
ZR_MAGIC_BEAN_SALESMAN,
ZR_OPEN_GROTTO_CHEST,
ZR_FROGS_ZELDAS_LULLABY,
ZR_FROGS_EPONAS_SONG,
ZR_FROGS_SARIAS_SONG,
ZR_FROGS_SUNS_SONG,
ZR_FROGS_SONG_OF_TIME,
ZR_FROGS_IN_THE_RAIN,
ZR_FROGS_OCARINA_GAME,
ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH,

View File

@ -31,6 +31,21 @@ void AreaTable_Init_ZorasDomain() {
LocationAccess(ZR_FROGS_IN_THE_RAIN, {[]{return IsChild && CanPlay(SongOfStorms);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && SongOfStorms;}}),
LocationAccess(ZR_FROGS_ZELDAS_LULLABY, {[]{return IsChild && CanPlay(ZeldasLullaby);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && ZeldasLullaby;}}),
LocationAccess(ZR_FROGS_EPONAS_SONG, {[]{return IsChild && CanPlay(EponasSong);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && EponasSong;}}),
LocationAccess(ZR_FROGS_SARIAS_SONG, {[]{return IsChild && CanPlay(SariasSong);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && SariasSong;}}),
LocationAccess(ZR_FROGS_SUNS_SONG, {[]{return IsChild && CanPlay(SunsSong);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && SunsSong;}}),
LocationAccess(ZR_FROGS_SONG_OF_TIME, {[]{return IsChild && CanPlay(SongOfTime);},
/*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) ||
((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsChild && SongOfTime;}}),
LocationAccess(ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH, {[]{return IsChild || CanUse(HOVER_BOOTS) || (IsAdult && LogicZoraRiverLower);}}),
LocationAccess(ZR_NEAR_DOMAIN_FREESTANDING_POH, {[]{return IsChild || CanUse(HOVER_BOOTS) || (IsAdult && LogicZoraRiverUpper);}}),
LocationAccess(ZR_GS_LADDER, {[]{return IsChild && AtNight && CanChildAttack && CanGetNightTimeGS;}}),

View File

@ -385,6 +385,13 @@ string_view merchantsHintsDesc = "These hints will make Medigoron and the
"The Clearer Hints setting will affect how they\n" //
"refer to the item."; //
/*------------------------------ //
| SHUFFLE FROG SONG RUPEES | //
------------------------------*/ //
string_view frogSongRupeesDesc = "Enabling this adds 5 Purple Rupees to the item\n" //
"pool and shuffles the rewards from playing Zelda's\n"
"Lullaby, Epona's Song, Saria's Song, Sun's Song,\n"
"and Song of Time to the frogs in Zora's River.\n";//
/*------------------------------ //
| SHUFFLE ADULT TRADE | //
------------------------------*/ //
string_view adultTradeDesc = "Enabling this adds all of the adult trade quest\n"//

View File

@ -126,6 +126,8 @@ extern string_view magicBeansDesc;
extern string_view merchantsDesc;
extern string_view merchantsHintsDesc;
extern string_view frogSongRupeesDesc;
extern string_view adultTradeDesc;
extern string_view chestMinigameDesc;

View File

@ -166,6 +166,7 @@ namespace Settings {
Option ShuffleGerudoToken = Option::Bool("Shuffle Gerudo Card", {"Off", "On"}, {gerudoTokenDesc});
Option ShuffleMagicBeans = Option::Bool("Shuffle Magic Beans", {"Off", "On"}, {magicBeansDesc});
Option ShuffleMerchants = Option::U8 ("Shuffle Merchants", {"Off", "On (No Hints)", "On (With Hints)"}, {merchantsDesc, merchantsHintsDesc});
Option ShuffleFrogSongRupees = Option::Bool("Shuffle Frog Song Rupees",{"Off", "On"}, {frogSongRupeesDesc});
Option ShuffleAdultTradeQuest = Option::Bool("Shuffle Adult Trade", {"Off", "On"}, {adultTradeDesc});
Option ShuffleChestMinigame = Option::U8 ("Shuffle Chest Minigame", {"Off", "On (Separate)", "On (Pack)"}, {chestMinigameDesc});
std::vector<Option *> shuffleOptions = {
@ -183,6 +184,7 @@ namespace Settings {
&ShuffleGerudoToken,
&ShuffleMagicBeans,
&ShuffleMerchants,
&ShuffleFrogSongRupees,
&ShuffleAdultTradeQuest,
&ShuffleChestMinigame,
};
@ -1263,6 +1265,7 @@ namespace Settings {
ctx.shuffleGerudoToken = (ShuffleGerudoToken) ? 1 : 0;
ctx.shuffleMagicBeans = (ShuffleMagicBeans) ? 1 : 0;
ctx.shuffleMerchants = ShuffleMerchants.Value<uint8_t>();
ctx.shuffleFrogSongRupees= (ShuffleFrogSongRupees) ? 1 : 0;
ctx.shuffleAdultTradeQuest = (ShuffleAdultTradeQuest) ? 1 : 0;
ctx.shuffleChestMinigame = ShuffleChestMinigame.Value<uint8_t>();
@ -1724,6 +1727,21 @@ namespace Settings {
IncludeAndHide({WASTELAND_BOMBCHU_SALESMAN});
}
//Force include frog song rupees if they're not shuffled
if (ShuffleFrogSongRupees) {
Unhide({ZR_FROGS_ZELDAS_LULLABY});
Unhide({ZR_FROGS_EPONAS_SONG});
Unhide({ZR_FROGS_SARIAS_SONG});
Unhide({ZR_FROGS_SUNS_SONG});
Unhide({ZR_FROGS_SONG_OF_TIME});
} else {
IncludeAndHide({ZR_FROGS_ZELDAS_LULLABY});
IncludeAndHide({ZR_FROGS_EPONAS_SONG});
IncludeAndHide({ZR_FROGS_SARIAS_SONG});
IncludeAndHide({ZR_FROGS_SUNS_SONG});
IncludeAndHide({ZR_FROGS_SONG_OF_TIME});
}
//Force include adult trade quest if Shuffle Adult Trade Quest is off
std::vector<uint32_t> adultTradeLocations = {KAK_TRADE_POCKET_CUCCO, LW_TRADE_COJIRO, KAK_TRADE_ODD_MUSHROOM, LW_TRADE_ODD_POTION, GV_TRADE_SAW, DMT_TRADE_BROKEN_SWORD, ZD_TRADE_PRESCRIPTION, LH_TRADE_FROG, DMT_TRADE_EYEDROPS};
if (ShuffleAdultTradeQuest) {
@ -2261,6 +2279,7 @@ namespace Settings {
&ShuffleCows,
&ShuffleMagicBeans,
&ShuffleMerchants,
&ShuffleFrogSongRupees,
&ShuffleAdultTradeQuest,
&GossipStoneHints,
};
@ -2516,6 +2535,7 @@ namespace Settings {
SkipChildStealth.SetSelectedIndex(cvarSettings[RSK_SKIP_CHILD_STEALTH]);
ShuffleGerudoToken.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD]);
ShuffleFrogSongRupees.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_FROG_SONG_RUPEES]);
// the checkbox works because 0 is "Off" and 1 is "Fairy Ocarina"
StartingOcarina.SetSelectedIndex(cvarSettings[RSK_STARTING_OCARINA]);

View File

@ -403,6 +403,7 @@ typedef struct {
uint8_t shuffleGerudoToken;
uint8_t shuffleMagicBeans;
uint8_t shuffleMerchants;
uint8_t shuffleFrogSongRupees;
uint8_t shuffleAdultTradeQuest;
uint8_t shuffleChestMinigame;
@ -904,6 +905,7 @@ void UpdateSettings(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettin
extern Option ShuffleGerudoToken;
extern Option ShuffleMagicBeans;
extern Option ShuffleMerchants;
extern Option ShuffleFrogSongRupees;
extern Option ShuffleAdultTradeQuest;
extern Option ShuffleChestMinigame;

View File

@ -183,6 +183,11 @@ std::unordered_map<std::string, RandomizerCheck> SpoilerfileCheckNameToEnum = {
{ "DMC Deku Scrub Grotto Center", RC_DMC_DEKU_SCRUB_GROTTO_CENTER },
{ "ZR Open Grotto Chest", RC_ZR_OPEN_GROTTO_CHEST },
{ "ZR Magic Bean Salesman", RC_ZR_MAGIC_BEAN_SALESMAN },
{ "ZR Frogs Zelda's Lullaby", RC_ZR_FROGS_ZELDAS_LULLABY },
{ "ZR Frogs Epona's Song", RC_ZR_FROGS_EPONAS_SONG },
{ "ZR Frogs Saria's Song", RC_ZR_FROGS_SARIAS_SONG },
{ "ZR Frogs Sun's Song", RC_ZR_FROGS_SUNS_SONG },
{ "ZR Frogs Song of Time", RC_ZR_FROGS_SONG_OF_TIME },
{ "ZR Frogs in the Rain", RC_ZR_FROGS_IN_THE_RAIN },
{ "ZR Frogs Ocarina Game", RC_ZR_FROGS_OCARINA_GAME },
{ "ZR Near Open Grotto Freestanding PoH", RC_ZR_NEAR_OPEN_GROTTO_FREESTANDING_POH },
@ -3525,6 +3530,7 @@ void GenerateRandomizerImgui() {
cvarSettings[RSK_SHUFFLE_WEIRD_EGG] = ((CVar_GetS32("gRandomizeSkipChildZelda", 0) == 0) && CVar_GetS32("gRandomizeShuffleWeirdEgg", 0));
cvarSettings[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] = CVar_GetS32("gRandomizeShuffleGerudoToken", 0);
cvarSettings[RSK_SHUFFLE_FROG_SONG_RUPEES] = CVar_GetS32("gRandomizeShuffleFrogSongRupees", 0);
cvarSettings[RSK_ITEM_POOL] = CVar_GetS32("gRandomizeItemPool", 1);
cvarSettings[RSK_ICE_TRAPS] = CVar_GetS32("gRandomizeIceTraps", 1);
cvarSettings[RSK_GOSSIP_STONE_HINTS] = CVar_GetS32("gRandomizeGossipStoneHints", 1);
@ -3617,6 +3623,7 @@ void DrawRandoEditor(bool& open) {
const char* randoShuffleGerudoToken[2] = { "Off", "On" };
const char* randoShuffleMagicBeans[2] = { "Off", "On" };
const char* randoShuffleMerchants[3] = { "Off", "On (no hints)", "On (with hints)" };
const char* randoShuffleFrogSongRupees[2] = { "Off", "On" };
const char* randoShuffleAdultTrade[2] = { "Off", "On" };
// Shuffle Dungeon Items Settings
@ -4036,9 +4043,8 @@ void DrawRandoEditor(bool& open) {
PaddedSeparator();
// Shuffle Cows
ImGui::Text(Settings::ShuffleCows.GetName().c_str());
SohImGui::EnhancementCheckbox(Settings::ShuffleCows.GetName().c_str(), "gRandomizeShuffleCows");
InsertHelpHoverText("Cows give a randomized item from the pool upon performing Epona's Song in front of them.");
SohImGui::EnhancementCombobox("gRandomizeShuffleCows", randoShuffleCows, 2, 0);
PaddedSeparator();
if(CVar_GetS32("gRandomizeStartingKokiriSword", 0) == 0) {
@ -4102,6 +4108,17 @@ void DrawRandoEditor(bool& open) {
"The Gerudo Card is required to enter the Gerudo Training Grounds, opening "
"the gate to Haunted Wasteland and the Horseback Archery minigame."
);
PaddedSeparator();
// Shuffle Frog Song Rupees
SohImGui::EnhancementCheckbox(Settings::ShuffleFrogSongRupees.GetName().c_str(), "gRandomizeShuffleFrogSongRupees");
InsertHelpHoverText(
"Shuffles 5 Purple Rupees into to the item pool, and allows\n"
"you to earn items by playing songs at the Frog Choir.\n"
"\n"
"This setting does not effect the item earned from playing\n"
"the Song of Storms and the frog song minigame."
);
}
ImGui::PopItemWidth();

View File

@ -978,6 +978,7 @@ typedef enum {
RSK_SHUFFLE_COWS,
RSK_SHUFFLE_WEIRD_EGG,
RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD,
RSK_SHUFFLE_FROG_SONG_RUPEES,
RSK_ITEM_POOL,
RSK_ICE_TRAPS,
RSK_GOSSIP_STONE_HINTS,

View File

@ -1,8 +1,9 @@
#pragma once
static struct {
std::thread thread;
std::condition_variable cv_to_thread, cv_from_thread;
std::mutex mutex;
bool initialized;
bool running;
bool processing;
} audio;

View File

@ -83,11 +83,73 @@ extern "C" void ResourceMgr_CacheDirectory(const char* resName);
extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path);
std::unordered_map<std::string, ExtensionEntry> ExtensionCache;
void OTRAudio_Thread() {
while (audio.running) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (!audio.processing && audio.running) {
audio.cv_to_thread.wait(Lock);
}
if (!audio.running) {
break;
}
}
std::unique_lock<std::mutex> Lock(audio.mutex);
//AudioMgr_ThreadEntry(&gAudioMgr);
// 528 and 544 relate to 60 fps at 32 kHz 32000/60 = 533.333..
// in an ideal world, one third of the calls should use num_samples=544 and two thirds num_samples=528
//#define SAMPLES_HIGH 560
//#define SAMPLES_LOW 528
// PAL values
//#define SAMPLES_HIGH 656
//#define SAMPLES_LOW 624
// 44KHZ values
#define SAMPLES_HIGH 752
#define SAMPLES_LOW 720
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1 )
#define NUM_AUDIO_CHANNELS 2
int samples_left = AudioPlayer_Buffered();
u32 num_audio_samples = samples_left < AudioPlayer_GetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW;
// 3 is the maximum authentic frame divisor.
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS), num_audio_samples);
}
AudioPlayer_Play((u8*)audio_buffer, num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
audio.processing = false;
audio.cv_from_thread.notify_one();
}
}
// C->C++ Bridge
extern "C" void OTRAudio_Init()
{
// Precache all our samples, sequences, etc...
ResourceMgr_CacheDirectory("audio");
if (!audio.running) {
audio.running = true;
audio.thread = std::thread(OTRAudio_Thread);
}
}
extern "C" void OTRAudio_Exit() {
// Tell the audio thread to stop
{
std::unique_lock<std::mutex> Lock(audio.mutex);
audio.running = false;
}
audio.cv_to_thread.notify_all();
// Wait until the audio thread quit
audio.thread.join();
}
extern "C" void OTRExtScanner() {
@ -128,6 +190,10 @@ extern "C" void InitOTR() {
OTRExtScanner();
}
extern "C" void DeinitOTR() {
OTRAudio_Exit();
}
#ifdef _WIN32
extern "C" uint64_t GetFrequency() {
LARGE_INTEGER nFreq;
@ -227,56 +293,10 @@ extern "C" void Graph_StartFrame() {
// C->C++ Bridge
extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
#ifndef __SWITCH__
if (!audio.initialized) {
audio.initialized = true;
std::thread([]() {
for (;;) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (!audio.processing) {
audio.cv_to_thread.wait(Lock);
}
}
std::unique_lock<std::mutex> Lock(audio.mutex);
//AudioMgr_ThreadEntry(&gAudioMgr);
// 528 and 544 relate to 60 fps at 32 kHz 32000/60 = 533.333..
// in an ideal world, one third of the calls should use num_samples=544 and two thirds num_samples=528
//#define SAMPLES_HIGH 560
//#define SAMPLES_LOW 528
// PAL values
//#define SAMPLES_HIGH 656
//#define SAMPLES_LOW 624
// 44KHZ values
#define SAMPLES_HIGH 752
#define SAMPLES_LOW 720
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1 )
#define NUM_AUDIO_CHANNELS 2
int samples_left = AudioPlayer_Buffered();
u32 num_audio_samples = samples_left < AudioPlayer_GetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW;
// 3 is the maximum authentic frame divisor.
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS), num_audio_samples);
}
AudioPlayer_Play((u8*)audio_buffer, num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
audio.processing = false;
audio.cv_from_thread.notify_one();
}
}).detach();
}
{
std::unique_lock<std::mutex> Lock(audio.mutex);
audio.processing = true;
}
#endif
audio.cv_to_thread.notify_one();
std::vector<std::unordered_map<Mtx*, MtxF>> mtx_replacements;
@ -319,14 +339,12 @@ extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
last_fps = fps;
last_update_rate = R_UPDATE_RATE;
#ifndef __SWITCH__
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (audio.processing) {
audio.cv_from_thread.wait(Lock);
}
}
#endif
// OTRTODO: FIGURE OUT END FRAME POINT
/* if (OTRGlobals::Instance->context->GetWindow()->lastScancode != -1)

View File

@ -481,22 +481,6 @@ static void RunFrame()
uint64_t ticksA, ticksB;
ticksA = GetPerfCounter();
#ifdef __SWITCH__
#define SAMPLES_HIGH 752
#define SAMPLES_LOW 720
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1 )
#define NUM_AUDIO_CHANNELS 2
int samples_left = AudioPlayer_Buffered();
u32 num_audio_samples = samples_left < AudioPlayer_GetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW;
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS), num_audio_samples);
}
AudioPlayer_Play((u8*)audio_buffer, num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
#endif
Graph_StartFrame();
// TODO: Workaround for rumble being too long. Implement os thread functions.

View File

@ -36,12 +36,14 @@ void Main_LogSystemHeap(void) {
osSyncPrintf(VT_RST);
}
void main(int argc, char** argv)
int main(int argc, char** argv)
{
GameConsole_Init();
InitOTR();
BootCommands_Init();
Main(0);
DeinitOTR();
return 0;
}
void Main(void* arg) {

View File

@ -18,9 +18,8 @@ void func_809E0070(Actor* thisx, GlobalContext* globalCtx);
void func_809DF494(EnCow* this, GlobalContext* globalCtx);
void func_809DF6BC(EnCow* this, GlobalContext* globalCtx);
struct CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx);
CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx);
void EnCow_MoveForRandomizer(EnCow* this, GlobalContext* globalCtx);
GetItemID EnCow_GetRandomizerItem(EnCow* this, GlobalContext* globalCtx);
void func_809DF778(EnCow* this, GlobalContext* globalCtx);
void func_809DF7D8(EnCow* this, GlobalContext* globalCtx);
void func_809DF870(EnCow* this, GlobalContext* globalCtx);
@ -216,12 +215,7 @@ void func_809DF730(EnCow* this, GlobalContext* globalCtx) {
}
}
struct CowInfo {
int cowId;
RandomizerCheck randomizerCheck;
};
struct CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx) {
CowInfo EnCow_GetInfo(EnCow* this, GlobalContext* globalCtx) {
struct CowInfo cowInfo;
cowInfo.cowId = -1;
@ -294,21 +288,10 @@ void EnCow_MoveForRandomizer(EnCow* this, GlobalContext* globalCtx) {
}
void EnCow_SetCowMilked(EnCow* this, GlobalContext* globalCtx) {
struct CowInfo cowInfo = EnCow_GetInfo(this, globalCtx);
gSaveContext.cowsMilked[cowInfo.cowId] = 1;
}
GetItemID EnCow_GetRandomizerItem(EnCow* this, GlobalContext* globalCtx) {
GetItemID itemId = ITEM_NONE;
struct CowInfo cowInfo = EnCow_GetInfo(this, globalCtx);
if (!gSaveContext.cowsMilked[cowInfo.cowId]) {
itemId = Randomizer_GetItemIdFromKnownCheck(cowInfo.randomizerCheck, GI_MILK);
} else if (Inventory_HasEmptyBottle()) {
itemId = GI_MILK;
}
return itemId;
CowInfo cowInfo = EnCow_GetInfo(this, globalCtx);
Player* player = GET_PLAYER(globalCtx);
player->pendingFlag.flagID = cowInfo.cowId;
player->pendingFlag.flagType = FLAG_COW_MILKED;
}
void func_809DF778(EnCow* this, GlobalContext* globalCtx) {
@ -316,16 +299,7 @@ void func_809DF778(EnCow* this, GlobalContext* globalCtx) {
this->actor.parent = NULL;
this->actionFunc = func_809DF730;
} else {
if (gSaveContext.n64ddFlag) {
GetItemID itemId = EnCow_GetRandomizerItem(this, globalCtx);
func_8002F434(&this->actor, globalCtx, itemId, 10000.0f, 100.0f);
EnCow_SetCowMilked(this, globalCtx);
if (itemId == GI_ICE_TRAP) {
Message_StartTextbox(globalCtx, 0xF8, &this->actor);
}
} else {
func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f);
}
func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f);
}
}
@ -334,15 +308,13 @@ void func_809DF7D8(EnCow* this, GlobalContext* globalCtx) {
this->actor.flags &= ~ACTOR_FLAG_16;
Message_CloseTextbox(globalCtx);
this->actionFunc = func_809DF778;
if (!gSaveContext.n64ddFlag) {
func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f);
}
func_8002F434(&this->actor, globalCtx, GI_MILK, 10000.0f, 100.0f);
}
}
void func_809DF870(EnCow* this, GlobalContext* globalCtx) {
if ((Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(globalCtx)) {
if (Inventory_HasEmptyBottle() || (gSaveContext.n64ddFlag && EnCow_GetRandomizerItem(this, globalCtx) != ITEM_NONE)) {
if (Inventory_HasEmptyBottle()) {
Message_ContinueTextbox(globalCtx, 0x2007);
this->actionFunc = func_809DF7D8;
} else {
@ -363,6 +335,23 @@ void func_809DF8FC(EnCow* this, GlobalContext* globalCtx) {
func_809DF494(this, globalCtx);
}
bool EnCow_HasBeenMilked(EnCow* this, GlobalContext* globalCtx) {
CowInfo cowInfo = EnCow_GetInfo(this, globalCtx);
return gSaveContext.cowsMilked[cowInfo.cowId];
}
void EnCow_GivePlayerRandomizedItem(EnCow* this, GlobalContext* globalCtx) {
if (!EnCow_HasBeenMilked(this, globalCtx)) {
CowInfo cowInfo = EnCow_GetInfo(this, globalCtx);
GetItemID itemId = Randomizer_GetItemIdFromKnownCheck(cowInfo.randomizerCheck, GI_MILK);
func_8002F434(&this->actor, globalCtx, itemId, 10000.0f, 100.0f);
} else {
// once we've gotten the rando reward from the cow,
// return them to the their default action function
this->actionFunc = func_809DF96C;
}
}
void func_809DF96C(EnCow* this, GlobalContext* globalCtx) {
if ((globalCtx->msgCtx.ocarinaMode == OCARINA_MODE_00) || (globalCtx->msgCtx.ocarinaMode == OCARINA_MODE_04)) {
if (DREG(53) != 0) {
@ -373,6 +362,19 @@ void func_809DF96C(EnCow* this, GlobalContext* globalCtx) {
if ((this->actor.xzDistToPlayer < 150.0f) &&
(ABS((s16)(this->actor.yawTowardsPlayer - this->actor.shape.rot.y)) < 0x61A8)) {
DREG(53) = 0;
// when randomized with cowsanity, if we haven't gotten the
// reward from this cow yet, give that, otherwise use the
// vanilla cow behavior
if (gSaveContext.n64ddFlag &&
Randomizer_GetSettingValue(RSK_SHUFFLE_COWS) &&
!EnCow_HasBeenMilked(this, globalCtx)) {
EnCow_SetCowMilked(this, globalCtx);
// setting the ocarina mode here prevents intermittent issues
// with the item get not triggering until walking away
globalCtx->msgCtx.ocarinaMode = OCARINA_MODE_00;
this->actionFunc = EnCow_GivePlayerRandomizedItem;
return;
}
this->actionFunc = func_809DF8FC;
this->actor.flags |= ACTOR_FLAG_16;
func_8002F2CC(&this->actor, globalCtx, 170.0f);

View File

@ -21,4 +21,9 @@ typedef struct EnCow {
/* 0x027C */ EnCowActionFunc actionFunc;
} EnCow; // size = 0x0280
typedef struct CowInfo {
int cowId;
RandomizerCheck randomizerCheck;
} CowInfo;
#endif

View File

@ -43,6 +43,7 @@ void EnFr_OcarinaMistake(EnFr* this, GlobalContext* globalCtx);
void EnFr_SetupReward(EnFr* this, GlobalContext* globalCtx, u8 unkCondition);
void EnFr_PrintTextBox(EnFr* this, GlobalContext* globalCtx);
void EnFr_TalkBeforeReward(EnFr* this, GlobalContext* globalCtx);
RandomizerCheck EnFr_RandomizerCheckFromSongIndex(u16 songIndex);
void EnFr_SetReward(EnFr* this, GlobalContext* globalCtx);
// Deactivate
@ -920,6 +921,23 @@ void EnFr_TalkBeforeReward(EnFr* this, GlobalContext* globalCtx) {
}
}
RandomizerCheck EnFr_RandomizerCheckFromSongIndex(u16 songIndex) {
switch (songIndex) {
case FROG_ZL:
return RC_ZR_FROGS_ZELDAS_LULLABY;
case FROG_EPONA:
return RC_ZR_FROGS_EPONAS_SONG;
case FROG_SARIA:
return RC_ZR_FROGS_SARIAS_SONG;
case FROG_SUNS:
return RC_ZR_FROGS_SUNS_SONG;
case FROG_SOT:
return RC_ZR_FROGS_SONG_OF_TIME;
default:
return RC_UNKNOWN_CHECK;
}
}
void EnFr_SetReward(EnFr* this, GlobalContext* globalCtx) {
u16 songIndex;
@ -930,7 +948,11 @@ void EnFr_SetReward(EnFr* this, GlobalContext* globalCtx) {
if ((songIndex >= FROG_ZL) && (songIndex <= FROG_SOT)) {
if (!(gSaveContext.eventChkInf[13] & sSongIndex[songIndex])) {
gSaveContext.eventChkInf[13] |= sSongIndex[songIndex];
this->reward = GI_RUPEE_PURPLE;
if (!gSaveContext.n64ddFlag) {
this->reward = GI_RUPEE_PURPLE;
} else {
this->reward = Randomizer_GetItemIdFromKnownCheck(EnFr_RandomizerCheckFromSongIndex(songIndex), GI_RUPEE_PURPLE);
}
} else {
this->reward = GI_RUPEE_BLUE;
}
@ -1016,7 +1038,7 @@ void EnFr_GiveReward(EnFr* this, GlobalContext* globalCtx) {
}
void EnFr_SetIdle(EnFr* this, GlobalContext* globalCtx) {
if ((Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_DONE) && Message_ShouldAdvance(globalCtx)) {
if ((Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_DONE) && Message_ShouldAdvance(globalCtx) || (gSaveContext.n64ddFlag && this->reward == GI_ICE_TRAP)) {
this->actionFunc = EnFr_Idle;
}
}

View File

@ -6262,6 +6262,9 @@ void Player_SetPendingFlag(Player* this, GlobalContext* globalCtx) {
case FLAG_SCENE_TREASURE:
Flags_SetTreasure(globalCtx, this->pendingFlag.flagID);
break;
case FLAG_COW_MILKED:
gSaveContext.cowsMilked[this->pendingFlag.flagID] = 1;
break;
case FLAG_NONE:
default:
break;