Added some of the settings, loaded from the server, implemented game win condition (pretty untested)

This commit is contained in:
Jerom Venneker
2025-03-03 21:24:59 +01:00
parent ea2e718c06
commit 61563fadba
13 changed files with 268 additions and 57 deletions

2
APCpp

Submodule APCpp updated: 38d5cf0798...505a174a3b

View File

@@ -3,12 +3,21 @@
## Fork Overview ## 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. 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. 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 ## Website

View File

@@ -41,6 +41,7 @@
#include "objects/object_link_child/object_link_child.h" #include "objects/object_link_child/object_link_child.h"
#include "soh_assets.h" #include "soh_assets.h"
#include "kaleido.h" #include "kaleido.h"
#include "soh/Enhancements/randomizer/archipelago.h"
extern "C" { extern "C" {
#include <z64.h> #include <z64.h>
@@ -876,6 +877,7 @@ void RegisterBossDefeatTimestamps() {
case ACTOR_BOSS_GANON2: case ACTOR_BOSS_GANON2:
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.ship.stats.gameComplete = true; gSaveContext.ship.stats.gameComplete = true;
ArchipelagoClient::getInstance().send_game_won();
break; break;
case ACTOR_BOSS_GANONDROF: case ACTOR_BOSS_GANONDROF:
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME;

View File

@@ -36,6 +36,7 @@ auto SubscribeToSlotData() {
ArchipelagoClient::ArchipelagoClient() { ArchipelagoClient::ArchipelagoClient() {
ItemRecievedCallback = nullptr; ItemRecievedCallback = nullptr;
game_won = false;
namespace apc = AP_Client_consts; namespace apc = AP_Client_consts;
CVarSetInteger("archipelago_connected", 0); CVarSetInteger("archipelago_connected", 0);
@@ -64,71 +65,71 @@ void registerSlotCallbacks() {
SubscribeToSlotData<"bridge_medallions">(); SubscribeToSlotData<"bridge_medallions">();
SubscribeToSlotData<"bridge_rewards">(); SubscribeToSlotData<"bridge_rewards">();
SubscribeToSlotData<"bridge_tokens">(); SubscribeToSlotData<"bridge_tokens">();
SubscribeToSlotData<"bridge_hearts">(); // SubscribeToSlotData<"bridge_hearts">();
SubscribeToSlotData<"shuffle_ganon_bosskey">(); SubscribeToSlotData<"shuffle_ganon_bosskey">();
SubscribeToSlotData<"ganon_bosskey_medallions">(); SubscribeToSlotData<"ganon_bosskey_medallions">();
SubscribeToSlotData<"ganon_bosskey_stones">(); SubscribeToSlotData<"ganon_bosskey_stones">();
SubscribeToSlotData<"ganon_bosskey_rewards">(); SubscribeToSlotData<"ganon_bosskey_rewards">();
SubscribeToSlotData<"ganon_bosskey_tokens">(); SubscribeToSlotData<"ganon_bosskey_tokens">();
SubscribeToSlotData<"ganon_bosskey_hearts">(); // SubscribeToSlotData<"ganon_bosskey_hearts">();
SubscribeToSlotData<"trials">(); SubscribeToSlotData<"trials">();
SubscribeToSlotData<"triforce_hunt">(); SubscribeToSlotData<"triforce_hunt">();
SubscribeToSlotData<"triforce_goal">(); SubscribeToSlotData<"triforce_goal">();
SubscribeToSlotData<"extra_triforce_percentage">(); // SubscribeToSlotData<"extra_triforce_percentage">();
SubscribeToSlotData<"shopsanity">(); // SubscribeToSlotData<"shopsanity">();
SubscribeToSlotData<"shop_slots">(); // SubscribeToSlotData<"shop_slots">();
SubscribeToSlotData<"shopsanity_prices">(); SubscribeToSlotData<"shopsanity_prices">();
SubscribeToSlotData<"tokensanity">(); // SubscribeToSlotData<"tokensanity">();
SubscribeToSlotData<"dungeon_shortcuts">(); // SubscribeToSlotData<"dungeon_shortcuts">();
SubscribeToSlotData<"mq_dungeons_mode">(); // SubscribeToSlotData<"mq_dungeons_mode">();
SubscribeToSlotData<"mq_dungeons_count">(); // SubscribeToSlotData<"mq_dungeons_count">();
SubscribeToSlotData<"shuffle_interior_entrances">(); // SubscribeToSlotData<"shuffle_interior_entrances">();
SubscribeToSlotData<"shuffle_grotto_entrances">(); // SubscribeToSlotData<"shuffle_grotto_entrances">();
SubscribeToSlotData<"shuffle_dungeon_entrances">(); // SubscribeToSlotData<"shuffle_dungeon_entrances">();
SubscribeToSlotData<"shuffle_overworld_entrances">(); // SubscribeToSlotData<"shuffle_overworld_entrances">();
SubscribeToSlotData<"shuffle_bosses">(); // SubscribeToSlotData<"shuffle_bosses">();
SubscribeToSlotData<"key_rings">(); // SubscribeToSlotData<"key_rings">();
SubscribeToSlotData<"enhance_map_compass">(); // SubscribeToSlotData<"enhance_map_compass">();
SubscribeToSlotData<"shuffle_mapcompass">(); // SubscribeToSlotData<"shuffle_mapcompass">();
SubscribeToSlotData<"shuffle_smallkeys">(); // SubscribeToSlotData<"shuffle_smallkeys">();
SubscribeToSlotData<"shuffle_hideoutkeys">(); // SubscribeToSlotData<"shuffle_hideoutkeys">();
SubscribeToSlotData<"shuffle_bosskeys">(); // SubscribeToSlotData<"shuffle_bosskeys">();
SubscribeToSlotData<"logic_rules">(); // SubscribeToSlotData<"logic_rules">();
SubscribeToSlotData<"logic_no_night_tokens_without_suns_song">(); // SubscribeToSlotData<"logic_no_night_tokens_without_suns_song">();
SubscribeToSlotData<"warp_songs">(); // SubscribeToSlotData<"warp_songs">();
SubscribeToSlotData<"shuffle_song_items">(); // SubscribeToSlotData<"shuffle_song_items">();
SubscribeToSlotData<"shuffle_medigoron_carpet_salesman">(); // SubscribeToSlotData<"shuffle_medigoron_carpet_salesman">();
SubscribeToSlotData<"shuffle_frog_song_rupees">(); // SubscribeToSlotData<"shuffle_frog_song_rupees">();
SubscribeToSlotData<"shuffle_scrubs">(); // SubscribeToSlotData<"shuffle_scrubs">();
SubscribeToSlotData<"shuffle_child_trade">(); // SubscribeToSlotData<"shuffle_child_trade">();
SubscribeToSlotData<"shuffle_freestanding_items">(); // SubscribeToSlotData<"shuffle_freestanding_items">();
SubscribeToSlotData<"shuffle_pots">(); // SubscribeToSlotData<"shuffle_pots">();
SubscribeToSlotData<"shuffle_crates">(); // SubscribeToSlotData<"shuffle_crates">();
SubscribeToSlotData<"shuffle_cows">(); // SubscribeToSlotData<"shuffle_cows">();
SubscribeToSlotData<"shuffle_beehives">(); // SubscribeToSlotData<"shuffle_beehives">();
SubscribeToSlotData<"shuffle_kokiri_sword">(); // SubscribeToSlotData<"shuffle_kokiri_sword">();
SubscribeToSlotData<"shuffle_ocarinas">(); // SubscribeToSlotData<"shuffle_ocarinas">();
SubscribeToSlotData<"shuffle_gerudo_card">(); // SubscribeToSlotData<"shuffle_gerudo_card">();
SubscribeToSlotData<"shuffle_beans">(); // SubscribeToSlotData<"shuffle_beans">();
SubscribeToSlotData<"starting_age">(); SubscribeToSlotData<"starting_age">();
SubscribeToSlotData<"bombchus_in_logic">(); // SubscribeToSlotData<"bombchus_in_logic">();
SubscribeToSlotData<"spawn_positions">(); // SubscribeToSlotData<"spawn_positions">();
SubscribeToSlotData<"owl_drops">(); // SubscribeToSlotData<"owl_drops">();
SubscribeToSlotData<"no_epona_race">(); SubscribeToSlotData<"no_epona_race">();
SubscribeToSlotData<"skip_some_minigame_phases">(); // SubscribeToSlotData<"skip_some_minigame_phases">();
SubscribeToSlotData<"complete_mask_quest">(); SubscribeToSlotData<"complete_mask_quest">();
SubscribeToSlotData<"free_scarecrow">(); SubscribeToSlotData<"free_scarecrow">();
SubscribeToSlotData<"plant_beans">(); // SubscribeToSlotData<"plant_beans">();
SubscribeToSlotData<"chicken_count">(); SubscribeToSlotData<"chicken_count">();
SubscribeToSlotData<"big_poe_count">(); SubscribeToSlotData<"big_poe_count">();
SubscribeToSlotData<"fae_torch_count">(); // SubscribeToSlotData<"fae_torch_count">();
SubscribeToSlotData<"blue_fire_arrows">(); SubscribeToSlotData<"blue_fire_arrows">();
SubscribeToSlotData<"damage_multiplier">(); SubscribeToSlotData<"damage_multiplier">();
SubscribeToSlotData<"deadly_bonks">(); // SubscribeToSlotData<"deadly_bonks">();
SubscribeToSlotData<"starting_tod">(); // SubscribeToSlotData<"starting_tod">();
SubscribeToSlotData<"junk_ice_traps">(); // SubscribeToSlotData<"junk_ice_traps">();
SubscribeToSlotData<"start_with_consumables">(); SubscribeToSlotData<"start_with_consumables">();
SubscribeToSlotData<"adult_trade_start">(); // SubscribeToSlotData<"adult_trade_start">();
} }
bool ArchipelagoClient::start_client() { bool ArchipelagoClient::start_client() {
@@ -193,6 +194,9 @@ bool ArchipelagoClient::isConnected() {
void ArchipelagoClient::check_location(RandomizerCheck SoH_check_id) { void ArchipelagoClient::check_location(RandomizerCheck SoH_check_id) {
std::string_view ap_name = Rando::StaticData::SohCheckToAP[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)); int64_t ap_item_id = CheckNameToId(std::string(ap_name));
SPDLOG_TRACE("Checked: {}({}), sending to AP server", ap_name, ap_item_id); 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) { void ArchipelagoClient::on_item_recieved(int64_t recieved_item_id, bool notify_player) {
// call each callback // call each callback
SPDLOG_TRACE("Trying to give rupie...");
std::string item_name = getAPitemName(recieved_item_id); std::string item_name = getAPitemName(recieved_item_id);
ArchipelagoClient& ap_client = ArchipelagoClient::getInstance(); ArchipelagoClient& ap_client = ArchipelagoClient::getInstance();
if(ap_client.ItemRecievedCallback) { 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???? 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<AP_NetworkItem> network_
getInstance().scouted_items = network_items; getInstance().scouted_items = network_items;
} }
void ArchipelagoClient::send_game_won() {
if(!game_won) {
AP_StoryComplete();
game_won = true;
}
}
char* ArchipelagoClient::get_server_address_buff() { char* ArchipelagoClient::get_server_address_buff() {
return server_address; return server_address;
} }

View File

@@ -49,6 +49,8 @@ class ArchipelagoClient {
// todo move me back down when done testing // todo move me back down when done testing
static void on_item_recieved(int64_t recieved_item_id, bool notify_player); static void on_item_recieved(int64_t recieved_item_id, bool notify_player);
void send_game_won();
protected: protected:
ArchipelagoClient(); ArchipelagoClient();
@@ -62,6 +64,8 @@ class ArchipelagoClient {
char slot_name[AP_Client_consts::MAX_PLAYER_NAME_LENGHT]; char slot_name[AP_Client_consts::MAX_PLAYER_NAME_LENGHT];
char password[AP_Client_consts::MAX_PLAYER_NAME_LENGHT]; char password[AP_Client_consts::MAX_PLAYER_NAME_LENGHT];
bool game_won;
std::map<std::string, int> slot_data; std::map<std::string, int> slot_data;
std::set<int64_t> locations; std::set<int64_t> locations;
std::vector<AP_NetworkItem> scouted_items; std::vector<AP_NetworkItem> scouted_items;

View File

@@ -366,6 +366,7 @@ void Context::ParseArchipelago() {
mSpoilerLoaded = false; mSpoilerLoaded = false;
ArchipelagoClient& ap_client = ArchipelagoClient::getInstance(); ArchipelagoClient& ap_client = ArchipelagoClient::getInstance();
Rando::Settings::GetInstance()->ParseArchipelago(ap_client.get_slot_data());
ParseArchipelagoItemsLocations(ap_client.get_scouted_items()); ParseArchipelagoItemsLocations(ap_client.get_scouted_items());
// lets see if counting AP_loaded as spoiler loaded does the trick // 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) { void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) {
// first fill all the items with their vanilla location
nlohmann::json locationsJson = spoilerFileJson["locations"]; nlohmann::json locationsJson = spoilerFileJson["locations"];
for (auto it = locationsJson.begin(); it != locationsJson.end(); ++it) { for (auto it = locationsJson.begin(); it != locationsJson.end(); ++it) {
RandomizerCheck rc = StaticData::locationNameToEnum[it.key()]; RandomizerCheck rc = StaticData::locationNameToEnum[it.key()];
@@ -405,10 +407,21 @@ void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) {
} }
} }
void Context::ParseArchipelagoItemsLocations(std::vector<AP_NetworkItem> scouted_items) { void Context::ParseArchipelagoItemsLocations(const std::vector<AP_NetworkItem>& scouted_items) {
int playerId = AP_GetPlayerID(); // todo change me when the client is developed further 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<RandomizerCheck>(rc))->GetVanillaItem());
}
for(const AP_NetworkItem& ap_item: scouted_items) { for(const AP_NetworkItem& ap_item: scouted_items) {
const RandomizerCheck rc = StaticData::APcheckToSoh.find(ap_item.locationName)->second; const RandomizerCheck rc = StaticData::APcheckToSoh.find(ap_item.locationName)->second;
if(rc == RC_KF_MIDOS_TOP_RIGHT_CHEST) {
continue;
}
if(playerId == ap_item.player) { if(playerId == ap_item.player) {
// our item // our item
SPDLOG_TRACE("Populated item {} at location {}", ap_item.itemName, ap_item.locationName); SPDLOG_TRACE("Populated item {} at location {}", ap_item.itemName, ap_item.locationName);

View File

@@ -126,7 +126,8 @@ class Context {
RandomizerArea GetAreaFromString(std::string str); RandomizerArea GetAreaFromString(std::string str);
void ParseArchipelago(); void ParseArchipelago();
void ParseArchipelagoItemsLocations(const std::vector<AP_NetworkItem>); void ParseArchipelagoSettings(const std::map<std::string, int>& slot_data);
void ParseArchipelagoItemsLocations(const std::vector<AP_NetworkItem>& slot_data);
/** /**
* @brief Get the hash for the current seed. * @brief Get the hash for the current seed.

View File

@@ -39,6 +39,7 @@
#include "soh/util.h" #include "soh/util.h"
#include "fishsanity.h" #include "fishsanity.h"
#include "randomizerTypes.h" #include "randomizerTypes.h"
#include "archipelago.h"
extern std::map<RandomizerCheckArea, std::string> rcAreaNames; extern std::map<RandomizerCheckArea, std::string> 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)) { 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.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.ship.stats.gameComplete = 1; gSaveContext.ship.stats.gameComplete = 1;
ArchipelagoClient::getInstance().send_game_won();
Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY); Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY);
Play_PerformSave(play); Play_PerformSave(play);
GameInteractor_SetTriforceHuntCreditsWarpActive(true); GameInteractor_SetTriforceHuntCreditsWarpActive(true);

View File

@@ -2226,6 +2226,96 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
} }
} }
void Settings::ParseArchipelago(const std::map<std::string, int>& 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() { void Settings::ReloadOptions() {
for (int i = 0; i < RSK_MAX; i++) { for (int i = 0; i < RSK_MAX; i++) {
mOptions[i].SetFromCVar(); mOptions[i].SetFromCVar();

View File

@@ -112,6 +112,8 @@ class Settings {
* @param spoilerFileJson * @param spoilerFileJson
*/ */
void ParseJson(nlohmann::json spoilerFileJson); void ParseJson(nlohmann::json spoilerFileJson);
void ParseArchipelago(const std::map<std::string, int>& slot_data);
std::map<RandomizerArea, std::vector<RandomizerTrick>> mTricksByArea = {}; std::map<RandomizerArea, std::vector<RandomizerTrick>> mTricksByArea = {};
void ReloadOptions(); void ReloadOptions();

View File

@@ -331,6 +331,82 @@ const std::unordered_map<RandomizerCheck, std::string_view>generate_SohcheckToAP
std::unordered_map<std::string_view, RandomizerGet> StaticData::APitemToSoh = generate_APitemToSoh_mapping(); std::unordered_map<std::string_view, RandomizerGet> StaticData::APitemToSoh = generate_APitemToSoh_mapping();
std::unordered_map<std::string_view, RandomizerCheck> StaticData::APcheckToSoh = generate_APcheckToSoh_mapping(); std::unordered_map<std::string_view, RandomizerCheck> StaticData::APcheckToSoh = generate_APcheckToSoh_mapping();
std::unordered_map<RandomizerCheck, std::string_view> StaticData::SohCheckToAP = generate_SohcheckToAP_mapping(); std::unordered_map<RandomizerCheck, std::string_view> StaticData::SohCheckToAP = generate_SohcheckToAP_mapping();
std::unordered_map<std::string_view, std::string_view> 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" }
};
} }

View File

@@ -72,7 +72,7 @@ class StaticData {
static std::unordered_map<std::string_view, RandomizerGet> APitemToSoh; static std::unordered_map<std::string_view, RandomizerGet> APitemToSoh;
static std::unordered_map<std::string_view, RandomizerCheck> APcheckToSoh; static std::unordered_map<std::string_view, RandomizerCheck> APcheckToSoh;
static std::unordered_map<RandomizerCheck, std::string_view> SohCheckToAP; static std::unordered_map<RandomizerCheck, std::string_view> SohCheckToAP;
static std::unordered_map<std::string_view, std::string_view> APsettingToHoSsetting;
StaticData(); StaticData();
~StaticData(); ~StaticData();
}; };

View File

@@ -14,6 +14,7 @@
#include "soh/Enhancements/debugger/debugSaveEditor.h" #include "soh/Enhancements/debugger/debugSaveEditor.h"
#include "soh_assets.h" #include "soh_assets.h"
#include "assets/textures/parameter_static/parameter_static.h" #include "assets/textures/parameter_static/parameter_static.h"
#include "soh/Enhancements/randomizer/archipelago.h"
extern "C" { extern "C" {
extern SaveContext gSaveContext; extern SaveContext gSaveContext;
@@ -347,6 +348,7 @@ void HandleDragAndDrop(std::vector<SplitObject>& objectList, int targetIndex, co
void TimeSplitCompleteSplits() { void TimeSplitCompleteSplits() {
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.ship.stats.gameComplete = true; gSaveContext.ship.stats.gameComplete = true;
ArchipelagoClient::getInstance().send_game_won();
} }
void TimeSplitsSkipSplit(uint32_t index) { void TimeSplitsSkipSplit(uint32_t index) {