From c531e7ba7de9e692d4620f3fcd75731361dcd9f8 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Tue, 26 Nov 2024 06:47:48 -0500 Subject: [PATCH] Add Servers Tab To Launcher --- dependencies/runtime/src | 2 +- docs/CHANGELOG.md | 1 + docs/MULTIPLAYER.md | 16 -- docs/README.md | 1 - launcher/src/client/configuration.h | 20 +- launcher/src/client/ui.cpp | 204 +++++++++++++++---- launcher/src/logger/crash-report.cpp | 4 +- launcher/src/ui/frame.cpp | 8 +- launcher/src/ui/frame.h | 2 + libreborn/CMakeLists.txt | 1 + libreborn/include/libreborn/servers.h | 20 ++ libreborn/include/libreborn/util.h | 6 +- libreborn/src/util/servers.cpp | 82 ++++++++ mods/include/mods/server/server_properties.h | 9 +- mods/src/game-mode/ui.cpp | 3 +- mods/src/misc/misc.cpp | 2 +- mods/src/multiplayer/multiplayer.cpp | 98 +-------- mods/src/options/options.cpp | 2 +- mods/src/server/server.cpp | 31 ++- mods/src/server/server_properties.cpp | 4 +- 20 files changed, 333 insertions(+), 183 deletions(-) delete mode 100644 docs/MULTIPLAYER.md create mode 100644 libreborn/include/libreborn/servers.h create mode 100644 libreborn/src/util/servers.cpp diff --git a/dependencies/runtime/src b/dependencies/runtime/src index 043d926f..c1b4b027 160000 --- a/dependencies/runtime/src +++ b/dependencies/runtime/src @@ -1 +1 @@ -Subproject commit 043d926f32a3315d92bce1d9bbb0ccdcf99c11a2 +Subproject commit c1b4b02770dee1f5dfca2ca21a627baf10942cde diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a1c1a10b..d42c9829 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -107,6 +107,7 @@ * `overwrite_calls` Now Scans VTables * Unify Server/Client Builds * Controller Support Removed +* Brand New Launcher UI! **2.5.3** * Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default) diff --git a/docs/MULTIPLAYER.md b/docs/MULTIPLAYER.md deleted file mode 100644 index 6dc3c8d4..00000000 --- a/docs/MULTIPLAYER.md +++ /dev/null @@ -1,16 +0,0 @@ -# Multiplayer -MCPI-Reborn supports two ways to play multiplayer. - -## Local Network (LAN) -This is also supported by vanilla MCPI. Just load a world in MCPI and other devices on the network can join. - -## External Servers -Unlike vanilla MCPI, MCPI-Reborn allows you to natively join a server outside of the local network. Just modify `~/.minecraft-pi/servers.txt` and it should show up in MCPI's server list. - -### Example `~/.minecraft-pi/servers.txt` -``` -# Default Port Is 19132 -example.com -# Custom Port -example.com:19133 -``` diff --git a/docs/README.md b/docs/README.md index 49ab91d1..408cb1e2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,6 @@ * [View Dedicated Server](DEDICATED_SERVER.md) * [View Credits](CREDITS.md) * [View Terminology](TERMINOLOGY.md) -* [View Multiplayer](MULTIPLAYER.md) * [View In-Game Controls](CONTROLS.md) * [View Custom Skins](CUSTOM_SKINS.md) * [View Changelog](CHANGELOG.md) diff --git a/launcher/src/client/configuration.h b/launcher/src/client/configuration.h index 67e3dda2..3240198a 100644 --- a/launcher/src/client/configuration.h +++ b/launcher/src/client/configuration.h @@ -3,10 +3,10 @@ #include #include "../options/parser.h" -#include "cache.h" #include "../ui/frame.h" #include +#include // Default Configuration #define DEFAULT_USERNAME "StevePi" @@ -29,15 +29,25 @@ struct ConfigurationUI final : Frame { explicit ConfigurationUI(State &state_, bool &save_settings_); int render() override; private: - void update_render_distance(); - int draw_bottom(); - void draw_main(); + // Bottom Row + int get_render_distance_index() const; + int draw_bottom(bool hide_reset_revert) const; + // General + void draw_main() const; + // Advanced void draw_advanced() const; static void draw_category(FlagNode &category); + // Server List + bool are_servers_unsaved() const; + void draw_servers(); + void draw_server_list(); + // State const State original_state; State &state; bool &save_settings; - int render_distance_index; + // Server List + ServerList last_saved_servers; + ServerList servers; }; // Handle Non-Launch Commands diff --git a/launcher/src/client/ui.cpp b/launcher/src/client/ui.cpp index a5623132..e1a0f20a 100644 --- a/launcher/src/client/ui.cpp +++ b/launcher/src/client/ui.cpp @@ -1,38 +1,38 @@ #include +#include + +#include #include "configuration.h" #include // Render Distances -static std::vector render_distances = { +static constexpr std::array render_distances = { "Far", "Normal", "Short", "Tiny" }; +// Tooltips/Text +static constexpr std::string revert_text = "Revert"; +static constexpr std::string revert_tooltip_text = "Last Saved"; +static constexpr std::string make_tooltip(const std::string &text, const std::string &type) { + return "Use " + text + ' ' + type; +} + // Construct static constexpr int size = 400; ConfigurationUI::ConfigurationUI(State &state_, bool &save_settings_): Frame("Launcher", size, size), original_state(state_), state(state_), - save_settings(save_settings_) { - update_render_distance(); -} -void ConfigurationUI::update_render_distance() { - render_distance_index = 0; - for (std::vector::size_type i = 0; i < render_distances.size(); i++) { - if (std::string(render_distances[i]) == state.render_distance) { - render_distance_index = int(i); - break; - } - } -} + save_settings(save_settings_) {} // Render int ConfigurationUI::render() { + bool on_servers_tab = false; if (ImGui::BeginChild("Main", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() /* Leave Room For Bottom Row */), ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { // Tabs if (ImGui::BeginTabBar("TabBar")) { @@ -46,45 +46,64 @@ int ConfigurationUI::render() { draw_advanced(); ImGui::EndTabItem(); } + // Servers Tab + if (ImGui::BeginTabItem("Servers", nullptr, are_servers_unsaved() ? ImGuiTabItemFlags_UnsavedDocument : ImGuiTabItemFlags_None)) { + draw_servers(); + ImGui::EndTabItem(); + on_servers_tab = true; + } ImGui::EndTabBar(); } } ImGui::EndChild(); // Bottom Row - return draw_bottom(); + return draw_bottom(on_servers_tab); } // Bottom Row -int ConfigurationUI::draw_bottom() { - // Reset All Settings - const State default_state; - std::vector> reset_options = { - {"Revert", "Last Saved", &original_state}, - {"Reset", "Default", &default_state}, - }; - for (const std::tuple &option : reset_options) { - const State &new_state = *std::get<2>(option); - ImGui::BeginDisabled(state == new_state); - if (ImGui::Button(std::get<0>(option))) { - state = new_state; - update_render_distance(); +int ConfigurationUI::draw_bottom(const bool hide_reset_revert) const { + // Reset Settings + if (!hide_reset_revert) { + const State default_state; + constexpr const char *tooltip_type = "Settings"; + std::vector> reset_options = { + {revert_text, make_tooltip(revert_tooltip_text, tooltip_type), &original_state}, + {"Reset", make_tooltip("Default", tooltip_type), &default_state}, + }; + for (const std::tuple &option : reset_options) { + const State &new_state = *std::get<2>(option); + ImGui::BeginDisabled(state == new_state); + if (ImGui::Button(std::get<0>(option).c_str())) { + state = new_state; + } + ImGui::SetItemTooltip("%s", std::get<1>(option).c_str()); + ImGui::EndDisabled(); + ImGui::SameLine(); } - ImGui::SetItemTooltip("Use %s Settings", std::get<1>(option)); - ImGui::EndDisabled(); - ImGui::SameLine(); } // Right-Align Buttons int ret = 0; - draw_right_aligned_buttons({"Quit", "Launch"}, [&ret](const int id, const bool was_clicked) { + bool unsaved_servers = are_servers_unsaved(); + draw_right_aligned_buttons({quit_text, "Launch"}, [&ret, unsaved_servers](const int id, const bool was_clicked) { if (id == 0) { // Quit if (was_clicked) { ret = -1; } ImGui::SetItemTooltip("Changes Will Not Be Saved!"); - } else if (was_clicked) { + // Disable Launch if Server List Is Unsaved + if (unsaved_servers) { + ImGui::BeginDisabled(); + } + } else if (id == 1) { // Launch - ret = 1; + if (unsaved_servers) { + ImGui::SetItemTooltip("Server List Is Unsaved"); + ImGui::EndDisabled(); + } + if (was_clicked) { + ret = 1; + } } }); // Return @@ -92,7 +111,17 @@ int ConfigurationUI::draw_bottom() { } // Main Tab -void ConfigurationUI::draw_main() { +int ConfigurationUI::get_render_distance_index() const { + int render_distance_index = 0; + for (std::vector::size_type i = 0; i < render_distances.size(); i++) { + if (std::string(render_distances[i]) == state.render_distance) { + render_distance_index = int(i); + break; + } + } + return render_distance_index; +} +void ConfigurationUI::draw_main() const { const ImGuiStyle &style = ImGui::GetStyle(); const char *labels[] = {"Username", "Render Distance"}; // Calculate Label Size @@ -103,21 +132,23 @@ void ConfigurationUI::draw_main() { ImGui::PushItemWidth(-label_size); // Options ImGui::InputText(labels[0], &state.username); - ImGui::Combo(labels[1], &render_distance_index, render_distances.data(), int(render_distances.size())); - state.render_distance = render_distances[render_distance_index]; + int render_distance_index = get_render_distance_index(); + if (ImGui::Combo(labels[1], &render_distance_index, render_distances.data(), int(render_distances.size()))) { + state.render_distance = render_distances[render_distance_index]; + } ImGui::PopItemWidth(); ImGui::Checkbox("Save Settings On Launch", &save_settings); } // Advanced Tab -static std::string get_label(const FlagNode &node) { - return node.name + "##" + std::to_string(node.id); +static std::string get_label_for_flag_node(const FlagNode &node) { + return node.name + "##FlagNode" + std::to_string(node.id); } void ConfigurationUI::draw_advanced() const { if (ImGui::BeginChild("Features", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) { // Categories for (FlagNode &category : state.flags.root.children) { - std::string label = get_label(category); + const std::string label = get_label_for_flag_node(category); if (ImGui::CollapsingHeader(label.c_str())) { draw_category(category); } @@ -125,18 +156,107 @@ void ConfigurationUI::draw_advanced() const { } ImGui::EndChild(); } - -// Feature Categories void ConfigurationUI::draw_category(FlagNode &category) { for (FlagNode &child : category.children) { - std::string label = get_label(child); + const std::string label = get_label_for_flag_node(child); if (!child.children.empty()) { + // Sub-Category if (ImGui::TreeNode(label.c_str())) { draw_category(child); ImGui::TreePop(); } } else { + // Flag ImGui::Checkbox(label.c_str(), &child.value); } } +} + +// Servers +bool ConfigurationUI::are_servers_unsaved() const { + return servers.to_string() != last_saved_servers.to_string(); +} +void ConfigurationUI::draw_servers() { + // Add/Clear + bool scroll_to_bottom = false; + if (ImGui::Button("Add")) { + servers.entries.emplace_back("", DEFAULT_MULTIPLAYER_PORT); + scroll_to_bottom = true; + } + ImGui::SameLine(); + ImGui::BeginDisabled(servers.entries.empty()); + if (ImGui::Button("Clear")) { + servers.entries.clear(); + } + ImGui::EndDisabled(); + ImGui::SameLine(); + // Revert/Save + int clicked_button = -1; + ImGui::BeginDisabled(!are_servers_unsaved()); + draw_right_aligned_buttons({revert_text.c_str(), "Save"}, [&clicked_button](const int id, const bool was_clicked) { + if (id == 0) { + ImGui::SetItemTooltip("%s", make_tooltip(revert_tooltip_text, "Server List").c_str()); + } + if (was_clicked) { + clicked_button = id; + } + }); + ImGui::EndDisabled(); + if (clicked_button == 1) { + // Save + servers.save(); + last_saved_servers = servers; + } else if (clicked_button == 0) { + // Revert To Last Saved Server List + servers = last_saved_servers; + } + // List + if (ImGui::BeginChild("ServerList", ImVec2(0, 0), ImGuiChildFlags_Borders)) { + draw_server_list(); + if (scroll_to_bottom) { + ImGui::SetScrollHereY(1.0f); + } + } + ImGui::EndChild(); +} +void ConfigurationUI::draw_server_list() { + for (std::vector::size_type i = 0; i < servers.entries.size(); ++i) { + ServerList::Entry &entry = servers.entries[i]; + + // Calculate Item Widths + const ImGuiStyle &style = ImGui::GetStyle(); + const std::string port_width_text = std::to_string(int(std::numeric_limits::max()) * 2); // Should Comfortably Fit All Port Numbers + const std::string delete_text = "Delete"; + const float port_width = get_frame_width(port_width_text.c_str()); + const float width_needed = (style.ItemSpacing.x * 2.0f) + port_width + get_frame_width(delete_text.c_str()); + + // Labels + const std::string base_label = "##ServerEntry" + std::to_string(i); + + // Hints + const char *address_hint = "Address"; + const char *port_hint = "Port"; + + // Address + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - width_needed); + ImGui::InputTextWithHint((base_label + address_hint).c_str(), address_hint, &entry.first, ImGuiInputTextFlags_CharsNoBlank); + ImGui::PopItemWidth(); + + // Port + ServerList::port_t &port = entry.second; + std::string port_str = port > 0 ? std::to_string(port) : ""; + ImGui::SameLine(); + ImGui::PushItemWidth(port_width); + if (ImGui::InputTextWithHint((base_label + port_hint).c_str(), port_hint, &port_str, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_NoHorizontalScroll)) { + port = ServerList::parse_port(port_str); + } + ImGui::PopItemWidth(); + + // Delete + ImGui::SameLine(); + if (ImGui::Button((delete_text + base_label).c_str())) { + servers.entries.erase(servers.entries.begin() + int(i)); + i--; + } + } } \ No newline at end of file diff --git a/launcher/src/logger/crash-report.cpp b/launcher/src/logger/crash-report.cpp index 9f5cd12f..a6da7933 100644 --- a/launcher/src/logger/crash-report.cpp +++ b/launcher/src/logger/crash-report.cpp @@ -11,7 +11,7 @@ struct CrashReport final : Frame { explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) { // Open File - std::ifstream stream(filename, std::ios_base::binary | std::ios_base::ate); + std::ifstream stream(filename, std::ios::binary | std::ios::ate); if (stream) { // Read File const std::streamoff size = stream.tellg(); @@ -53,7 +53,7 @@ struct CrashReport final : Frame { // Right-Aligned int ret = 0; const std::string &log_ref = log; - draw_right_aligned_buttons({"Copy", "Quit"}, [&ret, &log_ref](const int id, const bool was_clicked) { + draw_right_aligned_buttons({"Copy", quit_text}, [&ret, &log_ref](const int id, const bool was_clicked) { if (was_clicked) { if (id == 0) { // Copy Log diff --git a/launcher/src/ui/frame.cpp b/launcher/src/ui/frame.cpp index 3a96b689..6ad8cd26 100644 --- a/launcher/src/ui/frame.cpp +++ b/launcher/src/ui/frame.cpp @@ -69,7 +69,7 @@ int Frame::run() { int width, height; glfwGetFramebufferSize(window, &width, &height); ImGui::SetNextWindowSize({float(width), float(height)}); - if (ImGui::Begin("###Frame", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::Begin("##Frame", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse)) { ret = render(); } ImGui::End(); @@ -103,6 +103,10 @@ void Frame::setup_style(const float scale) { } // Right-Aligned Buttons +float Frame::get_frame_width(const char *str) { + const ImGuiStyle &style = ImGui::GetStyle(); + return ImGui::CalcTextSize(str).x + style.FramePadding.x * 2.0f; +} void Frame::draw_right_aligned_buttons(const std::vector &buttons, const std::function &callback) { // Calculate Position const ImGuiStyle &style = ImGui::GetStyle(); @@ -111,7 +115,7 @@ void Frame::draw_right_aligned_buttons(const std::vector &buttons, if (width_needed > 0) { width_needed += style.ItemSpacing.x; } - width_needed += ImGui::CalcTextSize(text).x + style.FramePadding.x * 2.0f; + width_needed += get_frame_width(text); } ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed); // Draw diff --git a/launcher/src/ui/frame.h b/launcher/src/ui/frame.h index 372459f6..d66c4e01 100644 --- a/launcher/src/ui/frame.h +++ b/launcher/src/ui/frame.h @@ -19,7 +19,9 @@ struct Frame { protected: // API For Sub-Classes ImFont *monospace = nullptr; + static float get_frame_width(const char *str); static void draw_right_aligned_buttons(const std::vector &buttons, const std::function &callback); + static constexpr const char *quit_text = "Quit"; private: // Properties GLFWwindow *window = nullptr; diff --git a/libreborn/CMakeLists.txt b/libreborn/CMakeLists.txt index 2e8e0ccb..7084eebb 100644 --- a/libreborn/CMakeLists.txt +++ b/libreborn/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(reborn-util SHARED src/util/flags/node.cpp src/util/flags/flags.cpp src/util/flags/available-feature-flags # Show In IDE + src/util/servers.cpp ) embed_resource(reborn-util src/util/flags/available-feature-flags) target_link_libraries(reborn-util PRIVATE utf8cpp) diff --git a/libreborn/include/libreborn/servers.h b/libreborn/include/libreborn/servers.h new file mode 100644 index 00000000..fd527ef9 --- /dev/null +++ b/libreborn/include/libreborn/servers.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +// Parse servers.txt +struct ServerList { + // Type + typedef unsigned short port_t; + typedef std::pair Entry; + // Load + static port_t parse_port(const std::string &s); + ServerList(); + // Save + std::string to_string() const; + void save() const; + // Entries + std::vector entries{}; +}; \ No newline at end of file diff --git a/libreborn/include/libreborn/util.h b/libreborn/include/libreborn/util.h index 144fc472..d3900b0b 100644 --- a/libreborn/include/libreborn/util.h +++ b/libreborn/include/libreborn/util.h @@ -60,4 +60,8 @@ std::string home_get(); // Format Time std::string format_time(const char *fmt); -std::string format_time(const char *fmt, int time); \ No newline at end of file +std::string format_time(const char *fmt, int time); + +// Default MCPI Port +// This Macro DOES NOT Control MCPI +#define DEFAULT_MULTIPLAYER_PORT 19132 \ No newline at end of file diff --git a/libreborn/src/util/servers.cpp b/libreborn/src/util/servers.cpp new file mode 100644 index 00000000..794215f8 --- /dev/null +++ b/libreborn/src/util/servers.cpp @@ -0,0 +1,82 @@ +#include +#include +#include + +#include +#include + +// File +static std::string get_server_list_path() { + return home_get() + "/servers.txt"; +} + +// Seperator +#define PORT_SEPERATOR_CHAR ':' + +// Load +ServerList::port_t ServerList::parse_port(const std::string &s) { + unsigned long port = std::strtoul(s.c_str(), nullptr, 10); + constexpr port_t max = std::numeric_limits::max(); + if (port > max) { + port = max; + } + return static_cast(port); +} +ServerList::ServerList() { + // Open File + std::ifstream server_list_file(get_server_list_path()); + if (!server_list_file) { + // File Does Not Exist + return; + } + + // Iterate Lines + std::string line; + while (std::getline(server_list_file, line)) { + // Check Line + if (!line.empty()) { + // Skip Comments + if (line[0] == '#') { + continue; + } + // Parse + std::string address; + std::string port_str; + size_t separator_pos = line.find_last_of(PORT_SEPERATOR_CHAR); + if (separator_pos == std::string::npos) { + port_str = std::to_string(DEFAULT_MULTIPLAYER_PORT); + address = line; + } else { + address = line.substr(0, separator_pos); + port_str = line.substr(separator_pos + 1); + } + // Done + entries.emplace_back(address, parse_port(port_str)); + } + } + + // Close + server_list_file.close(); +} + +// Save +std::string ServerList::to_string() const { + std::stringstream out; + out << "# External Servers File\n"; + for (const Entry &entry : entries) { + out << entry.first << PORT_SEPERATOR_CHAR << std::to_string(entry.second) << '\n'; + } + return out.str(); +} +void ServerList::save() const { + // Open File + std::ofstream file(get_server_list_path()); + if (!file) { + // Failure + WARN("Unable To Save Server List"); + } + // Write + file << to_string(); + // Close + file.close(); +} diff --git a/mods/include/mods/server/server_properties.h b/mods/include/mods/server/server_properties.h index f15d21e6..b93960e9 100644 --- a/mods/include/mods/server/server_properties.h +++ b/mods/include/mods/server/server_properties.h @@ -1,16 +1,15 @@ #pragma once #include -#include #include #include struct ServerProperty { static std::vector &get_all(); - const char *const key; - const char *const def; - const char *const comment; - ServerProperty(const char *const key_, const char *const def_, const char *const comment_): + const std::string key; + const std::string def; + const std::string comment; + ServerProperty(const std::string &key_, const std::string &def_, const std::string &comment_): key(key_), def(def_), comment(comment_) { diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp index af78d564..f26dfeaf 100644 --- a/mods/src/game-mode/ui.cpp +++ b/mods/src/game-mode/ui.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -209,7 +210,7 @@ static void create_world(Minecraft *minecraft, std::string name, const bool is_c minecraft->selectLevel(folder, name, settings); // Multiplayer - minecraft->hostMultiplayer(19132); + minecraft->hostMultiplayer(DEFAULT_MULTIPLAYER_PORT); // Open ProgressScreen ProgressScreen *screen = ProgressScreen::allocate(); diff --git a/mods/src/misc/misc.cpp b/mods/src/misc/misc.cpp index 76601953..a86cd246 100644 --- a/mods/src/misc/misc.cpp +++ b/mods/src/misc/misc.cpp @@ -204,7 +204,7 @@ static void Player_stopUsingItem_injection(Player_stopUsingItem_t original, Play // Read Asset File static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) AppPlatform_readAssetFile_t original, __attribute__((unused)) AppPlatform *app_platform, const std::string &path) { // Open File - std::ifstream stream("data/" + path, std::ios_base::binary | std::ios_base::ate); + std::ifstream stream("data/" + path, std::ios::binary | std::ios::ate); if (!stream) { // Does Not Exist AppPlatform_readAssetFile_return_value ret; diff --git a/mods/src/multiplayer/multiplayer.cpp b/mods/src/multiplayer/multiplayer.cpp index 5d2919c2..0bce82cc 100644 --- a/mods/src/multiplayer/multiplayer.cpp +++ b/mods/src/multiplayer/multiplayer.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -6,102 +5,25 @@ #include #include -#include +#include #include #include -// Load Server List -struct server_list_entry { - std::string address; - int port; -}; -static std::vector server_list_entries; -static bool server_list_loaded = false; -static void load_servers() { - // Prepare - server_list_entries.clear(); - - // Open Servers File - std::string file(home_get()); - file.append("/servers.txt"); - - // Create Stream - std::ifstream server_list_file(file); - - if (!server_list_file.good()) { - // Write Defaults - std::ofstream server_list_file_output(file); - server_list_file_output << "# External Servers File\n"; - server_list_file_output << "# Example: thebrokenrail.com\n"; - server_list_file_output.close(); - // Re-Open Stream - server_list_file = std::ifstream(file); - } - - // Check Servers File - if (!server_list_file.is_open()) { - ERR("Unable To Open %s", file.c_str()); - } - - // Iterate Lines - { - std::string line; - while (std::getline(server_list_file, line)) { - // Check Line - if (!line.empty()) { - if (line[0] == '#') { - continue; - } - // Parse - std::string address; - std::string port_str; - size_t separator_pos = line.find_last_of(':'); - if (separator_pos == std::string::npos) { - port_str = "19132"; - address = line; - } else { - address = line.substr(0, separator_pos); - port_str = line.substr(separator_pos + 1); - } - // Check Line - if (address.empty() || port_str.empty() || port_str.find_first_not_of("0123456789") != std::string::npos) { - // Invalid Line - WARN("Invalid Server: %s", line.c_str()); - continue; - } - // Parse Port - int port = std::stoi(port_str); - // Done - server_list_entry entry = { - .address = address, - .port = port - }; - server_list_entries.push_back(entry); - } - } - } - - // Close - server_list_file.close(); -} - -// Iterare Server List -static void iterate_servers(const std::function &callback) { +// Iterate Server List +static void iterate_servers(const std::function &callback) { // Load - if (!server_list_loaded) { - load_servers(); - server_list_loaded = true; - } - + static ServerList server_list; // Loop - for (const server_list_entry &entry : server_list_entries) { - callback(entry.address.c_str(), entry.port); + for (const ServerList::Entry &entry : server_list.entries) { + if (!entry.first.empty() && entry.second > 0) { + callback(entry.first.c_str(), entry.second); + } } } // Ping External Servers -static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t original, RakNetInstance *rak_net_instance, int32_t base_port) { +static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t original, RakNetInstance *rak_net_instance, const int32_t base_port) { // Call Original Method original(rak_net_instance, base_port); @@ -109,7 +31,7 @@ static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t RakNet_RakPeer *rak_peer = rak_net_instance->peer; // Add External Servers - iterate_servers([rak_peer](const char *address, int port) { + iterate_servers([rak_peer](const char *address, ServerList::port_t port) { rak_peer->Ping(address, port, true, 0); }); } diff --git a/mods/src/options/options.cpp b/mods/src/options/options.cpp index 667803b8..e39de2e5 100644 --- a/mods/src/options/options.cpp +++ b/mods/src/options/options.cpp @@ -99,7 +99,7 @@ static std::vector OptionsFile_getOptionStrings_v2(OptionsFile *opt const std::string path = options_file->options_txt_path; // Parse std::vector ret; - std::ifstream stream(path, std::ios::binary); + std::ifstream stream(path); if (stream) { std::string line; while (std::getline(stream, line)) { diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 9f52f746..cb04bc46 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -38,22 +38,21 @@ ServerProperties &get_server_properties() { } // Default Server Properties -struct ServerPropertyTypes { - const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day"); - const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List"); - const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)"); - const ServerProperty port = ServerProperty("port", "19132", "Port"); - const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)"); - const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)"); - const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)"); - const ServerProperty world_name = ServerProperty("world-name", "world", "World To Select"); - const ServerProperty max_players = ServerProperty("max-players", "4", "Maximum Player Count"); - const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist"); - const ServerProperty enable_death_messages = ServerProperty("death-messages", "true", "Enable Death Messages"); - const ServerProperty enable_cave_generation = ServerProperty("generate-caves", "true", "Generate Caves"); -}; -static ServerPropertyTypes &get_property_types() { - static ServerPropertyTypes types; +static auto &get_property_types() { + static struct { + const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day"); + const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List"); + const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)"); + const ServerProperty port = ServerProperty("port", std::to_string(DEFAULT_MULTIPLAYER_PORT), "Port"); + const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)"); + const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)"); + const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)"); + const ServerProperty world_name = ServerProperty("world-name", "world", "World To Select"); + const ServerProperty max_players = ServerProperty("max-players", "4", "Maximum Player Count"); + const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist"); + const ServerProperty enable_death_messages = ServerProperty("death-messages", "true", "Enable Death Messages"); + const ServerProperty enable_cave_generation = ServerProperty("generate-caves", "true", "Generate Caves"); + } types; return types; } diff --git a/mods/src/server/server_properties.cpp b/mods/src/server/server_properties.cpp index 7552781c..13caafde 100644 --- a/mods/src/server/server_properties.cpp +++ b/mods/src/server/server_properties.cpp @@ -1,3 +1,5 @@ +#include + #include std::vector &ServerProperty::get_all() { @@ -6,7 +8,7 @@ std::vector &ServerProperty::get_all() { } static bool is_true(std::string const& val) { - return (val == "true" || val == "yes" || val == "1"); + return val == "true" || val == "yes" || val == "1"; } void ServerProperties::load(std::istream &stream) {