From 61563fadba016053496a6c07c275b12c28f4338f Mon Sep 17 00:00:00 2001 From: Jerom Venneker Date: Mon, 3 Mar 2025 21:24:59 +0100 Subject: [PATCH] Added some of the settings, loaded from the server, implemented game win condition (pretty untested) --- APCpp | 2 +- README.md | 15 ++- soh/soh/Enhancements/mods.cpp | 2 + .../Enhancements/randomizer/archipelago.cpp | 108 ++++++++++-------- soh/soh/Enhancements/randomizer/archipelago.h | 4 + soh/soh/Enhancements/randomizer/context.cpp | 15 ++- soh/soh/Enhancements/randomizer/context.h | 3 +- .../Enhancements/randomizer/randomizer.cpp | 2 + soh/soh/Enhancements/randomizer/settings.cpp | 90 +++++++++++++++ soh/soh/Enhancements/randomizer/settings.h | 2 + .../Enhancements/randomizer/static_data.cpp | 78 ++++++++++++- soh/soh/Enhancements/randomizer/static_data.h | 2 +- .../Enhancements/timesplits/TimeSplits.cpp | 2 + 13 files changed, 268 insertions(+), 57 deletions(-) diff --git a/APCpp b/APCpp index 38d5cf079..505a174a3 160000 --- a/APCpp +++ b/APCpp @@ -1 +1 @@ -Subproject commit 38d5cf0798fb4134b0414ad680a0c2798d0bc311 +Subproject commit 505a174a3b0f55f71f69e221d23003b223836fd7 diff --git a/README.md b/README.md index 128af0a73..40b5c669d 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,21 @@ ## Fork Overview -Currently playing around trying to see if we're able to hook the existing Archipelago Ocarina of time randomizer into Ship of Harkinian. +This Repo serves as a proof of concept for SoH to connect to Archipellago, the current implementation tries to implement the existing OoT AP world. +Mainly to check out the SoH Repo and to have something to develop against while the others on the discord are working on an AP world for SoH. + +I'm not entierly happy with this implementation, but I'd like to create a bespoke Client to connect to AP with instead of relying on the current 3rd party clien't I've just thrown in to try out. + You can currently connect to the multiworld server, scout the items. -If you have no randomizer genereted (the `Randomizer` folder is empty) and press the `Link up` button, you'll be able to start a randomizer save file +If you have no randomizer genereted (the `Randomizer` folder is empty) `Connect` to the multiworld from the main screen, click the `Scout` button to load all of the item locations and finally press the `Link up` button, you'll be able to start a randomizer save file. with the items populated with the location from the server. -Sending and recieving is not implemented yet, Multiworld items are currently just recovery hearts +Sending and Recieving should be implemented, Saving the game doesn't save the current item index localy (the library is a bit anoying with that). + +Not all checks have been mapped, and some may be mapped incorrectly. +The victory condition should be implemented but has largely gone untested. +Results may varry + ## Website diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index c5b59eac7..76a3fade2 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -41,6 +41,7 @@ #include "objects/object_link_child/object_link_child.h" #include "soh_assets.h" #include "kaleido.h" +#include "soh/Enhancements/randomizer/archipelago.h" extern "C" { #include @@ -876,6 +877,7 @@ void RegisterBossDefeatTimestamps() { case ACTOR_BOSS_GANON2: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.gameComplete = true; + ArchipelagoClient::getInstance().send_game_won(); break; case ACTOR_BOSS_GANONDROF: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME; diff --git a/soh/soh/Enhancements/randomizer/archipelago.cpp b/soh/soh/Enhancements/randomizer/archipelago.cpp index bcbc615e6..b14df700b 100644 --- a/soh/soh/Enhancements/randomizer/archipelago.cpp +++ b/soh/soh/Enhancements/randomizer/archipelago.cpp @@ -36,6 +36,7 @@ auto SubscribeToSlotData() { ArchipelagoClient::ArchipelagoClient() { ItemRecievedCallback = nullptr; + game_won = false; namespace apc = AP_Client_consts; CVarSetInteger("archipelago_connected", 0); @@ -64,71 +65,71 @@ void registerSlotCallbacks() { SubscribeToSlotData<"bridge_medallions">(); SubscribeToSlotData<"bridge_rewards">(); SubscribeToSlotData<"bridge_tokens">(); - SubscribeToSlotData<"bridge_hearts">(); +// SubscribeToSlotData<"bridge_hearts">(); SubscribeToSlotData<"shuffle_ganon_bosskey">(); SubscribeToSlotData<"ganon_bosskey_medallions">(); SubscribeToSlotData<"ganon_bosskey_stones">(); SubscribeToSlotData<"ganon_bosskey_rewards">(); SubscribeToSlotData<"ganon_bosskey_tokens">(); - SubscribeToSlotData<"ganon_bosskey_hearts">(); +// SubscribeToSlotData<"ganon_bosskey_hearts">(); SubscribeToSlotData<"trials">(); SubscribeToSlotData<"triforce_hunt">(); SubscribeToSlotData<"triforce_goal">(); - SubscribeToSlotData<"extra_triforce_percentage">(); - SubscribeToSlotData<"shopsanity">(); - SubscribeToSlotData<"shop_slots">(); +// SubscribeToSlotData<"extra_triforce_percentage">(); +// SubscribeToSlotData<"shopsanity">(); +// SubscribeToSlotData<"shop_slots">(); SubscribeToSlotData<"shopsanity_prices">(); - SubscribeToSlotData<"tokensanity">(); - SubscribeToSlotData<"dungeon_shortcuts">(); - SubscribeToSlotData<"mq_dungeons_mode">(); - SubscribeToSlotData<"mq_dungeons_count">(); - SubscribeToSlotData<"shuffle_interior_entrances">(); - SubscribeToSlotData<"shuffle_grotto_entrances">(); - SubscribeToSlotData<"shuffle_dungeon_entrances">(); - SubscribeToSlotData<"shuffle_overworld_entrances">(); - SubscribeToSlotData<"shuffle_bosses">(); - SubscribeToSlotData<"key_rings">(); - SubscribeToSlotData<"enhance_map_compass">(); - SubscribeToSlotData<"shuffle_mapcompass">(); - SubscribeToSlotData<"shuffle_smallkeys">(); - SubscribeToSlotData<"shuffle_hideoutkeys">(); - SubscribeToSlotData<"shuffle_bosskeys">(); - SubscribeToSlotData<"logic_rules">(); - SubscribeToSlotData<"logic_no_night_tokens_without_suns_song">(); - SubscribeToSlotData<"warp_songs">(); - SubscribeToSlotData<"shuffle_song_items">(); - SubscribeToSlotData<"shuffle_medigoron_carpet_salesman">(); - SubscribeToSlotData<"shuffle_frog_song_rupees">(); - SubscribeToSlotData<"shuffle_scrubs">(); - SubscribeToSlotData<"shuffle_child_trade">(); - SubscribeToSlotData<"shuffle_freestanding_items">(); - SubscribeToSlotData<"shuffle_pots">(); - SubscribeToSlotData<"shuffle_crates">(); - SubscribeToSlotData<"shuffle_cows">(); - SubscribeToSlotData<"shuffle_beehives">(); - SubscribeToSlotData<"shuffle_kokiri_sword">(); - SubscribeToSlotData<"shuffle_ocarinas">(); - SubscribeToSlotData<"shuffle_gerudo_card">(); - SubscribeToSlotData<"shuffle_beans">(); +// SubscribeToSlotData<"tokensanity">(); +// SubscribeToSlotData<"dungeon_shortcuts">(); +// SubscribeToSlotData<"mq_dungeons_mode">(); +// SubscribeToSlotData<"mq_dungeons_count">(); +// SubscribeToSlotData<"shuffle_interior_entrances">(); +// SubscribeToSlotData<"shuffle_grotto_entrances">(); +// SubscribeToSlotData<"shuffle_dungeon_entrances">(); +// SubscribeToSlotData<"shuffle_overworld_entrances">(); +// SubscribeToSlotData<"shuffle_bosses">(); +// SubscribeToSlotData<"key_rings">(); +// SubscribeToSlotData<"enhance_map_compass">(); +// SubscribeToSlotData<"shuffle_mapcompass">(); +// SubscribeToSlotData<"shuffle_smallkeys">(); +// SubscribeToSlotData<"shuffle_hideoutkeys">(); +// SubscribeToSlotData<"shuffle_bosskeys">(); +// SubscribeToSlotData<"logic_rules">(); +// SubscribeToSlotData<"logic_no_night_tokens_without_suns_song">(); +// SubscribeToSlotData<"warp_songs">(); +// SubscribeToSlotData<"shuffle_song_items">(); +// SubscribeToSlotData<"shuffle_medigoron_carpet_salesman">(); +// SubscribeToSlotData<"shuffle_frog_song_rupees">(); +// SubscribeToSlotData<"shuffle_scrubs">(); +// SubscribeToSlotData<"shuffle_child_trade">(); +// SubscribeToSlotData<"shuffle_freestanding_items">(); +// SubscribeToSlotData<"shuffle_pots">(); +// SubscribeToSlotData<"shuffle_crates">(); +// SubscribeToSlotData<"shuffle_cows">(); +// SubscribeToSlotData<"shuffle_beehives">(); +// SubscribeToSlotData<"shuffle_kokiri_sword">(); +// SubscribeToSlotData<"shuffle_ocarinas">(); +// SubscribeToSlotData<"shuffle_gerudo_card">(); +// SubscribeToSlotData<"shuffle_beans">(); SubscribeToSlotData<"starting_age">(); - SubscribeToSlotData<"bombchus_in_logic">(); - SubscribeToSlotData<"spawn_positions">(); - SubscribeToSlotData<"owl_drops">(); +// SubscribeToSlotData<"bombchus_in_logic">(); +// SubscribeToSlotData<"spawn_positions">(); +// SubscribeToSlotData<"owl_drops">(); SubscribeToSlotData<"no_epona_race">(); - SubscribeToSlotData<"skip_some_minigame_phases">(); +// SubscribeToSlotData<"skip_some_minigame_phases">(); SubscribeToSlotData<"complete_mask_quest">(); SubscribeToSlotData<"free_scarecrow">(); - SubscribeToSlotData<"plant_beans">(); +// SubscribeToSlotData<"plant_beans">(); SubscribeToSlotData<"chicken_count">(); SubscribeToSlotData<"big_poe_count">(); - SubscribeToSlotData<"fae_torch_count">(); +// SubscribeToSlotData<"fae_torch_count">(); SubscribeToSlotData<"blue_fire_arrows">(); SubscribeToSlotData<"damage_multiplier">(); - SubscribeToSlotData<"deadly_bonks">(); - SubscribeToSlotData<"starting_tod">(); - SubscribeToSlotData<"junk_ice_traps">(); +// SubscribeToSlotData<"deadly_bonks">(); +// SubscribeToSlotData<"starting_tod">(); +// SubscribeToSlotData<"junk_ice_traps">(); SubscribeToSlotData<"start_with_consumables">(); - SubscribeToSlotData<"adult_trade_start">(); +// SubscribeToSlotData<"adult_trade_start">(); } bool ArchipelagoClient::start_client() { @@ -193,6 +194,9 @@ bool ArchipelagoClient::isConnected() { void ArchipelagoClient::check_location(RandomizerCheck SoH_check_id) { std::string_view ap_name = Rando::StaticData::SohCheckToAP[SoH_check_id]; + if(ap_name.empty()) { + return; + } int64_t ap_item_id = CheckNameToId(std::string(ap_name)); SPDLOG_TRACE("Checked: {}({}), sending to AP server", ap_name, ap_item_id); @@ -226,11 +230,10 @@ void ArchipelagoClient::on_clear_items() { void ArchipelagoClient::on_item_recieved(int64_t recieved_item_id, bool notify_player) { // call each callback - SPDLOG_TRACE("Trying to give rupie..."); std::string item_name = getAPitemName(recieved_item_id); ArchipelagoClient& ap_client = ArchipelagoClient::getInstance(); if(ap_client.ItemRecievedCallback) { - SPDLOG_TRACE("Giving Rupie! {}", item_name); + SPDLOG_TRACE("item recieved: {}, notify: {}", item_name, notify_player); ap_client.ItemRecievedCallback.operator()(item_name); // somehow passing it through the itemname breaks it???? } } @@ -246,6 +249,13 @@ void ArchipelagoClient::on_location_scouted(std::vector network_ getInstance().scouted_items = network_items; } +void ArchipelagoClient::send_game_won() { + if(!game_won) { + AP_StoryComplete(); + game_won = true; + } +} + char* ArchipelagoClient::get_server_address_buff() { return server_address; } diff --git a/soh/soh/Enhancements/randomizer/archipelago.h b/soh/soh/Enhancements/randomizer/archipelago.h index 9310b656d..8f4487b9a 100644 --- a/soh/soh/Enhancements/randomizer/archipelago.h +++ b/soh/soh/Enhancements/randomizer/archipelago.h @@ -49,6 +49,8 @@ class ArchipelagoClient { // todo move me back down when done testing static void on_item_recieved(int64_t recieved_item_id, bool notify_player); + void send_game_won(); + protected: ArchipelagoClient(); @@ -62,6 +64,8 @@ class ArchipelagoClient { char slot_name[AP_Client_consts::MAX_PLAYER_NAME_LENGHT]; char password[AP_Client_consts::MAX_PLAYER_NAME_LENGHT]; + bool game_won; + std::map slot_data; std::set locations; std::vector scouted_items; diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index fa5f083b6..3e700c1cb 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -366,6 +366,7 @@ void Context::ParseArchipelago() { mSpoilerLoaded = false; ArchipelagoClient& ap_client = ArchipelagoClient::getInstance(); + Rando::Settings::GetInstance()->ParseArchipelago(ap_client.get_slot_data()); ParseArchipelagoItemsLocations(ap_client.get_scouted_items()); // lets see if counting AP_loaded as spoiler loaded does the trick @@ -383,6 +384,7 @@ void Context::ParseHashIconIndexesJson(nlohmann::json spoilerFileJson) { } void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) { + // first fill all the items with their vanilla location nlohmann::json locationsJson = spoilerFileJson["locations"]; for (auto it = locationsJson.begin(); it != locationsJson.end(); ++it) { RandomizerCheck rc = StaticData::locationNameToEnum[it.key()]; @@ -405,10 +407,21 @@ void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) { } } -void Context::ParseArchipelagoItemsLocations(std::vector scouted_items) { +void Context::ParseArchipelagoItemsLocations(const std::vector& scouted_items) { int playerId = AP_GetPlayerID(); // todo change me when the client is developed further + + // init the item table with regular items first + for(int rc = 1; rc <= RC_MAX; rc++) { + itemLocationTable[rc].SetPlacedItem(StaticData::GetLocation(static_cast(rc))->GetVanillaItem()); + } + for(const AP_NetworkItem& ap_item: scouted_items) { const RandomizerCheck rc = StaticData::APcheckToSoh.find(ap_item.locationName)->second; + + if(rc == RC_KF_MIDOS_TOP_RIGHT_CHEST) { + continue; + } + if(playerId == ap_item.player) { // our item SPDLOG_TRACE("Populated item {} at location {}", ap_item.itemName, ap_item.locationName); diff --git a/soh/soh/Enhancements/randomizer/context.h b/soh/soh/Enhancements/randomizer/context.h index c308de715..6f63b3a4d 100644 --- a/soh/soh/Enhancements/randomizer/context.h +++ b/soh/soh/Enhancements/randomizer/context.h @@ -126,7 +126,8 @@ class Context { RandomizerArea GetAreaFromString(std::string str); void ParseArchipelago(); - void ParseArchipelagoItemsLocations(const std::vector); + void ParseArchipelagoSettings(const std::map& slot_data); + void ParseArchipelagoItemsLocations(const std::vector& slot_data); /** * @brief Get the hash for the current seed. diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 5632c3fab..10e3c625e 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -39,6 +39,7 @@ #include "soh/util.h" #include "fishsanity.h" #include "randomizerTypes.h" +#include "archipelago.h" extern std::map rcAreaNames; @@ -4109,6 +4110,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { if (gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected == (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1)) { gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.gameComplete = 1; + ArchipelagoClient::getInstance().send_game_won(); Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY); Play_PerformSave(play); GameInteractor_SetTriforceHuntCreditsWarpActive(true); diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 2ca325635..df7c3cb50 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -2226,6 +2226,96 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { } } +void Settings::ParseArchipelago(const std::map& slot_data) { + for(const auto& slot_it : slot_data) { + const std::string& APname = slot_it.first; + int value = slot_it.second; + + // remap value if needed + if(APname == "open_forest") { + if(value == 0) { // open and closed options swapped + value = 2; + } else if (value == 2) { + value = 0; + } + } + if(APname == "open_kakoriko") { + if(value == 2) { + value = 0; // closed + } else { + value = 1; // count the "zelda" option as open + } + } + else if(APname == "open_door_of_time") { + if(value == 1) { + value = 2; // AP doesn't have the song only option + } + } + else if(APname == "zora_fountain") { + if(value == 0) { // open and closed options swapped + value = 2; + } else if (value == 2) { + value = 0; + } + } + else if(APname == "bridge") { + if(value == 0) { // always open and vanilla are swapped + value = 1; + } else if (value == 1) { + value = 0; + } else if (value == 6) { // no support for ap hearts option + value = 1; // revert to always open so its at least beatable + } else if (value == 5) { + value++; // Token option is one over, because AP doesn't have dungeon blue warp count as option + } + } + else if(APname == "shopsanity_prices") { + value = 1; // default shopsanity to cheap ballanced for now + } + else if(APname == "start_with_consumables") { // just setting this shouldn't magically work I think + if(value == 1) { + const RandomizerSettingKey stick_index = StaticData::optionNameToEnum["Start with Stick Ammo"]; + mContext->GetOption(stick_index).Set(mOptions[stick_index].GetValueFromText("Yes")); + const RandomizerSettingKey nut_index = StaticData::optionNameToEnum["Start with Stick Ammo"]; + mContext->GetOption(nut_index).Set(mOptions[nut_index].GetValueFromText("Yes")); + } else { + const RandomizerSettingKey stick_index = StaticData::optionNameToEnum["Start with Stick Ammo"]; + mContext->GetOption(stick_index).Set(mOptions[stick_index].GetValueFromText("No")); + const RandomizerSettingKey nut_index = StaticData::optionNameToEnum["Start with Stick Ammo"]; + mContext->GetOption(nut_index).Set(mOptions[nut_index].GetValueFromText("No")); + } + } + const std::string& setting_name = std::string(StaticData::APsettingToHoSsetting[APname]); + const RandomizerSettingKey index = StaticData::optionNameToEnum[setting_name]; + mContext->GetOption(index).Set(value); + SPDLOG_INFO("Parsed Setting {}: ({}, {})", APname, (int)index, value); + } + + // maybe we have to set a couple of settings manually, if ap doesn't set them + { + //const RandomizerSettingKey index = StaticData::optionNameToEnum["Starting Age"]; + //mContext->GetOption(index).Set(mOptions[index].GetValueFromText("Random")); + } + { + const RandomizerSettingKey index = StaticData::optionNameToEnum["Ganon's Trials"]; + mContext->GetOption(index).Set(mOptions[index].GetValueFromText("Set Number")); + } + { + const RandomizerSettingKey index = StaticData::optionNameToEnum["Logic"]; + mContext->GetOption(index).Set(mOptions[index].GetValueFromText("Glitchless")); + } + { + const RandomizerSettingKey index = StaticData::optionNameToEnum["Starting Hearts"]; + mContext->GetOption(index).Set(2); + } + { + const RandomizerSettingKey index = StaticData::optionNameToEnum["Token Shuffle"]; + mContext->GetOption(index).Set(mOptions[index].GetValueFromText("Off")); + } + //const RandomizerSettingKey index = StaticData::optionNameToEnum["Sleeping Waterfall"]; + //mContext->GetOption(index).Set(0); +} + void Settings::ReloadOptions() { for (int i = 0; i < RSK_MAX; i++) { mOptions[i].SetFromCVar(); diff --git a/soh/soh/Enhancements/randomizer/settings.h b/soh/soh/Enhancements/randomizer/settings.h index 6810b6832..3fb53d622 100644 --- a/soh/soh/Enhancements/randomizer/settings.h +++ b/soh/soh/Enhancements/randomizer/settings.h @@ -112,6 +112,8 @@ class Settings { * @param spoilerFileJson */ void ParseJson(nlohmann::json spoilerFileJson); + + void ParseArchipelago(const std::map& slot_data); std::map> mTricksByArea = {}; void ReloadOptions(); diff --git a/soh/soh/Enhancements/randomizer/static_data.cpp b/soh/soh/Enhancements/randomizer/static_data.cpp index a0ed4d5ef..66750b057 100644 --- a/soh/soh/Enhancements/randomizer/static_data.cpp +++ b/soh/soh/Enhancements/randomizer/static_data.cpp @@ -331,6 +331,82 @@ const std::unordered_mapgenerate_SohcheckToAP std::unordered_map StaticData::APitemToSoh = generate_APitemToSoh_mapping(); std::unordered_map StaticData::APcheckToSoh = generate_APcheckToSoh_mapping(); std::unordered_map StaticData::SohCheckToAP = generate_SohcheckToAP_mapping(); - +std::unordered_map StaticData::APsettingToHoSsetting = { + { "open_forest", "Closed Forest" }, + { "open_kakoriko", "Kakariko Gate" }, + { "open_door_of_time", "Door of Time" }, + { "zora_fountain", "Zora's Fountain" }, + { "gerudo_fortress", "Fortress Carpenters" }, + { "bridge", "Rainbow Bridge" }, // TODO underlying options may not overlap + { "bridge_stones", "Bridge Stone Count" }, + { "bridge_medallions", "Bridge Medallion Count" }, + { "bridge_rewards", "Bridge Reward Count" }, + { "bridge_tokens", "Bridge Token Count" }, + { "bridge_hearts", "NOT_SUPPORTED" }, + { "shuffle_ganon_bosskey", "Ganon's Boss Key" }, + { "ganon_bosskey_medallions", "GCBK Medallion Count" }, + { "ganon_bosskey_stones", "GCBK Stone Count" }, + { "ganon_bosskey_rewards", "GCBK Reward Count" }, + { "ganon_bosskey_tokens", "GCBK Token Count" }, + { "ganon_bosskey_hearts", "NOT_SUPPORTED" }, + { "trials", "Ganon's Trials Count" }, + { "triforce_hunt", "Triforce Hunt" }, + { "triforce_goal", "Triforce Hunt Required Pieces" }, + { "extra_triforce_percentage", "CUSTOM_IMPLEMENTATION" }, // TODO calc "Triforce Hunt Required Pieces" from percentage, Actually not really required to make the game run I think + { "shopsanity", "Shop Shuffle" }, + { "shop_slots", "Shops Item Count" }, + { "shopsanity_prices", "Shops Prices" }, // Item Prizes not in slot data, anything above starting wallet will be lowered to max 99 + { "tokensanity", "Token Shuffle" }, + { "dungeon_shortcuts", "NOT_SUPPORTED" }, // TODO could be implemented manually through + { "mq_dungeons_mode", "NOT_SUPORTED" }, // Not sure if we can figure this one out + { "mq_dungeons_count", "NOT_SUPPORTED" }, // Slot data doesn't expose the master quest dungeons used + { "shuffle_interior_entrances", "NOT_SUPPORTED" }, // Mapping not in Slot Data + { "shuffle_grotto_entrances", "NOT_SUPPORTED" }, // Mapping not in Slot Data + { "shuffle_dungeon_entrances", "NOT_SUPPORTED" }, // Mapping not in Slot Data + { "shuffle_overworld_entrances", "NOT_SUPPORTED" }, // Mapping not in Slot Data + { "shuffle_bosses", "NOT_SUPPORTED" }, // Mapping not in Slot Data + { "key_rings", "Key Rings" }, // slot data not exposed when set to random, however may not matter if you just can just recieve the key ring, may only be needed for logic + { "enhance_map_compass", "NOT_SUPPORTED" }, // Can't find it in rando settings, may be a qol setting + { "shuffle_mapcompass", "Maps/Compasses" }, // NOT REQUIRED + { "shuffle_smallkeys", "Small Key Shuffle" }, // NOT REQUIRED + { "shuffle_hideoutkeys", "Gerudo Fortress Keys" }, // NOT REQUIRED + { "shuffle_bosskeys", "Boss Key Shuffle" }, // NOT REQUIRED + { "logic_rules", "Logic" }, // NOT REQUIRED + { "logic_no_night_tokens_without_suns_song", "Night Skulltula's Expect Sun's Song" }, // NOT REQUIRED + { "warp_songs", "NOT_SUPORTED" }, // slot data not exposed + { "shuffle_song_items", "Shuffle Songs" }, // NOT REQUIRED + { "shuffle_medigoron_carpet_salesman", "NOT_SUPPORTED" }, // NOT REQURIED, , Should set "Shuffle Merchants" option (This option also sets granny) + { "shuffle_frog_song_rupees", "Shuffle Frog Song Rupees" }, // NOT REQUIRED + { "shuffle_scrubs", "Scrubs Shuffle" }, // NOT REQUIRED + { "shuffle_child_trade", "NOT_SUPPORTED" }, // NOT REQUIRED + { "shuffle_freestanding_items", "NOT_SUPPORTED" }, // NOT REQUIRED + { "shuffle_pots", "Shuffle Pots" }, // NOT REQUIRED + { "shuffle_crates", "MAYBE_SUPPORTED_TODO" }, // Maybe Requred, TODO TEST + { "shuffle_cows", "Shuffle Cows" }, // NOT REQUIRED + { "shuffle_beehives", "Shuffle Beehives" }, // NOT REQUIRED + { "shuffle_kokiri_sword", "Shuffle Kokiri Sword" }, // NOT REQUIRED + { "shuffle_ocarinas", "Shuffle Ocarinas" }, // NOT REQUIRED + { "shuffle_gerudo_card", "Shuffle Gerudo Membership Card" }, //NOT REQUIRED + { "shuffle_beans", "NOT_SUPPORTED" }, // NOT REQUIRED, Should set "Shuffle Merchants" option (This option also sets granny) + { "starting_age", "Selected Starting Age" }, // should this also set "Seelcted Starting Age" + { "bombchus_in_logic", "NOT_SUPPORTED" }, // NOT REQUIRED, Probably Implemented as a trick + { "spawn_positions", "NOT_SUPPORTED" }, + { "owl_drops", "NOT_SUPPORTRED" }, + { "no_epona_race", "Skip Epona Race" }, + { "skip_some_minigame_phases", "NOT_IMPLEMENTED" }, // should be under quality of life options + { "complete_mask_quest", "Complete Mask Quest" }, + { "free_scarecrow", "Skip Scarecrow's Song" }, + { "plant_beans", "NOT_SUPPORTRED" }, + { "chicken_count", "Cuccos to return" }, + { "big_poe_count", "Big Poe Target Count" }, + { "fae_torch_count", "NOT_SUPPORTED" }, + { "blue_fire_arrows", "Blue Fire Arrows" }, + { "damage_multiplier", "Damage Multiplier" }, + { "deadly_bonks", "NOT_SUPPORTED" }, + { "starting_tod", "NOT_SUPPORTED" }, + { "junk_ice_traps", "Ice Traps" }, // NOT REQUIRED + { "start_with_consumables", "CUSTOM_IMPLEMENTATION" }, // might be able to just set "Start with Stick Ammo" and "Start with Nut Ammo", TODO check starting consumables + { "adult_trade_start", "NOT_SUPPORTED" } +}; } diff --git a/soh/soh/Enhancements/randomizer/static_data.h b/soh/soh/Enhancements/randomizer/static_data.h index bbf83de00..f6a32e7f5 100644 --- a/soh/soh/Enhancements/randomizer/static_data.h +++ b/soh/soh/Enhancements/randomizer/static_data.h @@ -72,7 +72,7 @@ class StaticData { static std::unordered_map APitemToSoh; static std::unordered_map APcheckToSoh; static std::unordered_map SohCheckToAP; - + static std::unordered_map APsettingToHoSsetting; StaticData(); ~StaticData(); }; diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index b17f95b82..f7b8216f5 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -14,6 +14,7 @@ #include "soh/Enhancements/debugger/debugSaveEditor.h" #include "soh_assets.h" #include "assets/textures/parameter_static/parameter_static.h" +#include "soh/Enhancements/randomizer/archipelago.h" extern "C" { extern SaveContext gSaveContext; @@ -347,6 +348,7 @@ void HandleDragAndDrop(std::vector& objectList, int targetIndex, co void TimeSplitCompleteSplits() { gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.gameComplete = true; + ArchipelagoClient::getInstance().send_game_won(); } void TimeSplitsSkipSplit(uint32_t index) {