From 7ee39edee76c0171eb4b4c7ebc6b508c30b6e085 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Wed, 24 Jun 2026 03:22:42 -0700 Subject: [PATCH] add death link support, add needed archipelago item messages to language yamls Archipelago items will still be green rupees for now until we decide how we want to handle custom game assets --- src/d/actor/d_a_alink_demo.inc | 11 +++ src/d/d_file_sel_info.cpp | 7 +- src/dusk/archipelago/archipelago_context.cpp | 89 +++++++++++++++++-- src/dusk/archipelago/archipelago_context.hpp | 9 ++ src/dusk/imgui/ImGuiArchipelagoDebug.cpp | 10 +++ .../data/text/languages/english.yaml | 13 +++ .../generator/data/text/languages/french.yaml | 13 +++ .../generator/data/text/languages/german.yaml | 13 +++ .../data/text/languages/italian.yaml | 13 +++ .../data/text/languages/spanish.yaml | 13 +++ .../generator/data/text/text_overrides.yaml | 4 + src/dusk/randomizer/randomizer.cmake | 2 +- 12 files changed, 186 insertions(+), 11 deletions(-) diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index 42651dfd27..d0dda1b36f 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -23,6 +23,8 @@ #include "d/actor/d_a_npc_tkc.h" #include +#include "dusk/archipelago/archipelago_context.hpp" + #if TARGET_PC #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" @@ -3302,6 +3304,15 @@ int daAlink_c::procCoDeadInit(int param_0) { field_0x3080 = 0; mProcVar5.field_0x3012 = 0x3F; field_0x3198 = -1; + +#if TARGET_PC + // trigger archipelago death link if applicable + if (dusk::archi::ArchipelagoContext::IsConnected()) { + dusk::archi::ArchipelagoContext::TryHandleDeathLink(); + } + +#endif + return 1; } diff --git a/src/d/d_file_sel_info.cpp b/src/d/d_file_sel_info.cpp index f99ff7ef4c..63df8cbd0b 100644 --- a/src/d/d_file_sel_info.cpp +++ b/src/d/d_file_sel_info.cpp @@ -16,6 +16,7 @@ #include "dusk/string.hpp" #include "dusk/version.hpp" +#include "dusk/archipelago/archipelago_context.hpp" dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) { mArchive = i_archive; @@ -120,9 +121,11 @@ int dFile_info_c::setSaveData(dSv_save_c* i_savedata, BOOL i_validChksum, u8 i_d // If this is a randomizer file auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(i_dataNo).getValue(); if (!curFileSeedHash.empty()) { - // Overwrite "Save time" text with "Randomizer" + bool isArchipelago = dusk::archi::ArchipelagoContext::IsSeedHashArchipelago(curFileSeedHash); + + // Overwrite "Save time" text with "Randomizer" or "Archipelago" auto saveTimeText = (J2DTextBox*)mFileInfo.Scr->search(MULTI_CHAR('f_s_t_02')); - dusk::SafeStringCopy(saveTimeText->getStringPtr(), "Randomizer"); + dusk::SafeStringCopy(saveTimeText->getStringPtr(), isArchipelago ? "Archipelago" : "Randomizer"); saveTimeText->setHBinding(J2DTextBoxHBinding::HBIND_LEFT); // Overwrite the "Total play time" text with the seed hash diff --git a/src/dusk/archipelago/archipelago_context.cpp b/src/dusk/archipelago/archipelago_context.cpp index 2207851fdf..0964c4bbcf 100644 --- a/src/dusk/archipelago/archipelago_context.cpp +++ b/src/dusk/archipelago/archipelago_context.cpp @@ -4,6 +4,7 @@ #include "Archipelago.h" #include "d/d_item.h" +#include "d/actor/d_a_alink.h" #include "dusk/config.hpp" #include "dusk/logging.h" #include "dusk/randomizer/game/tools.h" @@ -136,6 +137,17 @@ void ParseMessageData() { auto msg = AP_GetLatestMessage(); switch (msg->type) { + case AP_MessageType::ItemSend: { + auto sendMsg = (AP_ItemSendMessage*)msg; + ui::push_toast({ + .title = "Item Sent", + .content = fmt::format("Sent {} to {}", sendMsg->item, sendMsg->recvPlayer), + .duration = std::chrono::seconds(3), + }); + + DuskLog.info("[{}] {}", getMessageTypeName(msg->type), msg->text); + break; + } case AP_MessageType::ItemRecv: { auto recvMsg = (AP_ItemRecvMessage*)msg; @@ -147,7 +159,6 @@ void ParseMessageData() { // fallthrough for debug logging text contents } case AP_MessageType::Plaintext: - case AP_MessageType::ItemSend: case AP_MessageType::Hint: case AP_MessageType::Countdown: DuskLog.info("[{}] {}", getMessageTypeName(msg->type), msg->text); @@ -266,6 +277,45 @@ std::string ArchipelagoContext::getLocationNameFromApId(int apId) const { return ""; } +bool ArchipelagoContext::tryKillPlayer() { + if (!m_isNeedPlayerDeath) + return false; + + auto linkActor = daAlink_getAlinkActorClass(); + + if (!linkActor) + return false; + + switch (linkActor->mProcID) { + case daAlink_c::PROC_WAIT: + case daAlink_c::PROC_TIRED_WAIT: + case daAlink_c::PROC_MOVE: + case daAlink_c::PROC_WOLF_WAIT: + case daAlink_c::PROC_WOLF_TIRED_WAIT: + case daAlink_c::PROC_WOLF_MOVE: + case daAlink_c::PROC_ATN_MOVE: + case daAlink_c::PROC_WOLF_ATN_AC_MOVE: { + // Check if link is currently in a cutscene + if (linkActor->checkEventRun()) + break; + + // Ensure that link is not currently in a message-based event. + if (linkActor->getEventId() != 0) + break; + + dComIfGs_setLife(0); + + m_isNeedPlayerDeath = false; + + return true; + } + default: + break; + } + + return false; +} + ArchipelagoContext::ArchipelagoContext() = default; void ArchipelagoContext::SetServerIp(const std::string_view& ip) { @@ -295,7 +345,7 @@ const std::string& ArchipelagoContext::GetPassword() { std::string ArchipelagoContext::GetArchipelagoSeedName() { if (IsConnected()) { auto& roomInfo = instance().m_roomInfo; - return fmt::format("AP_{}_{}", GetSlotName(), roomInfo.seed_name); + return fmt::format("AP_{}", roomInfo.seed_name); }else { DuskLog.fatal("Archipelago was not connected when attempting to get seed name!"); } @@ -332,6 +382,13 @@ bool ArchipelagoContext::ConnectToServer(bool isBlocking) { AP_NetworkVersion ver{0, 6,7}; AP_SetClientVersion(&ver); + AP_SetDeathLinkSupported(true); + + AP_SetDeathLinkRecvCallback([](std::string source, std::string cause) { + DuskLog.info("Player {} sent death link. Cause: {}", source, cause); + RequestPlayerDeath(true); + }); + AP_SetItemClearCallback([]() { DuskLog.info("Item Clear Callback Called!"); instance().m_isNeedResetInv = true; @@ -395,14 +452,11 @@ bool ArchipelagoContext::IsConnected() { } 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()) { AP_GetRoomInfo(&instance().m_roomInfo); + instance().m_isEnableDeathLink = AP_IsDeathLinkEnabled(); RequestAllLocationScout(); } @@ -425,6 +479,12 @@ void ArchipelagoContext::Execute() { return; // end execution early so next frame can re-add inventory if needed } + // process death links + if (instance().tryKillPlayer()) { + // if successful, don't bother processing item queue or location checks + return; + } + // drain pending item queue here instance().m_queueMutex.lock(); if (!instance().m_receivedItemsQueue.empty()) { @@ -541,7 +601,8 @@ void ArchipelagoContext::HandleReceiveLocationScout(const std::vectorGetName(); if (!instance().m_locationItemInfo.contains(locName)) { - DuskLog.warn("No item found for ({}).", locName); + DuskLog.debug("No item found for ({}).", locName); continue; } @@ -659,6 +720,13 @@ bool ArchipelagoContext::IsReceivedLocationScouts() { return !instance().m_locationItemInfo.empty(); } +void ArchipelagoContext::TryHandleDeathLink() { + if (instance().m_isEnableDeathLink && !instance().m_isFromDeathLink) { + // TODO: come up with better death messages + AP_DeathLinkSend("%YOU% was unable to become the Hero of Twilight."); + } +} + void ArchipelagoContext::RequestAllLocationScout(bool isHint) { std::set locations; // TEMP: apworld has 475 locations with ids in sequential order, so add them all individually to location set @@ -670,6 +738,11 @@ void ArchipelagoContext::RequestAllLocationScout(bool isHint) { AP_SendLocationScouts(locations, isHint); } +void ArchipelagoContext::RequestPlayerDeath(bool isDeathLink) { + instance().m_isNeedPlayerDeath = true; + instance().m_isFromDeathLink = isDeathLink; +} + bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Config& config, const std::string& settingsStr) { YAML::Node apConfigYaml; try { diff --git a/src/dusk/archipelago/archipelago_context.hpp b/src/dusk/archipelago/archipelago_context.hpp index 8ce5efcb30..b2b8660d19 100644 --- a/src/dusk/archipelago/archipelago_context.hpp +++ b/src/dusk/archipelago/archipelago_context.hpp @@ -37,12 +37,15 @@ namespace dusk::archi bool m_isUpdateLocations = false; bool m_isNeedResetInv = false; bool m_isAllowUpdateLocations = false; + bool m_isEnableDeathLink = false; // AP Data std::unordered_map m_locationItemInfo; std::map m_initLocationCollectState; AP_RoomInfo m_roomInfo; std::string m_SettingsFile; + bool m_isNeedPlayerDeath = false; + bool m_isFromDeathLink = false; // TEMP std::map m_apItemToGameItem; @@ -57,6 +60,8 @@ namespace dusk::archi int getItemIdFromApId(int apId); std::string getLocationNameFromApId(int apId) const; + + bool tryKillPlayer(); public: ArchipelagoContext(); @@ -112,10 +117,14 @@ namespace dusk::archi static bool IsReceivedLocationScouts(); + static void TryHandleDeathLink(); + // State Requesters static void RequestAllLocationScout(bool isHint = false); + static void RequestPlayerDeath(bool isDeathLink = false); + // AP -> Internal Rando Converters static bool GenerateConfigFromAP(randomizer::seedgen::config::Config& config, const std::string& settingsStr); diff --git a/src/dusk/imgui/ImGuiArchipelagoDebug.cpp b/src/dusk/imgui/ImGuiArchipelagoDebug.cpp index b43c7f3f88..7a83904f51 100644 --- a/src/dusk/imgui/ImGuiArchipelagoDebug.cpp +++ b/src/dusk/imgui/ImGuiArchipelagoDebug.cpp @@ -62,6 +62,16 @@ void ImGuiArchipelagoDebug::drawWindow() { } } + ImGui::SeparatorText("Debug Buttons"); + + static bool isSimulateDeathLink = false; + + if (ImGui::Button("Request Death Link")) { + archi::ArchipelagoContext::RequestPlayerDeath(isSimulateDeathLink); + } + ImGui::SameLine(); + ImGui::Checkbox("Simulate Death Link", &isSimulateDeathLink); + ImGui::End(); } } \ No newline at end of file diff --git a/src/dusk/randomizer/generator/data/text/languages/english.yaml b/src/dusk/randomizer/generator/data/text/languages/english.yaml index df186835f5..fcfe619a12 100644 --- a/src/dusk/randomizer/generator/data/text/languages/english.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/english.yaml @@ -288,6 +288,14 @@ Progressive Wallet: Cryptic: Text: a {money bag} +Archipelago Item: + Standard: + Text: Archipelago Item + Pretty: + Text: an {Archipelago Item} + Cryptic: + Text: an {item from another world} + Upper Zoras River Portal: Standard: Text: Upper Zoras River Portal @@ -1604,6 +1612,11 @@ No Required Dungeons Text: Text: No Required Dungeons # ITEM GET TEXT +Archipelago Item Get Item Text: + Standard: + Text: |- + You got an Archipelago Item! + Foolish Get Item Text: Standard: Text: |- diff --git a/src/dusk/randomizer/generator/data/text/languages/french.yaml b/src/dusk/randomizer/generator/data/text/languages/french.yaml index 4d0e30a146..6e23f2cc33 100644 --- a/src/dusk/randomizer/generator/data/text/languages/french.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/french.yaml @@ -288,6 +288,14 @@ Progressive Wallet: Cryptic: Text: a {money bag} +Archipelago Item: + Standard: + Text: Archipelago Item + Pretty: + Text: an {Archipelago Item} + Cryptic: + Text: an {item from another world} + Upper Zoras River Portal: Standard: Text: Upper Zoras River Portal @@ -1604,6 +1612,11 @@ No Required Dungeons Text: Text: No Required Dungeons # ITEM GET TEXT +Archipelago Item Get Item Text: + Standard: + Text: |- + You got an Archipelago Item! + Foolish Get Item Text: Standard: Text: |- diff --git a/src/dusk/randomizer/generator/data/text/languages/german.yaml b/src/dusk/randomizer/generator/data/text/languages/german.yaml index c3310a286c..fe6d8d9460 100644 --- a/src/dusk/randomizer/generator/data/text/languages/german.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/german.yaml @@ -288,6 +288,14 @@ Progressive Wallet: Cryptic: Text: a {money bag} +Archipelago Item: + Standard: + Text: Archipelago Item + Pretty: + Text: an {Archipelago Item} + Cryptic: + Text: an {item from another world} + Upper Zoras River Portal: Standard: Text: Upper Zoras River Portal @@ -1604,6 +1612,11 @@ No Required Dungeons Text: Text: No Required Dungeons # ITEM GET TEXT +Archipelago Item Get Item Text: + Standard: + Text: |- + You got an Archipelago Item! + Foolish Get Item Text: Standard: Text: |- diff --git a/src/dusk/randomizer/generator/data/text/languages/italian.yaml b/src/dusk/randomizer/generator/data/text/languages/italian.yaml index 057b380393..99821fa95d 100644 --- a/src/dusk/randomizer/generator/data/text/languages/italian.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/italian.yaml @@ -288,6 +288,14 @@ Progressive Wallet: Cryptic: Text: a {money bag} +Archipelago Item: + Standard: + Text: Archipelago Item + Pretty: + Text: an {Archipelago Item} + Cryptic: + Text: an {item from another world} + Upper Zoras River Portal: Standard: Text: Upper Zoras River Portal @@ -1604,6 +1612,11 @@ No Required Dungeons Text: Text: No Required Dungeons # ITEM GET TEXT +Archipelago Item Get Item Text: + Standard: + Text: |- + You got an Archipelago Item! + Foolish Get Item Text: Standard: Text: |- diff --git a/src/dusk/randomizer/generator/data/text/languages/spanish.yaml b/src/dusk/randomizer/generator/data/text/languages/spanish.yaml index 38dd5e9ec8..9aa880e042 100644 --- a/src/dusk/randomizer/generator/data/text/languages/spanish.yaml +++ b/src/dusk/randomizer/generator/data/text/languages/spanish.yaml @@ -288,6 +288,14 @@ Progressive Wallet: Cryptic: Text: a {money bag} +Archipelago Item: + Standard: + Text: Archipelago Item + Pretty: + Text: an {Archipelago Item} + Cryptic: + Text: an {item from another world} + Upper Zoras River Portal: Standard: Text: Upper Zoras River Portal @@ -1604,6 +1612,11 @@ No Required Dungeons Text: Text: No Required Dungeons # ITEM GET TEXT +Archipelago Item Get Item Text: + Standard: + Text: |- + You got an Archipelago Item! + Foolish Get Item Text: Standard: Text: |- diff --git a/src/dusk/randomizer/generator/data/text/text_overrides.yaml b/src/dusk/randomizer/generator/data/text/text_overrides.yaml index 3ab5245d8a..bac0dc0a64 100644 --- a/src/dusk/randomizer/generator/data/text/text_overrides.yaml +++ b/src/dusk/randomizer/generator/data/text/text_overrides.yaml @@ -11,6 +11,10 @@ # Group: 0 # Message Id: 122 +- Name: Archipelago Item Get Item Text + Group: 0 + Message Id: 321 + - Name: Shadow Crystal Get Item Text Group: 0 Message Id: 151 diff --git a/src/dusk/randomizer/randomizer.cmake b/src/dusk/randomizer/randomizer.cmake index 1a6107fd93..5e9e619c80 100644 --- a/src/dusk/randomizer/randomizer.cmake +++ b/src/dusk/randomizer/randomizer.cmake @@ -56,7 +56,7 @@ message(STATUS "randomizer: Fetching APCpp") FetchContent_Declare( APCpp GIT_REPOSITORY https://github.com/CraftyBoss/APCpp.git - GIT_TAG 3557bbb + GIT_TAG 2d92f75 ) FetchContent_MakeAvailable(yaml-cpp base64pp battery-embed APCpp)