diff --git a/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png b/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png new file mode 100644 index 000000000..ff2bddbcc Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png differ diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index 26799f8e7..aa50cb4a3 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -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; diff --git a/soh/include/functions.h b/soh/include/functions.h index 8deb36953..826c336e6 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -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); diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 6a0e0b720..15e068fb4 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -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; diff --git a/soh/soh/Enhancements/FileSelectEnhancements.cpp b/soh/soh/Enhancements/FileSelectEnhancements.cpp index 4d8208733..b75e7d81c 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.cpp +++ b/soh/soh/Enhancements/FileSelectEnhancements.cpp @@ -111,6 +111,24 @@ std::array 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) { diff --git a/soh/soh/Enhancements/FileSelectEnhancements.h b/soh/soh/Enhancements/FileSelectEnhancements.h index 680c7be1e..b76f728e7 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.h +++ b/soh/soh/Enhancements/FileSelectEnhancements.h @@ -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; diff --git a/soh/soh/Network/Archipelago/Archipelago.cpp b/soh/soh/Network/Archipelago/Archipelago.cpp index 4421a90ba..067973f4f 100644 --- a/soh/soh/Network/Archipelago/Archipelago.cpp +++ b/soh/soh/Network/Archipelago/Archipelago.cpp @@ -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( 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 coloredNodes; + std::vector 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,19 +232,21 @@ bool ArchipelagoClient::StartClient() { }); apClient->set_bounced_handler([&](const nlohmann::json data) { - std::list tags = data["tags"]; - bool deathLink = (std::find(tags.begin(), tags.end(), "DeathLink") != tags.end()); - - if (deathLink && data["data"]["source"] != apClient->get_slot()) { - if (GameInteractor::IsSaveLoaded()) { - gSaveContext.health = 0; - std::string prefixText = std::string(data["data"]["source"]) + " died."; - Notification::Emit({ .prefix = prefixText, .message = "Cause:", .suffix = data["data"]["cause"] }); - std::string deathLinkMessage = "[LOG] Received death link from " + std::string(data["data"]["source"]) + - ". Cause: " + std::string(data["data"]["cause"]); - ArchipelagoConsole_SendMessage(deathLinkMessage.c_str()); - - isDeathLinkedDeath = true; + if(data.contains("tags")) { + std::list tags = data["tags"]; + bool deathLink = (std::find(tags.begin(), tags.end(), "DeathLink") != tags.end()); + + if (deathLink && data["data"]["source"] != apClient->get_slot()) { + if (GameInteractor::IsSaveLoaded()) { + gSaveContext.health = 0; + std::string prefixText = std::string(data["data"]["source"]) + " died."; + Notification::Emit({ .prefix = prefixText, .message = "Cause:", .suffix = data["data"]["cause"] }); + std::string deathLinkMessage = "[LOG] Received death link from " + std::string(data["data"]["source"]) + + ". Cause: " + std::string(data["data"]["cause"]); + ArchipelagoConsole_SendMessage(deathLinkMessage.c_str()); + + isDeathLinkedDeath = 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, "", diff --git a/soh/soh/Network/Archipelago/Archipelago.h b/soh/soh/Network/Archipelago/Archipelago.h index ed77fb5ee..00973536f 100644 --- a/soh/soh/Network/Archipelago/Archipelago.h +++ b/soh/soh/Network/Archipelago/Archipelago.h @@ -5,6 +5,7 @@ #include #include #include +#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; bool itemQueued; bool disconnecting; bool isDeathLinkedDeath; int retries; + std::string uri; + std::string password; protected: ArchipelagoClient(); diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp index 4ee7b1e06..f5b687169 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp @@ -3,8 +3,9 @@ #include "soh/SohGui/UIWidgets.hpp" #include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" +#include "ArchipelagoTypes.h" -std::vector> Items; +std::vector> 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 line; + std::vector 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 nodes) { +void ArchipelagoConsole_PrintJson(const std::vector 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& line : Items) { - for (const ArchipelagoClient::ColoredTextNode& node : line) { + for (const std::vector& 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") { - return ImVec4(1.0f, 0.4f, 0.4f, 1.0f); - } else if (color == "LOG") { - return ImVec4(0.7f, 0.7f, 1.0f, 1.0f); - } else if (color == "black") { - return ImVec4(0.000f, 0.000f, 0.000f, 1.00f); - } else if (color == "red") { - return ImVec4(0.933f, 0.000f, 0.000f, 1.00f); - } else if (color == "green") { - return ImVec4(0.000f, 1.000f, 0.498f, 1.00f); - } else if (color == "yellow") { - return ImVec4(0.980f, 0.980f, 0.824f, 1.00f); - } else if (color == "blue") { - return ImVec4(0.392f, 0.584f, 0.929f, 1.00f); - } else if (color == "cyan") { - return ImVec4(0.000f, 0.933f, 0.933f, 1.00f); - } else if (color == "magenta") { - return ImVec4(0.933f, 0.000f, 0.933f, 1.00f); - } else if (color == "slateblue") { - return ImVec4(0.427f, 0.545f, 0.910f, 1.00f); - } else if (color == "plum") { - return ImVec4(0.686f, 0.600f, 0.937f, 1.00f); - } else if (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") { - return ImVec4(1.000, 0.467f, 0.000f, 1.000f); - } - return ImVec4(0.93f, 0.93f, 0.93f, 1.00f); -} \ No newline at end of file +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); + case apt::COLOR_LOG: + return ImVec4(0.7f, 0.7f, 1.0f, 1.0f); + case apt::COLOR_BLACK: + return ImVec4(0.000f, 0.000f, 0.000f, 1.00f); + case apt::COLOR_RED: + return ImVec4(0.933f, 0.000f, 0.000f, 1.00f); + case apt::COLOR_GREEN: + return ImVec4(0.000f, 1.000f, 0.498f, 1.00f); + case apt::COLOR_YELLOW: + return ImVec4(0.980f, 0.980f, 0.824f, 1.00f); + case apt::COLOR_BLUE: + return ImVec4(0.392f, 0.584f, 0.929f, 1.00f); + case apt::COLOR_CYAN: + return ImVec4(0.000f, 0.933f, 0.933f, 1.00f); + case apt::COLOR_MAGENTA: + return ImVec4(0.933f, 0.000f, 0.933f, 1.00f); + case apt::COLOR_SLATEBLUE: + return ImVec4(0.427f, 0.545f, 0.910f, 1.00f); + case apt::COLOR_PLUM: + return ImVec4(0.686f, 0.600f, 0.937f, 1.00f); + case apt::COLOR_SALMON: + return ImVec4(0.980f, 0.502f, 0.447f, 1.00f); + 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); + }; +} + \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h index cd782ce86..e0631fcb6 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h @@ -3,15 +3,18 @@ #define ARCHIPELAGO_CONSOLE_WINDOW_H #include -#include "Archipelago.h" #include #include +#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 nodes); -ImVec4 getColorVal(const std::string& color); +void ArchipelagoConsole_PrintJson(const std::vector nodes); #endif // ARCHIPELAGO_CONSOLE_WINDOW_H \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h index f7c530314..2a393c081 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h +++ b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h @@ -3,6 +3,7 @@ #define ARCHIPELAGO_SETTINGS_WINDOW_H #include +#include "window/gui/GuiWindow.h" class ArchipelagoSettingsWindow final : public Ship::GuiWindow { public: diff --git a/soh/soh/Network/Archipelago/ArchipelagoTypes.h b/soh/soh/Network/Archipelago/ArchipelagoTypes.h new file mode 100644 index 000000000..e2da978b1 --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoTypes.h @@ -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 \ No newline at end of file diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 53b65a848..76e9afeee 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -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(); } diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index e3d4e9048..74029ce59 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -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 diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 370d6fdf9..4ec72f0d5 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -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) { diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 8cdb16a91..7baad1dac 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -33,6 +33,10 @@ typedef struct { s32 filenameLanguage; s32 gregFound; s32 hasWallet; + + char archiRoomSeed[100]; + char slotName[17]; + char archiUri[50]; } SaveFileMetaInfo; typedef enum { diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 0fc923070..3459d7d80 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -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 @@ -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; +} diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 0e323d565..6ace3962e 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -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);