From 109417ea56623cb9f899745cfabdbc7f94151896 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:08:51 +0000 Subject: [PATCH 01/11] Initial plan From e116e20d5ecb49070b4e51db5ea32050076c1a5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:23:52 +0000 Subject: [PATCH 02/11] Add auto-connect feature for local development Co-authored-by: adam4813 <3011917+adam4813@users.noreply.github.com> --- README.md | 18 ++++++++++++++++++ client/main.cpp | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00b050c7..44b7f4af 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,24 @@ Install the following. This is a pretty extensive list and may be more than need * `ninja-multi-vcpkg-debug` - Debug configuration test preset * `ninja-multi-vcpkg-release` - Release configuration test preset +# Configuration +The client can be configured using a Lua configuration file at `assets/scripts/config.lua`. If the file doesn't exist, default values will be used. + +## Auto-Connect for Local Development +For local development, you can enable automatic connection to a local server by setting the following in your config file: + +```lua +-- Window settings +window_width = 1024 +window_height = 768 + +-- Auto-connect settings (for local development) +auto_connect = true +default_username = "player" +``` + +When `auto_connect` is enabled and `default_username` is set, the client will automatically connect to `localhost` on startup, eliminating the need to manually enter credentials each time. If auto-connect fails, the connection window will be shown as normal. + # Documentation Documentation is done via Doxygen for C++ code and supplementary docs must be maintained for the Lua API in `docs/`. diff --git a/client/main.cpp b/client/main.cpp index ee6115ef..245ef286 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -133,7 +133,40 @@ int main(int argc, char* argv[]) { gui.AddWindowDrawFunction(server_connect_window.GetWindowName(), [&server_connect_window, &gui]() { server_connect_window.Draw(&gui); }); - gui.ShowWindow(server_connect_window.GetWindowName()); + + // Check for auto-connect configuration + bool auto_connect = game.config_script->environment.get_or("auto_connect", false); + std::string default_username = game.config_script->environment.get_or("default_username", std::string("")); + + if (auto_connect && !default_username.empty()) { + log->info("Auto-connecting to localhost as '{}'", default_username); + connection.RegisterConnectFunc([&connection, default_username, &log]() { + tec::proto::UserLogin user_login; + user_login.set_username(default_username); + user_login.set_password(""); + tec::networking::MessageOut msg(tec::networking::LOGIN); + user_login.SerializeToZeroCopyStream(&msg); + connection.Send(msg); + log->info("Auto-login sent for user '{}'", default_username); + }); + if (connection.Connect(tec::networking::LOCAL_HOST)) { + // Hide the connect window on successful AUTHENTICATED message + std::string window_name = server_connect_window.GetWindowName(); + connection.RegisterMessageHandler( + tec::networking::MessageType::AUTHENTICATED, + [&gui, window_name, &log](tec::networking::MessageIn&) { + log->info("Auto-connect authentication successful"); + gui.HideWindow(window_name); + }); + } else { + log->warn("Auto-connect failed, showing connect window"); + gui.ShowWindow(server_connect_window.GetWindowName()); + } + } else { + // Show connect window normally if auto-connect is disabled + gui.ShowWindow(server_connect_window.GetWindowName()); + } + gui.AddWindowDrawFunction( ping_times_window.GetWindowName(), [&ping_times_window, &gui]() { ping_times_window.Draw(&gui); }); gui.AddWindowDrawFunction(console.GetWindowName(), [&console, &gui]() { console.Draw(&gui); }); From 9b3fa951970111a7f8b696ff3dac64d2151ddcc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:26:43 +0000 Subject: [PATCH 03/11] Fix race condition: register handlers before connecting Co-authored-by: adam4813 <3011917+adam4813@users.noreply.github.com> --- client/main.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/client/main.cpp b/client/main.cpp index 245ef286..16bd210b 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -140,6 +140,8 @@ int main(int argc, char* argv[]) { if (auto_connect && !default_username.empty()) { log->info("Auto-connecting to localhost as '{}'", default_username); + + // Register handlers before connecting to avoid race condition connection.RegisterConnectFunc([&connection, default_username, &log]() { tec::proto::UserLogin user_login; user_login.set_username(default_username); @@ -149,16 +151,17 @@ int main(int argc, char* argv[]) { connection.Send(msg); log->info("Auto-login sent for user '{}'", default_username); }); - if (connection.Connect(tec::networking::LOCAL_HOST)) { - // Hide the connect window on successful AUTHENTICATED message - std::string window_name = server_connect_window.GetWindowName(); - connection.RegisterMessageHandler( - tec::networking::MessageType::AUTHENTICATED, - [&gui, window_name, &log](tec::networking::MessageIn&) { - log->info("Auto-connect authentication successful"); - gui.HideWindow(window_name); - }); - } else { + + // Hide the connect window on successful AUTHENTICATED message + std::string window_name = server_connect_window.GetWindowName(); + connection.RegisterMessageHandler( + tec::networking::MessageType::AUTHENTICATED, + [&gui, window_name, &log](tec::networking::MessageIn&) { + log->info("Auto-connect authentication successful"); + gui.HideWindow(window_name); + }); + + if (!connection.Connect(tec::networking::LOCAL_HOST)) { log->warn("Auto-connect failed, showing connect window"); gui.ShowWindow(server_connect_window.GetWindowName()); } From 581e869b8a8132c3cab06b870d5958be28f60789 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:28:26 +0000 Subject: [PATCH 04/11] Refactor: eliminate duplicate config reads, add capture clarification Co-authored-by: adam4813 <3011917+adam4813@users.noreply.github.com> --- client/main.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/main.cpp b/client/main.cpp index 16bd210b..e0026637 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -121,7 +121,11 @@ int main(int argc, char* argv[]) { tec::ServerConnectWindow server_connect_window(connection); tec::PingTimesWindow ping_times_window(connection); tec::DebugInfo debug_info_window(game); - tec::ServerConnectWindow::SetUsername(game.config_script->environment.get_or("default_username", std::string(""))); + + // Read configuration once for use in both GUI and auto-connect + std::string default_username = game.config_script->environment.get_or("default_username", std::string("")); + bool auto_connect = game.config_script->environment.get_or("auto_connect", false); + tec::ServerConnectWindow::SetUsername(default_username); console.AddConsoleCommand("msg", "msg : Send a message to all clients.", [&connection](const std::string& message) { connection.SendChatMessage(message); @@ -134,10 +138,6 @@ int main(int argc, char* argv[]) { server_connect_window.Draw(&gui); }); - // Check for auto-connect configuration - bool auto_connect = game.config_script->environment.get_or("auto_connect", false); - std::string default_username = game.config_script->environment.get_or("default_username", std::string("")); - if (auto_connect && !default_username.empty()) { log->info("Auto-connecting to localhost as '{}'", default_username); @@ -153,6 +153,7 @@ int main(int argc, char* argv[]) { }); // Hide the connect window on successful AUTHENTICATED message + // Capture window_name by value to ensure it remains valid for the async handler std::string window_name = server_connect_window.GetWindowName(); connection.RegisterMessageHandler( tec::networking::MessageType::AUTHENTICATED, From d1f0932444e36026ceeed44786c51003f39a2202 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:29:45 +0000 Subject: [PATCH 05/11] Add comment clarifying lambda capture strategy Co-authored-by: adam4813 <3011917+adam4813@users.noreply.github.com> --- client/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/main.cpp b/client/main.cpp index e0026637..2f05ae96 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -142,6 +142,7 @@ int main(int argc, char* argv[]) { log->info("Auto-connecting to localhost as '{}'", default_username); // Register handlers before connecting to avoid race condition + // Capture default_username by value for async safety (prevents future modifications from affecting the login) connection.RegisterConnectFunc([&connection, default_username, &log]() { tec::proto::UserLogin user_login; user_login.set_username(default_username); From bdc7407d57dc2b4c041f65629bc762da0bd905cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:46:37 +0000 Subject: [PATCH 06/11] Refactor main.cpp into ClientApp class for better maintainability Co-authored-by: adam4813 <3011917+adam4813@users.noreply.github.com> --- client/CMakeLists.txt | 1 + client/client-app.cpp | 295 ++++++++++++++++++++++++++++++++++++++++++ client/client-app.hpp | 64 +++++++++ client/main.cpp | 261 +------------------------------------ 4 files changed, 364 insertions(+), 257 deletions(-) create mode 100644 client/client-app.cpp create mode 100644 client/client-app.hpp diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 3652aec2..c6f7fe46 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -8,6 +8,7 @@ add_interface_lib( TARGET ${CLIENT_LIB_NAME} FILE_LIST + client-app.cpp game.cpp imgui-system.cpp client-game-state-queue.cpp diff --git a/client/client-app.cpp b/client/client-app.cpp new file mode 100644 index 00000000..06a77225 --- /dev/null +++ b/client/client-app.cpp @@ -0,0 +1,295 @@ +#include "client-app.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "events.hpp" +#include "filesystem.hpp" +#include "net-message.hpp" +#include "resources/md5anim.hpp" +#include "resources/md5mesh.hpp" +#include "resources/obj.hpp" +#include "resources/script-file.hpp" +#include "resources/vorbis-stream.hpp" +#include "server-connection.hpp" + +namespace tec { + +namespace { + +void RegisterFileFactories() { + AddFileFactory(); + AddFileFactory(); + AddFileFactory(); + AddFileFactory(); + AddFileFactory(); +} + +std::string CalculateAspectRatioString(unsigned int window_width, unsigned int window_height) { + double ratio = static_cast(window_width) / static_cast(window_height); + double intVal = std::floor(ratio); + double precision = 1000; + double fraction = std::round((ratio - intVal) * precision); + double gcd = std::gcd(static_cast(fraction), static_cast(precision)); + double numer = fraction / gcd; + double denom = precision / gcd; + + std::ostringstream aspect_ratio; + aspect_ratio << static_cast(std::round((intVal * denom) + numer)) << ":" + << static_cast(std::round(denom)); + return aspect_ratio.str(); +} + +} // anonymous namespace + +ClientApp::ClientApp(int argc, char* argv[]) { + auto log_level = ParseLogLevel(argc, argv); + InitializeLogger(log_level); + + log_->info("Asset path: {}", Path::GetAssetsBasePath()); + + game_ = std::make_unique(os_); + LoadConfiguration(); + InitializeWindow(); + InitializeGame(); + InitializeGUI(); + RegisterConsoleCommands(); + SetupAutoConnect(); + RegisterGUIWindows(); + + RegisterFileFactories(); + BuildTestVoxelVolume(); +} + +spdlog::level::level_enum ClientApp::ParseLogLevel(int argc, char* argv[]) { + auto log_level = spdlog::level::info; + for (int i = 1; i < argc; i++) { + if (std::string(argv[i]) == "-v") { + log_level = spdlog::level::debug; + } + else if (std::string(argv[i]) == "-vv") { + log_level = spdlog::level::trace; + } + } + return log_level; +} + +void ClientApp::InitializeLogger(spdlog::level::level_enum log_level) { + std::vector sinks; + sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared(console_)); + log_ = std::make_shared("console_log", begin(sinks), end(sinks)); + log_->set_level(log_level); + log_->set_pattern("%v"); + spdlog::register_logger(log_); +} + +void ClientApp::LoadConfiguration() { + window_width_ = game_->config_script->environment.get_or("window_width", WINDOW_WIDTH); + window_height_ = game_->config_script->environment.get_or("window_height", WINDOW_HEIGHT); + default_username_ = game_->config_script->environment.get_or("default_username", std::string("")); + auto_connect_ = game_->config_script->environment.get_or("auto_connect", false); +} + +void ClientApp::InitializeWindow() { + log_->info("Initializing OpenGL..."); + if (!os_.InitializeWindow(window_width_, window_height_, "Trillek Engine 0.14", 4, 0)) { + log_->warn("The OpenGL 4.0 context wasn't created properly, attempting fallback"); + if (!os_.InitializeWindow(window_width_, window_height_, "Trillek Engine 0.14", 3, 3)) { + log_->error("Exiting. Can not create OpenGL 4.0 or 3.3 context. please update drivers and try again."); + exit(1); + } + } + + const std::string default_aspect_ratio = CalculateAspectRatioString(window_width_, window_height_); + const std::string aspect_ratio = game_->config_script->environment.get_or("aspect_ratio", default_aspect_ratio); + auto numer = stoi(aspect_ratio.substr(0, aspect_ratio.find(':'))); + auto denom = stoi(aspect_ratio.substr(aspect_ratio.find(':') + 1)); + os_.SetWindowAspectRatio(numer, denom); +} + +void ClientApp::InitializeGame() { + console_.AddConsoleCommand("exit", "exit : Exit from TEC", [this](const std::string&) { os_.Quit(); }); + game_->Startup(console_); +} + +void ClientApp::InitializeGUI() { + log_->info("Initializing GUI system..."); + gui_ = std::make_unique(os_.GetWindow()); + gui_->CreateGUI(); + + // Create GUI windows + auto& connection = game_->GetServerConnection(); + active_entity_tooltip_ = std::make_unique(*game_); + server_connect_window_ = std::make_unique(connection); + ping_times_window_ = std::make_unique(connection); + debug_info_window_ = std::make_unique(*game_); + + ServerConnectWindow::SetUsername(default_username_); +} + +void ClientApp::RegisterConsoleCommands() { + auto& connection = game_->GetServerConnection(); + auto* lua_sys = game_->GetLuaSystem(); + + console_.AddConsoleCommand("msg", "msg : Send a message to all clients.", + [&connection](const std::string& message) { + connection.SendChatMessage(message); + }); + + console_.AddConsoleCommand("lua", "lua : Execute a string in lua", + [lua_sys](const std::string& message) { + lua_sys->ExecuteString(message); + }); + + console_.AddConsoleCommand( + "connect", + "connect [username][ip] : Connect to a server [ip] with the provided [username]", + [&connection](const std::string& args) { + std::vector splitArgs = SplitString(args); + if (splitArgs.size() >= 2) { + connection.Connect(splitArgs[1]); + std::string username = splitArgs[0]; + connection.RegisterConnectFunc([&connection, username]() { + proto::UserLogin user_login; + user_login.set_username(username); + user_login.set_password(""); + networking::MessageOut msg(networking::LOGIN); + user_login.SerializeToZeroCopyStream(&msg); + connection.Send(msg); + }); + } + }); + + console_.AddSlashHandler([&connection](const std::string& chat_command_message) { + std::size_t argument_break_offset = chat_command_message.find_first_of(" "); + auto data = std::make_shared(); + data->command = chat_command_message.substr(0, argument_break_offset); + + if (argument_break_offset < chat_command_message.size()) { + std::string command_args = chat_command_message.substr(argument_break_offset + 1); + data->args = SplitString(command_args); + } + EventSystem::Get()->Emit(data); + + if (connection.GetClientID() != 0) { + proto::ChatCommand chat_command(data->Out()); + networking::MessageOut msg(networking::CHAT_COMMAND); + chat_command.SerializeToZeroCopyStream(&msg); + connection.Send(msg); + } + }); + + lua_sys->GetGlobalState().set("OS", &os_); +} + +void ClientApp::SetupAutoConnect() { + auto& connection = game_->GetServerConnection(); + + if (auto_connect_ && !default_username_.empty()) { + log_->info("Auto-connecting to localhost as '{}'", default_username_); + + // Register handlers before connecting to avoid race condition + connection.RegisterConnectFunc([&connection, username = default_username_, log = log_]() { + proto::UserLogin user_login; + user_login.set_username(username); + user_login.set_password(""); + networking::MessageOut msg(networking::LOGIN); + user_login.SerializeToZeroCopyStream(&msg); + connection.Send(msg); + log->info("Auto-login sent for user '{}'", username); + }); + + // Hide the connect window on successful AUTHENTICATED message + std::string window_name = server_connect_window_->GetWindowName(); + connection.RegisterMessageHandler( + networking::MessageType::AUTHENTICATED, + [gui = gui_.get(), window_name, log = log_](networking::MessageIn&) { + log->info("Auto-connect authentication successful"); + gui->HideWindow(window_name); + }); + + if (!connection.Connect(networking::LOCAL_HOST)) { + log_->warn("Auto-connect failed, showing connect window"); + gui_->ShowWindow(server_connect_window_->GetWindowName()); + } + } + else { + gui_->ShowWindow(server_connect_window_->GetWindowName()); + } +} + +void ClientApp::RegisterGUIWindows() { + auto& connection = game_->GetServerConnection(); + + gui_->AddWindowDrawFunction(server_connect_window_->GetWindowName(), + [window = server_connect_window_.get(), gui = gui_.get()]() { + window->Draw(gui); + }); + + gui_->AddWindowDrawFunction(ping_times_window_->GetWindowName(), + [window = ping_times_window_.get(), gui = gui_.get()]() { + window->Draw(gui); + }); + + gui_->AddWindowDrawFunction(console_.GetWindowName(), + [console = &console_, gui = gui_.get()]() { + console->Draw(gui); + }); + gui_->ShowWindow(console_.GetWindowName()); + + gui_->AddWindowDrawFunction(active_entity_tooltip_->GetWindowName(), + [window = active_entity_tooltip_.get(), gui = gui_.get()]() { + window->Draw(gui); + }); + gui_->ShowWindow(active_entity_tooltip_->GetWindowName()); + + gui_->AddWindowDrawFunction(debug_info_window_->GetWindowName(), + [window = debug_info_window_.get(), gui = gui_.get()]() { + window->Draw(gui); + }); + + connection.RegisterMessageHandler( + networking::MessageType::CLIENT_ID, + [gui = gui_.get(), window = ping_times_window_.get(), log = log_](networking::MessageIn& message) { + std::string client_id = message.ToString(); + log->info("You are connected as client ID {}", client_id); + gui->ShowWindow(window->GetWindowName()); + }); +} + +int ClientApp::Run() { + os_.DetachContext(); + + std::thread gameThread([this]() { + os_.MakeCurrent(); + + while (!os_.Closing()) { + const double delta = os_.GetDeltaTime(); + + game_->Update(delta); + gui_->Update(delta); + console_.Update(delta); + os_.SwapBuffers(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + while (!os_.Closing()) { + os_.OSMessageLoop(); + } + + gameThread.join(); + OS::Terminate(); + + return 0; +} + +} // namespace tec diff --git a/client/client-app.hpp b/client/client-app.hpp new file mode 100644 index 00000000..8d3659da --- /dev/null +++ b/client/client-app.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include +#include + +#include "game.hpp" +#include "gui/active-entity-tooltip.hpp" +#include "gui/console.hpp" +#include "gui/debug-info.hpp" +#include "gui/server-connect.hpp" +#include "imgui-system.hpp" +#include "os.hpp" + +namespace tec { + +/** + * @brief Main client application class that manages initialization and lifecycle + */ +class ClientApp { +public: + ClientApp(int argc, char* argv[]); + ~ClientApp() = default; + + // Run the application main loop + int Run(); + +private: + // Initialization methods + void InitializeLogger(spdlog::level::level_enum log_level); + void InitializeWindow(); + void InitializeGame(); + void InitializeGUI(); + void RegisterConsoleCommands(); + void SetupAutoConnect(); + void RegisterGUIWindows(); + + // Configuration + spdlog::level::level_enum ParseLogLevel(int argc, char* argv[]); + void LoadConfiguration(); + + // Member variables + Console console_; + OS os_; + std::shared_ptr log_; + std::unique_ptr game_; + std::unique_ptr gui_; + + // GUI windows + std::unique_ptr active_entity_tooltip_; + std::unique_ptr server_connect_window_; + std::unique_ptr ping_times_window_; + std::unique_ptr debug_info_window_; + + // Configuration values + std::string default_username_; + bool auto_connect_ = false; + unsigned int window_width_ = WINDOW_WIDTH; + unsigned int window_height_ = WINDOW_HEIGHT; +}; + +} // namespace tec diff --git a/client/main.cpp b/client/main.cpp index 2f05ae96..e1290b71 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,264 +1,11 @@ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "filesystem.hpp" -#include "game.hpp" -#include "gui/active-entity-tooltip.hpp" -#include "gui/console.hpp" -#include "gui/debug-info.hpp" -#include "gui/server-connect.hpp" -#include "imgui-system.hpp" -#include "net-message.hpp" -#include "os.hpp" -#include "resources/md5anim.hpp" -#include "resources/md5mesh.hpp" -#include "resources/obj.hpp" -#include "resources/script-file.hpp" -#include "resources/vorbis-stream.hpp" -#include "server-connection.hpp" +#include "client-app.hpp" namespace tec { -void RegisterFileFactories() { - AddFileFactory(); - AddFileFactory(); - AddFileFactory(); - AddFileFactory(); - AddFileFactory(); -} +// Forward declaration for external linkage void BuildTestVoxelVolume(); } // namespace tec -auto InitializeLogger(spdlog::level::level_enum log_level, tec::Console& console) { - std::vector sinks; - sinks.push_back(std::make_shared()); - sinks.push_back(std::make_shared(console)); - auto log = std::make_shared("console_log", begin(sinks), end(sinks)); - log->set_level(log_level); - log->set_pattern("%v"); // [%l] [thread %t] %v"); // Format on stdout - spdlog::register_logger(log); - return log; -} - -// TODO write a proper arguments parser -// Now only search for -v or -vv to set log level -auto ParseLogLevel(int argc, char* argv[]) { - auto log_level = spdlog::level::info; - for (int i = 1; i < argc; i++) { - if (std::string(argv[i]) == "-v") { - log_level = spdlog::level::debug; - } - else if (std::string(argv[i]) == "-vv") { - log_level = spdlog::level::trace; - } - } - return log_level; -} - -/** - * \brief Finds an approximate aspect ratio - * Adapted from - * https://www.geeksforgeeks.org/convert-given-decimal-number-into-an-irreducible-fraction/ Could be - * further enhanced to compare values against a known set of ratios to find the best match \return - * std:string The aspect ratio in the form of "A:B" - */ -std::string CalculateAspectRatioString(const unsigned int window_width, const unsigned int window_height) { - double ratio = static_cast(window_width) / static_cast(window_height); - double intVal = std::floor(ratio); - double precision = 1000; - double fraction = std::round((ratio - intVal) * precision); - double gcd = std::gcd(static_cast(fraction), static_cast(precision)); - double numer = fraction / gcd; - double denom = precision / gcd; - - std::ostringstream aspect_ratio; - aspect_ratio << static_cast(std::round((intVal * denom) + numer)) << ":" - << static_cast(std::round(denom)); - return aspect_ratio.str(); -} - int main(int argc, char* argv[]) { - tec::Console console; - tec::OS os; - - auto log = InitializeLogger(ParseLogLevel(argc, argv), console); - - log->info("Asset path: {}", tec::Path::GetAssetsBasePath()); - - tec::Game game(os); - - const unsigned int window_width = game.config_script->environment.get_or("window_width", WINDOW_WIDTH); - const unsigned int window_height = game.config_script->environment.get_or("window_height", WINDOW_HEIGHT); - std::string window_title = "Trillek Engine 0.14"; - - log->info("Initializing OpenGL..."); - if (!os.InitializeWindow(window_width, window_height, window_title, 4, 0)) { - log->warn("The OpenGL 4.0 context wasn't created properly, attempting fallback"); - if (!os.InitializeWindow(window_width, window_height, window_title, 3, 3)) { - log->error("Exiting. Can not create OpenGL 4.0 or 3.3 context. please update drivers and try again."); - exit(1); - } - } - - const std::string default_aspect_ratio = CalculateAspectRatioString(window_width, window_height); - const std::string aspect_ratio = game.config_script->environment.get_or("aspect_ratio", default_aspect_ratio); - auto numer = stoi(aspect_ratio.substr(0, aspect_ratio.find(':'))); - auto denom = stoi(aspect_ratio.substr(aspect_ratio.find(':') + 1)); - os.SetWindowAspectRatio(numer, denom); - console.AddConsoleCommand("exit", "exit : Exit from TEC", [&os](const std::string&) { os.Quit(); }); - game.Startup(console); - - tec::ActiveEntityTooltip active_entity_tooltip(game); - tec::networking::ServerConnection& connection = game.GetServerConnection(); - tec::ServerConnectWindow server_connect_window(connection); - tec::PingTimesWindow ping_times_window(connection); - tec::DebugInfo debug_info_window(game); - - // Read configuration once for use in both GUI and auto-connect - std::string default_username = game.config_script->environment.get_or("default_username", std::string("")); - bool auto_connect = game.config_script->environment.get_or("auto_connect", false); - tec::ServerConnectWindow::SetUsername(default_username); - - console.AddConsoleCommand("msg", "msg : Send a message to all clients.", [&connection](const std::string& message) { - connection.SendChatMessage(message); - }); - - log->info("Initializing GUI system..."); - tec::IMGUISystem gui(os.GetWindow()); - gui.CreateGUI(); - gui.AddWindowDrawFunction(server_connect_window.GetWindowName(), [&server_connect_window, &gui]() { - server_connect_window.Draw(&gui); - }); - - if (auto_connect && !default_username.empty()) { - log->info("Auto-connecting to localhost as '{}'", default_username); - - // Register handlers before connecting to avoid race condition - // Capture default_username by value for async safety (prevents future modifications from affecting the login) - connection.RegisterConnectFunc([&connection, default_username, &log]() { - tec::proto::UserLogin user_login; - user_login.set_username(default_username); - user_login.set_password(""); - tec::networking::MessageOut msg(tec::networking::LOGIN); - user_login.SerializeToZeroCopyStream(&msg); - connection.Send(msg); - log->info("Auto-login sent for user '{}'", default_username); - }); - - // Hide the connect window on successful AUTHENTICATED message - // Capture window_name by value to ensure it remains valid for the async handler - std::string window_name = server_connect_window.GetWindowName(); - connection.RegisterMessageHandler( - tec::networking::MessageType::AUTHENTICATED, - [&gui, window_name, &log](tec::networking::MessageIn&) { - log->info("Auto-connect authentication successful"); - gui.HideWindow(window_name); - }); - - if (!connection.Connect(tec::networking::LOCAL_HOST)) { - log->warn("Auto-connect failed, showing connect window"); - gui.ShowWindow(server_connect_window.GetWindowName()); - } - } else { - // Show connect window normally if auto-connect is disabled - gui.ShowWindow(server_connect_window.GetWindowName()); - } - - gui.AddWindowDrawFunction( - ping_times_window.GetWindowName(), [&ping_times_window, &gui]() { ping_times_window.Draw(&gui); }); - gui.AddWindowDrawFunction(console.GetWindowName(), [&console, &gui]() { console.Draw(&gui); }); - gui.ShowWindow(console.GetWindowName()); - gui.AddWindowDrawFunction(active_entity_tooltip.GetWindowName(), [&active_entity_tooltip, &gui]() { - active_entity_tooltip.Draw(&gui); - }); - gui.ShowWindow(active_entity_tooltip.GetWindowName()); - gui.AddWindowDrawFunction( - debug_info_window.GetWindowName(), [&debug_info_window, &gui]() { debug_info_window.Draw(&gui); }); - - connection.RegisterMessageHandler( - tec::networking::MessageType::CLIENT_ID, - [&gui, &ping_times_window, &log](tec::networking::MessageIn& message) { - std::string client_id = message.ToString(); - log->info("You are connected as client ID {}", client_id); - gui.ShowWindow(ping_times_window.GetWindowName()); - }); - - tec::LuaSystem* lua_sys = game.GetLuaSystem(); - lua_sys->GetGlobalState().set("OS", &os); // register instance - - tec::RegisterFileFactories(); - tec::BuildTestVoxelVolume(); - - console.AddConsoleCommand("lua", "lua : Execute a string in lua", [&lua_sys](const std::string& message) { - lua_sys->ExecuteString(message); - }); - console.AddConsoleCommand( - "connect", - "connect [username][ip] : Connect to a server [ip] with the provided [username]", - [&connection](const std::string& args) { - std::vector splitArgs = tec::SplitString(args); - if (splitArgs.size() >= 2) { - connection.Connect(splitArgs[1]); - std::string username = splitArgs[0]; - connection.RegisterConnectFunc([&connection, username]() { - tec::proto::UserLogin user_login; - user_login.set_username(username); - user_login.set_password(""); - tec::networking::MessageOut msg(tec::networking::LOGIN); - user_login.SerializeToZeroCopyStream(&msg); - connection.Send(msg); - }); - } - }); - console.AddSlashHandler([&connection](const std::string& chat_command_message) { - std::size_t argument_break_offset = chat_command_message.find_first_of(" "); - std::shared_ptr data = std::make_shared(); - data->command = chat_command_message.substr(0, argument_break_offset); - - if (argument_break_offset < chat_command_message.size()) { - std::string command_args = chat_command_message.substr(argument_break_offset + 1); - data->args = tec::SplitString(command_args); - } - tec::EventSystem::Get()->Emit(data); // Handle command locally - - if (connection.GetClientID() != 0) { // If connected, send command to server - tec::proto::ChatCommand chat_command(data->Out()); - tec::networking::MessageOut msg(tec::networking::CHAT_COMMAND); - chat_command.SerializeToZeroCopyStream(&msg); - connection.Send(msg); - } - }); - - os.DetachContext(); - - std::thread gameThread([&]() { - os.MakeCurrent(); - - while (!os.Closing()) { - const double delta = os.GetDeltaTime(); - - game.Update(delta); - gui.Update(delta); - console.Update(delta); - os.SwapBuffers(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - }); - - while (!os.Closing()) { - os.OSMessageLoop(); - } - - gameThread.join(); - tec::OS::Terminate(); - - return 0; + tec::ClientApp app(argc, argv); + return app.Run(); } From 66fac08d505da86e79774b069539c74b2d09b851 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 2 Nov 2025 19:06:56 -0600 Subject: [PATCH 07/11] Update CI configuration to use Ubuntu 22.04 and macOS-latest-large --- .github/workflows/tec.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tec.yml b/.github/workflows/tec.yml index d8982af3..6ae0e5c4 100644 --- a/.github/workflows/tec.yml +++ b/.github/workflows/tec.yml @@ -23,8 +23,8 @@ jobs: windows-latest, windows-2019, ubuntu-latest, - ubuntu-20.04, - macOS-latest, + ubuntu-22.04, + macOS-latest-large, ] arch: [x86, x64] include: @@ -36,14 +36,14 @@ jobs: vcpkgTripletOs: linux - os: ubuntu-20.04 vcpkgTripletOs: linux - - os: macOS-latest + - os: macOS-latest-large vcpkgTripletOs: osx exclude: - os: ubuntu-latest arch: x86 - os: ubuntu-20.04 arch: x86 - - os: macOS-latest + - os: macOS-latest-large arch: x86 env: buildDir: "${{ github.workspace }}/build" @@ -154,7 +154,7 @@ jobs: windows-2016, ubuntu-latest, ubuntu-20.04, - macOS-latest, + macOS-latest-large, ] arch: [x64] if: startsWith(github.ref, 'refs/tags/v') From 6ad8954afe8dd0e0640bc72e341e2c89f9ff3ec1 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 2 Nov 2025 19:09:20 -0600 Subject: [PATCH 08/11] Update CI configuration to use Windows 2022 and upgrade upload-artifact action to v4 --- .github/workflows/tec.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tec.yml b/.github/workflows/tec.yml index 6ae0e5c4..8a6730fc 100644 --- a/.github/workflows/tec.yml +++ b/.github/workflows/tec.yml @@ -21,7 +21,7 @@ jobs: os: [ windows-latest, - windows-2019, + windows-2022, ubuntu-latest, ubuntu-22.04, macOS-latest-large, @@ -30,7 +30,7 @@ jobs: include: - os: windows-latest vcpkgTripletOs: windows - - os: windows-2019 + - os: windows-2022 vcpkgTripletOs: windows - os: ubuntu-latest vcpkgTripletOs: linux @@ -72,7 +72,7 @@ jobs: buildPreset: ${{ env.CONFIG_PRESET }}-${{ env.BUILD_PRESET }} testPreset: ${{ env.CONFIG_PRESET }}-${{ env.BUILD_PRESET }} - name: Upload the build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "trillek-${{ matrix.os }}" path: | From 2b3735a04d0cda9cda74768669a39e218df8fea0 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 2 Nov 2025 19:32:17 -0600 Subject: [PATCH 09/11] Enhance login flow by integrating ImGui GLFW and OpenGL bindings, updating CMake requirements, and refining keyboard input handling --- CMakeLists.txt | 2 +- client/client-app.cpp | 2 ++ client/imgui-system.cpp | 36 +++++++++++------------------------- client/imgui-system.hpp | 2 ++ vcpkg.json | 7 +++++-- 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6279bbea..4e29fa5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.28) project(trillek VERSION 0.1 DESCRIPTION "Trillek Game Engine" LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/client/client-app.cpp b/client/client-app.cpp index 06a77225..172a24af 100644 --- a/client/client-app.cpp +++ b/client/client-app.cpp @@ -22,6 +22,8 @@ namespace tec { +void BuildTestVoxelVolume(); + namespace { void RegisterFileFactories() { diff --git a/client/imgui-system.cpp b/client/imgui-system.cpp index b23ef86d..ba2bdcdf 100644 --- a/client/imgui-system.cpp +++ b/client/imgui-system.cpp @@ -37,25 +37,6 @@ IMGUISystem::IMGUISystem(GLFWwindow* _window) { io.LogFilename = logfilename.c_str(); #endif - // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; - io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; - io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; - io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; - io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; - io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; - io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; - io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; - io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; - io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; - io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; - io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; - io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; - io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; - io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; - io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; - io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; - io.SetClipboardTextFn = SetClipboardText; io.GetClipboardTextFn = GetClipboardText; @@ -64,6 +45,9 @@ IMGUISystem::IMGUISystem(GLFWwindow* _window) { if (!IMGUISystem::font_texture) { CreateDeviceObjects(); } + + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init(); } IMGUISystem::~IMGUISystem() { @@ -92,6 +76,8 @@ IMGUISystem::~IMGUISystem() { ImGui::GetIO().Fonts->TexID = 0; font_texture = 0; } + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); } @@ -282,7 +268,7 @@ void IMGUISystem::CreateDeviceObjects() { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier - io.Fonts->TexID = (void*)(intptr_t)font_texture; + io.Fonts->TexID = font_texture; // Cleanup (don't clear the input data if you want to append new fonts later) io.Fonts->ClearInputData(); @@ -424,10 +410,10 @@ void IMGUISystem::On(eid, std::shared_ptr data) { void IMGUISystem::On(eid, std::shared_ptr data) { auto& io = ImGui::GetIO(); if (data->action == KeyboardEvent::KEY_DOWN) { - io.KeysDown[data->key] = true; + io.KeysData[data->key].Down = true; } else if (data->action == KeyboardEvent::KEY_UP) { - io.KeysDown[data->key] = false; + io.KeysData[data->key].Down = false; } if (data->action == KeyboardEvent::KEY_CHAR) { @@ -438,8 +424,8 @@ void IMGUISystem::On(eid, std::shared_ptr data) { } (void)data->mods; // Modifiers are not reliable across systems - io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; - io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; - io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; + io.KeyCtrl = io.KeysData[GLFW_KEY_LEFT_CONTROL].Down || io.KeysData[GLFW_KEY_RIGHT_CONTROL].Down; + io.KeyShift = io.KeysData[GLFW_KEY_LEFT_SHIFT].Down || io.KeysData[GLFW_KEY_RIGHT_SHIFT].Down; + io.KeyAlt = io.KeysData[GLFW_KEY_LEFT_ALT].Down || io.KeysData[GLFW_KEY_RIGHT_ALT].Down; } } // end namespace tec diff --git a/client/imgui-system.hpp b/client/imgui-system.hpp index c051c0e1..50f18f1d 100644 --- a/client/imgui-system.hpp +++ b/client/imgui-system.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "command-queue.hpp" #include "event-system.hpp" diff --git a/vcpkg.json b/vcpkg.json index e71ab0ac..7a06cb0b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -12,11 +12,14 @@ "openal-soft", "protobuf", "zlib", - "imgui", + { + "name": "imgui", + "features":["glfw-binding","opengl3-binding"] + }, "spdlog", "sol2", "gtest" ], "supports": "(windows & x86) | (windows & x64) | (linux & x64) | (osx & x64)", - "builtin-baseline": "10b7a178346f3f0abef60cecd5130e295afd8da4" + "builtin-baseline": "dd3097e305afa53f7b4312371f62058d2e665320" } From 8f6f1dde96999e74d7384a17b561f6634668e8a9 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 2 Nov 2025 19:34:15 -0600 Subject: [PATCH 10/11] Update VCPKG_COMMIT_ID in CI configuration for dependency management --- .github/workflows/tec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tec.yml b/.github/workflows/tec.yml index 8a6730fc..8c907e77 100644 --- a/.github/workflows/tec.yml +++ b/.github/workflows/tec.yml @@ -49,7 +49,7 @@ jobs: buildDir: "${{ github.workspace }}/build" BUILD_PRESET: release CONFIG_PRESET: ninja-multi-vcpkg-ci - VCPKG_COMMIT_ID: 10b7a178346f3f0abef60cecd5130e295afd8da4 + VCPKG_COMMIT_ID: dd3097e305afa53f7b4312371f62058d2e665320 VCPKG_DEFAULT_TRIPLET: ${{ matrix.arch }}-${{ matrix.vcpkgTripletOs }} steps: - uses: actions/checkout@v4 From 4c2a978414ae118d1959b5661a1fa912ffddcce1 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 2 Nov 2025 19:53:07 -0600 Subject: [PATCH 11/11] Fix macOS environment name in CI configuration for consistency Update CI configuration to use Ubuntu 22.04 for consistency across environments Add arch to upload-artifact name --- .github/workflows/tec.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tec.yml b/.github/workflows/tec.yml index 8c907e77..e01276ef 100644 --- a/.github/workflows/tec.yml +++ b/.github/workflows/tec.yml @@ -24,7 +24,7 @@ jobs: windows-2022, ubuntu-latest, ubuntu-22.04, - macOS-latest-large, + macos-latest-large, ] arch: [x86, x64] include: @@ -34,16 +34,16 @@ jobs: vcpkgTripletOs: windows - os: ubuntu-latest vcpkgTripletOs: linux - - os: ubuntu-20.04 + - os: ubuntu-22.04 vcpkgTripletOs: linux - - os: macOS-latest-large + - os: macos-latest-large vcpkgTripletOs: osx exclude: - os: ubuntu-latest arch: x86 - - os: ubuntu-20.04 + - os: ubuntu-22.04 arch: x86 - - os: macOS-latest-large + - os: macos-latest-large arch: x86 env: buildDir: "${{ github.workspace }}/build" @@ -74,7 +74,7 @@ jobs: - name: Upload the build artifact uses: actions/upload-artifact@v4 with: - name: "trillek-${{ matrix.os }}" + name: "trillek-${{ matrix.os }}-${{ matrix.arch }}" path: | assets/ ${{ env.CLIENT_BUILD_DIR }}/trillek-client @@ -153,8 +153,8 @@ jobs: windows-latest, windows-2016, ubuntu-latest, - ubuntu-20.04, - macOS-latest-large, + ubuntu-22.04, + macos-latest-large, ] arch: [x64] if: startsWith(github.ref, 'refs/tags/v') @@ -163,12 +163,12 @@ jobs: - name: Download a single artifact uses: actions/download-artifact@v4 with: - name: "trillek-${{ matrix.os }}" + name: "trillek-${{ matrix.os }}-${{ matrix.arch }}" - name: Package release run: | - zip --junk-paths -r -u trillek-${{ matrix.os }}.zip build/client - zip --junk-paths -r -u trillek-${{ matrix.os }}.zip build/server - zip -r -u trillek-${{ matrix.os }}.zip assets/ + zip --junk-paths -r -u trillek-${{ matrix.os }}-${{ matrix.arch }}.zip build/client + zip --junk-paths -r -u trillek-${{ matrix.os }}-${{ matrix.arch }}.zip build/server + zip -r -u trillek-${{ matrix.os }}-${{ matrix.arch }}.zip assets/ - name: Upload Release Asset id: upload-release-asset uses: actions/upload-release-asset@v1