mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-24 15:43:13 -04:00
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
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
#include "d/actor/d_a_npc_tkc.h"
|
||||
#include <cstring>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::vector<AP_Network
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: atm this is a sort of lazy solution to not having direct access to what location was checked when an execItemGet is called
|
||||
// so eventually finding a way to properly associate locations with their respective item get funcs would benefit this system
|
||||
void ArchipelagoContext::UpdateCheckedLocations() {
|
||||
auto& world = instance().m_archiWorld;
|
||||
|
||||
@@ -556,7 +617,7 @@ void ArchipelagoContext::UpdateCheckedLocations() {
|
||||
auto locName = location->GetName();
|
||||
|
||||
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<int64_t> 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 {
|
||||
|
||||
@@ -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<std::string, GameLocationInfo> m_locationItemInfo;
|
||||
std::map<int, bool> m_initLocationCollectState;
|
||||
AP_RoomInfo m_roomInfo;
|
||||
std::string m_SettingsFile;
|
||||
bool m_isNeedPlayerDeath = false;
|
||||
bool m_isFromDeathLink = false;
|
||||
|
||||
// TEMP
|
||||
std::map<int, TEMP_GameItemInfo> 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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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: |-
|
||||
<fast>You got an <red>Archipelago Item<white>!
|
||||
|
||||
Foolish Get Item Text:
|
||||
Standard:
|
||||
Text: |-
|
||||
|
||||
@@ -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: |-
|
||||
<fast>You got an <red>Archipelago Item<white>!
|
||||
|
||||
Foolish Get Item Text:
|
||||
Standard:
|
||||
Text: |-
|
||||
|
||||
@@ -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: |-
|
||||
<fast>You got an <red>Archipelago Item<white>!
|
||||
|
||||
Foolish Get Item Text:
|
||||
Standard:
|
||||
Text: |-
|
||||
|
||||
@@ -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: |-
|
||||
<fast>You got an <red>Archipelago Item<white>!
|
||||
|
||||
Foolish Get Item Text:
|
||||
Standard:
|
||||
Text: |-
|
||||
|
||||
@@ -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: |-
|
||||
<fast>You got an <red>Archipelago Item<white>!
|
||||
|
||||
Foolish Get Item Text:
|
||||
Standard:
|
||||
Text: |-
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user