Files
Shipwright/soh/soh/Network/Anchor/Menu.cpp
T
2025-11-30 14:38:29 -07:00

258 lines
11 KiB
C++

#include "Anchor.h"
#include <libultraship/libultraship.h>
#include "soh/SohGui/SohGui.hpp"
#include "soh/SohGui/SohMenu.h"
#include "soh/util.h"
namespace SohGui {
extern std::shared_ptr<SohMenu> mSohMenu;
extern std::shared_ptr<AnchorRoomWindow> mAnchorRoomWindow;
} // namespace SohGui
static const char* pvpModes[3] = { "Off", "On", "On + Friendly Fire" };
static std::vector<const char*> teleportModes = { "None", "Team Only", "All" };
static std::vector<const char*> showLocationsModes = { "None", "Team Only", "All" };
void AnchorMainMenu(WidgetInfo& info) {
auto anchor = Anchor::Instance;
std::string host = CVarGetString(CVAR_REMOTE_ANCHOR("Host"), "anchor.hm64.org");
uint16_t port = CVarGetInteger(CVAR_REMOTE_ANCHOR("Port"), 43383);
std::string anchorTeamId = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
std::string anchorRoomId = CVarGetString(CVAR_REMOTE_ANCHOR("RoomId"), "");
std::string anchorName = CVarGetString(CVAR_REMOTE_ANCHOR("Name"), "");
bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535 &&
!SohUtils::IsStringEmpty(anchorRoomId) && !SohUtils::IsStringEmpty(anchorName);
ImGui::SeparatorText("Connection Settings");
ImGui::BeginDisabled(anchor->isEnabled);
ImGui::Text("Host & Port");
if (UIWidgets::InputString("##Host", &host,
UIWidgets::InputOptions()
.Size(ImGui::GetContentRegionAvail() -
ImVec2((ImGui::GetFontSize() * 5 + ImGui::GetStyle().ItemSpacing.x), 0))
.Color(THEME_COLOR))) {
CVarSetString(CVAR_REMOTE_ANCHOR("Host"), host.c_str());
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::SameLine();
UIWidgets::PushStyleInput(THEME_COLOR);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) {
CVarSetInteger(CVAR_REMOTE_ANCHOR("Port"), port);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
UIWidgets::PopStyleInput();
ImGui::Text("Name");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (UIWidgets::InputString("##Name", &anchorName, UIWidgets::InputOptions().Color(THEME_COLOR))) {
CVarSetString(CVAR_REMOTE_ANCHOR("Name"), anchorName.c_str());
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::Text("Room ID");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (UIWidgets::InputString("##RoomId", &anchorRoomId,
UIWidgets::InputOptions().IsSecret(anchor->isEnabled).Color(THEME_COLOR))) {
CVarSetString(CVAR_REMOTE_ANCHOR("RoomId"), anchorRoomId.c_str());
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::Text("Team ID (Items & Flags Shared)");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (UIWidgets::InputString("##TeamId", &anchorTeamId, UIWidgets::InputOptions().Color(THEME_COLOR))) {
CVarSetString(CVAR_REMOTE_ANCHOR("TeamId"), anchorTeamId.c_str());
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::Spacing();
if (UIWidgets::Button("Restore Defaults", UIWidgets::ButtonOptions()
.Size(ImVec2(ImGui::GetContentRegionAvail().x / 2, 0))
.Color(UIWidgets::Colors::Red))) {
CVarSetString(CVAR_REMOTE_ANCHOR("Host"), "anchor.hm64.org");
CVarSetInteger(CVAR_REMOTE_ANCHOR("Port"), 43383);
CVarSetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
CVarSetString(CVAR_REMOTE_ANCHOR("RoomId"), "");
CVarSetString(CVAR_REMOTE_ANCHOR("Name"), "");
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::SameLine();
if (UIWidgets::Button("Global Room", UIWidgets::ButtonOptions()
.Color(UIWidgets::Colors::Blue)
.Tooltip("Always-online public room so you don't have to experience "
"Hyrule alone. PVP and syncing are disabled."))) {
CVarSetString(CVAR_REMOTE_ANCHOR("Host"), "anchor.hm64.org");
CVarSetInteger(CVAR_REMOTE_ANCHOR("Port"), 43383);
CVarSetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
CVarSetString(CVAR_REMOTE_ANCHOR("RoomId"), "soh-global");
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
}
ImGui::EndDisabled();
ImGui::Spacing();
ImGui::BeginDisabled(!isFormValid);
const char* buttonLabel = anchor->isEnabled ? "Disable" : "Enable";
UIWidgets::PushStyleButton(anchor->isEnabled ? UIWidgets::ColorValues.at(UIWidgets::Colors::Red)
: UIWidgets::ColorValues.at(UIWidgets::Colors::Green));
if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) {
if (anchor->isEnabled) {
CVarClear(CVAR_REMOTE_ANCHOR("Enabled"));
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
anchor->Disable();
} else {
CVarSetInteger(CVAR_REMOTE_ANCHOR("Enabled"), 1);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
anchor->Enable();
}
}
UIWidgets::PopStyleButton();
ImGui::EndDisabled();
ImGui::Spacing();
if (!anchor->isEnabled) {
return;
}
if (!anchor->isConnected) {
ImGui::Text("Connecting...");
return;
}
ImGui::SeparatorText("Current Room");
ImGui::Text("%s Connected", ICON_FA_CHECK);
if (IS_ARCHIPELAGO)
ImGui::BeginDisabled();
UIWidgets::PushStyleButton(THEME_COLOR);
if (ImGui::Button("Request Team State")) {
anchor->SendPacket_RequestTeamState();
}
UIWidgets::Tooltip("Try this if you are missing items or flags that your team members have collected");
UIWidgets::PopStyleButton();
if (IS_ARCHIPELAGO)
ImGui::EndDisabled();
ImGui::SameLine();
UIWidgets::WindowButton("Toggle Anchor Room Window", CVAR_WINDOW("AnchorRoom"), SohGui::mAnchorRoomWindow);
if (!SohGui::mAnchorRoomWindow->IsVisible()) {
SohGui::mAnchorRoomWindow->DrawElement();
}
}
void AnchorAdminMenu(WidgetInfo& info) {
auto anchor = Anchor::Instance;
bool isGlobalRoom = (std::string("soh-global") == CVarGetString(CVAR_REMOTE_ANCHOR("RoomId"), ""));
if (!anchor->isEnabled || !anchor->isConnected || anchor->roomState.ownerClientId != anchor->ownClientId ||
isGlobalRoom) {
return;
}
ImGui::SeparatorText("Room Settings (Admin Only)");
UIWidgets::PushStyleButton(THEME_COLOR);
if (ImGui::Button("Clear All Team State")) {
std::set<std::string> teams;
for (auto& [clientId, client] : Anchor::Instance->clients) {
teams.insert(client.teamId);
}
for (auto& team : teams) {
anchor->SendPacket_ClearTeamState(team);
}
}
UIWidgets::PopStyleButton();
if (UIWidgets::CVarCombobox("PvP Mode:", CVAR_REMOTE_ANCHOR("RoomSettings.PvpMode"), pvpModes,
UIWidgets::ComboboxOptions()
.DefaultIndex(1)
.LabelPosition(UIWidgets::LabelPositions::Above)
.Color(THEME_COLOR))) {
anchor->SendPacket_UpdateRoomState();
}
if (UIWidgets::CVarCombobox("Show Locations For:", CVAR_REMOTE_ANCHOR("RoomSettings.ShowLocationsMode"),
showLocationsModes,
UIWidgets::ComboboxOptions()
.DefaultIndex(1)
.LabelPosition(UIWidgets::LabelPositions::Above)
.Color(THEME_COLOR))) {
anchor->SendPacket_UpdateRoomState();
}
if (UIWidgets::CVarCombobox("Allow Teleporting To:", CVAR_REMOTE_ANCHOR("RoomSettings.TeleportMode"), teleportModes,
UIWidgets::ComboboxOptions()
.DefaultIndex(1)
.LabelPosition(UIWidgets::LabelPositions::Above)
.Color(THEME_COLOR))) {
anchor->SendPacket_UpdateRoomState();
}
if (IS_ARCHIPELAGO) {
CVarSetInteger(CVAR_REMOTE_ANCHOR("RoomSettings.SyncItemsAndFlags"), 0);
ImGui::BeginDisabled();
}
if (UIWidgets::CVarCheckbox("Sync Items & Flags", CVAR_REMOTE_ANCHOR("RoomSettings.SyncItemsAndFlags"),
UIWidgets::CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) {
anchor->SendPacket_UpdateRoomState();
}
if (IS_ARCHIPELAGO) {
ImGui::EndDisabled();
}
}
void AnchorInstructionsMenu(WidgetInfo& info) {
auto anchor = Anchor::Instance;
ImGui::SeparatorText("Usage Instructions");
ImGui::TextWrapped("1. All players involved should start at the file select screen");
ImGui::TextWrapped("2. Come up with a unique Room ID (this is basically your password) and enter it, along with "
"your desired player name and team ID and click Enable");
ImGui::TextWrapped("3. The host should configure the randomizer settings and generate a seed, then share the newly "
"generated JSON spoiler file with other players.");
ImGui::TextWrapped("4. All players should load the same JSON spoiler file (drag it into SoH window), make sure "
"seed icons match, then create a new file.");
ImGui::TextWrapped("5. All players should now load into their game. IMPORTANT! If using an existing save/seed "
"ensure the player with the most progress loads the file first.");
ImGui::TextWrapped("6. After everyone has loaded in, verify on the network tab that it doesn't warn about anyone "
"being on a wrong version or seed.");
ImGui::Spacing();
ImGui::TextWrapped(
"Note: Team ID is used to group players together in the same team, sharing items and flags. Make sure all "
"players who want to share progress use the same Team ID. All players with the same Team ID should be using "
"the same randomizer seed, while players on different teams can use different seeds.");
}
#ifdef ENABLE_REMOTE_CONTROL
void RegisterAnchorMenu() {
WidgetPath path = { "Network", "Anchor", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "AnchorMainMenu", WIDGET_CUSTOM)
.CustomFunction(AnchorMainMenu)
.HideInSearch(true);
path.column = SECTION_COLUMN_2;
SohGui::mSohMenu->AddWidget(path, "AnchorAdminMenu", WIDGET_CUSTOM)
.CustomFunction(AnchorAdminMenu)
.HideInSearch(true);
SohGui::mSohMenu->AddWidget(path, "AnchorInstructionsMenu", WIDGET_CUSTOM)
.CustomFunction(AnchorInstructionsMenu)
.HideInSearch(true);
}
static RegisterMenuInitFunc menuInitFunc(RegisterAnchorMenu);
#endif