Add Servers Tab To Launcher
This commit is contained in:
parent
bddc299664
commit
c531e7ba7d
2
dependencies/runtime/src
vendored
2
dependencies/runtime/src
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 043d926f32a3315d92bce1d9bbb0ccdcf99c11a2
|
Subproject commit c1b4b02770dee1f5dfca2ca21a627baf10942cde
|
@ -107,6 +107,7 @@
|
|||||||
* `overwrite_calls` Now Scans VTables
|
* `overwrite_calls` Now Scans VTables
|
||||||
* Unify Server/Client Builds
|
* Unify Server/Client Builds
|
||||||
* Controller Support Removed
|
* Controller Support Removed
|
||||||
|
* Brand New Launcher UI!
|
||||||
|
|
||||||
**2.5.3**
|
**2.5.3**
|
||||||
* Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default)
|
* Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default)
|
||||||
|
@ -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
|
|
||||||
```
|
|
@ -3,7 +3,6 @@
|
|||||||
* [View Dedicated Server](DEDICATED_SERVER.md)
|
* [View Dedicated Server](DEDICATED_SERVER.md)
|
||||||
* [View Credits](CREDITS.md)
|
* [View Credits](CREDITS.md)
|
||||||
* [View Terminology](TERMINOLOGY.md)
|
* [View Terminology](TERMINOLOGY.md)
|
||||||
* [View Multiplayer](MULTIPLAYER.md)
|
|
||||||
* [View In-Game Controls](CONTROLS.md)
|
* [View In-Game Controls](CONTROLS.md)
|
||||||
* [View Custom Skins](CUSTOM_SKINS.md)
|
* [View Custom Skins](CUSTOM_SKINS.md)
|
||||||
* [View Changelog](CHANGELOG.md)
|
* [View Changelog](CHANGELOG.md)
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "../options/parser.h"
|
#include "../options/parser.h"
|
||||||
#include "cache.h"
|
|
||||||
#include "../ui/frame.h"
|
#include "../ui/frame.h"
|
||||||
|
|
||||||
#include <libreborn/flags.h>
|
#include <libreborn/flags.h>
|
||||||
|
#include <libreborn/servers.h>
|
||||||
|
|
||||||
// Default Configuration
|
// Default Configuration
|
||||||
#define DEFAULT_USERNAME "StevePi"
|
#define DEFAULT_USERNAME "StevePi"
|
||||||
@ -29,15 +29,25 @@ struct ConfigurationUI final : Frame {
|
|||||||
explicit ConfigurationUI(State &state_, bool &save_settings_);
|
explicit ConfigurationUI(State &state_, bool &save_settings_);
|
||||||
int render() override;
|
int render() override;
|
||||||
private:
|
private:
|
||||||
void update_render_distance();
|
// Bottom Row
|
||||||
int draw_bottom();
|
int get_render_distance_index() const;
|
||||||
void draw_main();
|
int draw_bottom(bool hide_reset_revert) const;
|
||||||
|
// General
|
||||||
|
void draw_main() const;
|
||||||
|
// Advanced
|
||||||
void draw_advanced() const;
|
void draw_advanced() const;
|
||||||
static void draw_category(FlagNode &category);
|
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;
|
const State original_state;
|
||||||
State &state;
|
State &state;
|
||||||
bool &save_settings;
|
bool &save_settings;
|
||||||
int render_distance_index;
|
// Server List
|
||||||
|
ServerList last_saved_servers;
|
||||||
|
ServerList servers;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle Non-Launch Commands
|
// Handle Non-Launch Commands
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <libreborn/util.h>
|
||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include <imgui_stdlib.h>
|
#include <imgui_stdlib.h>
|
||||||
|
|
||||||
// Render Distances
|
// Render Distances
|
||||||
static std::vector render_distances = {
|
static constexpr std::array render_distances = {
|
||||||
"Far",
|
"Far",
|
||||||
"Normal",
|
"Normal",
|
||||||
"Short",
|
"Short",
|
||||||
"Tiny"
|
"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
|
// Construct
|
||||||
static constexpr int size = 400;
|
static constexpr int size = 400;
|
||||||
ConfigurationUI::ConfigurationUI(State &state_, bool &save_settings_):
|
ConfigurationUI::ConfigurationUI(State &state_, bool &save_settings_):
|
||||||
Frame("Launcher", size, size),
|
Frame("Launcher", size, size),
|
||||||
original_state(state_),
|
original_state(state_),
|
||||||
state(state_),
|
state(state_),
|
||||||
save_settings(save_settings_) {
|
save_settings(save_settings_) {}
|
||||||
update_render_distance();
|
|
||||||
}
|
|
||||||
void ConfigurationUI::update_render_distance() {
|
|
||||||
render_distance_index = 0;
|
|
||||||
for (std::vector<std::string>::size_type i = 0; i < render_distances.size(); i++) {
|
|
||||||
if (std::string(render_distances[i]) == state.render_distance) {
|
|
||||||
render_distance_index = int(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
int ConfigurationUI::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)) {
|
if (ImGui::BeginChild("Main", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() /* Leave Room For Bottom Row */), ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||||
// Tabs
|
// Tabs
|
||||||
if (ImGui::BeginTabBar("TabBar")) {
|
if (ImGui::BeginTabBar("TabBar")) {
|
||||||
@ -46,45 +46,64 @@ int ConfigurationUI::render() {
|
|||||||
draw_advanced();
|
draw_advanced();
|
||||||
ImGui::EndTabItem();
|
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::EndTabBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
// Bottom Row
|
// Bottom Row
|
||||||
return draw_bottom();
|
return draw_bottom(on_servers_tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom Row
|
// Bottom Row
|
||||||
int ConfigurationUI::draw_bottom() {
|
int ConfigurationUI::draw_bottom(const bool hide_reset_revert) const {
|
||||||
// Reset All Settings
|
// Reset Settings
|
||||||
const State default_state;
|
if (!hide_reset_revert) {
|
||||||
std::vector<std::tuple<const char *, const char *, const State *>> reset_options = {
|
const State default_state;
|
||||||
{"Revert", "Last Saved", &original_state},
|
constexpr const char *tooltip_type = "Settings";
|
||||||
{"Reset", "Default", &default_state},
|
std::vector<std::tuple<std::string, std::string, const State *>> reset_options = {
|
||||||
};
|
{revert_text, make_tooltip(revert_tooltip_text, tooltip_type), &original_state},
|
||||||
for (const std::tuple<const char *, const char *, const State *> &option : reset_options) {
|
{"Reset", make_tooltip("Default", tooltip_type), &default_state},
|
||||||
const State &new_state = *std::get<2>(option);
|
};
|
||||||
ImGui::BeginDisabled(state == new_state);
|
for (const std::tuple<std::string, std::string, const State *> &option : reset_options) {
|
||||||
if (ImGui::Button(std::get<0>(option))) {
|
const State &new_state = *std::get<2>(option);
|
||||||
state = new_state;
|
ImGui::BeginDisabled(state == new_state);
|
||||||
update_render_distance();
|
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
|
// Right-Align Buttons
|
||||||
int ret = 0;
|
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) {
|
if (id == 0) {
|
||||||
// Quit
|
// Quit
|
||||||
if (was_clicked) {
|
if (was_clicked) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("Changes Will Not Be Saved!");
|
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
|
// Launch
|
||||||
ret = 1;
|
if (unsaved_servers) {
|
||||||
|
ImGui::SetItemTooltip("Server List Is Unsaved");
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
if (was_clicked) {
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Return
|
// Return
|
||||||
@ -92,7 +111,17 @@ int ConfigurationUI::draw_bottom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main Tab
|
// Main Tab
|
||||||
void ConfigurationUI::draw_main() {
|
int ConfigurationUI::get_render_distance_index() const {
|
||||||
|
int render_distance_index = 0;
|
||||||
|
for (std::vector<std::string>::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 ImGuiStyle &style = ImGui::GetStyle();
|
||||||
const char *labels[] = {"Username", "Render Distance"};
|
const char *labels[] = {"Username", "Render Distance"};
|
||||||
// Calculate Label Size
|
// Calculate Label Size
|
||||||
@ -103,21 +132,23 @@ void ConfigurationUI::draw_main() {
|
|||||||
ImGui::PushItemWidth(-label_size);
|
ImGui::PushItemWidth(-label_size);
|
||||||
// Options
|
// Options
|
||||||
ImGui::InputText(labels[0], &state.username);
|
ImGui::InputText(labels[0], &state.username);
|
||||||
ImGui::Combo(labels[1], &render_distance_index, render_distances.data(), int(render_distances.size()));
|
int render_distance_index = get_render_distance_index();
|
||||||
state.render_distance = render_distances[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::PopItemWidth();
|
||||||
ImGui::Checkbox("Save Settings On Launch", &save_settings);
|
ImGui::Checkbox("Save Settings On Launch", &save_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advanced Tab
|
// Advanced Tab
|
||||||
static std::string get_label(const FlagNode &node) {
|
static std::string get_label_for_flag_node(const FlagNode &node) {
|
||||||
return node.name + "##" + std::to_string(node.id);
|
return node.name + "##FlagNode" + std::to_string(node.id);
|
||||||
}
|
}
|
||||||
void ConfigurationUI::draw_advanced() const {
|
void ConfigurationUI::draw_advanced() const {
|
||||||
if (ImGui::BeginChild("Features", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) {
|
if (ImGui::BeginChild("Features", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
// Categories
|
// Categories
|
||||||
for (FlagNode &category : state.flags.root.children) {
|
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())) {
|
if (ImGui::CollapsingHeader(label.c_str())) {
|
||||||
draw_category(category);
|
draw_category(category);
|
||||||
}
|
}
|
||||||
@ -125,18 +156,107 @@ void ConfigurationUI::draw_advanced() const {
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feature Categories
|
|
||||||
void ConfigurationUI::draw_category(FlagNode &category) {
|
void ConfigurationUI::draw_category(FlagNode &category) {
|
||||||
for (FlagNode &child : category.children) {
|
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()) {
|
if (!child.children.empty()) {
|
||||||
|
// Sub-Category
|
||||||
if (ImGui::TreeNode(label.c_str())) {
|
if (ImGui::TreeNode(label.c_str())) {
|
||||||
draw_category(child);
|
draw_category(child);
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Flag
|
||||||
ImGui::Checkbox(label.c_str(), &child.value);
|
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<ServerList::Entry>::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<ServerList::port_t>::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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@
|
|||||||
struct CrashReport final : Frame {
|
struct CrashReport final : Frame {
|
||||||
explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) {
|
explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) {
|
||||||
// Open File
|
// 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) {
|
if (stream) {
|
||||||
// Read File
|
// Read File
|
||||||
const std::streamoff size = stream.tellg();
|
const std::streamoff size = stream.tellg();
|
||||||
@ -53,7 +53,7 @@ struct CrashReport final : Frame {
|
|||||||
// Right-Aligned
|
// Right-Aligned
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
const std::string &log_ref = log;
|
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 (was_clicked) {
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
// Copy Log
|
// Copy Log
|
||||||
|
@ -69,7 +69,7 @@ int Frame::run() {
|
|||||||
int width, height;
|
int width, height;
|
||||||
glfwGetFramebufferSize(window, &width, &height);
|
glfwGetFramebufferSize(window, &width, &height);
|
||||||
ImGui::SetNextWindowSize({float(width), float(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();
|
ret = render();
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
@ -103,6 +103,10 @@ void Frame::setup_style(const float scale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Right-Aligned Buttons
|
// 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<const char *> &buttons, const std::function<void(int, bool)> &callback) {
|
void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback) {
|
||||||
// Calculate Position
|
// Calculate Position
|
||||||
const ImGuiStyle &style = ImGui::GetStyle();
|
const ImGuiStyle &style = ImGui::GetStyle();
|
||||||
@ -111,7 +115,7 @@ void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons,
|
|||||||
if (width_needed > 0) {
|
if (width_needed > 0) {
|
||||||
width_needed += style.ItemSpacing.x;
|
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);
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed);
|
||||||
// Draw
|
// Draw
|
||||||
|
@ -19,7 +19,9 @@ struct Frame {
|
|||||||
protected:
|
protected:
|
||||||
// API For Sub-Classes
|
// API For Sub-Classes
|
||||||
ImFont *monospace = nullptr;
|
ImFont *monospace = nullptr;
|
||||||
|
static float get_frame_width(const char *str);
|
||||||
static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback);
|
static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback);
|
||||||
|
static constexpr const char *quit_text = "Quit";
|
||||||
private:
|
private:
|
||||||
// Properties
|
// Properties
|
||||||
GLFWwindow *window = nullptr;
|
GLFWwindow *window = nullptr;
|
||||||
|
@ -16,6 +16,7 @@ add_library(reborn-util SHARED
|
|||||||
src/util/flags/node.cpp
|
src/util/flags/node.cpp
|
||||||
src/util/flags/flags.cpp
|
src/util/flags/flags.cpp
|
||||||
src/util/flags/available-feature-flags # Show In IDE
|
src/util/flags/available-feature-flags # Show In IDE
|
||||||
|
src/util/servers.cpp
|
||||||
)
|
)
|
||||||
embed_resource(reborn-util src/util/flags/available-feature-flags)
|
embed_resource(reborn-util src/util/flags/available-feature-flags)
|
||||||
target_link_libraries(reborn-util PRIVATE utf8cpp)
|
target_link_libraries(reborn-util PRIVATE utf8cpp)
|
||||||
|
20
libreborn/include/libreborn/servers.h
Normal file
20
libreborn/include/libreborn/servers.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Parse servers.txt
|
||||||
|
struct ServerList {
|
||||||
|
// Type
|
||||||
|
typedef unsigned short port_t;
|
||||||
|
typedef std::pair<std::string, port_t> Entry;
|
||||||
|
// Load
|
||||||
|
static port_t parse_port(const std::string &s);
|
||||||
|
ServerList();
|
||||||
|
// Save
|
||||||
|
std::string to_string() const;
|
||||||
|
void save() const;
|
||||||
|
// Entries
|
||||||
|
std::vector<Entry> entries{};
|
||||||
|
};
|
@ -60,4 +60,8 @@ std::string home_get();
|
|||||||
|
|
||||||
// Format Time
|
// Format Time
|
||||||
std::string format_time(const char *fmt);
|
std::string format_time(const char *fmt);
|
||||||
std::string format_time(const char *fmt, int time);
|
std::string format_time(const char *fmt, int time);
|
||||||
|
|
||||||
|
// Default MCPI Port
|
||||||
|
// This Macro DOES NOT Control MCPI
|
||||||
|
#define DEFAULT_MULTIPLAYER_PORT 19132
|
82
libreborn/src/util/servers.cpp
Normal file
82
libreborn/src/util/servers.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <libreborn/servers.h>
|
||||||
|
#include <libreborn/util.h>
|
||||||
|
|
||||||
|
// 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<port_t>::max();
|
||||||
|
if (port > max) {
|
||||||
|
port = max;
|
||||||
|
}
|
||||||
|
return static_cast<port_t>(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();
|
||||||
|
}
|
@ -1,16 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <istream>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct ServerProperty {
|
struct ServerProperty {
|
||||||
static std::vector<const ServerProperty *> &get_all();
|
static std::vector<const ServerProperty *> &get_all();
|
||||||
const char *const key;
|
const std::string key;
|
||||||
const char *const def;
|
const std::string def;
|
||||||
const char *const comment;
|
const std::string comment;
|
||||||
ServerProperty(const char *const key_, const char *const def_, const char *const comment_):
|
ServerProperty(const std::string &key_, const std::string &def_, const std::string &comment_):
|
||||||
key(key_),
|
key(key_),
|
||||||
def(def_),
|
def(def_),
|
||||||
comment(comment_) {
|
comment(comment_) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <symbols/minecraft.h>
|
#include <symbols/minecraft.h>
|
||||||
|
|
||||||
#include <libreborn/patch.h>
|
#include <libreborn/patch.h>
|
||||||
|
#include <libreborn/util.h>
|
||||||
|
|
||||||
#include <mods/text-input-box/TextInputScreen.h>
|
#include <mods/text-input-box/TextInputScreen.h>
|
||||||
#include <mods/touch/touch.h>
|
#include <mods/touch/touch.h>
|
||||||
@ -209,7 +210,7 @@ static void create_world(Minecraft *minecraft, std::string name, const bool is_c
|
|||||||
minecraft->selectLevel(folder, name, settings);
|
minecraft->selectLevel(folder, name, settings);
|
||||||
|
|
||||||
// Multiplayer
|
// Multiplayer
|
||||||
minecraft->hostMultiplayer(19132);
|
minecraft->hostMultiplayer(DEFAULT_MULTIPLAYER_PORT);
|
||||||
|
|
||||||
// Open ProgressScreen
|
// Open ProgressScreen
|
||||||
ProgressScreen *screen = ProgressScreen::allocate();
|
ProgressScreen *screen = ProgressScreen::allocate();
|
||||||
|
@ -204,7 +204,7 @@ static void Player_stopUsingItem_injection(Player_stopUsingItem_t original, Play
|
|||||||
// Read Asset File
|
// 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) {
|
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
|
// 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) {
|
if (!stream) {
|
||||||
// Does Not Exist
|
// Does Not Exist
|
||||||
AppPlatform_readAssetFile_return_value ret;
|
AppPlatform_readAssetFile_return_value ret;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#include <fstream>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -6,102 +5,25 @@
|
|||||||
#include <symbols/minecraft.h>
|
#include <symbols/minecraft.h>
|
||||||
|
|
||||||
#include <libreborn/patch.h>
|
#include <libreborn/patch.h>
|
||||||
#include <libreborn/util.h>
|
#include <libreborn/servers.h>
|
||||||
|
|
||||||
#include <mods/init/init.h>
|
#include <mods/init/init.h>
|
||||||
#include <mods/feature/feature.h>
|
#include <mods/feature/feature.h>
|
||||||
|
|
||||||
// Load Server List
|
// Iterate Server List
|
||||||
struct server_list_entry {
|
static void iterate_servers(const std::function<void(const char *address, ServerList::port_t port)> &callback) {
|
||||||
std::string address;
|
|
||||||
int port;
|
|
||||||
};
|
|
||||||
static std::vector<server_list_entry> 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<void(const char *address, int port)> &callback) {
|
|
||||||
// Load
|
// Load
|
||||||
if (!server_list_loaded) {
|
static ServerList server_list;
|
||||||
load_servers();
|
|
||||||
server_list_loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
for (const server_list_entry &entry : server_list_entries) {
|
for (const ServerList::Entry &entry : server_list.entries) {
|
||||||
callback(entry.address.c_str(), entry.port);
|
if (!entry.first.empty() && entry.second > 0) {
|
||||||
|
callback(entry.first.c_str(), entry.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping External Servers
|
// 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
|
// Call Original Method
|
||||||
original(rak_net_instance, base_port);
|
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;
|
RakNet_RakPeer *rak_peer = rak_net_instance->peer;
|
||||||
|
|
||||||
// Add External Servers
|
// 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);
|
rak_peer->Ping(address, port, true, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ static std::vector<std::string> OptionsFile_getOptionStrings_v2(OptionsFile *opt
|
|||||||
const std::string path = options_file->options_txt_path;
|
const std::string path = options_file->options_txt_path;
|
||||||
// Parse
|
// Parse
|
||||||
std::vector<std::string> ret;
|
std::vector<std::string> ret;
|
||||||
std::ifstream stream(path, std::ios::binary);
|
std::ifstream stream(path);
|
||||||
if (stream) {
|
if (stream) {
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(stream, line)) {
|
while (std::getline(stream, line)) {
|
||||||
|
@ -38,22 +38,21 @@ ServerProperties &get_server_properties() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default Server Properties
|
// Default Server Properties
|
||||||
struct ServerPropertyTypes {
|
static auto &get_property_types() {
|
||||||
const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day");
|
static struct {
|
||||||
const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List");
|
const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day");
|
||||||
const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)");
|
const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List");
|
||||||
const ServerProperty port = ServerProperty("port", "19132", "Port");
|
const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)");
|
||||||
const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)");
|
const ServerProperty port = ServerProperty("port", std::to_string(DEFAULT_MULTIPLAYER_PORT), "Port");
|
||||||
const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)");
|
const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)");
|
||||||
const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)");
|
const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)");
|
||||||
const ServerProperty world_name = ServerProperty("world-name", "world", "World To Select");
|
const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)");
|
||||||
const ServerProperty max_players = ServerProperty("max-players", "4", "Maximum Player Count");
|
const ServerProperty world_name = ServerProperty("world-name", "world", "World To Select");
|
||||||
const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist");
|
const ServerProperty max_players = ServerProperty("max-players", "4", "Maximum Player Count");
|
||||||
const ServerProperty enable_death_messages = ServerProperty("death-messages", "true", "Enable Death Messages");
|
const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist");
|
||||||
const ServerProperty enable_cave_generation = ServerProperty("generate-caves", "true", "Generate Caves");
|
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() {
|
} types;
|
||||||
static ServerPropertyTypes types;
|
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include <mods/server/server_properties.h>
|
#include <mods/server/server_properties.h>
|
||||||
|
|
||||||
std::vector<const ServerProperty *> &ServerProperty::get_all() {
|
std::vector<const ServerProperty *> &ServerProperty::get_all() {
|
||||||
@ -6,7 +8,7 @@ std::vector<const ServerProperty *> &ServerProperty::get_all() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool is_true(std::string const& val) {
|
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) {
|
void ServerProperties::load(std::istream &stream) {
|
||||||
|
Loading…
Reference in New Issue
Block a user