add inventory resetting on server connect, properly handle getting new items from locations, integrate some of archi into new rando ui stuff

For the most part, solo archipelago runs seem to work fine now using the original TP apworld, but im sure there are plenty of weird issues still that need to be handled.
This commit is contained in:
CraftyBoss
2026-06-19 17:07:42 -07:00
parent 7537f1c4a8
commit 6366ab61d1
24 changed files with 894 additions and 175 deletions
+32
View File
@@ -1128,6 +1128,15 @@ void dComIfGs_setWarpItemData(char const* stage, cXyz pos, s16 angle, s8 roomNo,
u8 param_5);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageTbox(int i_stageNo, int i_no);
#if TARGET_PC
void dComIfGs_onStageTbox(int i_stageNo, int i_no);
void dComIfGs_offStageTbox(int i_stageNo, int i_no);
void dComIfGs_onStageItem(int i_stageNo, int i_no);
void dComIfGs_offStageItem(int i_stageNo, int i_no);
#endif
void dComIfGs_onStageSwitch(int i_stageNo, int i_no);
void dComIfGs_offStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
@@ -1990,6 +1999,23 @@ inline void dComIfGs_onRegionFlag(int i_stageNo, int i_no) {
const int shift = i_no % 8;
regionFlags[offset] |= (0x80 >> shift);
}
inline void dComIfGs_onSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().onTbox(i_no);
}
inline void dComIfGs_offSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().offTbox(i_no);
}
inline void dComIfGs_onSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().onItem(i_no);
}
inline void dComIfGs_offSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().offItem(i_no);
}
#endif
inline BOOL dComIfGs_isSaveTbox(int i_stageNo, int i_no) {
@@ -2456,6 +2482,12 @@ inline void dComIfGs_onItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.onItem(i_bitNo, i_roomNo);
}
#if TARGET_PC
inline void dComIfGs_offItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.offItem(i_bitNo, i_roomNo);
}
#endif
inline bool dComIfGs_isItem(int i_bitNo, int i_roomNo) {
return g_dComIfG_gameInfo.info.isItem(i_bitNo, i_roomNo);
}
+3
View File
@@ -993,6 +993,9 @@ public:
BOOL isSwitch(int i_no, int i_roomNo) const;
BOOL revSwitch(int i_no, int i_roomNo);
void onItem(int i_no, int i_roomNo);
#if TARGET_PC
void offItem(int i_no, int i_roomNo);
#endif
BOOL isItem(int i_no, int i_roomNo) const;
void onActor(int i_no, int i_roomNo);
void offActor(int i_no, int i_roomNo);
+40
View File
@@ -2093,6 +2093,41 @@ BOOL dComIfGs_isStageTbox(int i_stageNo, int i_no) {
}
}
#if TARGET_PC
void dComIfGs_onStageTbox(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_onTbox(i_no);
} else {
dComIfGs_onSaveTbox(i_stageNo, i_no);
}
}
void dComIfGs_offStageTbox(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_offTbox(i_no);
} else {
dComIfGs_offSaveTbox(i_stageNo, i_no);
}
}
void dComIfGs_onStageItem(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_onItem(i_no, -1);
} else {
dComIfGs_onSaveItem(i_no);
}
}
void dComIfGs_offStageItem(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
// Need to subtract 0x80 (MEMORY_ITEM constant in d_save.cpp) because the above function does it
dComIfGs_offItem(i_stageNo, i_no - 0x80);
} else {
dComIfGs_offSaveItem(i_no);
}
}
#endif
void dComIfGs_onStageSwitch(int i_stageNo, int i_no) {
#if TARGET_PC
// Avoid trying to get the save table if stag info is NULL
@@ -2107,7 +2142,12 @@ void dComIfGs_onStageSwitch(int i_stageNo, int i_no) {
}
void dComIfGs_offStageSwitch(int i_stageNo, int i_no) {
#if TARGET_PC
// Avoid trying to get the save table if stag info is NULL
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
#else
if (i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
#endif
dComIfGs_offSwitch(i_no, -1);
}
+28 -12
View File
@@ -23,6 +23,8 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#include "dusk/archipelago/archipelago_context.hpp"
#if TARGET_PC
#include "dusk/config.hpp"
#include "dusk/menu_pointer.h"
@@ -1037,18 +1039,23 @@ void dFile_select_c::dataSelectStart() {
makeRecInfo(mSelectNum);
#if TARGET_PC
// Load the randomizer seed if one is tied to this file
auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(mSelectNum).getValue();
// If this is a vanilla file, clear rando data structures
if (curFileSeedHash.empty()) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
}
// Reset randomizer state if we're switching to a different file
else if (curFileSeedHash != randomizer_GetContext().mHash || g_randomizerState.mFileNum != mSelectNum) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
randomizer_GetContext().LoadFromHash(curFileSeedHash);
// TODO: not this please D:
if (dusk::archi::ArchipelagoContext::IsConnected()) {
dusk::archi::ArchipelagoContext::GenerateLocalWorldData();
}else {
// Load the randomizer seed if one is tied to this file
auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(mSelectNum).getValue();
// If this is a vanilla file, clear rando data structures
if (curFileSeedHash.empty()) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
}
// Reset randomizer state if we're switching to a different file
else if (curFileSeedHash != randomizer_GetContext().mHash || g_randomizerState.mFileNum != mSelectNum) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
randomizer_GetContext().LoadFromHash(curFileSeedHash);
}
}
#endif
@@ -1401,6 +1408,15 @@ void dFile_select_c::selectDataPlayTypeMove() {
modal.hide(true);
dusk::ui::push_document(std::make_unique<dusk::ui::FileSelectRandomizerWindow>(this));
}},
// If Archipelago is selected, open archipelago settings window
dusk::ui::ModalAction{
.label = "Archipelago",
.onPressed = [this](dusk::ui::Modal& modal) {
mDusk.mBackToFileSelect = false;
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
modal.hide(true);
dusk::archi::ArchipelagoContext::GenerateLocalWorldData();
}},
},
// If we dismiss this modal, go back to file selection
.onDismiss = [this](dusk::ui::Modal& modal) {
+2 -3
View File
@@ -553,6 +553,7 @@ void execItemGet(u8 i_itemNo) {
if (randomizer_IsActive()) {
i_itemNo = verifyProgressiveItem(i_itemNo);
g_randomizerState.mUpdateTracker = true;
dusk::archi::ArchipelagoContext::SetNeedUpdateLocations(true);
}
#endif
@@ -2184,9 +2185,7 @@ void item_func_MIRROR_PIECE_1() {
}
void item_func_ARCHIPELAGO_ITEM() {
if (randomizer_IsActive()) {
dusk::archi::ArchipelagoContext::UpdateCheckedLocations();
}
}
#endif
+26
View File
@@ -2068,6 +2068,32 @@ void dSv_info_c::onItem(int i_no, int i_roomNo) {
}
}
#if TARGET_PC
void dSv_info_c::offItem(int i_no, int i_roomNo) {
JUT_ASSERT(4398, (0 <= i_no && i_no < (MEMORY_ITEM+ DAN_ITEM+ ZONE_ITEM+ ONEZONE_ITEM)) || i_no == -1 || i_no == 255);
if (i_no == -1 || i_no == 255) {
return;
}
if (i_no < MEMORY_ITEM) {
mDan.offItem(i_no);
} else if (i_no < (MEMORY_ITEM + DAN_ITEM)) {
mMemory.getBit().offItem(i_no - MEMORY_ITEM);
} else {
JUT_ASSERT(4414, 0 <= i_roomNo && i_roomNo < 64);
int zoneNo = dComIfGp_roomControl_getZoneNo(i_roomNo);
JUT_ASSERT(4416, 0 <= zoneNo && zoneNo < ZONE_MAX);
if (i_no < (MEMORY_ITEM + DAN_ITEM + ZONE_ITEM)) {
mZone[zoneNo].getBit().offItem(i_no - (MEMORY_ITEM + DAN_ITEM));
} else {
mZone[zoneNo].getBit().offOneItem(i_no - (MEMORY_ITEM + DAN_ITEM + ZONE_ITEM));
}
}
}
#endif
BOOL dSv_info_c::isItem(int i_no, int i_roomNo) const {
JUT_ASSERT(4488, (0 <= i_no && i_no < (MEMORY_ITEM+ DAN_ITEM+ ZONE_ITEM+ ONEZONE_ITEM)) || i_no == -1 || i_no == 255);
+507 -68
View File
@@ -7,6 +7,9 @@
#include "dusk/config.hpp"
#include "dusk/logging.h"
#include "dusk/randomizer/game/tools.h"
#include "dusk/randomizer/game/verify_item_functions.h"
#include "dusk/randomizer/generator/logic/hints.hpp"
#include "dusk/ui/rando_config.hpp"
#include "dusk/ui/ui.hpp"
namespace dusk::archi
@@ -39,7 +42,7 @@ static auto sArchiSettingToDusklight = std::to_array<SettingsNameConvert>({
{"lanayru_twilight_cleared", "Lanayru Twilight Cleared"},
{"skip_mdh", "Skip Midna's Desparate Hour"},
{"open_map", "Unlock Map Regions"},
{"increase_wallet", "Increase Wallet Capacity"},
{"increase_wallet", "Logic Increase Wallet Capacity"},
{"transform_anywhere", "Logic Transform Anywhere"},
{"bonks_do_damage", "Bonks Do Damage"},
{"skip_lakebed_entrance", "Lakebed Does Not Require Water Bombs"},
@@ -128,6 +131,20 @@ void ArchipelagoContext::LoadTempItemInfo() {
itemName
};
}
// add temporary replacement IDs for items not included in the base rando
m_apItemToGameItem[16] = { // Water Bombs (3)
0x16,
randomizer::logic::item::Importance::JUNK,
"Water Bombs 5"
};
m_apItemToGameItem[20] = { // Bomblings (3)
0x1A,
randomizer::logic::item::Importance::JUNK,
"Bomblings 5"
};
}
void ArchipelagoContext::LoadTempLocationInfo() {
@@ -164,15 +181,19 @@ void ArchipelagoContext::itemRecvImpl(int id, bool notify) {
return;
}
m_isAllowUpdateLocations = true; // guards against triggering UpdateCheckedLocations
auto& item = m_apItemToGameItem[id];
DuskLog.info("[AP] Adding Item: {}", item.itemName);
if (item.importance == randomizer::logic::item::Importance::MAJOR) {
g_randomizerState.addItemToEventQueue(item.itemId);
if (notify && item.importance == randomizer::logic::item::Importance::MAJOR) {
DuskLog.info("[AP] Adding Item: {}", item.itemName);
g_randomizerState.addItemToEventQueue(verifyProgressiveItem(item.itemId));
}else {
DuskLog.info("[AP] Silently Adding Item: {}", item.itemName);
execItemGet(item.itemId);
}
m_isAllowUpdateLocations = false;
}
int ArchipelagoContext::getItemIdFromApId(int apId) {
@@ -194,9 +215,7 @@ std::string ArchipelagoContext::getLocationNameFromApId(int apId) const {
return "";
}
ArchipelagoContext::ArchipelagoContext() {
}
ArchipelagoContext::ArchipelagoContext() = default;
void ArchipelagoContext::SetServerIp(const std::string_view& ip) {
getSettings().archipelago.serverIP.setValue(std::string(ip));
@@ -222,6 +241,22 @@ const std::string& ArchipelagoContext::GetPassword() {
return getSettings().archipelago.serverPass.getValue();
}
std::string ArchipelagoContext::GetArchipelagoSeedName() {
if (IsConnected()) {
auto& roomInfo = instance().m_roomInfo;
return fmt::format("AP_{}_{}", GetSlotName(), roomInfo.seed_name);
}else {
DuskLog.fatal("Archipelago was not connected when attempting to get seed name!");
}
}
void ArchipelagoContext::GetSeedDirectoryPath(std::filesystem::path& outPath) {
if (IsConnected()) {
auto& roomInfo = instance().m_roomInfo;
outPath = ui::GetRandomizerPath() / "archipelago" / GetArchipelagoSeedName();
}
}
void ArchipelagoContext::ConnectToServer() {
config::Save();
@@ -236,16 +271,17 @@ void ArchipelagoContext::ConnectToServer() {
AP_SetItemClearCallback([]() {
DuskLog.info("Item Clear Callback Called!");
// HandleResetInventory();
instance().m_isNeedResetInv = true;
});
AP_SetItemRecvCallback([](int id, bool notify) {
DuskLog.info("Item Receive Callback Called! Item: {} Notify: {}", id, notify);
HandleItemReceived(id, notify);
AP_SetItemRecvCallback([](AP_NetworkItem& item, bool notify) {
DuskLog.info("Item Receive Callback Called! Item: {} Notify: {}", item.item, notify);
HandleItemReceived(item, notify);
});
AP_SetLocationCheckedCallback([](int loc) {
DuskLog.info("Location Checked Callback Called! Location: {}", loc);
SetLocationChecked(loc, true);
});
AP_SetLocationInfoCallback([](std::vector<AP_NetworkItem> items) {
@@ -260,7 +296,7 @@ void ArchipelagoContext::ConnectToServer() {
return;
}
std::thread messageThread = std::thread(MainThreadFunc);
std::thread messageThread = std::thread(MessageThreadFunc);
messageThread.detach();
}
@@ -278,62 +314,82 @@ bool ArchipelagoContext::IsConnected() {
return status == AP_ConnectionStatus::Connected || status == AP_ConnectionStatus::Authenticated;
}
void ArchipelagoContext::MainThreadFunc() {
void ArchipelagoContext::MessageThreadFunc() {
// wait a bit before checking connection state, as websocket is probably not connected yet
// (i really am not liking APCpp, why cant I check if the websocket is in the process of connecting???)
std::this_thread::sleep_for(std::chrono::seconds(2));
DuskLog.info("AP Thread started.");
if (IsConnected())
if (IsConnected()) {
AP_GetRoomInfo(&instance().m_roomInfo);
RequestAllLocationScout();
}
while (IsConnected()) {
if (AP_IsMessagePending())
ParseMessageData();
instance().m_queueMutex.lock();
if (randomizer_IsActive() && !instance().m_inactiveItemsQueue.empty()) {
for (auto item : instance().m_inactiveItemsQueue) {
instance().itemRecvImpl(item, false);
}
instance().m_inactiveItemsQueue.clear();
}
instance().m_queueMutex.unlock();
}
DuskLog.info("AP Thread ended.");
}
void ArchipelagoContext::HandleItemReceived(int id, bool notify) {
int relativeId = id - ARCHI_ITEM_OFFSET;
void ArchipelagoContext::Execute() {
if (!IsConnected())
return;
// && ((relativeId >= 0 && relativeId <= 6) || relativeId == 7)
if (!notify) {
// reset player inventory if server requested it
if (instance().m_isNeedResetInv) {
HandleResetInventory();
instance().m_isNeedResetInv = false;
return; // end execution early so next frame can re-add inventory if needed
}
// drain pending item queue here
instance().m_queueMutex.lock();
if (!instance().m_receivedItemsQueue.empty()) {
for (auto item : instance().m_receivedItemsQueue) {
instance().itemRecvImpl(item.first, item.second);
}
instance().m_receivedItemsQueue.clear();
}
instance().m_queueMutex.unlock();
// update location checks here if we need to
if (instance().m_isUpdateLocations) {
UpdateCheckedLocations();
instance().m_isUpdateLocations = false;
}
}
void ArchipelagoContext::HandleItemReceived(AP_NetworkItem& netItem, bool notify) {
int relativeId = netItem.item - ARCHI_ITEM_OFFSET;
if (!notify && ((relativeId >= 0 && relativeId <= 6) || relativeId == 7)) {
// skip rupee refills so players cant abuse disconnect/reconnect
return;
}
if (!randomizer_IsActive()) {
DuskLog.info("Randomizer not active, adding item to queue.");
instance().m_queueMutex.lock();
instance().m_inactiveItemsQueue.push_back(relativeId);
instance().m_queueMutex.unlock();
if (netItem.location != -1 && IsLocationChecked(netItem.location)) {
// no need to handle item if its location has already been checked
return;
}
instance().itemRecvImpl(relativeId, notify);
instance().m_queueMutex.lock();
instance().m_receivedItemsQueue.push_back({relativeId, notify});
instance().m_queueMutex.unlock();
}
void ArchipelagoContext::HandleResetInventory() {
DuskLog.info("Resetting Inventory.");
// NOTE: this does not clear ALL things from save, so if a player managed to do something while disconnected from the archi, it might mess with things
auto& playerInfo = g_dComIfG_gameInfo.info.getPlayer();
// reset items
playerInfo.getItem().init();
playerInfo.getGetItem().init();
// reset collect (poes, shards, swords)
playerInfo.getCollect().init();
@@ -342,13 +398,46 @@ void ArchipelagoContext::HandleResetInventory() {
playerInfo.getPlayerStatusA().setWalletSize(WALLET);
// dont reset rupees, and instead reject rupee updates while refilling inv
// sync all location collect flags with current collection status obtained from initial room connection
UpdateAllLocationState();
// clear all item-related flags
dComIfGs_offEventBit(0x2580); // Power up dominion rod
// shadow crystal
dComIfGs_offEventBit(0xD04); // Can transform at will
dComIfGs_offEventBit(0x501); // Midna Charge Unlocked
// hidden skills
dComIfGs_offEventBit(0x2904); // ENDING BLOW
dComIfGs_offEventBit(0x2908); // SHIELD ATTACK
dComIfGs_offEventBit(0x2902); // BACK SLICE
dComIfGs_offEventBit(0x2901); // HELM SPLITTER
dComIfGs_offEventBit(0x2A80); // MORTAL DRAW
dComIfGs_offEventBit(0x2A40); // JUMP STRIKE
dComIfGs_offEventBit(0x2A20); // GREAT SPIN
}
void ArchipelagoContext::HandleReceiveLocationScout(const std::vector<AP_NetworkItem>& items) {
for (const auto& item : items) {
int parsedItemId = dItemNo_Randomizer_ARCHIPELAGO_ITEM_e;
int parsedItemId;
std::string parsedItemName;
if (item.player == AP_GetPlayerID()) {
parsedItemId = instance().getItemIdFromApId(item.item - ARCHI_ITEM_OFFSET);
int adjustedId = item.item - ARCHI_ITEM_OFFSET;
if (instance().m_apItemToGameItem.contains(adjustedId)) {
auto& itemInfo = instance().m_apItemToGameItem[adjustedId];
parsedItemId = itemInfo.itemId;
parsedItemName = itemInfo.itemName;
}else {
parsedItemId = -1;
parsedItemName = "Unknown";
}
}else {
parsedItemId = dItemNo_Randomizer_ARCHIPELAGO_ITEM_e;
parsedItemName = "Archipelago Item";
}
int locationId = item.location - ARCHI_ITEM_OFFSET;
@@ -359,26 +448,31 @@ void ArchipelagoContext::HandleReceiveLocationScout(const std::vector<AP_Network
continue;
}
bool collected = false;
if (instance().m_initLocationCollectState.contains(item.location))
collected = instance().m_initLocationCollectState[item.location];
instance().m_locationItemInfo[locName] = {
parsedItemId,
parsedItemName,
locName,
item.location,
false
collected
};
}
}
void ArchipelagoContext::UpdateCheckedLocations() {
// TODO: we need a randomizer world to be able to check all valid locations and compare collection
// state against a previously cached value, and if collected we can send a location update packet.
auto& world = instance().m_archiWorld;
return;
// non-usable example code
// replace this with however we'll be getting our world
std::unique_ptr<randomizer::logic::world::World> world = std::make_unique<randomizer::logic::world::World>(1, nullptr);
bool changed = false;
for (auto location : world->GetAllLocations()) {
// skip locations that aren't progression, which are locations that just aren't randomized
if (!location->IsProgression()) {
continue;
}
auto locName = location->GetName();
if (!instance().m_locationItemInfo.contains(locName)) {
@@ -393,8 +487,92 @@ void ArchipelagoContext::UpdateCheckedLocations() {
if (isCollected && !cachedLocData.collected) {
cachedLocData.collected = true;
AP_SendItem(cachedLocData.apLocationId);
changed = true;
}
}
if (!changed) {
DuskLog.warn("No locations had any changes! this might not be normal.");
}
}
void ArchipelagoContext::SetNeedUpdateLocations(bool update) {
if (!instance().m_isAllowUpdateLocations)
instance().m_isUpdateLocations = update;
}
bool ArchipelagoContext::IsLocationChecked(int locId) {
auto& world = instance().m_archiWorld;
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
if (locInfo.collected)
return true;
if (auto location = world->GetLocation(locInfo.locationName, true)) {
return isLocationObtained(location);
}
DuskLog.error("Failed to obtain location: {}", locName);
return false;
}
}
return false;
}
void ArchipelagoContext::SetLocationChecked(int locId, bool collected) {
// func was ran before location scouts could be sent out, cache result until scouts return.
if (instance().m_locationItemInfo.empty()) {
instance().m_initLocationCollectState[locId] = collected;
return;
}
auto& world = instance().m_archiWorld;
for (auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
locInfo.collected = collected;
// update location flags if possible
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
return;
setLocationCollected(location, collected);
return;
}
}
DuskLog.warn("No location found for locId {}.", locId);
}
void ArchipelagoContext::UpdateLocationState(int locId, bool collected) {
auto& world = instance().m_archiWorld;
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
continue;
setLocationCollected(location, collected);
return;
}
}
DuskLog.warn("No location found for locId {}.", locId);
}
void ArchipelagoContext::UpdateAllLocationState() {
auto& world = instance().m_archiWorld;
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
continue;
setLocationCollected(location, locInfo.collected);
}
}
void ArchipelagoContext::RequestAllLocationScout(bool isHint) {
@@ -412,7 +590,7 @@ void ArchipelagoContext::SetAPConfigYamlPath(const std::string_view& path) {
instance().m_apConfigPath = path;
}
bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Config& outConfig) {
bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Config& config) {
if (instance().m_apConfigPath.empty()) {
DuskLog.warn("AP Config Path Empty!");
return false;
@@ -431,17 +609,8 @@ bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Confi
return false;
}
outConfig.SetSeed("Archipelago");
randomizer::seedgen::settings::Settings settings;
auto defaultSettings = randomizer::seedgen::settings::GetAllSettingsInfo();
// add default settings to config first
for (const auto& [name, info] : *defaultSettings) {
if (info->GetType() == randomizer::seedgen::settings::Type::STANDARD)
settings.InsertSetting(name, randomizer::seedgen::settings::Setting(info.get(), info->GetDefaultOption()));
}
config.SetSeed("Archipelago");
randomizer::seedgen::settings::Settings& settings = config.GetSettings();
// update settings using ap config
for (const auto& apSettingEntry : apConfigYaml["Twilight Princess"]) {
@@ -470,11 +639,16 @@ bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Confi
auto& setting = settings.GetMap().at(settingConvert.dusklightName);
if (apSettingValue) {
setting.SetCurrentOption("On");
}else {
setting.SetCurrentOption("Off");
}
setting.SetCurrentOption(apSettingValue ? "On" : "Off");
continue;
}
if (apSettingName == "poe_shuffled") {
auto& setting = settings.GetMap().at("Poe Souls");
bool apSettingValue = apSettingEntry.second.as<bool>();
// this setting has more options, but the current apworld only has off or on for now.
setting.SetCurrentOption(apSettingValue ? "All" : "Vanilla");
continue;
}
@@ -482,16 +656,152 @@ bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Confi
auto apSettingValue = apSettingEntry.second.as<std::string>();
// TODO: the rest of the translations
// TODO: clean up this if-else hellscape
if (apSettingName == "castle_requirements") {
auto& setting = settings.GetMap().at("Hyrule Barrier Requirements");
// ap assumes max mirror shards/fused shadows/dungeons, so update those settings as well
if(apSettingValue == "open")
setting.SetCurrentOption("Open");
else if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "fused_shadows") {
setting.SetCurrentOption("Fused Shadows");
settings.GetMap().at("Hyrule Barrier Fused Shadows").SetCurrentOption("3");
}else if(apSettingValue == "mirror_shards") {
setting.SetCurrentOption("Mirror Shards");
settings.GetMap().at("Hyrule Barrier Mirror Shards").SetCurrentOption("4");
}else if(apSettingValue == "all_dungeons") {
setting.SetCurrentOption("Dungeons");
settings.GetMap().at("Hyrule Barrier Dungeons").SetCurrentOption("8");
}
}else if (apSettingName == "palace_requirements") {
auto& setting = settings.GetMap().at("Palace of Twilight Requirements");
if(apSettingValue == "open")
setting.SetCurrentOption("Open");
else if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "fused_shadows")
setting.SetCurrentOption("Fused Shadows");
else if(apSettingValue == "mirror_shards")
setting.SetCurrentOption("Mirror Shards");
}else if (apSettingName == "faron_woods_logic") {
auto& setting = settings.GetMap().at("Faron Woods Logic");
if(apSettingValue == "open")
setting.SetCurrentOption("Open");
else if(apSettingValue == "closed")
setting.SetCurrentOption("Closed");
}else if (apSettingName == "small_key_settings") {
auto& setting = settings.GetMap().at("Small Keys");
if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "own_dungeon")
setting.SetCurrentOption("Own Dungeon");
else if(apSettingValue == "any_dungeon")
setting.SetCurrentOption("Any Dungeon");
else if(apSettingValue == "anywhere")
setting.SetCurrentOption("Anywhere");
else if(apSettingValue == "startwith")
setting.SetCurrentOption("Keysy");
}else if (apSettingName == "big_key_settings") {
auto& setting = settings.GetMap().at("Big Keys");
if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "own_dungeon")
setting.SetCurrentOption("Own Dungeon");
else if(apSettingValue == "any_dungeon")
setting.SetCurrentOption("Any Dungeon");
else if(apSettingValue == "anywhere")
setting.SetCurrentOption("Anywhere");
else if(apSettingValue == "startwith")
setting.SetCurrentOption("Keysy");
}else if (apSettingName == "map_and_compass_settings") {
auto& setting = settings.GetMap().at("Maps and Compasses");
if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "own_dungeon")
setting.SetCurrentOption("Own Dungeon");
else if(apSettingValue == "any_dungeon")
setting.SetCurrentOption("Any Dungeon");
else if(apSettingValue == "anywhere")
setting.SetCurrentOption("Anywhere");
else if(apSettingValue == "startwith")
setting.SetCurrentOption("Keysy");
}else if (apSettingName == "trap_frequency") {
auto& setting = settings.GetMap().at("Trap Item Frequency");
if(apSettingValue == "no_traps")
setting.SetCurrentOption("None");
else if(apSettingValue == "few")
setting.SetCurrentOption("Few");
else if(apSettingValue == "many")
setting.SetCurrentOption("Many");
else if(apSettingValue == "mayhem")
setting.SetCurrentOption("Mayhem");
else if(apSettingValue == "nightmare")
setting.SetCurrentOption("Nightmare");
}else if (apSettingName == "damage_magnification") {
auto& setting = settings.GetMap().at("Logic Damage Multiplier");
if(apSettingValue == "vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "double")
setting.SetCurrentOption("Double");
else if(apSettingValue == "triple")
setting.SetCurrentOption("Triple");
else if(apSettingValue == "quadruple")
setting.SetCurrentOption("Quadruple");
else if(apSettingValue == "ohko")
setting.SetCurrentOption("OHKO");
}else if (apSettingName == "goron_mines_entrance") {
auto& setting = settings.GetMap().at("Goron Mines Entrance");
if(apSettingValue == "closed")
setting.SetCurrentOption("Closed");
else if(apSettingValue == "no_wrestling")
setting.SetCurrentOption("No Wrestling");
else if(apSettingValue == "open")
setting.SetCurrentOption("Open");
}else if (apSettingName == "tot_entrance") {
auto& setting = settings.GetMap().at("Sacred Grove Does Not Require Skull Kid");
auto& setting2 = settings.GetMap().at("Temple of Time Sword Requirement");
if(apSettingValue == "closed") {
setting.SetCurrentOption("Off");
setting2.SetCurrentOption("Master Sword");
}else if (apSettingValue == "open_grove") {
setting.SetCurrentOption("On");
setting2.SetCurrentOption("Master Sword");
}else if (apSettingValue == "open") {
setting.SetCurrentOption("On");
setting2.SetCurrentOption("None");
}
}else if (apSettingName == "logic_rules") {
auto& setting = settings.GetMap().at("Logic Rules");
if(apSettingValue == "glitchless") {
setting.SetCurrentOption("All Locations Reachable");
}else if (apSettingValue == "glitched") { // this might not be the most direct translation
setting.SetCurrentOption("Beatable Only");
}
}
}
outConfig.GetSettingsList().push_back(settings);
return true;
}
@@ -502,4 +812,133 @@ int ArchipelagoContext::GetItemAtLocation(const std::string& locName) {
}
return instance().m_locationItemInfo[locName].itemId;
}
int ArchipelagoContext::GetItemAtLocation(int locId) {
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
return locInfo.itemId;
}
}
return 0;
}
void ArchipelagoContext::CreateArchipelagoWorld() {
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
auto trackerRando = randomizer::Randomizer(workingDir);
trackerRando.GenerateTrackerWorld(false);
instance().m_archiWorld = std::move(trackerRando.GetWorlds().front());
}
void ArchipelagoContext::FillArchipelagoWorld() {
auto& world = instance().m_archiWorld;
if (world == nullptr) {
DuskLog.error("Archipelago world was not created!");
return;
}
auto& locationInfo = instance().m_locationItemInfo;
// fill all locations with data pulled from archi session
for (auto location : world->GetAllLocations()) {
// skip locations that aren't progression, which are locations that just aren't randomized
if (!location->IsProgression()) {
location->SetCurrentItem(location->GetOriginalItem());
continue;
}
auto locName = location->GetName();
if (!locationInfo.contains(locName)) {
if (!location->HasCategories("Warp Portal") &&
!location->HasCategories("Placeholder") &&
!location->HasCategories("Hint Sign"))
DuskLog.warn("Missing archipelago location data for: {}", locName);
auto origItem = location->GetOriginalItem();
// set location to original item
if (origItem->GetID() != -1) // ensure item is not nothing
location->SetCurrentItem(origItem);
else
DuskLog.info("Location ({}) does not have an original item!", locName);
continue;
}
auto& locInfo = locationInfo[locName];
if (locInfo.itemId != -1) {
location->SetCurrentItem(world->GetItem(locInfo.itemId));
}else {
DuskLog.info("Skipping location ({}) as item is -1.", locName);
}
}
}
void ArchipelagoContext::CreateRandomizerContext() {
auto& world = instance().m_archiWorld;
// Set hint texts before writing context
randomizer::logic::hints::GenerateAllHints(world);
// TODO: generate archipelago item get text replacements
auto randoData = WriteSeedData(world.get());
randoData.mHash = GetArchipelagoSeedName();
randomizer_GetContext() = randoData;
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
auto writeToFileResult = randoData.WriteToFile(workingDir / "seed.dat");
if (writeToFileResult.has_value()) {
DuskLog.error("Failed to create Rando Data. Reason: {}", writeToFileResult.value());
return;
}
}
void ArchipelagoContext::LoadRandomizerContext() {
randomizer_GetContext() = RandomizerContext();
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
randomizer_GetContext().LoadFromPath(workingDir / "seed.dat");
randomizer_GetContext().mHash = GetArchipelagoSeedName();
}
void ArchipelagoContext::GenerateLocalWorldData() {
bool createContext = false;
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
if (std::filesystem::exists(workingDir)) {
instance().m_config.LoadFromFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
}else {
std::filesystem::create_directories(workingDir);
// creates base yamls at directory if they dont exist yet
instance().m_config.LoadFromFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
GenerateConfigFromAP(instance().m_config);
instance().m_config.WriteToFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
createContext = true;
}
CreateArchipelagoWorld();
FillArchipelagoWorld();
if (createContext) {
CreateRandomizerContext();
}else {
LoadRandomizerContext();
}
}
} // dusk::archi
+50 -10
View File
@@ -10,26 +10,38 @@ namespace dusk::archi
class ArchipelagoContext {
private:
struct TEMP_GameItemInfo {
int itemId;
randomizer::logic::item::Importance importance;
int itemId = -1;
randomizer::logic::item::Importance importance = randomizer::logic::item::Importance::INVALID;
std::string itemName;
};
struct TEMP_GameLocationInfo {
int apId;
int apId = -1;
std::string locName;
};
struct GameLocationInfo {
int itemId;
int64_t apLocationId;
bool collected;
int itemId = -1;
std::string itemName;
std::string locationName;
int64_t apLocationId = -1;
bool collected = false;
};
std::vector<int> m_inactiveItemsQueue;
std::vector<std::pair<int, bool>> m_receivedItemsQueue;
std::mutex m_queueMutex;
// Rando Data
randomizer::seedgen::config::Config m_config;
std::unique_ptr<randomizer::logic::world::World> m_archiWorld = nullptr;
bool m_isUpdateLocations = false;
bool m_isNeedResetInv = false;
bool m_isAllowUpdateLocations = false;
// AP Data
std::unordered_map<std::string, GameLocationInfo> m_locationItemInfo;
std::map<int, bool> m_initLocationCollectState;
AP_RoomInfo m_roomInfo;
// TEMP
std::map<int, TEMP_GameItemInfo> m_apItemToGameItem;
@@ -58,6 +70,10 @@ namespace dusk::archi
static const std::string& GetSlotName();
static const std::string& GetPassword();
static std::string GetArchipelagoSeedName();
static void GetSeedDirectoryPath(std::filesystem::path& outPath);
// Connection Handlers
static void ConnectToServer();
@@ -68,9 +84,11 @@ namespace dusk::archi
// State Handlers
static void MainThreadFunc();
static void MessageThreadFunc();
static void HandleItemReceived(int id, bool notify);
static void Execute();
static void HandleItemReceived(AP_NetworkItem& id, bool notify);
static void HandleResetInventory();
@@ -78,6 +96,16 @@ namespace dusk::archi
static void UpdateCheckedLocations();
static void SetNeedUpdateLocations(bool update);
static bool IsLocationChecked(int locId);
static void SetLocationChecked(int locId, bool collected);
static void UpdateLocationState(int locId, bool collected);
static void UpdateAllLocationState();
// State Requesters
static void RequestAllLocationScout(bool isHint = false);
@@ -86,9 +114,21 @@ namespace dusk::archi
static void SetAPConfigYamlPath(const std::string_view& path);
static bool GenerateConfigFromAP(randomizer::seedgen::config::Config& outConfig);
static bool GenerateConfigFromAP(randomizer::seedgen::config::Config& config);
static int GetItemAtLocation(const std::string& locName);
static int GetItemAtLocation(int locId);
static void CreateArchipelagoWorld();
static void FillArchipelagoWorld();
static void CreateRandomizerContext();
static void LoadRandomizerContext();
static void GenerateLocalWorldData();
};
} // dusk::archi
+5 -5
View File
@@ -6,6 +6,7 @@
#include "aurora/lib/window.hpp"
#include "dusk/file_select.hpp"
#include "dusk/archipelago/archipelago_context.hpp"
#include "dusk/ui/rando_seed_generation.hpp"
namespace dusk {
@@ -69,12 +70,11 @@ void ImGuiArchipelagoDebug::drawWindow() {
OpenApFilePicker();
}
if (ImGui::Button("Test Config Convert")) {
randomizer::seedgen::config::Config config;
archi::ArchipelagoContext::GenerateConfigFromAP(config);
}
if (archi::ArchipelagoContext::IsConnected()) {
if (ImGui::Button("Test Create World Data")) {
archi::ArchipelagoContext::GenerateLocalWorldData();
}
if (ImGui::Button("Disconnect")) {
archi::ArchipelagoContext::DisconnectFromServer();
}
+12 -2
View File
@@ -20,6 +20,8 @@
#include <numeric>
#include <ranges>
#include "dusk/archipelago/archipelago_context.hpp"
namespace dusk {
@@ -254,8 +256,16 @@ namespace dusk {
auto trackerHash = trackerRando->GetConfig().GetHash(false);
// If no hash, or seeds switched, try to create tracker world from currently active seed
if (trackerHash.empty() || (trackerHash != contextHash && !contextHash.empty())) {
*trackerRando = randomizer::Randomizer(ui::GetRandomizerPath());
trackerRando->GenerateTrackerWorld();
if (archi::ArchipelagoContext::IsConnected()) {
std::filesystem::path workingDir;
archi::ArchipelagoContext::GetSeedDirectoryPath(workingDir);
*trackerRando = randomizer::Randomizer(workingDir);
trackerRando->GenerateTrackerWorld(false);
}else {
*trackerRando = randomizer::Randomizer(ui::GetRandomizerPath());
trackerRando->GenerateTrackerWorld();
}
}
if (randomizer_IsActive()) {
@@ -22,10 +22,14 @@
#include "d/d_meter2_draw.h"
#include "d/d_meter2_info.h"
#include "d/d_msg_flow.h"
#include "dusk/archipelago/archipelago_context.hpp"
std::optional<std::string> RandomizerContext::WriteToFile() {
return WriteToFile(this->GetSeedDataPath());
}
std::ofstream seedData(this->GetSeedDataPath());
std::optional<std::string> RandomizerContext::WriteToFile(const fspath& path) {
std::ofstream seedData(path);
if (!seedData.is_open()) {
return "Could not open seed data file";
}
@@ -137,7 +141,11 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
return std::nullopt;
}
auto in = LoadYAML(this->GetSeedDataPath());
return LoadFromPath(this->GetSeedDataPath());
}
std::optional<std::string> RandomizerContext::LoadFromPath(const fspath& path) {
auto in = LoadYAML(path);
// Necessary settings
for (const auto& settingNode : in["mSettings"] ) {
@@ -587,6 +595,8 @@ int RandomizerState::execute() {
handleFoolishItem();
}
dusk::archi::ArchipelagoContext::Execute();
return 1;
}
@@ -1001,6 +1011,11 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
// Set data for all locations
for (const auto& location : world->GetAllLocations()) {
// skip locations with nothing
if (location->GetCurrentItem()->GetID() == -1) {
continue;
}
const auto& metaData = location->GetMetadata();
// Chest Overrides
@@ -80,7 +80,9 @@ public:
} mStartLocation;
std::optional<std::string> WriteToFile();
std::optional<std::string> WriteToFile(const fspath& path);;
std::optional<std::string> LoadFromHash(const std::string& hash);
std::optional<std::string> LoadFromPath(const fspath& path);
std::filesystem::path GetSeedDataPath() const;
enum Settings {
@@ -267,4 +269,9 @@ u32 getStageObjCRC32(u8* data, size_t size);
*/
bool GenerateAndWriteSeed(std::string& generationStatusMsg);
/*
* Creates RandomizerContext that contains all needed data for the seed.
*/
RandomizerContext WriteSeedData(randomizer::logic::world::World* world);
#endif //DUSK_RANDOMIZER_CONTEXT_HPP
+76
View File
@@ -495,8 +495,84 @@ randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world:
return pool;
}
void setLocationCollected(randomizer::logic::location::Location* location, bool collect) {
auto& locationMeta = location->GetMetadata();
if (auto& chestNode = locationMeta["Chest"]) {
auto tboxId = chestNode[0]["Tbox Id"].as<u8>();
auto stageId = getStageSaveId(chestNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageTbox(stageId, tboxId);
else
dComIfGs_offStageTbox(stageId, tboxId);
}
if (auto& poeNode = locationMeta["Poe"]) {
auto flag = poeNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(poeNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageSwitch(stageId, flag);
else
dComIfGs_offStageSwitch(stageId, flag);
}
if (auto& freeStandingItemNode = locationMeta["Freestanding Item"]) {
auto flag = freeStandingItemNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(freeStandingItemNode[0]["Stage"].as<u8>());
// big baba uses tbox, hardcode this edge case
if (location->GetName() == "Forest Temple Big Baba Key") {
if (collect)
dComIfGs_onStageTbox(stageId, flag);
else
dComIfGs_offStageTbox(stageId, flag);
}else {
if (collect)
dComIfGs_onStageItem(stageId, flag);
else
dComIfGs_offStageItem(stageId, flag);
}
}
if (auto& eventFlagNode = locationMeta["Event Flag"]) {
auto flag = eventFlagNode.as<u16>();
if (collect)
dComIfGs_onEventBit(flag);
else
dComIfGs_offEventBit(flag);
}
if (auto& wolfNode = locationMeta["Golden Wolf"]) {
auto flag = wolfNode[0]["Flag"].as<u16>();
if (collect)
dComIfGs_onEventBit(flag);
else
dComIfGs_offEventBit(flag);
}
if (auto& switchFlagNode = locationMeta["Switch Flag"]) {
auto flag = switchFlagNode["Flag"].as<u8>();
auto stageId = getStageSaveId(switchFlagNode["Stage"].as<u8>());
if (collect)
dComIfGs_onStageSwitch(stageId, flag);
else
dComIfGs_offStageSwitch(stageId, flag);
}
if (auto& itemFlagNode = locationMeta["Item Flag"]) {
auto flag = itemFlagNode["Flag"].as<u8>();
auto stageId = getStageSaveId(itemFlagNode["Stage"].as<u8>());
if (collect)
dComIfGs_onStageItem(stageId, flag);
else
dComIfGs_offStageItem(stageId, flag);
}
if (auto& twilitInsectNode = locationMeta["Twilit Insect"]) {
auto flag = twilitInsectNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(twilitInsectNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageTbox(stageId, flag);
else
dComIfGs_offStageTbox(stageId, flag);
}
}
bool isLocationObtained(randomizer::logic::location::Location* location) {
auto& locationMeta = location->GetMetadata();
if (auto& chestNode = locationMeta["Chest"]) {
auto tboxId = chestNode[0]["Tbox Id"].as<u8>();
auto stageId = getStageSaveId(chestNode[0]["Stage"].as<u8>());
+5
View File
@@ -25,6 +25,11 @@ int getTempleKeysFound(int saveId);
*/
randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world::World* world);
/*
* Updates locations relevant flag in save to whatever state is supplied.
*/
void setLocationCollected(randomizer::logic::location::Location* location, bool collect);
/*
* Finds locations relevant flag in save (using its metadata) and checks if it's been set.
*/
@@ -1055,9 +1055,10 @@
# Importance: Junk
# Id: 0xDB
#- Name: Unused
# Importance: Junk
# Id: 0xDC
- Name: Archipelago Item
Importance: Minor
Id: 0xDC
APItemId: -1
#- Name: Unused
# Importance: Junk
@@ -232,7 +232,7 @@ namespace randomizer::logic::entrance
return this->_decoupled;
}
void Entrance::SetDisbled(const bool& disabled)
void Entrance::SetDisabled(const bool& disabled)
{
this->_disabled = disabled;
LOG_TO_DEBUG(this->GetOriginalName() + " disabled status set to " + (disabled ? "True" : "False"));
@@ -102,7 +102,7 @@ namespace randomizer::logic::entrance
bool IsShuffled() const;
void SetDecoupled(const bool& decoupled);
bool IsDecoupled() const;
void SetDisbled(const bool& disabled);
void SetDisabled(const bool& disabled);
bool IsDisabled() const;
void SetPrimary(const bool& primary);
bool IsPrimary() const;
+50 -49
View File
@@ -4,36 +4,33 @@
#include "world.hpp"
namespace randomizer::logic::hints {
static const std::unordered_map<std::string, std::string> dungeonColors = {
{"Forest Temple", "<green>"},
{"Goron Mines", "<red>"},
{"Lakebed Temple", "<blue>"},
{"Arbiters Grounds", "<orange>"},
{"Snowpeak Ruins", "<light blue>"},
{"Temple of Time", "<dark green>"},
{"City in the Sky", "<yellow>"},
{"Palace of Twilight", "<purple>"},
// {"Hyrule Castle", "<silver>"}
};
// Tell the player which dungeons are required on the sign in front of Link's House
static void GenerateRequiredDungeonsHint(world::WorldPool& worlds) {
static const std::unordered_map<std::string, std::string> dungeonColors = {
{"Forest Temple", "<green>"},
{"Goron Mines", "<red>"},
{"Lakebed Temple", "<blue>"},
{"Arbiters Grounds", "<orange>"},
{"Snowpeak Ruins", "<light blue>"},
{"Temple of Time", "<dark green>"},
{"City in the Sky", "<yellow>"},
{"Palace of Twilight", "<purple>"},
// {"Hyrule Castle", "<silver>"}
};
for (const auto& world : worlds) {
auto& requiredDungeonText = world->AddNewText("Links House Sign");
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
requiredDungeonText += dungeonColors.at(dungeonName) + getTextObject(dungeonName) + "\n";
}
static void GenerateRequiredDungeonsHint(world::World* world) {
auto& requiredDungeonText = world->AddNewText("Links House Sign");
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
requiredDungeonText += dungeonColors.at(dungeonName) + getTextObject(dungeonName) + "\n";
}
}
if (requiredDungeonText.Empty()) {
requiredDungeonText += getTextObject("No Required Dungeons Text");
}
if (requiredDungeonText.Empty()) {
requiredDungeonText += getTextObject("No Required Dungeons Text");
}
}
static void doItemTextReplacement(const std::unique_ptr<world::World>& world,
static void doItemTextReplacement(world::World* world,
const std::string& locationName,
const std::list<std::string>& textNames,
Text::Color color) {
@@ -48,40 +45,44 @@ namespace randomizer::logic::hints {
}
}
static void GenerateItemTextReplacements(world::WorldPool& worlds) {
for (const auto& world : worlds) {
doItemTextReplacement(world, "Fishing Hole Bottle", {"Fishing Hole Sign Text"}, Text::GREEN);
doItemTextReplacement(world, "Charlo Donation Blessing", {"Charlo Donation Ask Text"}, Text::GREEN);
doItemTextReplacement(world, "Sera Shop Slingshot", {"Slingshot Shop Text",
"Slingshot Shop Too Expensive Text", "Slingshot Shop Purchase Confirmation Text",
"Slingshot Shop After Purchase Text"}, Text::ORANGE);
static void GenerateItemTextReplacements(world::World* world) {
doItemTextReplacement(world, "Fishing Hole Bottle", {"Fishing Hole Sign Text"}, Text::GREEN);
doItemTextReplacement(world, "Charlo Donation Blessing", {"Charlo Donation Ask Text"}, Text::GREEN);
doItemTextReplacement(world, "Sera Shop Slingshot", {"Slingshot Shop Text",
"Slingshot Shop Too Expensive Text", "Slingshot Shop Purchase Confirmation Text",
"Slingshot Shop After Purchase Text"}, Text::ORANGE);
doItemTextReplacement(world, "Barnes Bomb Bag", {"Barnes Special Offer Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Wooden Shield", {"Kakariko Malo Mart Wooden Shield Purchase Confirmation Text",
"Kakariko Malo Mart Wooden Shield Too Expensive Text", "Kakariko Malo Mart Wooden Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Barnes Bomb Bag", {"Barnes Special Offer Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Wooden Shield", {"Kakariko Malo Mart Wooden Shield Purchase Confirmation Text",
"Kakariko Malo Mart Wooden Shield Too Expensive Text", "Kakariko Malo Mart Wooden Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hylian Shield", {"Kakariko Malo Mart Hylian Shield Purchase Confirmation Text",
"Kakariko Malo Mart Hylian Shield Too Expensive Text", "Kakariko Malo Mart Hylian Shield After Purchase Text",
"Kakariko Malo Mart Hylian Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hylian Shield", {"Kakariko Malo Mart Hylian Shield Purchase Confirmation Text",
"Kakariko Malo Mart Hylian Shield Too Expensive Text", "Kakariko Malo Mart Hylian Shield After Purchase Text",
"Kakariko Malo Mart Hylian Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Red Potion", {"Kakariko Malo Mart Red Potion Too Expensive Text",
"Kakariko Malo Mart Red Potion Purchase Confirmation Text", "Kakariko Malo Mart Red Potion Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Red Potion", {"Kakariko Malo Mart Red Potion Too Expensive Text",
"Kakariko Malo Mart Red Potion Purchase Confirmation Text", "Kakariko Malo Mart Red Potion Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hawkeye", {"Kakariko Malo Mart Hawkeye Purchase Confirmation Text",
"Kakariko Malo Mart Hawkeye Too Expensive Text", "Kakariko Malo Mart Hawkeye After Purchase Text",
"Kakariko Malo Mart Hawkeye Coming Soon Text", "Kakariko Malo Mart Hawkeye Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hawkeye", {"Kakariko Malo Mart Hawkeye Purchase Confirmation Text",
"Kakariko Malo Mart Hawkeye Too Expensive Text", "Kakariko Malo Mart Hawkeye After Purchase Text",
"Kakariko Malo Mart Hawkeye Coming Soon Text", "Kakariko Malo Mart Hawkeye Text"}, Text::ORANGE);
doItemTextReplacement(world, "Castle Town Malo Mart Magic Armor", {"Chudleys Shop Magic Armor Text",
"Castle Town Malo Mart Magic Armor After Purchase Text", "Castle Town Malo Mart Magic Armor Text",
"Castle Town Malo Mart Magic Armor Sold Out Text"}, Text::ORANGE);
doItemTextReplacement(world, "Castle Town Malo Mart Magic Armor", {"Chudleys Shop Magic Armor Text",
"Castle Town Malo Mart Magic Armor After Purchase Text", "Castle Town Malo Mart Magic Armor Text",
"Castle Town Malo Mart Magic Armor Sold Out Text"}, Text::ORANGE);
doItemTextReplacement(world, "Coro Bottle", {"Coro Bottle Offer 1 Text",
"Coro Bottle Offer 2 Text", "Coro Bottle Offer 3 Text", "Coro Bottle Offer 4 Text"}, Text::ORANGE);
}
doItemTextReplacement(world, "Coro Bottle", {"Coro Bottle Offer 1 Text",
"Coro Bottle Offer 2 Text", "Coro Bottle Offer 3 Text", "Coro Bottle Offer 4 Text"}, Text::ORANGE);
}
void GenerateAllHints(world::WorldPool& worlds) {
GenerateRequiredDungeonsHint(worlds);
GenerateItemTextReplacements(worlds);
for (const auto& world : worlds) {
GenerateAllHints(world);
}
}
void GenerateAllHints(const std::unique_ptr<world::World>& world) {
GenerateRequiredDungeonsHint(world.get());
GenerateItemTextReplacements(world.get());
}
}
@@ -3,5 +3,6 @@
namespace randomizer::logic::hints {
void GenerateAllHints(world::WorldPool& worldPool);
void GenerateAllHints(const std::unique_ptr<world::World>& world);
}
@@ -842,7 +842,7 @@ namespace randomizer::logic::world
// Disable the dungeon's starting entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisbled(true);
entrance->SetDisabled(true);
}
// Run an accessibility search to see which locations inherently require accessing this dungeon
@@ -864,7 +864,7 @@ namespace randomizer::logic::world
// Re-enable the dungeon's entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisbled(false);
entrance->SetDisabled(false);
}
}
}
@@ -916,7 +916,7 @@ namespace randomizer::logic::world
// Disable the dungeon's starting entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisbled(true);
entrance->SetDisabled(true);
}
// Check if the game is beatable, set dungeon as required if so. If the dungeon is not required and barren
@@ -942,7 +942,7 @@ namespace randomizer::logic::world
// Re-enable the dungeon's entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisbled(false);
entrance->SetDisabled(false);
}
}
}
@@ -1169,11 +1169,13 @@ namespace randomizer::logic::world
return this->_startingItemPool;
}
location::Location* World::GetLocation(const std::string& name)
location::Location* World::GetLocation(const std::string& name, const bool& ignoreError)
{
if (!this->_locationTable.contains(name))
{
throw std::runtime_error("Unknown location name \"" + name + "\"");
if (!ignoreError)
throw std::runtime_error("Unknown location name \"" + name + "\"");
return nullptr;
}
return this->_locationTable.at(name).get();
}
@@ -131,7 +131,7 @@ namespace randomizer::logic::world
item::Item* GetGameWinningItem() const;
item_pool::ItemPool& GetItemPool();
item_pool::ItemPool& GetStartingItemPool();
location::Location* GetLocation(const std::string& name);
location::Location* GetLocation(const std::string& name, const bool& ignoreError = false);
location::LocationPool GetAllLocations(const bool& includeNonItemLocations = false);
area::Area* GetArea(const std::string& name, const bool& createIfNotFound = false);
area::Area* GetRootArea() const;
+15 -9
View File
@@ -15,6 +15,7 @@
#include <iostream>
#include "dusk/logging.h"
#include "dusk/archipelago/archipelago_context.hpp"
#include "dusk/ui/rando_config.hpp"
#include "dusk/randomizer/game/randomizer_context.hpp"
@@ -45,19 +46,24 @@ namespace randomizer
return std::nullopt;
}
void Randomizer::GenerateTrackerWorld() {
void Randomizer::GenerateTrackerWorld(bool useAntiSpoilerLog) {
auto contextHash = randomizer_GetContext().mHash;
if (contextHash.empty()) {
return;
if (!useAntiSpoilerLog) {
this->_config.LoadFromFile(GetConfigPath(), GetPrefPath());
this->_config.SetHash(contextHash);
}else {
if (contextHash.empty()) {
return;
}
std::filesystem::path seedSettings = dusk::ui::GetRandomizerSeedsPath() /
contextHash / (contextHash + " Anti-Spoiler Log.txt");
this->_config.LoadFromFile(seedSettings, GetPrefPath());
this->_config.SetHash(contextHash);
}
std::filesystem::path seedSettings = dusk::ui::GetRandomizerSeedsPath() /
contextHash / (contextHash + " Anti-Spoiler Log.txt");
this->_config.LoadFromFile(seedSettings, GetPrefPath());
this->_config.SetHash(contextHash);
std::unique_ptr<logic::world::World> world = std::make_unique<logic::world::World>(1, this);
world->SetSettings(this->_config.GetSettingsList().front());
// Always use logic when building a tracker world
+1 -1
View File
@@ -19,7 +19,7 @@ namespace randomizer
*/
std::optional<std::string> Generate();
void GenerateWorlds();
void GenerateTrackerWorld();
void GenerateTrackerWorld(bool useAntiSpoilerLog = true);
auto& GetConfig() { return this->_config; }
auto& GetWorlds() { return this->_worlds; }
+2 -2
View File
@@ -55,8 +55,8 @@ FetchContent_Declare(
message(STATUS "randomizer: Fetching APCpp")
FetchContent_Declare(
APCpp
GIT_REPOSITORY https://github.com/N00byKing/APCpp.git
GIT_TAG 9194179
GIT_REPOSITORY https://github.com/CraftyBoss/APCpp.git
GIT_TAG 5091686
)
FetchContent_MakeAvailable(yaml-cpp base64pp battery-embed APCpp)