Merge pull request #74 from jeromkiller/AddArchipelagoClientLib

Merge in jerom's progress
This commit is contained in:
aMannus
2025-07-25 11:59:56 +02:00
committed by GitHub
18 changed files with 415 additions and 100 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -466,6 +466,10 @@ static const ALIGN_ASSET(2) char gShipLogoDL[] = dgShipLogoDL;
#define dnintendo_rogo_static_Tex_LUS_000000 "__OTR__textures/nintendo_rogo_static/nintendo_rogo_static_Tex_LUS_000000"
static const ALIGN_ASSET(2) char nintendo_rogo_static_Tex_LUS_000000[] = dnintendo_rogo_static_Tex_LUS_000000;
// Archipelago Item Icons
#define dgArchipelagoItemTex "__OTR__textures/parameter_static/gArchipelagoOutline"
static const ALIGN_ASSET(2) char gArchipelagoItemTex[] = dgArchipelagoItemTex;
// Archipelago Item Models
#define dgArchipelagoItemDL "__OTR__objects/object_archipelago_item/gArchipelagoItemDL"
static const ALIGN_ASSET(2) char gArchipelagoItemDL[] = dgArchipelagoItemDL;

View File

@@ -1069,6 +1069,7 @@ void Interface_LoadItemIcon1(PlayState* play, u16 button);
void Interface_LoadItemIcon2(PlayState* play, u16 button);
void func_80084BF4(PlayState* play, u16 flag);
uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow);
uint16_t Interface_DrawTextLine_overlay(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow);
u8 Item_Give(PlayState* play, u8 item);
u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry);
u8 Item_CheckObtainability(u8 item);

View File

@@ -178,6 +178,8 @@ typedef struct ShipArchipelagoSaveContextData {
u32 lastReceivedItemIndex;
char roomHash[100];
char slotName[17];
char archiUri[50];
char roomPass[50];
ArchipelagoLocationData locations[RC_MAX];
} ShipArchipelagoSaveContextData;

View File

@@ -111,6 +111,24 @@ std::array<std::string, LANGUAGE_MAX> ArchipelagoSettingsMenuText[ASM_MAX]{
"Todo",
"Todo",
},
//ASM_CHAR_START_TO_CONNECT
{
"Start to automatically connect to this slot",
"Todo",
"Todo",
},
//ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT
{
"Connected to a different slot",
"Todo",
"Todo",
},
// ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO
{
"Z-Connection Settings",
"Z-Todo",
"Z-Todo",
}
};
const char* SohFileSelect_GetRandomizerSettingText(uint8_t optionIndex, uint8_t language) {

View File

@@ -30,6 +30,9 @@ typedef enum {
ASM_CONNECTING,
ASM_CONNECTED,
ASM_STATUS,
ASM_CHAR_START_TO_CONNECT,
ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT,
ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO,
ASM_MAX
} ArchipelagoSettingsMenuEnums;

View File

@@ -17,6 +17,8 @@
#include "soh/Notification/Notification.h"
#include "soh/ShipInit.hpp"
#include "soh/SaveManager.h"
#include "soh/util.h"
#include "soh/SohGui/SohGui.hpp"
extern "C" {
#include "variables.h"
@@ -31,6 +33,8 @@ ArchipelagoClient::ArchipelagoClient() {
itemQueued = false;
disconnecting = false;
isDeathLinkedDeath = false;
uri = "";
password = "";
}
ArchipelagoClient& ArchipelagoClient::GetInstance() {
@@ -39,15 +43,18 @@ ArchipelagoClient& ArchipelagoClient::GetInstance() {
}
bool ArchipelagoClient::StartClient() {
if (apClient != NULL) {
if (apClient != nullptr) {
apClient.reset();
}
disconnecting = false;
retries = 0;
uri = CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "localhost:38281");
password = CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), "");
apClient = std::unique_ptr<APClient>(
new APClient(uuid, AP_Client_consts::AP_GAME_NAME,
CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "localhost:38281"), "cacert.pem"));
uri, "cacert.pem"));
CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 1); // Connecting
@@ -58,6 +65,10 @@ bool ArchipelagoClient::StartClient() {
"server address and slot name correct?");
CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 2); // Connection error
disconnecting = true;
if(GameInteractor::IsSaveLoaded) {
SohGui::ShowArchipelagoSettingsMenu();
}
return;
}
ArchipelagoConsole_SendMessage("[ERROR] Could not connect to server, retrying...");
@@ -69,7 +80,7 @@ bool ArchipelagoClient::StartClient() {
tags.push_back("DeathLink");
}
apClient->ConnectSlot(CVarGetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), ""),
CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), ""), 0b001, tags);
password, 0b001, tags);
});
apClient->set_slot_connected_handler([&](const nlohmann::json data) {
@@ -88,6 +99,14 @@ bool ArchipelagoClient::StartClient() {
return;
}
// save the connection details in case they changed
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, uri,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, GetSlotName(),
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomPass, password,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass));
SynchSentLocations();
SynchReceivedLocations();
}
@@ -146,64 +165,64 @@ bool ArchipelagoClient::StartClient() {
return;
}
std::vector<ColoredTextNode> coloredNodes;
std::vector<AP_Text::ColoredTextNode> coloredNodes;
for (const APClient::TextNode& node : arg.data) {
APClient* client = apClient.get();
std::string color;
AP_Text::TextColor color = AP_Text::TextColor::COLOR_DEFAULT;
std::string text;
if (node.type == "player_id") {
int id = std::stoi(node.text);
if (color.empty() && id == client->get_player_number())
color = "magenta";
else if (color.empty())
color = "yellow";
if (color == AP_Text::TextColor::COLOR_DEFAULT && id == client->get_player_number())
color = AP_Text::TextColor::COLOR_MAGENTA;
else if (color == AP_Text::TextColor::COLOR_DEFAULT)
color = AP_Text::TextColor::COLOR_YELLOW;
text = client->get_player_alias(id);
} else if (node.type == "item_id") {
int64_t id = std::stoll(node.text);
if (color.empty()) {
if (color == AP_Text::TextColor::COLOR_DEFAULT) {
if (node.flags & APClient::ItemFlags::FLAG_ADVANCEMENT)
color = "plum";
color = AP_Text::TextColor::COLOR_PLUM;
else if (node.flags & APClient::ItemFlags::FLAG_NEVER_EXCLUDE)
color = "slateblue";
color = AP_Text::TextColor::COLOR_SLATEBLUE;
else if (node.flags & APClient::ItemFlags::FLAG_TRAP)
color = "salmon";
color = AP_Text::TextColor::COLOR_SALMON;
else
color = "cyan";
color = AP_Text::TextColor::COLOR_CYAN;
}
text = client->get_item_name(id, client->get_player_game(node.player));
} else if (node.type == "location_id") {
int64_t id = std::stoll(node.text);
if (color.empty())
color = "blue";
if (color == AP_Text::TextColor::COLOR_DEFAULT)
color = AP_Text::TextColor::COLOR_BLUE;
text = client->get_location_name(id, client->get_player_game(node.player));
} else if (node.type == "hint_status") {
text = node.text;
if (node.hintStatus == APClient::HINT_FOUND)
color = "green";
color = AP_Text::TextColor::COLOR_GREEN;
else if (node.hintStatus == APClient::HINT_UNSPECIFIED)
color = "grey";
color = AP_Text::TextColor::COLOR_GRAY;
else if (node.hintStatus == APClient::HINT_NO_PRIORITY)
color = "slateblue";
color = AP_Text::TextColor::COLOR_SLATEBLUE;
else if (node.hintStatus == APClient::HINT_AVOID)
color = "salmon";
color = AP_Text::TextColor::COLOR_SALMON;
else if (node.hintStatus == APClient::HINT_PRIORITY)
color = "plum";
color = AP_Text::TextColor::COLOR_PLUM;
else
color = "red"; // unknown status -> red
color = AP_Text::TextColor::COLOR_RED; // unknown status -> red
} else if (node.type == "ERROR") {
color = "ERROR";
color = AP_Text::TextColor::COLOR_ERROR;
text = node.text;
} else if (node.type == "LOG") {
color = "LOG";
color = AP_Text::TextColor::COLOR_LOG;
text = node.text;
} else {
color = "white";
color = AP_Text::TextColor::COLOR_WHITE;
text = node.text;
}
ColoredTextNode Colornode;
AP_Text::ColoredTextNode Colornode;
Colornode.color = color;
Colornode.text = text;
coloredNodes.push_back(Colornode);
@@ -213,6 +232,7 @@ bool ArchipelagoClient::StartClient() {
});
apClient->set_bounced_handler([&](const nlohmann::json data) {
if(data.contains("tags")) {
std::list<std::string> tags = data["tags"];
bool deathLink = (std::find(tags.begin(), tags.end(), "DeathLink") != tags.end());
@@ -228,6 +248,7 @@ bool ArchipelagoClient::StartClient() {
isDeathLinkedDeath = true;
}
}
}
});
return true;
@@ -240,6 +261,12 @@ bool ArchipelagoClient::StopClient() {
void ArchipelagoClient::GameLoaded() {
if (apClient == nullptr) {
if(IS_ARCHIPELAGO) {
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), gSaveContext.ship.quest.data.archipelago.archiUri);
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), gSaveContext.ship.quest.data.archipelago.slotName);
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("Password"), gSaveContext.ship.quest.data.archipelago.roomPass);
StartClient();
}
return;
}
@@ -251,8 +278,12 @@ void ArchipelagoClient::GameLoaded() {
}
if (!isRightSaveLoaded()) {
ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not associated with connected slot, disconnecting...");
disconnecting = true;
ArchipelagoConsole_SendMessage("Connec");
//disconnecting = true;
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), gSaveContext.ship.quest.data.archipelago.archiUri);
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), gSaveContext.ship.quest.data.archipelago.slotName);
CVarSetString(CVAR_REMOTE_ARCHIPELAGO("Password"), gSaveContext.ship.quest.data.archipelago.roomPass);
StartClient();
return;
}
@@ -372,6 +403,10 @@ void ArchipelagoClient::QueueItem(const ApItem item) {
}
void ArchipelagoClient::SendGameWon() {
if(apClient == nullptr) {
return;
}
if (!gameWon) {
apClient->StatusUpdate(APClient::ClientStatus::GOAL);
gameWon = true;
@@ -418,6 +453,23 @@ void ArchipelagoClient::Poll() {
apClient->poll();
}
bool ArchipelagoClient::slotMatch(const std::string& slotName, const std::string& roomHash) {
if (apClient == nullptr) {
return false;
}
if(disconnecting) {
return false;
}
const std::string seed = apClient->get_seed();
const std::string slot = GetSlotName();
const bool seedMatch = apClient->get_seed().compare(roomHash) == 0;
const bool slotMatch = GetSlotName().compare(slotName) == 0;
return seedMatch && slotMatch;
}
bool ArchipelagoClient::isRightSaveLoaded() const {
const bool seedMatch = apClient->get_seed().compare(gSaveContext.ship.quest.data.archipelago.roomHash) == 0;
const bool slotMatch = GetSlotName().compare(gSaveContext.ship.quest.data.archipelago.slotName) == 0;
@@ -425,7 +477,7 @@ bool ArchipelagoClient::isRightSaveLoaded() const {
}
const std::string ArchipelagoClient::GetSlotName() const {
if (apClient == NULL) {
if (apClient == nullptr) {
return "";
}
@@ -482,7 +534,7 @@ void ArchipelagoClient::OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSk
}
void ArchipelagoClient::SendDeathLink() {
if (apClient && CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0) && !isDeathLinkedDeath) {
if (apClient != nullptr && CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0) && !isDeathLinkedDeath) {
nlohmann::json data{ { "time", apClient->get_server_time() },
{ "cause", "Shipwrecked by King Harkinian." },
{ "source", apClient->get_slot() } };
@@ -518,6 +570,10 @@ extern "C" void Archipelago_InitSaveFile() {
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, client.apClient->get_slot(),
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, client.uri,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomPass, client.password,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass));
for (uint32_t i = 0; i < scoutedItems.size(); i++) {
RandomizerCheck rc = Rando::StaticData::locationNameToEnum[scoutedItems[i].locationName];
@@ -540,6 +596,10 @@ void LoadArchipelagoData() {
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash));
SaveManager::Instance->LoadCharArray("slotName", gSaveContext.ship.quest.data.archipelago.slotName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SaveManager::Instance->LoadCharArray("archiUri", gSaveContext.ship.quest.data.archipelago.archiUri,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri));
SaveManager::Instance->LoadCharArray("roomPass", gSaveContext.ship.quest.data.archipelago.roomPass,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass));
SaveManager::Instance->LoadArray(
"locations", ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations), [](size_t i) {
@@ -561,6 +621,8 @@ void SaveArchipelagoData(SaveContext* saveContext, int sectionID, bool fullSave)
SaveManager::Instance->SaveData("roomHash", saveContext->ship.quest.data.archipelago.roomHash);
SaveManager::Instance->SaveData("slotName", saveContext->ship.quest.data.archipelago.slotName);
SaveManager::Instance->SaveData("archiUri", saveContext->ship.quest.data.archipelago.archiUri);
SaveManager::Instance->SaveData("roomPass", gSaveContext.ship.quest.data.archipelago.roomPass);
SaveManager::Instance->SaveArray(
"locations", ARRAY_COUNT(saveContext->ship.quest.data.archipelago.locations), [&](size_t i) {
@@ -581,6 +643,10 @@ void InitArchipelagoData(bool isDebug) {
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass));
for (uint32_t i = 0; i < ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations); i++) {
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].itemName, "",

View File

@@ -5,6 +5,7 @@
#include <vector>
#include <nlohmann/json.hpp>
#include <queue>
#include "ArchipelagoTypes.h"
// Forward declaration
class APClient;
@@ -28,11 +29,6 @@ class ArchipelagoClient {
uint64_t index;
};
struct ColoredTextNode {
std::string text;
std::string color;
};
static ArchipelagoClient& GetInstance();
bool StartClient();
@@ -65,11 +61,15 @@ class ArchipelagoClient {
void SendMessageToConsole(const std::string message);
void Poll();
bool slotMatch(const std::string& slotName, const std::string& roomHash);
std::unique_ptr<APClient> apClient;
bool itemQueued;
bool disconnecting;
bool isDeathLinkedDeath;
int retries;
std::string uri;
std::string password;
protected:
ArchipelagoClient();

View File

@@ -3,8 +3,9 @@
#include "soh/SohGui/UIWidgets.hpp"
#include "soh/SohGui/SohGui.hpp"
#include "soh/OTRGlobals.h"
#include "ArchipelagoTypes.h"
std::vector<std::vector<ArchipelagoClient::ColoredTextNode>> Items;
std::vector<std::vector<AP_Text::ColoredTextNode>> Items;
bool autoScroll = true;
using namespace UIWidgets;
@@ -16,15 +17,15 @@ void ArchipelagoConsole_SendMessage(const char* fmt, ...) {
vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args);
buf[IM_ARRAYSIZE(buf) - 1] = 0;
va_end(args);
ArchipelagoClient::ColoredTextNode node;
AP_Text::ColoredTextNode node;
node.text = std::string(buf);
node.color = "white";
node.color = AP_Text::TextColor::COLOR_WHITE;
if (strstr(buf, "[ERROR]")) {
node.color = "ERROR";
node.color = AP_Text::TextColor::COLOR_ERROR;
} else if (strstr(buf, "[LOG]")) {
node.color = "LOG";
node.color = AP_Text::TextColor::COLOR_LOG;
}
std::vector<ArchipelagoClient::ColoredTextNode> line;
std::vector<AP_Text::ColoredTextNode> line;
line.push_back(node);
Items.push_back(line);
if (Items.size() > 50) {
@@ -32,7 +33,7 @@ void ArchipelagoConsole_SendMessage(const char* fmt, ...) {
}
}
void ArchipelagoConsole_PrintJson(const std::vector<ArchipelagoClient::ColoredTextNode> nodes) {
void ArchipelagoConsole_PrintJson(const std::vector<AP_Text::ColoredTextNode> nodes) {
Items.push_back(nodes);
if (Items.size() > 50) {
Items.erase(Items.begin());
@@ -50,8 +51,8 @@ void ArchipelagoConsoleWindow::DrawElement() {
if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 400), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (const std::vector<ArchipelagoClient::ColoredTextNode>& line : Items) {
for (const ArchipelagoClient::ColoredTextNode& node : line) {
for (const std::vector<AP_Text::ColoredTextNode>& line : Items) {
for (const AP_Text::ColoredTextNode& node : line) {
ImGui::PushStyleColor(ImGuiCol_Text, getColorVal(node.color));
ImGui::TextUnformatted(node.text.c_str());
ImGui::SameLine();
@@ -99,35 +100,41 @@ void ArchipelagoConsoleWindow::DrawElement() {
ImGui::PopStyleVar(4);
};
ImVec4 getColorVal(const std::string& color) { // TODO change color strings to an enum
if (color == "ERROR") {
ImVec4 ArchipelagoConsoleWindow::getColorVal(const AP_Text::TextColor color) {
using apt = AP_Text::TextColor;
switch(color) {
case apt::COLOR_ERROR:
return ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
} else if (color == "LOG") {
case apt::COLOR_LOG:
return ImVec4(0.7f, 0.7f, 1.0f, 1.0f);
} else if (color == "black") {
case apt::COLOR_BLACK:
return ImVec4(0.000f, 0.000f, 0.000f, 1.00f);
} else if (color == "red") {
case apt::COLOR_RED:
return ImVec4(0.933f, 0.000f, 0.000f, 1.00f);
} else if (color == "green") {
case apt::COLOR_GREEN:
return ImVec4(0.000f, 1.000f, 0.498f, 1.00f);
} else if (color == "yellow") {
case apt::COLOR_YELLOW:
return ImVec4(0.980f, 0.980f, 0.824f, 1.00f);
} else if (color == "blue") {
case apt::COLOR_BLUE:
return ImVec4(0.392f, 0.584f, 0.929f, 1.00f);
} else if (color == "cyan") {
case apt::COLOR_CYAN:
return ImVec4(0.000f, 0.933f, 0.933f, 1.00f);
} else if (color == "magenta") {
case apt::COLOR_MAGENTA:
return ImVec4(0.933f, 0.000f, 0.933f, 1.00f);
} else if (color == "slateblue") {
case apt::COLOR_SLATEBLUE:
return ImVec4(0.427f, 0.545f, 0.910f, 1.00f);
} else if (color == "plum") {
case apt::COLOR_PLUM:
return ImVec4(0.686f, 0.600f, 0.937f, 1.00f);
} else if (color == "salmon") {
case apt::COLOR_SALMON:
return ImVec4(0.980f, 0.502f, 0.447f, 1.00f);
} else if (color == "white") {
return ImVec4(0.93f, 0.93f, 0.93f, 1.00f);
} else if (color == "orange") {
case apt::COLOR_ORANGE:
return ImVec4(1.000, 0.467f, 0.000f, 1.000f);
}
case apt::COLOR_GRAY:
return ImVec4(0.53f, 0.53f, 0.53f, 1.00f);
case apt::COLOR_WHITE:
case apt::COLOR_DEFAULT:
default:
return ImVec4(0.93f, 0.93f, 0.93f, 1.00f);
};
}

View File

@@ -3,15 +3,18 @@
#define ARCHIPELAGO_CONSOLE_WINDOW_H
#include <libultraship/libultraship.h>
#include "Archipelago.h"
#include <vector>
#include <list>
#include "window/gui/GuiWindow.h"
#include "ArchipelagoTypes.h"
class ArchipelagoConsoleWindow final : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
~ArchipelagoConsoleWindow(){};
static ImVec4 getColorVal(const AP_Text::TextColor color);
protected:
void InitElement() override{};
void DrawElement() override;
@@ -19,7 +22,6 @@ class ArchipelagoConsoleWindow final : public Ship::GuiWindow {
};
void ArchipelagoConsole_SendMessage(const char* fmt, ...);
void ArchipelagoConsole_PrintJson(const std::vector<ArchipelagoClient::ColoredTextNode> nodes);
ImVec4 getColorVal(const std::string& color);
void ArchipelagoConsole_PrintJson(const std::vector<AP_Text::ColoredTextNode> nodes);
#endif // ARCHIPELAGO_CONSOLE_WINDOW_H

View File

@@ -3,6 +3,7 @@
#define ARCHIPELAGO_SETTINGS_WINDOW_H
#include <libultraship/libultraship.h>
#include "window/gui/GuiWindow.h"
class ArchipelagoSettingsWindow final : public Ship::GuiWindow {
public:

View File

@@ -0,0 +1,30 @@
#pragma once
#ifdef __cplusplus
namespace AP_Text {
enum class TextColor : char {
COLOR_DEFAULT = 0,
COLOR_ERROR,
COLOR_LOG,
COLOR_BLACK,
COLOR_RED,
COLOR_GREEN,
COLOR_YELLOW,
COLOR_BLUE,
COLOR_CYAN,
COLOR_MAGENTA,
COLOR_SLATEBLUE,
COLOR_PLUM,
COLOR_SALMON,
COLOR_WHITE,
COLOR_ORANGE,
COLOR_GRAY
};
struct ColoredTextNode {
std::string text;
AP_Text::TextColor color;
};
}
#endif

View File

@@ -2728,6 +2728,12 @@ extern "C" void ParseArchipelago() {
OTRGlobals::Instance->gRandoContext->ParseArchipelago();
}
extern "C" bool checkArchipelagoSlotInfo(const char* slotName, const char* roomHash) {
const std::string slot = std::string(slotName);
const std::string room = std::string(roomHash);
return ArchipelagoClient::GetInstance().slotMatch(slot, room);
}
extern "C" void CheckTracker_RecalculateAvailableChecks() {
CheckTracker::RecalculateAvailableChecks();
}

View File

@@ -173,6 +173,7 @@ void CheckTracker_RecalculateAvailableChecks();
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);
void ParseArchipelago();
bool checkArchipelagoSlotInfo(const char* slotName, const char* roomHash);
void Messagebox_ShowErrorBox(char* title, char* body);
#endif

View File

@@ -154,6 +154,9 @@ SaveManager::SaveManager() {
info.buildVersionMinor = 0;
info.buildVersionPatch = 0;
memset(&info.buildVersion, 0, sizeof(info.buildVersion));
memset(&info.archiUri, 0, sizeof(info.archiUri));
memset(&info.slotName, 0, sizeof(info.slotName));
}
}
@@ -509,6 +512,13 @@ void SaveManager::InitMeta(int fileNum) {
fileMetaInfo[fileNum].buildVersionPatch = gSaveContext.ship.stats.buildVersionPatch;
SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, gSaveContext.ship.stats.buildVersion,
ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion));
SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].archiUri, gSaveContext.ship.quest.data.archipelago.archiUri,
ARRAY_COUNT(fileMetaInfo[fileNum].archiUri));
SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].slotName, gSaveContext.ship.quest.data.archipelago.slotName,
ARRAY_COUNT(fileMetaInfo[fileNum].slotName));
SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].archiRoomSeed, gSaveContext.ship.quest.data.archipelago.roomHash,
ARRAY_COUNT(fileMetaInfo[fileNum].archiRoomSeed));
}
void SaveManager::InitFile(bool isDebug) {

View File

@@ -33,6 +33,10 @@ typedef struct {
s32 filenameLanguage;
s32 gregFound;
s32 hasWallet;
char archiRoomSeed[100];
char slotName[17];
char archiUri[50];
} SaveFileMetaInfo;
typedef enum {

View File

@@ -12,6 +12,7 @@
#include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
#include "soh/Enhancements/enhancementTypes.h"
#include "soh/Enhancements/FileSelectEnhancements.h"
#include "soh/ShipUtils.h"
#include <string.h>
@@ -3424,6 +3425,45 @@ void Interface_DrawLineupTick(PlayState* play) {
CLOSE_DISPS(play->state.gfxCtx);
}
void Interface_DrawArchipelagoStatusString(PlayState* play) {
s16 posX = OTRGetRectDimensionFromLeftEdge(28);
s16 posY = SCREEN_HEIGHT - 15;
int32_t scale = R_TEXT_CHAR_SCALE * 0.2f;
int32_t sTexSize = (scale / 100.0f) * 64.0f;
int32_t sTexScale = 1024.0f / (scale / 100.0f);
gDPLoadTextureBlock(play->state.gfxCtx->overlay.p++, gArchipelagoItemTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 64, 64, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, G_TX_NOLOD, G_TX_NOLOD);
gSPWideTextureRectangle(play->state.gfxCtx->overlay.p++, posX << 2, posY << 2, (posX + sTexSize) << 2,
(posY + sTexSize) << 2, G_TX_RENDERTILE, 0, 0, sTexScale, sTexScale);
//SPWideTextureRectangle(OVERLAY_DISP++, ((rMagicBarX + gSaveContext.magicCapacity) + 8) << 2, magicBarY << 2,
// ((rMagicBarX + gSaveContext.magicCapacity) + 16) << 2, (magicBarY + 16) << 2,
// G_TX_RENDERTILE, 256, 0, 1 << 10, 1 << 10);
posX += 13;
uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language;
char* statusText = SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language);
switch(CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) {
case 0: // Not Connected
statusText = SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language);
break;
case 1: // Connecting
case 2: // Connection error, retrying
case 3: // Connected
statusText = SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language);
break;
case 4: // Connected + Locations Scouted
statusText = SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language);
break;
}
Interface_DrawTextLine_overlay(play->state.gfxCtx, statusText, posX, posY, 255, 255, 255, 255, 0.8f, true);
}
void Interface_DrawMagicBar(PlayState* play) {
InterfaceContext* interfaceCtx = &play->interfaceCtx;
s16 magicDrop = R_MAGIC_BAR_LARGE_Y - R_MAGIC_BAR_SMALL_Y + 2;
@@ -5397,6 +5437,10 @@ void Interface_Draw(PlayState* play) {
Interface_DrawLineupTick(play);
}
if(IS_ARCHIPELAGO) {
Interface_DrawArchipelagoStatusString(play);
}
if (fullUi || gSaveContext.magicState > MAGIC_STATE_IDLE) {
Interface_DrawMagicBar(play);
}
@@ -6936,6 +6980,41 @@ void Interface_DrawTextCharacter(GraphicsContext* gfx, int16_t x, int16_t y, voi
CLOSE_DISPS(gfx);
}
void Interface_DrawTextCharacter_overlay(GraphicsContext* gfx, int16_t x, int16_t y, void* texture, uint16_t colorR,
uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale,
uint8_t textShadow) {
int32_t scale = R_TEXT_CHAR_SCALE * textScale;
int32_t sCharTexSize = (scale / 100.0f) * 16.0f;
int32_t sCharTexScale = 1024.0f / (scale / 100.0f);
OPEN_DISPS(gfx);
gDPPipeSync(OVERLAY_DISP++);
gDPLoadTextureBlock_4b(OVERLAY_DISP++, texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, FONT_CHAR_TEX_HEIGHT, 0,
G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
if (textShadow) {
// Draw drop shadow
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, colorA);
gSPWideTextureRectangle(OVERLAY_DISP++, (x + R_TEXT_DROP_SHADOW_OFFSET) << 2, (y + R_TEXT_DROP_SHADOW_OFFSET) << 2,
(x + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2,
(y + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2, G_TX_RENDERTILE, 0, 0, sCharTexScale,
sCharTexScale);
}
gDPPipeSync(OVERLAY_DISP++);
// Draw normal text
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, colorR, colorG, colorB, colorA);
gSPWideTextureRectangle(OVERLAY_DISP++, x << 2, y << 2, (x + sCharTexSize) << 2, (y + sCharTexSize) << 2,
G_TX_RENDERTILE, 0, 0, sCharTexScale, sCharTexScale);
CLOSE_DISPS(gfx);
}
uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR,
uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale,
uint8_t textShadow) {
@@ -6966,3 +7045,34 @@ uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, in
return kerningOffset;
}
uint16_t Interface_DrawTextLine_overlay(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR,
uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale,
uint8_t textShadow) {
uint16_t textureIndex;
uint16_t kerningOffset = 0;
uint16_t lineOffset = 0;
void* texture;
const char* processedText = Interface_ReplaceSpecialCharacters(text);
uint8_t textLength = strlen(processedText);
for (uint16_t i = 0; i < textLength; i++) {
if (processedText[i] == '\n') {
lineOffset += 15 * textScale;
kerningOffset = 0;
} else {
textureIndex = processedText[i] - 32;
if (textureIndex != 0) {
texture = Ship_GetCharFontTexture(processedText[i]);
Interface_DrawTextCharacter_overlay(gfx, (int16_t)(x + kerningOffset), (int16_t)(y + lineOffset), texture, colorR, colorG, colorB,
colorA, textScale, textShadow);
}
kerningOffset +=
(uint16_t)(Ship_GetCharFontWidth(processedText[i]) * (R_TEXT_CHAR_SCALE / 100.0f) * textScale);
}
}
return kerningOffset;
}

View File

@@ -2478,7 +2478,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) {
&deathCountSplit[2]);
// draw death count
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT) {
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT ||
Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) {
for (i = 0, vtxOffset = 0; i < 3; i++, vtxOffset += 4) {
FileChoose_DrawCharacter(this->state.gfxCtx, sp54->fontBuf + deathCountSplit[i] * FONT_CHAR_TEX_SIZE,
vtxOffset);
@@ -2504,7 +2505,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) {
i = Save_GetSaveMetaInfo(fileIndex)->healthCapacity / 0x10;
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT) {
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT ||
Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) {
// draw hearts
for (vtxOffset = 0, j = 0; j < i; j++, vtxOffset += 4) {
gSPVertex(POLY_OPA_DISP++, &this->windowContentVtx[D_8081284C[fileIndex] + vtxOffset] + 0x30, 4, 0);
@@ -2521,7 +2523,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) {
textAlpha = 255;
}
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 && this->menuMode == FS_MENU_MODE_SELECT) {
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 && this->menuMode == FS_MENU_MODE_SELECT &&
Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave == 0) {
DrawMoreInfo(this, fileIndex, textAlpha);
} else {
// draw quest items
@@ -2545,6 +2548,53 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) {
}
}
}
if(Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) {
uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language;
// Connection status text
int statusPos = 61 + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(ASM_STATUS, language),
58, 133, 200, 200, 200, textAlpha, 0.8f, true);
const bool connectedToThisSlot = checkArchipelagoSlotInfo(Save_GetSaveMetaInfo(this->selectedFileIndex)->slotName,
Save_GetSaveMetaInfo(this->selectedFileIndex)->archiRoomSeed);
switch(CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) {
case 0: // Not Connected
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language),
statusPos, 133, 255, 120, 120, textAlpha, 0.8f, true);
break;
case 1: // Connecting
case 2: // Connection error, retrying
case 3: // Connected
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language),
statusPos, 133, 185, 185, 185, textAlpha, 0.8f, true);
break;
case 4: // Connected + Locations Scouted
if(connectedToThisSlot) {
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language),
statusPos, 133, 120, 255, 120, textAlpha, 0.8f, true);
} else {
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT, language),
statusPos, 133, 255, 255, 120, textAlpha, 0.8f, true);
}
break;
}
if(!connectedToThisSlot) {
Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_START_TO_CONNECT, language),
58, 144, 200, 200, 200, textAlpha, 0.8f, true);
}
//Interface_DrawTextLine(this->state.gfxCtx,
// SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO, language), 95, 220,
// 100, 250, 255, textAlpha, 1.0f, true);
}
}
CLOSE_DISPS(this->state.gfxCtx);
@@ -2944,25 +2994,25 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
155, 185, 185, 185, textAlpha, 0.8f, true);
// Connection status text
Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(ASM_STATUS, language), 70,
int statusPos = 75 + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(ASM_STATUS, language), 70,
175, 255, 255, 255, textAlpha, 0.8f, true);
switch (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) {
case 0: // Not Connected
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language), 110, 175,
SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language), statusPos, 175,
255, 120, 120, textAlpha, 0.8f, true);
break;
case 1: // Connecting
case 2: // Connection error, retrying
case 3: // Connected
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language), 110, 175, 185,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language), statusPos, 175, 185,
185, 185, textAlpha, 0.8f, true);
break;
case 4: // Connected + Locations Scouted
Interface_DrawTextLine(this->state.gfxCtx,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language), 110, 175, 120,
SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language), statusPos, 175, 120,
255, 120, textAlpha, 0.8f, true);
break;
}
@@ -3005,7 +3055,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
// Draw the small file name box instead when more meta info is enabled
if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 &&
this->menuMode == FS_MENU_MODE_SELECT) {
this->menuMode == FS_MENU_MODE_SELECT && Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave == 0) {
// Location of file 1 small name box vertices
gSPVertex(POLY_OPA_DISP++, &this->windowContentVtx[68], 4, 0);