Merge pull request #61 from jeromkiller/AddArchipelagoClientLib
Add archipelago client lib
This commit is contained in:
18
README.md
18
README.md
@@ -1,6 +1,24 @@
|
||||

|
||||

|
||||
|
||||
## Fork Overview
|
||||
|
||||
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) `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 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
|
||||
|
||||
Official Website: https://www.shipofharkinian.com/
|
||||
|
||||
26
differences.md
Normal file
26
differences.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Differences between AP and Hos
|
||||
AP Has a seperate option for shuffle beans and combines Medigoron and Carpet salesman
|
||||
SoH combines the three and lumps Granny into the setting as well
|
||||
|
||||
## Missing in Hos
|
||||
[ ] HoS rainbow bridge does not support the "Heart count" option in Archipellago
|
||||
[ ] Enhance map and compass to give info, couldn't find it in rando settings, but may be a qol thing
|
||||
[ ] Option for preplanted beans
|
||||
[ ] Option for Shadow temple torch count
|
||||
[ ] Deadly Bonks Option (would be implemented in logic too, for crates)
|
||||
[ ] Starting at Certain time of day
|
||||
|
||||
[ ] "Zelda" option for Kakoriko Open setting
|
||||
|
||||
## Missing in AP
|
||||
No Greg (the green rupee)
|
||||
|
||||
|
||||
|
||||
## missing slot data
|
||||
[ ] Entrance mapping for entrance randomzier
|
||||
[ ] Gossip Stone hints
|
||||
[ ] Cosmetic settings
|
||||
[ ] Dungeon Shortcuts when set to count
|
||||
[ ] Used MQ dungeons when set to count
|
||||
[ ] Shop sanity Item Prizes
|
||||
@@ -39,6 +39,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 <z64.h>
|
||||
@@ -782,6 +783,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;
|
||||
|
||||
250
soh/soh/Enhancements/randomizer/archipelago.cpp
Normal file
250
soh/soh/Enhancements/randomizer/archipelago.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "archipelago.h"
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "soh/util.h"
|
||||
#include <apuuid.hpp>
|
||||
#include <apclient.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "randomizerTypes.h"
|
||||
#include "static_data.h"
|
||||
#include "../game-interactor/GameInteractor.h"
|
||||
|
||||
ArchipelagoClient::ArchipelagoClient() {
|
||||
std::string uuid = ap_get_uuid("uuid");
|
||||
|
||||
ItemRecievedCallback = nullptr;
|
||||
game_won = false;
|
||||
|
||||
namespace apc = AP_Client_consts;
|
||||
CVarSetInteger("archipelago_connected", 0);
|
||||
strncpy(server_address, CVarGetString(apc::SETTING_ADDRESS, apc::DEFAULT_SERVER_NAME), apc::MAX_ADDRESS_LENGTH);
|
||||
strncpy(slot_name, CVarGetString(apc::SETTING_NAME, ""), apc::MAX_PLAYER_NAME_LENGHT);
|
||||
|
||||
// call poll every frame
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([](){ArchipelagoClient::getInstance().poll();});
|
||||
}
|
||||
|
||||
ArchipelagoClient& ArchipelagoClient::getInstance() {
|
||||
static ArchipelagoClient Client;
|
||||
return Client;
|
||||
}
|
||||
|
||||
bool ArchipelagoClient::start_client() {
|
||||
if(apclient != NULL) {
|
||||
apclient.reset();
|
||||
}
|
||||
|
||||
apclient = std::unique_ptr<APClient>(new APClient(uuid, AP_Client_consts::AP_GAME_NAME, server_address));
|
||||
|
||||
apclient->set_room_info_handler([&]() {
|
||||
std::list<std::string> tags;
|
||||
// tags.push_back("DeathLink"); // todo, implement deathlink
|
||||
apclient->ConnectSlot(slot_name, password, 0b001, tags);
|
||||
});
|
||||
|
||||
apclient->set_items_received_handler([&](const std::list<APClient::NetworkItem>& items) {
|
||||
for(const APClient::NetworkItem& item : items) {
|
||||
on_item_recieved(item.item, false); // todo get rid of notify, since it doesn't work for us right now anyway
|
||||
}
|
||||
});
|
||||
|
||||
apclient->set_location_info_handler([&](const std::list<APClient::NetworkItem>& items) {
|
||||
scouted_items.clear();
|
||||
|
||||
for(const APClient::NetworkItem& item: items) {
|
||||
ApItem apItem;
|
||||
const std::string game = apclient->get_player_game(item.player);
|
||||
apItem.itemName = apclient->get_item_name(item.item, game);
|
||||
apItem.locationName = apclient->get_location_name(item.location, game);
|
||||
apItem.playerName = apclient->get_player_alias(item.player);
|
||||
apItem.flags = item.flags;
|
||||
apItem.index = item.index;
|
||||
scouted_items.push_back(apItem);
|
||||
|
||||
const std::string itemName = apItem.itemName;
|
||||
const std::string playerName = apItem.playerName;
|
||||
const std::string locationName = apItem.locationName;
|
||||
SPDLOG_TRACE("Location scouted: {} for {} in location {}", itemName, playerName, locationName);
|
||||
}
|
||||
|
||||
}); // todo maybe move these functions to a lambda, since they don't have to be static anymore
|
||||
apclient->set_location_checked_handler([&](const std::list<int64_t> locations) {
|
||||
// todo implement me
|
||||
});
|
||||
|
||||
save_data();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ArchipelagoClient::start_location_scouts() {
|
||||
std::set<int64_t> missing_loc_set = apclient->get_missing_locations();
|
||||
std::set<int64_t> found_loc_set = apclient->get_checked_locations();
|
||||
std::list<int64_t> location_list;
|
||||
for(const int64_t loc_id : missing_loc_set) {
|
||||
location_list.emplace_back(loc_id);
|
||||
}
|
||||
for(const int64_t loc_id : found_loc_set) {
|
||||
location_list.emplace_back(loc_id);
|
||||
}
|
||||
apclient->LocationScouts(location_list);
|
||||
}
|
||||
|
||||
void ArchipelagoClient::save_data() {
|
||||
CVarSetString(AP_Client_consts::SETTING_ADDRESS, server_address);
|
||||
CVarSetString(AP_Client_consts::SETTING_NAME, slot_name);
|
||||
}
|
||||
|
||||
bool ArchipelagoClient::isConnected() {
|
||||
return apclient->get_state() == APClient::State::SLOT_CONNECTED;
|
||||
}
|
||||
|
||||
void ArchipelagoClient::check_location(RandomizerCheck SoH_check_id) {
|
||||
//std::string_view ap_name = Rando::StaticData::SohCheckToAP[SoH_check_id];
|
||||
std::string ap_name = Rando::StaticData::GetLocation(SoH_check_id)->GetName();
|
||||
if(ap_name.empty()) {
|
||||
return;
|
||||
}
|
||||
int64_t ap_item_id = apclient->get_location_id(std::string(ap_name));
|
||||
SPDLOG_TRACE("Checked: {}({}), sending to AP server", ap_name, ap_item_id);
|
||||
|
||||
// currently not sending, because i only get so many real chances
|
||||
if(!isConnected()) {
|
||||
return;
|
||||
}
|
||||
apclient->LocationChecks({ap_item_id});
|
||||
}
|
||||
|
||||
void ArchipelagoClient::addItemRecievedCallback(std::function<void(const std::string&)> callback) {
|
||||
ItemRecievedCallback = callback;
|
||||
}
|
||||
|
||||
void ArchipelagoClient::removeItemRecievedCallback(std::function<void(const std::string&)> old_callback) {
|
||||
ItemRecievedCallback = nullptr;
|
||||
}
|
||||
|
||||
void ArchipelagoClient::on_connected() {
|
||||
// todo implement me
|
||||
SPDLOG_TRACE("AP Connected!!");
|
||||
}
|
||||
//void ArchipelagoClient::on_couldntConnect(AP_ConnectionStatus connection_status) {
|
||||
// // todo implement me
|
||||
//}
|
||||
|
||||
void ArchipelagoClient::on_item_recieved(int64_t recieved_item_id, bool notify_player) {
|
||||
// call each callback
|
||||
const std::string item_name = apclient->get_item_name(recieved_item_id, AP_Client_consts::AP_GAME_NAME);
|
||||
ArchipelagoClient& ap_client = ArchipelagoClient::getInstance();
|
||||
if(ap_client.ItemRecievedCallback) {
|
||||
SPDLOG_TRACE("item recieved: {}, notify: {}", item_name, notify_player);
|
||||
ap_client.ItemRecievedCallback.operator()(item_name); // somehow passing it through the itemname breaks it????
|
||||
}
|
||||
}
|
||||
|
||||
void ArchipelagoClient::send_game_won() {
|
||||
if(!game_won) {
|
||||
apclient->StatusUpdate(APClient::ClientStatus::GOAL);
|
||||
game_won = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ArchipelagoClient::poll() {
|
||||
if(apclient == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
apclient->poll();
|
||||
}
|
||||
|
||||
const std::string& ArchipelagoClient::get_slot_name() const {
|
||||
if(apclient == NULL) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return apclient->get_slot();
|
||||
}
|
||||
|
||||
char* ArchipelagoClient::get_server_address_buff() {
|
||||
return server_address;
|
||||
}
|
||||
char* ArchipelagoClient::get_slot_name_buff() {
|
||||
return slot_name;
|
||||
}
|
||||
char* ArchipelagoClient::get_password_buff() {
|
||||
return password;
|
||||
}
|
||||
|
||||
const std::map<std::string, int>& ArchipelagoClient::get_slot_data() {
|
||||
return slot_data;
|
||||
}
|
||||
|
||||
const std::vector<ArchipelagoClient::ApItem>& ArchipelagoClient::get_scouted_items() {
|
||||
return scouted_items;
|
||||
}
|
||||
|
||||
|
||||
void ArchipelagoWindow::ArchipelagoDrawConnectPage() {
|
||||
ArchipelagoClient& AP_client = ArchipelagoClient::getInstance();
|
||||
ImGui::SeparatorText("Connection info");
|
||||
ImGui::InputText("Server Address", AP_client.get_server_address_buff(), AP_Client_consts::MAX_ADDRESS_LENGTH);
|
||||
ImGui::InputText("Slot Name", AP_client.get_slot_name_buff(), AP_Client_consts::MAX_PLAYER_NAME_LENGHT);
|
||||
ImGui::InputText("Password (leave blank for no password)", AP_client.get_password_buff(), AP_Client_consts::MAX_PASSWORD_LENGTH, ImGuiInputTextFlags_Password);
|
||||
|
||||
static char connected_text[25] = "Disconnected";
|
||||
if(ImGui::Button("Connect")) {
|
||||
bool success = AP_client.start_client();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(connected_text);
|
||||
|
||||
APClient::State con_state = APClient::State::DISCONNECTED;
|
||||
|
||||
if(AP_client.apclient) {
|
||||
con_state = AP_client.apclient->get_state();
|
||||
}
|
||||
|
||||
switch (con_state) {
|
||||
case APClient::State::DISCONNECTED: {
|
||||
strncpy(connected_text, "Disconnected!", 25);
|
||||
break;
|
||||
}
|
||||
case APClient::State::SOCKET_CONNECTING: {
|
||||
strncpy(connected_text, "Socket Connecting!", 25);
|
||||
break;
|
||||
}
|
||||
case APClient::State::SOCKET_CONNECTED: {
|
||||
strncpy(connected_text, "Socket Connected!", 25);
|
||||
break;
|
||||
}
|
||||
case APClient::State::ROOM_INFO: {
|
||||
strncpy(connected_text, "Room info Recieved!", 25);
|
||||
break;
|
||||
}
|
||||
case APClient::State::SLOT_CONNECTED: {
|
||||
strncpy(connected_text, "Slot Connected!", 25);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if(ImGui::Button("scout")) {
|
||||
AP_client.start_location_scouts();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("link up")) {
|
||||
CVarSetInteger("archipelago_connected", 1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void ArchipelagoWindow::DrawElement() {
|
||||
ArchipelagoDrawConnectPage();
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
||||
if(ImGui::Button("give blue ruppie")) {
|
||||
ArchipelagoClient::getInstance().on_item_recieved(66077, true);
|
||||
}
|
||||
};
|
||||
100
soh/soh/Enhancements/randomizer/archipelago.h
Normal file
100
soh/soh/Enhancements/randomizer/archipelago.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include "archipelago_settings_window.h"
|
||||
|
||||
#include "randomizerTypes.h"
|
||||
#include "static_data.h"
|
||||
#include <vector>
|
||||
|
||||
// forward declerations
|
||||
class APClient;
|
||||
|
||||
|
||||
namespace AP_Client_consts {
|
||||
static constexpr int MAX_ADDRESS_LENGTH = 64;
|
||||
static constexpr int MAX_PLAYER_NAME_LENGHT = 17;
|
||||
static constexpr int MAX_PASSWORD_LENGTH = 32;
|
||||
static constexpr char const* DEFAULT_SERVER_NAME = "archipelago.gg:<port number>";
|
||||
|
||||
static constexpr char const* SETTING_ADDRESS = "AP_server_address";
|
||||
static constexpr char const* SETTING_NAME = "AP_slot_name";
|
||||
|
||||
static constexpr char const* AP_GAME_NAME = "Ocarina of Time (SoH)";
|
||||
}
|
||||
|
||||
class ArchipelagoClient{
|
||||
public:
|
||||
struct ApItem {
|
||||
std::string itemName;
|
||||
std::string locationName;
|
||||
std::string playerName;
|
||||
unsigned int flags;
|
||||
int index;
|
||||
};
|
||||
|
||||
static ArchipelagoClient& getInstance();
|
||||
|
||||
bool start_client();
|
||||
bool stop_client();
|
||||
|
||||
void start_location_scouts();
|
||||
|
||||
// getters
|
||||
const std::string& get_slot_name() const;
|
||||
|
||||
char* get_server_address_buff();
|
||||
char* get_slot_name_buff();
|
||||
char* get_password_buff();
|
||||
const std::map<std::string, int>& get_slot_data();
|
||||
const std::vector<ApItem>& get_scouted_items();
|
||||
|
||||
bool isConnected();
|
||||
void check_location(RandomizerCheck SoH_check_id);
|
||||
|
||||
// callback slots
|
||||
void addItemRecievedCallback(std::function<void(const std::string&)> callback);
|
||||
void removeItemRecievedCallback(std::function<void(const std::string&)> old_callback);
|
||||
|
||||
|
||||
// todo move me back down when done testing
|
||||
void on_item_recieved(int64_t recieved_item_id, bool notify_player);
|
||||
|
||||
void send_game_won();
|
||||
|
||||
void poll();
|
||||
|
||||
std::unique_ptr<APClient> apclient;
|
||||
|
||||
protected:
|
||||
ArchipelagoClient();
|
||||
|
||||
private:
|
||||
ArchipelagoClient(ArchipelagoClient &) = delete;
|
||||
void operator=(const ArchipelagoClient &) = delete;
|
||||
std::string uuid;
|
||||
|
||||
static std::shared_ptr<ArchipelagoClient> instance; // is this even used?
|
||||
static bool initialized;
|
||||
|
||||
char server_address[AP_Client_consts::MAX_ADDRESS_LENGTH];
|
||||
char slot_name[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::set<int64_t> locations;
|
||||
std::vector<ApItem> scouted_items;
|
||||
|
||||
void save_data();
|
||||
|
||||
// callback functions
|
||||
void on_connected();
|
||||
|
||||
void on_location_checked(int64_t location_id);
|
||||
void on_deathlink_recieved() { }; // TODO: implement me
|
||||
|
||||
// callbacks
|
||||
std::function<void(const std::string&)> ItemRecievedCallback;
|
||||
|
||||
};
|
||||
|
||||
1781
soh/soh/Enhancements/randomizer/archipelago_mappings.h
Normal file
1781
soh/soh/Enhancements/randomizer/archipelago_mappings.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#ifndef ARCHIPELAGO_H
|
||||
#define ARCHIPELAGO_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ARCHIPELAGO_H
|
||||
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
class ArchipelagoClient;
|
||||
|
||||
class ArchipelagoWindow : public Ship::GuiWindow {
|
||||
public:
|
||||
using GuiWindow::GuiWindow;
|
||||
|
||||
void InitElement() override {};
|
||||
void DrawElement() override;
|
||||
void UpdateElement() override{};
|
||||
|
||||
private:
|
||||
//std::shared_ptr<ArchipelagoClient AP_client = nullptr;
|
||||
|
||||
void ArchipelagoDrawConnectPage();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "macros.h"
|
||||
#include "3drando/hints.hpp"
|
||||
#include "../kaleido.h"
|
||||
#include "archipelago.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
@@ -344,6 +345,31 @@ void Context::SetSpoilerLoaded(const bool spoilerLoaded) {
|
||||
mSpoilerLoaded = spoilerLoaded;
|
||||
}
|
||||
|
||||
void Context::AddRecievedArchipelagoItem(const std::string& ap_item_id) {
|
||||
mAPrecieveQueue.emplace(ap_item_id);
|
||||
SPDLOG_TRACE("Item Pushed {}", ap_item_id);
|
||||
}
|
||||
|
||||
GetItemEntry Context::GetArchipelagoGIEntry() {
|
||||
SPDLOG_TRACE("Trying to get Item Entry");
|
||||
if(mAPrecieveQueue.empty()) {
|
||||
// something must have gone wrong here, just give a rupee
|
||||
return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, GI_HEART);
|
||||
}
|
||||
|
||||
// get the first item from the archipelago queue
|
||||
std::string recieved_ap_item = mAPrecieveQueue.front();
|
||||
RandomizerGet item_id = StaticData::itemNameToEnum[recieved_ap_item];
|
||||
//RandomizerGet item_id = StaticData::APitemToSoh[recieved_ap_item];
|
||||
assert(item_id != RG_NONE);
|
||||
|
||||
Item& item = StaticData::RetrieveItem(item_id);
|
||||
SPDLOG_TRACE("Found item! {}, {}", recieved_ap_item, (int)item_id);
|
||||
GetItemEntry item_entry = item.GetGIEntry_Copy();
|
||||
mAPrecieveQueue.pop();
|
||||
return item_entry; // todo: add custom text maybe?
|
||||
}
|
||||
|
||||
GetItemEntry Context::GetFinalGIEntry(const RandomizerCheck rc, const bool checkObtainability,
|
||||
const GetItemID ogItemId) {
|
||||
const auto itemLoc = GetItemLocation(rc);
|
||||
@@ -409,6 +435,19 @@ void Context::ParseSpoiler(const char* spoilerFileName) {
|
||||
} catch (...) { LUSLOG_ERROR("Failed to load Spoiler File: %s", spoilerFileName); }
|
||||
}
|
||||
|
||||
void Context::ParseArchipelago() {
|
||||
mSeedGenerated = false;
|
||||
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
|
||||
mSpoilerLoaded = true;
|
||||
mSeedGenerated = false;
|
||||
}
|
||||
|
||||
void Context::ParseHashIconIndexesJson(nlohmann::json spoilerFileJson) {
|
||||
nlohmann::json hashJson = spoilerFileJson["file_hash"];
|
||||
int index = 0;
|
||||
@@ -419,6 +458,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()];
|
||||
@@ -441,6 +481,37 @@ void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) {
|
||||
}
|
||||
}
|
||||
|
||||
void Context::ParseArchipelagoItemsLocations(const std::vector<ArchipelagoClient::ApItem>& scouted_items) {
|
||||
const std::string SlotName = ArchipelagoClient::getInstance().get_slot_name();
|
||||
|
||||
// init the item table with regular items first
|
||||
for(int rc = 1; rc <= RC_MAX; rc++) {
|
||||
// This may not even be needed
|
||||
const RandomizerGet vanillaItem = StaticData::GetLocation(static_cast<RandomizerCheck>(rc))->GetVanillaItem();
|
||||
itemLocationTable[rc].SetPlacedItem(vanillaItem);
|
||||
}
|
||||
|
||||
for(const ArchipelagoClient::ApItem& ap_item: scouted_items) {
|
||||
//const RandomizerCheck rc = StaticData::APcheckToSoh.find(ap_item.locationName)->second;
|
||||
const RandomizerCheck rc = StaticData::locationNameToEnum[ap_item.locationName];
|
||||
|
||||
if(SlotName == ap_item.playerName) {
|
||||
// our item
|
||||
SPDLOG_TRACE("Populated item {} at location {}", ap_item.itemName, ap_item.locationName);
|
||||
const RandomizerGet item = StaticData::itemNameToEnum[ap_item.itemName];
|
||||
//const RandomizerGet item = StaticData::APitemToSoh.find(ap_item.itemName)->second;
|
||||
itemLocationTable[rc].SetPlacedItem(item);
|
||||
} else {
|
||||
// other player item
|
||||
itemLocationTable[rc].SetPlacedItem(RG_ARCHIPELAGO_ITEM);
|
||||
// i'll have to figure out custom names at some point, this currently does nothing
|
||||
//overrides[rc] = ItemOverride(rc, RG_DEKU_NUTS_5);
|
||||
//std::string getText = ap_item.playerName + "'s " + ap_item.itemName;
|
||||
//overrides[rc].SetTrickName(Text(getText, getText, getText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::WriteHintJson(nlohmann::ordered_json& spoilerFileJson) {
|
||||
for (Hint hint : hintTable) {
|
||||
hint.logHint(spoilerFileJson);
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
#include "hint.h"
|
||||
#include "fishsanity.h"
|
||||
#include "trial.h"
|
||||
#include "archipelago.h"
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Singleton for storing and accessing dynamic Randomizer-related data
|
||||
*
|
||||
@@ -105,6 +107,8 @@ class Context {
|
||||
*/
|
||||
RandoOptionLACSCondition LACSCondition() const;
|
||||
GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE);
|
||||
void AddRecievedArchipelagoItem(const std::string& ap_item_id);
|
||||
GetItemEntry GetArchipelagoGIEntry();
|
||||
void ParseSpoiler(const char* spoilerFileName);
|
||||
void ParseHashIconIndexesJson(nlohmann::json spoilerFileJson);
|
||||
void ParseItemLocationsJson(nlohmann::json spoilerFileJson);
|
||||
@@ -121,6 +125,10 @@ class Context {
|
||||
bool allLocationsReachable = false;
|
||||
RandomizerArea GetAreaFromString(std::string str);
|
||||
|
||||
void ParseArchipelago();
|
||||
void ParseArchipelagoSettings(const std::map<std::string, int>& slot_data);
|
||||
void ParseArchipelagoItemsLocations(const std::vector<ArchipelagoClient::ApItem>& slot_data);
|
||||
|
||||
/**
|
||||
* @brief Get the hash for the current seed.
|
||||
*
|
||||
@@ -181,5 +189,6 @@ class Context {
|
||||
std::string mHash;
|
||||
std::string mSeedString;
|
||||
uint32_t mFinalSeed = 0;
|
||||
std::queue<std::string> mAPrecieveQueue = {};
|
||||
};
|
||||
} // namespace Rando
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "soh/Notification/Notification.h"
|
||||
#include "soh/SaveManager.h"
|
||||
#include "soh/Enhancements/randomizer/ShuffleFairies.h"
|
||||
#include "archipelago.h"
|
||||
|
||||
extern "C" {
|
||||
#include "macros.h"
|
||||
@@ -221,6 +222,12 @@ static std::queue<RandomizerCheck> randomizerQueuedChecks;
|
||||
static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK;
|
||||
static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE;
|
||||
|
||||
void ArchipelagoOnRecieveItem(const std::string& ap_item_name) {
|
||||
SPDLOG_TRACE("Recieve item handler called! {}", ap_item_name);
|
||||
randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECIEVED_ITEM);
|
||||
Rando::Context::GetInstance()->AddRecievedArchipelagoItem(ap_item_name);
|
||||
}
|
||||
|
||||
void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) {
|
||||
// Consume adult trade items
|
||||
if (RAND_GET_OPTION(RSK_SHUFFLE_ADULT_TRADE) && flagType == FLAG_RANDOMIZER_INF) {
|
||||
@@ -296,12 +303,18 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
GetItemEntry getItemEntry;
|
||||
RandomizerCheck rc = randomizerQueuedChecks.front();
|
||||
auto loc = Rando::Context::GetInstance()->GetItemLocation(rc);
|
||||
RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem();
|
||||
GetItemID vanillaItem = (GetItemID)Rando::StaticData::RetrieveItem(vanillaRandomizerGet).GetItemID();
|
||||
GetItemEntry getItemEntry =
|
||||
Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)vanillaRandomizerGet);
|
||||
|
||||
if(rc == RC_ARCHIPELAGO_RECIEVED_ITEM) {
|
||||
getItemEntry = Rando::Context::GetInstance()->GetArchipelagoGIEntry();
|
||||
} else {
|
||||
RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem();
|
||||
GetItemID vanillaItem = (GetItemID)Rando::StaticData::RetrieveItem(vanillaRandomizerGet).GetItemID();
|
||||
getItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)vanillaRandomizerGet);
|
||||
}
|
||||
SPDLOG_TRACE("RC found!");
|
||||
|
||||
if (loc->HasObtained()) {
|
||||
SPDLOG_INFO("RC {} already obtained, skipping", static_cast<uint32_t>(rc));
|
||||
@@ -360,11 +373,21 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) {
|
||||
if (randomizerQueuedCheck == RC_UNKNOWN_CHECK)
|
||||
return;
|
||||
|
||||
SPDLOG_TRACE("Dropped into recieve handler!");
|
||||
|
||||
auto loc = Rando::Context::GetInstance()->GetItemLocation(randomizerQueuedCheck);
|
||||
if (randomizerQueuedItemEntry.modIndex == receivedItemEntry.modIndex &&
|
||||
randomizerQueuedItemEntry.itemId == receivedItemEntry.itemId) {
|
||||
SPDLOG_INFO("Item received mod {} item {} from RC {}", receivedItemEntry.modIndex, receivedItemEntry.itemId,
|
||||
static_cast<uint32_t>(randomizerQueuedCheck));
|
||||
|
||||
// todo maybe move to seperate function
|
||||
// let arhipelago know we got this check
|
||||
if(randomizerQueuedCheck != RC_ARCHIPELAGO_RECIEVED_ITEM) {
|
||||
ArchipelagoClient& ap_client = ArchipelagoClient::getInstance();
|
||||
ap_client.check_location(randomizerQueuedCheck);
|
||||
}
|
||||
|
||||
loc->SetCheckStatus(RCSHOW_COLLECTED);
|
||||
CheckTracker::SpoilAreaFromCheck(randomizerQueuedCheck);
|
||||
CheckTracker::RecalculateAllAreaTotals();
|
||||
@@ -2423,6 +2446,8 @@ void RandomizerRegisterHooks() {
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(
|
||||
shuffleFreestandingOnVanillaBehaviorHook);
|
||||
|
||||
ArchipelagoClient::getInstance().removeItemRecievedCallback(ArchipelagoOnRecieveItem);
|
||||
|
||||
onFlagSetHook = 0;
|
||||
onSceneFlagSetHook = 0;
|
||||
onPlayerUpdateForRCQueueHook = 0;
|
||||
@@ -2537,5 +2562,7 @@ void RandomizerRegisterHooks() {
|
||||
if (RAND_GET_OPTION(RSK_SHUFFLE_FAIRIES)) {
|
||||
ShuffleFairies_RegisterHooks();
|
||||
}
|
||||
|
||||
ArchipelagoClient::getInstance().addItemRecievedCallback(ArchipelagoOnRecieveItem);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -405,6 +405,9 @@ void Rando::StaticData::InitItemTable() {
|
||||
itemTable[RG_MAGIC_DOUBLE] = Item(RG_MAGIC_DOUBLE, Text{ "Enhanced Magic Meter", "Jauge de Magie améliorée", "Verbessertes Magisches Maß" }, ITEMTYPE_ITEM, 0x8A, true, LOGIC_PROGRESSIVE_MAGIC, RHT_MAGIC_DOUBLE, RG_MAGIC_DOUBLE, OBJECT_GI_MAGICPOT, GID_MAGIC_LARGE, 0xE8, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER);
|
||||
itemTable[RG_TRIFORCE_PIECE] = Item(RG_TRIFORCE_PIECE, Text{ "Triforce Piece", "Triforce Piece", "Triforce-Fragment" }, ITEMTYPE_ITEM, 0xDF, true, LOGIC_TRIFORCE_PIECES, RHT_TRIFORCE_PIECE, RG_TRIFORCE_PIECE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
|
||||
|
||||
// Archipelago
|
||||
itemTable[RG_ARCHIPELAGO_ITEM] = Item(RG_ARCHIPELAGO_ITEM, Text{"AP Item", "AP Item", "AP Item"}, ITEMTYPE_EVENT, GI_RUPEE_GREEN, false, LOGIC_NONE, RHT_NONE, RG_ARCHIPELAGO_ITEM, OBJECT_GI_LETTER, GID_LETTER_ZELDA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_RANDOMIZER);
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Init itemNameToEnum
|
||||
|
||||
@@ -133,6 +133,8 @@ bool ItemLocation::HasObtained() const {
|
||||
}
|
||||
|
||||
void ItemLocation::SetCheckStatus(RandomizerCheckStatus status_) {
|
||||
if(rc == RC_ARCHIPELAGO_RECIEVED_ITEM) // never count the AP recieve trigger as 'collected'
|
||||
return;
|
||||
status = status_;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "soh/util.h"
|
||||
#include "fishsanity.h"
|
||||
#include "randomizerTypes.h"
|
||||
#include "archipelago.h"
|
||||
#include "soh/Notification/Notification.h"
|
||||
|
||||
extern std::map<RandomizerCheckArea, std::string> rcAreaNames;
|
||||
@@ -5172,7 +5173,7 @@ CustomMessage Randomizer::GetGoronMessage(u16 index) {
|
||||
void Randomizer::CreateCustomMessages() {
|
||||
// RANDTODO: Translate into french and german and replace GIMESSAGE_UNTRANSLATED
|
||||
// with GIMESSAGE(getItemID, itemID, english, german, french).
|
||||
const std::array<GetItemMessage, 112> getItemMessages = { {
|
||||
const std::array<GetItemMessage, 113> getItemMessages = { {
|
||||
GIMESSAGE(RG_GREG_RUPEE, ITEM_MASK_GORON, "You found %gGreg%w!", "%gGreg%w! Du hast ihn wirklich gefunden!",
|
||||
"Félicitation! Vous avez trouvé %gGreg%w!"),
|
||||
GIMESSAGE(RG_MASTER_SWORD, ITEM_SWORD_MASTER, "You found the %gMaster Sword%w!",
|
||||
@@ -5539,6 +5540,7 @@ void Randomizer::CreateCustomMessages() {
|
||||
GIMESSAGE(RG_DEKU_NUT_BAG, ITEM_NUT, "You found the %rDeku Nut Bag%w!&You can now hold Deku Nuts!",
|
||||
"Du hast eine %rDeku-Nuß-Tasche%w&gefunden! Nun kannst Du &%yDeku-Nüsse%w halten!",
|
||||
"Vous avez trouvé le %rSac de Noix& Mojo%w!&Vous pouvez maintenant porter des&Noix Mojo!"),
|
||||
GIMESSAGE_NO_GERMAN(RG_ARCHIPELAGO_ITEM, ITEM_BEAN, "You found an %rAP_ITEM%w", "Je m'apelle not %rNot Translated%w"),
|
||||
} };
|
||||
CreateGetItemMessages(getItemMessages);
|
||||
CreateRupeeMessages();
|
||||
@@ -5656,6 +5658,11 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
|
||||
return Return_Item_Entry(giEntry, RG_NONE);
|
||||
}
|
||||
|
||||
//if it's an archipelago item, don't give anything
|
||||
if(item == RG_ARCHIPELAGO_ITEM) {
|
||||
return Return_Item_Entry(giEntry, RG_NONE);
|
||||
}
|
||||
|
||||
// bottle items
|
||||
if (item >= RG_BOTTLE_WITH_RED_POTION && item <= RG_BOTTLE_WITH_BIG_POE) {
|
||||
for (u16 i = 0; i < 4; i++) {
|
||||
@@ -5878,6 +5885,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
|
||||
(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);
|
||||
Notification::Emit({
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
typedef enum {
|
||||
MOD_NONE,
|
||||
MOD_RANDOMIZER,
|
||||
MOD_ARCHIPELAGO // no actually used yet i think
|
||||
} ModIndex;
|
||||
typedef enum {
|
||||
TABLE_VANILLA = MOD_NONE,
|
||||
@@ -3472,6 +3473,7 @@ typedef enum {
|
||||
RC_DEKU_TREE_QUEEN_GOHMA_GRASS_8,
|
||||
// End Grass
|
||||
|
||||
RC_ARCHIPELAGO_RECIEVED_ITEM,
|
||||
RC_MAX
|
||||
} RandomizerCheck;
|
||||
|
||||
@@ -4019,6 +4021,7 @@ typedef enum {
|
||||
RG_BACK_TOWER_KEY,
|
||||
RG_HYLIA_LAB_KEY,
|
||||
RG_FISHING_HOLE_KEY,
|
||||
RG_ARCHIPELAGO_ITEM,
|
||||
// Logic Only
|
||||
RG_DISTANT_SCARECROW,
|
||||
RG_STICKS,
|
||||
|
||||
@@ -2906,6 +2906,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::AssignContext(std::shared_ptr<Context> ctx) {
|
||||
mContext = ctx;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@ class Settings {
|
||||
* @param spoilerFileJson
|
||||
*/
|
||||
void ParseJson(nlohmann::json spoilerFileJson);
|
||||
|
||||
void ParseArchipelago(const std::map<std::string, int>& slot_data);
|
||||
std::map<RandomizerArea, std::vector<RandomizerTrick>> mTricksByArea = {};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <unordered_map>
|
||||
#include "static_data.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "archipelago_mappings.h"
|
||||
|
||||
namespace Rando {
|
||||
|
||||
@@ -304,4 +305,110 @@ std::unordered_map<u32, RandomizerHint> StaticData::grottoChestParamsToHint{
|
||||
};
|
||||
|
||||
std::array<HintText, RHT_MAX> StaticData::hintTextTable = {};
|
||||
|
||||
const std::unordered_map<std::string_view, RandomizerGet>generate_APitemToSoh_mapping() {
|
||||
std::unordered_map<std::string_view, RandomizerGet> mapping;
|
||||
for(const auto& pairing : ap_item_mapping_pairs) {
|
||||
mapping[pairing.first] = pairing.second;
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string_view, RandomizerCheck>generate_APcheckToSoh_mapping() {
|
||||
std::unordered_map<std::string_view, RandomizerCheck> mapping;
|
||||
for(const auto& pairing : ap_check_mapping_pairs) {
|
||||
mapping[pairing.first] = pairing.second;
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
const std::unordered_map<RandomizerCheck, std::string_view>generate_SohcheckToAP_mapping() {
|
||||
std::unordered_map<RandomizerCheck, std::string_view> mapping;
|
||||
for(const auto& pairing : ap_check_mapping_pairs) {
|
||||
mapping[pairing.second] = pairing.first;
|
||||
}
|
||||
return 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<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" }
|
||||
};
|
||||
|
||||
} // namespace Rando
|
||||
|
||||
@@ -78,6 +78,10 @@ class StaticData {
|
||||
static std::unordered_map<u32, RandomizerHint> grottoChestParamsToHint;
|
||||
static std::array<HintText, RHT_MAX> hintTextTable;
|
||||
|
||||
static std::unordered_map<std::string_view, RandomizerGet> APitemToSoh;
|
||||
static std::unordered_map<std::string_view, RandomizerCheck> APcheckToSoh;
|
||||
static std::unordered_map<RandomizerCheck, std::string_view> SohCheckToAP;
|
||||
static std::unordered_map<std::string_view, std::string_view> APsettingToHoSsetting;
|
||||
StaticData();
|
||||
~StaticData();
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "assets/textures/parameter_static/parameter_static.h"
|
||||
#include <soh/SohGui/SohGui.hpp>
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "soh/Enhancements/randomizer/archipelago.h"
|
||||
|
||||
extern "C" {
|
||||
extern SaveContext gSaveContext;
|
||||
@@ -351,6 +352,7 @@ void HandleDragAndDrop(std::vector<SplitObject>& 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) {
|
||||
|
||||
@@ -2593,4 +2593,8 @@ void SoH_ProcessDroppedFiles(std::string filePath) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void parse_archipelago() {
|
||||
OTRGlobals::Instance->gRandoContext->ParseArchipelago();
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@@ -166,6 +166,7 @@ void CheckTracker_OnMessageClose();
|
||||
|
||||
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
|
||||
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);
|
||||
void parse_archipelago();
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -94,6 +94,7 @@ std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
|
||||
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
|
||||
std::shared_ptr<TimeSplitWindow> mTimeSplitWindow;
|
||||
std::shared_ptr<PlandomizerWindow> mPlandomizerWindow;
|
||||
std::shared_ptr<ArchipelagoWindow> mArchipelagoWindow;
|
||||
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
|
||||
std::shared_ptr<SohModalWindow> mModalWindow;
|
||||
std::shared_ptr<Notification::Window> mNotificationWindow;
|
||||
@@ -195,6 +196,9 @@ void SetupGuiElements() {
|
||||
mPlandomizerWindow =
|
||||
std::make_shared<PlandomizerWindow>(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760));
|
||||
gui->AddGuiWindow(mPlandomizerWindow);
|
||||
mArchipelagoWindow =
|
||||
std::make_shared<ArchipelagoWindow>(CVAR_WINDOW("ArchipelagoWindow"), "Archipelago", ImVec2(850, 760));
|
||||
gui->AddGuiWindow(mArchipelagoWindow);
|
||||
mModalWindow = std::make_shared<SohModalWindow>(CVAR_WINDOW("ModalWindow"), "Modal Window");
|
||||
gui->AddGuiWindow(mModalWindow);
|
||||
mModalWindow->Show();
|
||||
@@ -237,6 +241,7 @@ void Destroy() {
|
||||
mInputViewerSettings = nullptr;
|
||||
mTimeSplitWindow = nullptr;
|
||||
mPlandomizerWindow = nullptr;
|
||||
mArchipelagoWindow = nullptr;
|
||||
mTimeDisplayWindow = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "soh/Enhancements/randomizer/randomizer_settings_window.h"
|
||||
#include "soh/Enhancements/timesplits/TimeSplits.h"
|
||||
#include "soh/Enhancements/randomizer/Plandomizer.h"
|
||||
#include "soh/Enhancements/randomizer/archipelago.h"
|
||||
#include "SohModals.h"
|
||||
|
||||
namespace SohGui {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "soh/Enhancements/enemyrandomizer.h"
|
||||
#include "soh/Enhancements/timesplits/TimeSplits.h"
|
||||
#include "soh/Enhancements/randomizer/Plandomizer.h"
|
||||
#include "soh/Enhancements/randomizer/archipelago.h"
|
||||
#include "soh/Enhancements/TimeDisplay/TimeDisplay.h"
|
||||
|
||||
// FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but
|
||||
|
||||
@@ -100,6 +100,15 @@ void SohMenu::AddMenuRandomizer() {
|
||||
.WindowName("Plandomizer Editor")
|
||||
.Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window."));
|
||||
|
||||
// Archipelago
|
||||
path.sidebarName = "Archipelago";
|
||||
AddSidebarEntry("Randomizer", path.sidebarName, 1);
|
||||
AddWidget(path, "Popout Archipelago Development Window", WIDGET_WINDOW_BUTTON)
|
||||
.CVar(CVAR_WINDOW("ArchipelagoWindow"))
|
||||
.RaceDisable(false)
|
||||
.WindowName("Archipelago")
|
||||
.Options(WindowButtonOptions().Tooltip("Enables the Archipelago development Window."));
|
||||
|
||||
// Item Tracker
|
||||
path.sidebarName = "Item Tracker";
|
||||
AddSidebarEntry("Randomizer", path.sidebarName, 1);
|
||||
|
||||
@@ -1030,6 +1030,7 @@ void DrawSeedHashSprites(FileChooseContext* this) {
|
||||
u8 generating;
|
||||
int retries = 0;
|
||||
bool fileSelectSpoilerFileLoaded = false;
|
||||
bool fileSelectarchipelagoloaded = false;
|
||||
|
||||
void FileChoose_UpdateRandomizer() {
|
||||
if (CVarGetInteger(CVAR_GENERAL("RandoGenerating"), 0) != 0 && generating == 0) {
|
||||
@@ -1073,6 +1074,13 @@ void FileChoose_UpdateRandomizer() {
|
||||
remove(fileLoc);
|
||||
}
|
||||
}
|
||||
|
||||
if (CVarGetInteger("archipelago_connected", 0) != 0
|
||||
&& !fileSelectarchipelagoloaded) {
|
||||
parse_archipelago();
|
||||
fileSelectarchipelagoloaded = true;
|
||||
Audio_PlayFanfare(NA_BGM_HORSE_GOAL);
|
||||
}
|
||||
}
|
||||
|
||||
static s16 sLastFileChooseButtonIndex;
|
||||
@@ -3931,6 +3939,7 @@ void FileChoose_Init(GameState* thisx) {
|
||||
this->questType[1] = MIN_QUEST;
|
||||
this->questType[2] = MIN_QUEST;
|
||||
fileSelectSpoilerFileLoaded = false;
|
||||
fileSelectarchipelagoloaded = false;
|
||||
CVarSetInteger(CVAR_GENERAL("OnFileSelectNameEntry"), 0);
|
||||
|
||||
SREG(30) = 1;
|
||||
|
||||
Submodule subprojects/apclientpp updated: 4ab7ba6348...a5b7b96b6b
Reference in New Issue
Block a user