Merge pull request #70 from jeromkiller/AddArchipelagoClientLib

Add archipelago client lib
This commit is contained in:
aMannus
2025-05-30 18:59:33 +02:00
committed by GitHub
13 changed files with 264 additions and 108 deletions

View File

@@ -12,7 +12,7 @@ You can currently connect to the multiworld server, scout the items.
If you have no randomizer genereted (the `Randomizer` folder is empty) `Connect` to the multiworld from the main screen, click the `Scout` button to load all of the item locations and finally press the `Link up` button, you'll be able to start a randomizer save file.
with the items populated with the location from the server.
Sending and Recieving should be implemented, Saving the game doesn't save the current item index localy (the library is a bit anoying with that).
Sending and Receiving should be implemented, Saving the game doesn't save the current item index localy (the library is a bit anoying with that).
Not all checks have been mapped, and some may be mapped incorrectly.
The victory condition should be implemented but has largely gone untested.

View File

@@ -71,5 +71,5 @@ DEFINE_HOOK(OnAssetAltChange, ());
DEFINE_HOOK(OnKaleidoUpdate, ());
DEFINE_HOOK(OnRandomizerItemGivenHooks, (uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped));
DEFINE_HOOK(OnArchipelagoItemRecieved, (uint32_t rg));
DEFINE_HOOK(OnArchipelagoItemReceived, (uint32_t rg));
DEFINE_HOOK(OnRandomizerExternalCheck, (uint32_t rc));

View File

@@ -309,8 +309,8 @@ void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry
}
// MARK: Archipelago
void GameInteractor_ExecuteOnArchipelagoItemRecieved(uint32_t rg) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnArchipelagoItemRecieved>(rg);
void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnArchipelagoItemReceived>(rg);
}
void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc) {

View File

@@ -85,7 +85,7 @@ void GameInteractor_ExecuteOnKaleidoUpdate();
void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped);
// Mark: - Archipelago
void GameInteractor_ExecuteOnArchipelagoItemRecieved(uint32_t rg);
void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg);
void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc);
#ifdef __cplusplus

View File

@@ -346,27 +346,27 @@ void Context::SetSpoilerLoaded(const bool spoilerLoaded) {
mSpoilerLoaded = spoilerLoaded;
}
void Context::AddRecievedArchipelagoItem(const RandomizerGet item) {
mAPrecieveQueue.emplace(item);
void Context::AddReceivedArchipelagoItem(const RandomizerGet item) {
mAPreceiveQueue.emplace(item);
std::string logMessage = "[LOG] Item Pushed: " + item;
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
}
GetItemEntry Context::GetArchipelagoGIEntry() {
ArchipelagoConsole_SendMessage("[LOG] Trying to get Item Entry", true);
if(mAPrecieveQueue.empty()) {
if(mAPreceiveQueue.empty()) {
// something must have gone wrong here, just give a rupee
return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, GI_HEART);
}
// get the first item from the archipelago queue
RandomizerGet item_id = mAPrecieveQueue.front();
RandomizerGet item_id = mAPreceiveQueue.front();
assert(item_id != RG_NONE);
Item& item = StaticData::RetrieveItem(item_id);
SPDLOG_TRACE("Found item! {}, {}", item.GetName().GetEnglish(), (int)item_id);
GetItemEntry item_entry = item.GetGIEntry_Copy();
mAPrecieveQueue.pop();
mAPreceiveQueue.pop();
return item_entry; // todo: add custom text maybe?
}

View File

@@ -107,7 +107,7 @@ class Context {
*/
RandoOptionLACSCondition LACSCondition() const;
GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE);
void AddRecievedArchipelagoItem(const RandomizerGet item);
void AddReceivedArchipelagoItem(const RandomizerGet item);
GetItemEntry GetArchipelagoGIEntry();
void ParseSpoiler(const char* spoilerFileName);
void ParseHashIconIndexesJson(nlohmann::json spoilerFileJson);
@@ -189,6 +189,6 @@ class Context {
std::string mHash;
std::string mSeedString;
uint32_t mFinalSeed = 0;
std::queue<RandomizerGet> mAPrecieveQueue = {};
std::queue<RandomizerGet> mAPreceiveQueue = {};
};
} // namespace Rando

View File

@@ -222,11 +222,11 @@ static std::queue<RandomizerCheck> randomizerQueuedChecks;
static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK;
static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE;
void ArchipelagoOnRecieveItem(const int32_t item) {
void ArchipelagoOnReceiveItem(const int32_t item) {
std::string logMessage = "[LOG] Receive item handler called: " + item;
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECIEVED_ITEM);
Rando::Context::GetInstance()->AddRecievedArchipelagoItem(static_cast<RandomizerGet>(item));
randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECEIVED_ITEM);
Rando::Context::GetInstance()->AddReceivedArchipelagoItem(static_cast<RandomizerGet>(item));
}
void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) {
@@ -313,7 +313,7 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() {
auto loc = Rando::Context::GetInstance()->GetItemLocation(rc);
uint8_t isGiSkipped = 0;
if (rc == RC_ARCHIPELAGO_RECIEVED_ITEM) {
if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) {
getItemEntry = Rando::Context::GetInstance()->GetArchipelagoGIEntry();
} else {
RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem();
@@ -2540,7 +2540,7 @@ void RandomizerRegisterHooks() {
onCuccoOrChickenHatchHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnCuccoOrChickenHatch>(
RandomizerOnCuccoOrChickenHatch);
COND_HOOK(GameInteractor::OnArchipelagoItemRecieved, IS_ARCHIPELAGO, ArchipelagoOnRecieveItem);
COND_HOOK(GameInteractor::OnArchipelagoItemReceived, IS_ARCHIPELAGO, ArchipelagoOnReceiveItem);
COND_HOOK(GameInteractor::OnRandomizerExternalCheck, IS_ARCHIPELAGO, RandomizerOnExternalCheckHandler)
if (RAND_GET_OPTION(RSK_FISHSANITY) != RO_FISHSANITY_OFF) {

View File

@@ -133,7 +133,7 @@ bool ItemLocation::HasObtained() const {
}
void ItemLocation::SetCheckStatus(RandomizerCheckStatus status_) {
if(rc == RC_ARCHIPELAGO_RECIEVED_ITEM) // never count the AP recieve trigger as 'collected'
if(rc == RC_ARCHIPELAGO_RECEIVED_ITEM) // never count the AP receive trigger as 'collected'
return;
status = status_;
}

View File

@@ -3472,7 +3472,7 @@ typedef enum {
RC_DEKU_TREE_QUEEN_GOHMA_GRASS_8,
// End Grass
RC_ARCHIPELAGO_RECIEVED_ITEM,
RC_ARCHIPELAGO_RECEIVED_ITEM,
RC_MAX
} RandomizerCheck;

View File

@@ -29,10 +29,7 @@ ArchipelagoClient::ArchipelagoClient() {
gameWon = false;
itemQueued = false;
// call poll every frame
COND_HOOK(GameInteractor::OnGameFrameUpdate, true, [](){ArchipelagoClient::GetInstance().Poll();});
COND_HOOK(GameInteractor::OnLoadGame, true, [](int32_t file_id){ArchipelagoClient::GetInstance().GameLoaded();});
disconnecting = false;
}
ArchipelagoClient& ArchipelagoClient::GetInstance() {
@@ -45,6 +42,7 @@ bool ArchipelagoClient::StartClient() {
apClient.reset();
}
disconnecting = false;
apClient = std::unique_ptr<APClient>(
new APClient(uuid, AP_Client_consts::AP_GAME_NAME,
CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "localhost:38281"), "cacert.pem"));
@@ -66,12 +64,22 @@ bool ArchipelagoClient::StartClient() {
// if we are already in game when we connect
// we won't have to request an itemSynch
if(GameInteractor::IsSaveLoaded(true)) {
if(!isRightSaveLoaded()) {
disconnecting = true;
ArchipelagoConsole_SendMessage("[ERROR] Connected to incorrect slot, disconnecting...");
return;
}
SynchSentLocations();
SynchRecievedLocations();
SynchReceivedLocations();
}
});
apClient->set_items_received_handler([&](const std::list<APClient::NetworkItem>& items) {
if(disconnecting) {
return;
}
for(const APClient::NetworkItem& item : items) {
ApItem apItem;
const std::string game = apClient->get_player_game(item.player);
@@ -85,6 +93,10 @@ bool ArchipelagoClient::StartClient() {
});
apClient->set_location_info_handler([&](const std::list<APClient::NetworkItem>& items) {
if(disconnecting) {
return;
}
scoutedItems.clear();
for(const APClient::NetworkItem& item: items) {
@@ -108,25 +120,71 @@ bool ArchipelagoClient::StartClient() {
}); // todo maybe move these functions to a lambda, since they don't have to be static anymore
apClient->set_location_checked_handler([&](const std::list<int64_t> locations) {
if(disconnecting) {
return;
}
for(const int64_t apLoc : locations) {
QueueExternalCheck(apLoc);
}
});
apClient->set_print_json_handler([&](const APClient::PrintJSONArgs& arg) {
std::string tag = "[" + arg.type + "] ";
const int slot = apClient->get_player_number();
if(arg.type == "ItemSend") {
if((*arg.item).player == slot) {
tag = "[Found] ";
} else if (*arg.receiving == slot ) {
tag = "[Received] ";
}
if(disconnecting) {
return;
}
std::string text = tag + apClient->render_json(arg.data, APClient::RenderFormat::TEXT);
ArchipelagoConsole_SendMessage(text.c_str(), false);
std::vector<ColoredTextNode> coloredNodes;
for(const APClient::TextNode& node : arg.data) {
APClient* client = apClient.get();
std::string color;
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";
text = client->get_player_alias(id);
} else if (node.type == "item_id") {
int64_t id = std::stoll(node.text);
if(color.empty()) {
if (node.flags & APClient::ItemFlags::FLAG_ADVANCEMENT) color = "plum";
else if (node.flags & APClient::ItemFlags::FLAG_NEVER_EXCLUDE) color = "slateblue";
else if (node.flags & APClient::ItemFlags::FLAG_TRAP) color = "salmon";
else 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";
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";
else if (node.hintStatus == APClient::HINT_UNSPECIFIED) color = "grey";
else if (node.hintStatus == APClient::HINT_NO_PRIORITY) color = "slateblue";
else if (node.hintStatus == APClient::HINT_AVOID) color = "salmon";
else if (node.hintStatus == APClient::HINT_PRIORITY) color = "plum";
else color = "red"; // unknown status -> red
} else if (node.type == "ERROR") {
color = "ERROR";
text = node.text;
} else if (node.type == "LOG") {
color = "LOG";
text = node.text;
} else {
color = "white";
text = node.text;
}
ColoredTextNode Colornode;
Colornode.color = color;
Colornode.text = text;
coloredNodes.push_back(Colornode);
}
ArchipelagoConsole_PrintJson(coloredNodes);
});
return true;
@@ -139,7 +197,14 @@ void ArchipelagoClient::GameLoaded() {
// if its not an AP save, disconnect
if(!IS_ARCHIPELAGO) {
apClient->reset();
ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not not an archipelago save, disconnecting...");
disconnecting = true;
return;
}
if(!isRightSaveLoaded()) {
ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not associated with connected slot, disconnecting...");
disconnecting = true;
return;
}
@@ -147,7 +212,7 @@ void ArchipelagoClient::GameLoaded() {
SynchItems();
SynchSentLocations();
SynchRecievedLocations();
SynchReceivedLocations();
}
void ArchipelagoClient::StartLocationScouts() {
@@ -185,7 +250,7 @@ void ArchipelagoClient::SynchSentLocations() {
apClient->LocationChecks(checkedLocations);
}
void ArchipelagoClient::SynchRecievedLocations() {
void ArchipelagoClient::SynchReceivedLocations() {
// Open checks that have been found previously but went unsaved
for(const int64_t apLoc : apClient->get_checked_locations()) {
QueueExternalCheck(apLoc);
@@ -196,12 +261,17 @@ void ArchipelagoClient::QueueExternalCheck(const int64_t apLocation) {
const std::string checkName = apClient->get_location_name(apLocation, AP_Client_consts::AP_GAME_NAME);
const uint32_t RC = static_cast<uint32_t>(Rando::StaticData::locationNameToEnum[checkName]);
if(RC == RC_UNKNOWN_CHECK) {
ArchipelagoConsole_SendMessage("[ERROR] Attempting to queue RC_UNKOWN_CHECK, skipping", false);
return;
}
// Don't queue checks we already have
if(Rando::Context::GetInstance()->GetItemLocation(RC)->HasObtained()) {
return;
}
std::string locationLog = "[LOG] Externaly checking" + checkName;
std::string locationLog = "[LOG] Externaly checking: " + checkName;
ArchipelagoConsole_SendMessage(locationLog.c_str(), true);
GameInteractor_ExecuteOnRandomizerExternalCheck(RC);
@@ -212,12 +282,17 @@ bool ArchipelagoClient::IsConnected() {
}
void ArchipelagoClient::CheckLocation(RandomizerCheck sohCheckId) {
if(sohCheckId == RC_UNKNOWN_CHECK) {
ArchipelagoConsole_SendMessage("[ERROR] trying to send RC_UNKNOWN_CHECK, skipping", false);
return;
}
std::string apName = Rando::StaticData::GetLocation(sohCheckId)->GetName();
if (apName.empty()) {
return;
}
int64_t apItemId = apClient->get_location_id(std::string(apName));
int64_t apItemId = apClient->get_location_id(std::string(apName));
std::string logMessage = "[LOG] Checked: " + apName + "(" + std::to_string(apItemId) + "), sending to AP server";
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
@@ -234,21 +309,21 @@ void ArchipelagoClient::OnItemReceived(const ApItem apItem) {
return;
}
std::string logMessage = "[LOG] Recieved " + apItem.itemName;
std::string logMessage = "[LOG] Received " + apItem.itemName;
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
// add item to the queue
recieveQueue.push(apItem);
}
void ArchipelagoClient::QueueItem(const ApItem item) {
if(item.index < gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex) {
if(apItem.index < gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex) {
// Skip queueing any items we already have
std::string logMessage = "[LOG] Skipping giving " + item.itemName + ". We recieved this previously.";
std::string logMessage = "[LOG] Skipping giving " + apItem.itemName + ". We received this previously.";
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
return;
}
// add item to the queue
receiveQueue.push(apItem);
}
void ArchipelagoClient::QueueItem(const ApItem item) {
std::string logMessage = "[LOG] Giving " + item.itemName;
ArchipelagoConsole_SendMessage(logMessage.c_str(), true);
const RandomizerGet RG = Rando::StaticData::itemNameToEnum[item.itemName];
@@ -257,7 +332,7 @@ void ArchipelagoClient::QueueItem(const ApItem item) {
}
itemQueued = true;
GameInteractor_ExecuteOnArchipelagoItemRecieved(static_cast<int32_t>(RG));
GameInteractor_ExecuteOnArchipelagoItemReceived(static_cast<int32_t>(RG));
}
void ArchipelagoClient::SendGameWon() {
@@ -267,22 +342,49 @@ void ArchipelagoClient::SendGameWon() {
}
}
void ArchipelagoClient::SendMessage(const std::string message) {
// local commands not implemented yet
if(message.starts_with("/")) {
ArchipelagoConsole_SendMessage("Ship of Harkinian does not have any local commands yet.\nUse !help\" to see server commands instead", false);
return;
}
if(apClient == nullptr) {
ArchipelagoConsole_SendMessage("[ERROR] Could not send message. Please Connect to your slot.", false);
return;
}
apClient->Say(message);
}
void ArchipelagoClient::Poll() {
if(apClient == nullptr) {
return;
}
// queue another item to be recieved
if(!itemQueued && recieveQueue.size() > 0) {
if(disconnecting) {
apClient->reset();
apClient = nullptr;
return;
}
// queue another item to be received
if(!itemQueued && receiveQueue.size() > 0) {
const ApItem item = recieveQueue.front();
recieveQueue.pop();
const ApItem item = receiveQueue.front();
receiveQueue.pop();
QueueItem(item);
}
apClient->poll();
}
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;
return seedMatch && slotMatch;
}
const std::string ArchipelagoClient::GetSlotName() const {
if(apClient == NULL) {
return "";
@@ -301,7 +403,7 @@ const std::vector<ArchipelagoClient::ApItem>& ArchipelagoClient::GetScoutedItems
const char* ArchipelagoClient::GetConnectionStatus() {
if (!apClient) {
return "";
return "Disconnected!";
}
APClient::State clientStatus = apClient->get_state();
@@ -317,7 +419,7 @@ const char* ArchipelagoClient::GetConnectionStatus() {
return "Socket Connected!";
}
case APClient::State::ROOM_INFO: {
return "Room info Recieved!";
return "Room info Received!";
}
case APClient::State::SLOT_CONNECTED: {
return "Slot Connected!";
@@ -335,6 +437,12 @@ extern "C" void Archipelago_InitSaveFile() {
std::vector<ArchipelagoClient::ApItem> scoutedItems = ArchipelagoClient::GetInstance().GetScoutedItems();
ArchipelagoClient& client = ArchipelagoClient::GetInstance();
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, client.apClient->get_seed(),
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));
for (uint32_t i = 0; i < scoutedItems.size(); i++) {
RandomizerCheck rc = Rando::StaticData::locationNameToEnum[scoutedItems[i].locationName];
@@ -411,11 +519,16 @@ void InitArchipelagoData(bool isDebug) {
}
void RegisterArchipelago() {
// make sure the client is constructed
ArchipelagoClient::GetInstance();
CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("Connected"), 0);
COND_HOOK(GameInteractor::OnGameFrameUpdate, true, [](){ArchipelagoClient::GetInstance().Poll();});
COND_HOOK(GameInteractor::OnLoadGame, true, [](int32_t file_id){ArchipelagoClient::GetInstance().GameLoaded();});
COND_HOOK(GameInteractor::OnRandomizerItemGivenHooks, IS_ARCHIPELAGO,
[](uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) {
if (rc == RC_ARCHIPELAGO_RECIEVED_ITEM) {
if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) {
gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++;
ArchipelagoClient::GetInstance().itemQueued = false;
} else {

View File

@@ -27,6 +27,11 @@ class ArchipelagoClient{
uint64_t index;
};
struct ColoredTextNode {
std::string text;
std::string color;
};
static ArchipelagoClient& GetInstance();
bool StartClient();
@@ -36,7 +41,7 @@ class ArchipelagoClient{
void StartLocationScouts();
void SynchItems();
void SynchSentLocations();
void SynchRecievedLocations();
void SynchReceivedLocations();
// getters
const std::string GetSlotName() const;
@@ -53,11 +58,12 @@ class ArchipelagoClient{
void QueueExternalCheck(int64_t apLocation);
void SendGameWon();
void SendMessage(const std::string message);
void Poll();
std::unique_ptr<APClient> apClient;
bool itemQueued;
bool disconnecting;
protected:
ArchipelagoClient();
@@ -65,9 +71,12 @@ class ArchipelagoClient{
private:
ArchipelagoClient(ArchipelagoClient &) = delete;
void operator=(const ArchipelagoClient &) = delete;
bool isRightSaveLoaded() const;
std::string uuid;
static std::shared_ptr<ArchipelagoClient> instance; // is this even used?
static std::shared_ptr<ArchipelagoClient> instance;
static bool initialized;
bool gameWon;
@@ -75,7 +84,7 @@ class ArchipelagoClient{
nlohmann::json slotData;
std::set<int64_t> locations;
std::vector<ApItem> scoutedItems;
std::queue<ApItem> recieveQueue;
std::queue<ApItem> receiveQueue;
};
void LoadArchipelagoData();

View File

@@ -4,7 +4,7 @@
#include "soh/SohGui/SohGui.hpp"
#include "soh/OTRGlobals.h"
ImVector<char*> Items;
std::vector<std::vector<ArchipelagoClient::ColoredTextNode>> Items;
bool autoScroll = true;
using namespace UIWidgets;
@@ -19,7 +19,21 @@ void ArchipelagoConsole_SendMessage(const char* fmt, bool debugMessage, ...) {
vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args);
buf[IM_ARRAYSIZE(buf) - 1] = 0;
va_end(args);
Items.push_back(strdup(buf));
ArchipelagoClient::ColoredTextNode node;
node.text = std::string(buf);
node.color = "white";
if (strstr(buf, "[ERROR]")) {
node.color = "ERROR";
} else if (strstr(buf, "[LOG]")) {
node.color = "LOG";
}
std::vector<ArchipelagoClient::ColoredTextNode> line;
line.push_back(node);
Items.push_back(line);
}
void ArchipelagoConsole_PrintJson(const std::vector<ArchipelagoClient::ColoredTextNode> nodes) {
Items.push_back(nodes);
}
void ArchipelagoConsoleWindow::DrawElement() {
@@ -28,57 +42,19 @@ void ArchipelagoConsoleWindow::DrawElement() {
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15.0f, 12.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 1.0f));
if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 400), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (int i = 0; i < Items.Size; i++) {
const char* item = Items[i];
ImVec4 color;
bool hasColor = false;
if (strstr(item, "[ERROR]")) {
color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
hasColor = true;
} else if (strstr(item, "[LOG]")) {
color = ImVec4(0.7f, 0.7f, 1.0f, 1.0f);
hasColor = true;
} else if (strstr(item, "[Found]")) {
color = ImVec4(0.24f, 0.64f, 0.69f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Received]")) {
color = ImVec4(0.18f, 0.76f, 0.42f, 1.00f);
hasColor = true;
} else if (strstr(item, "[ItemCheat]")) {
color = ImVec4(0.97f, 0.26f, 0.26f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Hint]")) {
color = ImVec4(0.89f, 0.45f, 1.00f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Join]")) {
color = ImVec4(0.00f, 0.80f, 0.11f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Part]")) {
color = ImVec4(1.00f, 0.01f, 0.01f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Goal]")) {
color = ImVec4(0.00f, 0.70f, 0.21f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Release]")) {
color = ImVec4(0.51f, 0.21f, 0.61f, 1.00f);
hasColor = true;
} else if (strstr(item, "[Collect]")) {
color = ImVec4(0.51f, 0.21f, 0.61f, 1.00f);
hasColor = true;
}
if (hasColor) {
ImGui::PushStyleColor(ImGuiCol_Text, color);
}
ImGui::TextUnformatted(item);
if (hasColor) {
for(const std::vector<ArchipelagoClient::ColoredTextNode>& line : Items) {
for(const ArchipelagoClient::ColoredTextNode& node : line) {
ImGui::PushStyleColor(ImGuiCol_Text, getColorVal(node.color));
ImGui::TextUnformatted(node.text.c_str());
ImGui::SameLine();
ImGui::PopStyleColor();
}
ImGui::NewLine();
}
// Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame.
@@ -89,5 +65,58 @@ void ArchipelagoConsoleWindow::DrawElement() {
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(3);
static char textEntryBuf[1024];
static bool keepFocus = false;
if(keepFocus) {
ImGui::SetKeyboardFocusHere();
keepFocus = false;
}
if(ImGui::InputText("##AP_MessageField", textEntryBuf, 1023, ImGuiInputTextFlags_EnterReturnsTrue)) {
ArchipelagoClient::GetInstance().SendMessage(std::string(textEntryBuf));
textEntryBuf[0] = '\0';
keepFocus = true;
}
//keepFocus = ImGui::IsItemActive();
ImGui::SameLine();
if(ImGui::Button("Send")) {
ArchipelagoClient::GetInstance().SendMessage(std::string(textEntryBuf));
textEntryBuf[0] = '\0';
keepFocus = true;
}
};
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);
}

View File

@@ -3,6 +3,9 @@
#define ARCHIPELAGO_CONSOLE_WINDOW_H
#include <libultraship/libultraship.h>
#include "Archipelago.h"
#include <vector>
#include <list>
class ArchipelagoConsoleWindow final : public Ship::GuiWindow {
public:
@@ -16,5 +19,7 @@ class ArchipelagoConsoleWindow final : public Ship::GuiWindow {
};
void ArchipelagoConsole_SendMessage(const char* fmt, bool debugMessage = false, ...);
void ArchipelagoConsole_PrintJson(const std::vector<ArchipelagoClient::ColoredTextNode> nodes);
ImVec4 getColorVal(const std::string& color);
#endif // ARCHIPELAGO_CONSOLE_WINDOW_H