mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-07-13 02:03:44 +00:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c967a5608 | |||
| 57a58b1a38 | |||
| 2cf27ad837 | |||
| e8ef7de4b9 | |||
| 8ea7b75874 | |||
| 59503ccc90 | |||
| 7316dff519 | |||
| b1a89125fe | |||
| 4a6504ec2c | |||
| b3594dd147 | |||
| 7cc426cb96 | |||
| a91eb02f5f | |||
| 6b65907a7f | |||
| 468a6b340e | |||
| 056827546e | |||
| 6a47521c7c | |||
| eaab5eaf30 | |||
| 35b38f35bb | |||
| 2572530957 | |||
| 8551e68184 | |||
| 2f85c708c5 | |||
| 844b64f5d9 | |||
| d7369c3bc5 | |||
| a8ad9034b2 | |||
| b756ce3c3f | |||
| 3f05b72cc2 | |||
| 152393f8bf | |||
| dd36299436 | |||
| da65e97ed1 | |||
| 4cc163ea1c | |||
| 01bcc3d18c | |||
| c7f6835702 | |||
| 0327b41611 | |||
| 48135f4544 | |||
| 0411ba533d | |||
| 41dd4ff678 | |||
| e1c2d0d5fb | |||
| 69c2868025 | |||
| c8ca4564a1 | |||
| 0c6112c28a | |||
| 4860849f2f | |||
| 3bd67d959f | |||
| 13a86d3e77 | |||
| dff94a41be | |||
| 79ee5915b4 | |||
| 3fcf23977b | |||
| 487917482f | |||
| 2ca39a7368 | |||
| 9d8aeef423 | |||
| dee7f74906 | |||
| 66f014ae42 | |||
| 12245d81a1 | |||
| a6e0332e3c | |||
| c461a63d9d | |||
| 69726a9b03 | |||
| 9acb6951d6 | |||
| 6ebfe5743c | |||
| 3c138e2891 | |||
| 2279ba4d6b | |||
| 30b038a6bb | |||
| c39c7bb0a4 | |||
| 06b238d63f | |||
| 1856dd2002 | |||
| b068a9b48f | |||
| e8d66ef983 | |||
| 7f47337e1b | |||
| b0b4dc51b0 | |||
| 7abfae425d | |||
| 4791af4453 | |||
| a44f9b3dba | |||
| c51d713969 | |||
| 3781f2960a | |||
| 9e469b04f1 | |||
| 96273e1d06 | |||
| c717037895 | |||
| 692129cb81 | |||
| 6842dccfc3 | |||
| e77dfd5a57 | |||
| 54ad1c2715 | |||
| 48ce7c9721 | |||
| 3aa6784627 | |||
| bec09a4761 | |||
| 7d35595683 | |||
| 982cbf116c | |||
| 43e70d80d2 | |||
| 00a35a636c | |||
| 47e64a7343 | |||
| 7f5b3919f4 | |||
| 896e777e23 | |||
| aa58c1e211 | |||
| 49a9226dca | |||
| b10d5d0f4e | |||
| 5581fd1692 | |||
| d36bb7962c | |||
| 4e8bd993d5 | |||
| abff9bfbdb | |||
| b024533f90 | |||
| f9251ff92c | |||
| 99f41c28cb | |||
| bbd27c9cba | |||
| 4682922467 | |||
| 83fb387dfe | |||
| 2b61f11a86 | |||
| a8b1a205f7 | |||
| dd9376447a | |||
| aa185afabf |
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: lukka/run-vcpkg@v7
|
||||
id: runvcpkg
|
||||
with:
|
||||
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl boost-variant boost-spirit boost-phoenix boost-core boost-system boost-asio'
|
||||
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl boost-variant boost-spirit boost-phoenix boost-core boost-system boost-asio boost-uuid'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: "06b5f4a769d848d1a20fa0acd556019728b56273"
|
||||
vcpkgTriplet: 'x64-windows-static'
|
||||
|
||||
@@ -31,3 +31,6 @@
|
||||
[submodule "deps/doctest"]
|
||||
path = deps/doctest
|
||||
url = https://github.com/doctest/doctest
|
||||
[submodule "deps/lk-result"]
|
||||
path = deps/lk-result
|
||||
url = https://gitlab.com/lionkor/lk-result.git
|
||||
|
||||
+38
-13
@@ -4,11 +4,32 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
|
||||
message(STATUS "You can find build instructions and a list of dependencies in the README at \
|
||||
https://github.com/BeamMP/BeamMP-Server")
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cmake-tools
|
||||
GIT_REPOSITORY https://github.com/brobeson/cmake-tools.git
|
||||
GIT_TAG main
|
||||
)
|
||||
FetchContent_MakeAvailable(cmake-tools)
|
||||
list(APPEND CMAKE_MODULE_PATH "${cmake-tools_SOURCE_DIR}")
|
||||
|
||||
include(CMakeToolsVersionFromGit)
|
||||
|
||||
execute_process(
|
||||
COMMAND git log -1 --format=%h
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
OUTPUT_VARIABLE GIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
project(BeamMP-Server
|
||||
DESCRIPTION "Server for BeamMP - The Multiplayer Mod for BeamNG.drive"
|
||||
HOMEPAGE_URL https://beammp.com
|
||||
LANGUAGES CXX C)
|
||||
|
||||
# quality of life stuff
|
||||
set(CMAKE_COLOR_DIAGNOSTICS ON)
|
||||
|
||||
find_package(Git REQUIRED)
|
||||
# Update submodules as needed
|
||||
option(GIT_SUBMODULE "Check submodules during build" ON)
|
||||
@@ -22,12 +43,13 @@ if(GIT_SUBMODULE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
set(HTTPLIB_REQUIRE_OPENSSL ON)
|
||||
set(SENTRY_BUILD_SHARED_LIBS OFF)
|
||||
|
||||
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT=1)
|
||||
|
||||
option(WIN32_STATIC_RUNTIME "Build statically-linked runtime on windows (don't touch unless you know what you're doing)" ON)
|
||||
|
||||
# ------------------------ APPLE ---------------------------------
|
||||
if(APPLE)
|
||||
if(IS_DIRECTORY /opt/homebrew/Cellar/lua@5.3/5.3.6)
|
||||
@@ -46,18 +68,14 @@ if(APPLE)
|
||||
link_directories(/usr/local/opt/openssl@1.1/lib)
|
||||
endif()
|
||||
# ------------------------ WINDOWS ---------------------------------
|
||||
option(WIN32_STATIC_RUNTIME "Build statically-linked runtime on windows (don't touch unless you know what you're doing)" ON)
|
||||
elseif (WIN32)
|
||||
# this has to happen before sentry, so that crashpad on windows links with these settings.
|
||||
if (WIN32_STATIC_RUNTIME)
|
||||
message(STATUS "MSVC -> forcing use of statically-linked runtime.")
|
||||
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
|
||||
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
# ------------------------ LINUX ---------------------------------
|
||||
# ------------------------ UNIX ------------------------------------
|
||||
elseif (UNIX)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
option(SANITIZE "Turns on thread and UB sanitizers" OFF)
|
||||
if (SANITIZE)
|
||||
message(STATUS "sanitize is ON")
|
||||
@@ -81,10 +99,7 @@ endif()
|
||||
add_subdirectory("deps/sentry-native")
|
||||
|
||||
# ------------------------ C++ SETUP ---------------------------------
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
|
||||
endif ()
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
# ------------------------ DEPENDENCIES ------------------------------
|
||||
message(STATUS "Adding local source dependencies")
|
||||
@@ -123,6 +138,7 @@ set(BeamMP_Sources
|
||||
include/TPluginMonitor.h src/TPluginMonitor.cpp
|
||||
include/Environment.h
|
||||
include/BoostAliases.h
|
||||
include/Uuid.h src/Uuid.cpp
|
||||
)
|
||||
|
||||
set(BeamMP_Includes
|
||||
@@ -134,11 +150,15 @@ set(BeamMP_Includes
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/sol2/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/rapidjson/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/lk-result/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/fmt/include"
|
||||
)
|
||||
|
||||
set(BeamMP_Definitions
|
||||
SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}"
|
||||
BEAMMP_GIT_HASH="${GIT_HASH}"
|
||||
LK_RESULT_USE_FMT
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
@@ -177,6 +197,11 @@ if (UNIX)
|
||||
-fstack-protector
|
||||
-Wzero-as-null-pointer-constant
|
||||
)
|
||||
else()
|
||||
set(BeamMP_CompileOptions
|
||||
/bigobj
|
||||
/INCREMENTAL:NO /NODEFAULTLIB:MSVCRT /NODEFAULTLIB:LIBCMT
|
||||
)
|
||||
endif()
|
||||
|
||||
set(BeamMP_Libraries
|
||||
@@ -186,12 +211,12 @@ set(BeamMP_Libraries
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
sol2::sol2
|
||||
fmt::fmt
|
||||
Threads::Threads
|
||||
ZLIB::ZLIB
|
||||
${LUA_LIBRARIES}
|
||||
commandline
|
||||
sentry
|
||||
fmt::fmt
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
# v3.2.0
|
||||
|
||||
- ADDED `settings` command, which lets you `get`, `list`, and `set` config options from the console
|
||||
- ADDED `debug` command, which shows info about connected clients & networking (for developers)
|
||||
- ADDED `Util.GenerateUUID()`, which generates an RFC4122 UUID (universally unique identifier)
|
||||
- ADDED `version` command, which shows version information
|
||||
- ADDED `onPlayerRequestMods` event, letting Lua disallow individual mods from being sent to clients
|
||||
- CHANGED `onShutdown` to be called before all players are kicked
|
||||
|
||||
# v3.1.1
|
||||
|
||||
- FIXED bug which caused GetPlayerIdentifiers, GetPlayerName, etc not to work in `onPlayerDisconnect`
|
||||
- FIXED some issues which could cause the server to crash when receiving malformed data
|
||||
- FIXED a bug which caused a server to crash during authentication when receiving malformed data
|
||||
- FIXED minor vulnerability in chat message handling
|
||||
- FIXED a minor formatting bug in the `status` command
|
||||
|
||||
# v3.1.0
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/Beam
|
||||
Dependencies for **Windows** can be installed with `vcpkg`.
|
||||
These are:
|
||||
```
|
||||
lua zlib rapidjson openssl websocketpp curl
|
||||
lua zlib rapidjson openssl websocketpp curl boost
|
||||
```
|
||||
The triplet we use for releases is `x64-windows-static`.
|
||||
|
||||
@@ -107,11 +107,6 @@ curl
|
||||
##### Build Dependencies
|
||||
These are needed for you to *build* the server, in addition to the [runtime dependencies](#runtime-dependencies).
|
||||
|
||||
**Ubuntu 22.04**
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Ubuntu 22.04
|
||||
|
||||
+1
Submodule deps/lk-result added at 4a4eda8092
+24
-2
@@ -10,6 +10,7 @@
|
||||
#include "BoostAliases.h"
|
||||
#include "Common.h"
|
||||
#include "Compat.h"
|
||||
#include "TResourceManager.h"
|
||||
#include "VehicleData.h"
|
||||
|
||||
class TServer;
|
||||
@@ -70,6 +71,7 @@ public:
|
||||
void ClearCars();
|
||||
[[nodiscard]] int GetID() const { return mID; }
|
||||
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
||||
bool mUDPCONNECTED = false;
|
||||
[[nodiscard]] bool IsConnected() const { return mIsConnected; }
|
||||
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
||||
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
||||
@@ -87,6 +89,24 @@ public:
|
||||
void UpdatePingTime();
|
||||
int SecondsSinceLastPing();
|
||||
|
||||
// bytes received on UDP
|
||||
std::atomic_size_t UdpReceived = 0;
|
||||
// number of packets received on UDP
|
||||
std::atomic_size_t UdpPacketsReceived = 0;
|
||||
// bytes sent on UDP
|
||||
std::atomic_size_t UdpSent = 0;
|
||||
// number of packets sent on UDP
|
||||
std::atomic_size_t UdpPacketsSent = 0;
|
||||
|
||||
// bytes received on TCP
|
||||
std::atomic_size_t TcpReceived = 0;
|
||||
// bytes sent on TCP
|
||||
std::atomic_size_t TcpSent = 0;
|
||||
|
||||
TimeType::time_point ConnectionTime {};
|
||||
|
||||
ModMap AllowedMods;
|
||||
|
||||
private:
|
||||
void InsertVehicle(int ID, const std::string& Data);
|
||||
|
||||
@@ -110,7 +130,9 @@ private:
|
||||
std::string mRole;
|
||||
std::string mDID;
|
||||
int mID = -1;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime;
|
||||
std::chrono::time_point<TimeType> mLastPingTime;
|
||||
std::mutex mDisconnectMtx;
|
||||
};
|
||||
|
||||
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
|
||||
// Returns a valid client, or nullptr if no such client exists
|
||||
std::shared_ptr<TClient> GetClient(class TServer& Server, int ID);
|
||||
|
||||
+103
-30
@@ -27,6 +27,17 @@ namespace fs = std::filesystem;
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <variant>
|
||||
|
||||
using TimeType = std::chrono::system_clock;
|
||||
|
||||
#include <lk/Result.h>
|
||||
|
||||
template <typename T>
|
||||
using Result = lk::Result<T>;
|
||||
using Error = lk::Error;
|
||||
|
||||
// General
|
||||
constexpr std::string_view StrDebug = "Debug";
|
||||
constexpr std::string_view StrPrivate = "Private";
|
||||
@@ -44,6 +55,7 @@ constexpr std::string_view StrLogChat = "LogChat";
|
||||
constexpr std::string_view StrSendErrors = "SendErrors";
|
||||
constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
|
||||
constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
|
||||
constexpr std::string_view StrIncludeSubdirectories = "IncludeSubdirectories";
|
||||
|
||||
// HTTP
|
||||
constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled";
|
||||
@@ -54,7 +66,7 @@ constexpr std::string_view StrHTTPServerPort = "HTTPServerPort";
|
||||
constexpr std::string_view StrHTTPServerIP = "HTTPServerIP";
|
||||
|
||||
// Unused
|
||||
constexpr std::string_view StrCustomIP = "CustomIP";
|
||||
constexpr std::string_view StrCustomIP = "Unused.CustomIP";
|
||||
|
||||
struct Version {
|
||||
uint8_t major;
|
||||
@@ -68,6 +80,9 @@ struct Version {
|
||||
template <typename T>
|
||||
using SparseArray = std::unordered_map<size_t, T>;
|
||||
|
||||
template <typename K, typename V>
|
||||
using HashMap = std::unordered_map<K, V>;
|
||||
|
||||
using boost::variant;
|
||||
using boost::container::flat_map;
|
||||
|
||||
@@ -80,7 +95,7 @@ class Application final {
|
||||
public:
|
||||
// types
|
||||
|
||||
using SettingValue = variant<std::string, bool, int>;
|
||||
using SettingValue = std::variant<std::string, bool, int>;
|
||||
using SettingsMap = flat_map<std::string_view, SettingValue>;
|
||||
|
||||
static SettingsMap mSettings;
|
||||
@@ -148,6 +163,14 @@ public:
|
||||
|
||||
static std::string SettingToString(const SettingValue& Value);
|
||||
|
||||
// Keeps track of how many packets we dropped on UDP due to fundamentally being malformed
|
||||
static inline std::atomic_size_t MalformedUdpPackets { 0 };
|
||||
// Keeps track of how many packets we dropped on UDP due to
|
||||
// 1) not having a valid (known) player id
|
||||
// 2) player disconnecting
|
||||
// 3) packet failing to parse
|
||||
static inline std::atomic_size_t InvalidUdpPackets { 0 };
|
||||
|
||||
private:
|
||||
static void SetShutdown(bool Val);
|
||||
|
||||
@@ -170,6 +193,7 @@ void RegisterThread(const std::string& str);
|
||||
#define KB 1024llu
|
||||
#define MB (KB * 1024llu)
|
||||
#define GB (MB * 1024llu)
|
||||
#define TB (GB * 1024llu)
|
||||
#define SSU_UNRAW SECRET_SENTRY_URL
|
||||
|
||||
#define _file_basename std::filesystem::path(__FILE__).filename().string()
|
||||
@@ -210,41 +234,88 @@ void RegisterThread(const std::string& str);
|
||||
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
|
||||
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
|
||||
#define beammp_error(x) \
|
||||
do { \
|
||||
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
|
||||
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
|
||||
#define beammp_internal_error(x) Application::Console().Write(_this_location + std::string("[INTERNAL ERROR] ") + (x))
|
||||
|
||||
#define beammp_warn(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[WARN] ") + (x)); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_lua_error(x) \
|
||||
do { \
|
||||
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
|
||||
#define beammp_info(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[INFO] ") + (x)); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_lua_warn(x) \
|
||||
do { \
|
||||
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
|
||||
#define beammp_error(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
|
||||
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
||||
#define beammp_debug(x) \
|
||||
do { \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
||||
} \
|
||||
#define beammp_lua_error(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_event(x) \
|
||||
do { \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
||||
} \
|
||||
#define beammp_lua_warn(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define luaprint(x) \
|
||||
do { \
|
||||
try{ \
|
||||
Application::Console().Write(_this_location + std::string("[LUA] ") + (x)); \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_debug(x) \
|
||||
do { \
|
||||
try{ \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
||||
} \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_event(x) \
|
||||
do { \
|
||||
try{ \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
||||
} \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}': {}", #x, e.what()));\
|
||||
} \
|
||||
} while (false)
|
||||
// trace() is a debug-build debug()
|
||||
#if defined(DEBUG)
|
||||
#define beammp_trace(x) \
|
||||
do { \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
||||
} \
|
||||
#define beammp_trace(x) \
|
||||
do { \
|
||||
try{ \
|
||||
if (Application::GetSettingBool("Debug")) { \
|
||||
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
||||
} \
|
||||
} catch (const std::exception& e) { \
|
||||
beammp_internal_error(fmt::format("Exception in logging function, failed to print '{}'", #x)); \
|
||||
} \
|
||||
} while (false)
|
||||
#else
|
||||
#define beammp_trace(x)
|
||||
@@ -344,3 +415,5 @@ inline T DeComp(const T& Compressed) {
|
||||
|
||||
std::string GetPlatformAgnosticErrorString();
|
||||
#define S_DSN SU_RAW
|
||||
|
||||
std::string ToHumanReadableSize(size_t Size);
|
||||
|
||||
@@ -66,9 +66,9 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
|
||||
Sentry.LogAssert(#cond, _file_basename, _line, __func__); \
|
||||
} \
|
||||
} while (false)
|
||||
#define beammp_assert_not_reachable() \
|
||||
do { \
|
||||
#define beammp_assert_not_reachable() \
|
||||
do { \
|
||||
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
|
||||
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
|
||||
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
|
||||
} while (false)
|
||||
#endif // DEBUG
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
enum IterationDecision {
|
||||
Continue,
|
||||
Break,
|
||||
};
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
|
||||
+1
-1
@@ -6,8 +6,8 @@
|
||||
* and write locks and read locks are mutually exclusive.
|
||||
*/
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
// Use ReadLock(m) and WriteLock(m) to lock it.
|
||||
using RWMutex = std::shared_mutex;
|
||||
|
||||
@@ -36,6 +36,12 @@ private:
|
||||
void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_Clear(const std::string&, const std::vector<std::string>& args);
|
||||
void Command_Debug(const std::string&, const std::vector<std::string>& args);
|
||||
void Command_Version(const std::string&, const std::vector<std::string>& args);
|
||||
|
||||
void Autocomplete_Lua(const std::string&, std::vector<std::string>& suggestions);
|
||||
void Autocomplete_Kick(const std::string&, std::vector<std::string>& suggestions);
|
||||
void Autocomplete_Settings(const std::string&, std::vector<std::string>& suggestions);
|
||||
|
||||
void Command_Say(const std::string& FullCommand);
|
||||
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
|
||||
@@ -52,9 +58,17 @@ private:
|
||||
{ "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } },
|
||||
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
|
||||
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
|
||||
{ "debug", [this](const auto& a, const auto& b) { Command_Debug(a, b); } },
|
||||
{ "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
|
||||
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::function<void(const std::string&, std::vector<std::string>&)>> mCommandAutocompleteMap = {
|
||||
{ "lua", [this](const auto& a, auto& b) { Autocomplete_Lua(a, b); } },
|
||||
{ "kick", [this](const auto& a, auto& b) { Autocomplete_Kick(a, b); } },
|
||||
{ "settings", [this](const auto& a, auto& b) { Autocomplete_Settings(a, b); } },
|
||||
};
|
||||
|
||||
Commandline mCommandline;
|
||||
std::vector<std::string> mCachedLuaHistory;
|
||||
std::vector<std::string> mCachedRegularHistory;
|
||||
|
||||
+42
-19
@@ -10,6 +10,7 @@
|
||||
#include <lua.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
#include <set>
|
||||
@@ -18,19 +19,40 @@
|
||||
#include <vector>
|
||||
|
||||
#define SOL_ALL_SAFETIES_ON 1
|
||||
#define SOL_USER_C_ASSERT SOL_ON
|
||||
#define SOL_C_ASSERT(...) \
|
||||
do { \
|
||||
if (!(__VA_ARGS__)) { \
|
||||
beammp_lua_errorf("SOL2 assertion failure: Assertion `{}` failed in {}:{}. This *should* be a fatal error, but BeamMP Server overrides it to not be fatal. This may cause the Lua Engine to crash, or cause other issues.", #__VA_ARGS__, __FILE__, __LINE__); \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
struct JsonString {
|
||||
std::string value;
|
||||
};
|
||||
|
||||
// value used to keep nils in a table or array, across serialization boundaries like
|
||||
// JsonEncode, so that the nil stays at the same index and isn't treated like a special
|
||||
// value (e.g. one that can be ignored or discarded).
|
||||
const inline std::string BEAMMP_INTERNAL_NIL = "BEAMMP_SERVER_INTERNAL_NIL_VALUE";
|
||||
|
||||
using TLuaStateId = std::string;
|
||||
namespace fs = std::filesystem;
|
||||
/**
|
||||
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
|
||||
*/
|
||||
using TLuaArgTypes = std::variant<std::string, int, sol::variadic_args, bool, std::unordered_map<std::string, std::string>>;
|
||||
static constexpr size_t TLuaArgTypes_String = 0;
|
||||
static constexpr size_t TLuaArgTypes_Int = 1;
|
||||
static constexpr size_t TLuaArgTypes_VariadicArgs = 2;
|
||||
static constexpr size_t TLuaArgTypes_Bool = 3;
|
||||
static constexpr size_t TLuaArgTypes_StringStringMap = 4;
|
||||
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, std::unordered_map<std::string, size_t>, float>;
|
||||
enum TLuaType {
|
||||
String = 0,
|
||||
Int = 1,
|
||||
Json = 2,
|
||||
Bool = 3,
|
||||
StringStringMap = 4,
|
||||
StringSizeTMap = 5,
|
||||
Float = 6,
|
||||
};
|
||||
|
||||
class TLuaPlugin;
|
||||
|
||||
@@ -71,12 +93,12 @@ public:
|
||||
struct QueuedFunction {
|
||||
std::string FunctionName;
|
||||
std::shared_ptr<TLuaResult> Result;
|
||||
std::vector<TLuaArgTypes> Args;
|
||||
std::vector<TLuaValue> Args;
|
||||
std::string EventName; // optional, may be empty
|
||||
};
|
||||
|
||||
TLuaEngine();
|
||||
~TLuaEngine() noexcept {
|
||||
virtual ~TLuaEngine() noexcept {
|
||||
beammp_debug("Lua Engine terminated");
|
||||
}
|
||||
|
||||
@@ -98,8 +120,8 @@ public:
|
||||
return mLuaStates.size();
|
||||
}
|
||||
std::vector<std::string> GetLuaStateNames() {
|
||||
std::vector<std::string> names{};
|
||||
for(auto const& [stateId, _ ] : mLuaStates) {
|
||||
std::vector<std::string> names {};
|
||||
for (auto const& [stateId, _] : mLuaStates) {
|
||||
names.push_back(stateId);
|
||||
}
|
||||
return names;
|
||||
@@ -120,11 +142,11 @@ public:
|
||||
}
|
||||
|
||||
static void WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results,
|
||||
const std::optional<std::chrono::high_resolution_clock::duration>& Max = std::nullopt);
|
||||
const std::optional<TimeType::duration>& Max = std::nullopt);
|
||||
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
|
||||
bool HasState(TLuaStateId StateId);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args);
|
||||
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
|
||||
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
|
||||
/**
|
||||
@@ -144,7 +166,7 @@ public:
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<TLuaResult>> Results;
|
||||
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
|
||||
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
||||
|
||||
for (const auto& Event : mLuaEvents.at(EventName)) {
|
||||
for (const auto& Function : Event.second) {
|
||||
@@ -163,7 +185,7 @@ public:
|
||||
return {};
|
||||
}
|
||||
std::vector<std::shared_ptr<TLuaResult>> Results;
|
||||
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
|
||||
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
||||
const auto Handlers = GetEventHandlersForState(EventName, StateId);
|
||||
for (const auto& Handler : Handlers) {
|
||||
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
|
||||
@@ -198,10 +220,10 @@ private:
|
||||
public:
|
||||
StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine);
|
||||
StateThreadData(const StateThreadData&) = delete;
|
||||
~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
||||
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args);
|
||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy);
|
||||
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
|
||||
void AddPath(const fs::path& Path); // to be added to path and cpath
|
||||
void operator()() override;
|
||||
@@ -243,11 +265,12 @@ private:
|
||||
std::recursive_mutex mPathsMutex;
|
||||
std::mt19937 mMersenneTwister;
|
||||
std::uniform_real_distribution<double> mUniformRealDistribution01;
|
||||
std::vector<sol::object> JsonStringToArray(JsonString Str);
|
||||
};
|
||||
|
||||
struct TimedEvent {
|
||||
std::chrono::high_resolution_clock::duration Duration {};
|
||||
std::chrono::high_resolution_clock::time_point LastCompletion {};
|
||||
TimeType::duration Duration {};
|
||||
TimeType::time_point LastCompletion {};
|
||||
std::string EventName;
|
||||
TLuaStateId StateId;
|
||||
CallStrategy Strategy;
|
||||
|
||||
+3
-1
@@ -37,6 +37,7 @@ private:
|
||||
TResourceManager& mResourceManager;
|
||||
std::thread mUDPThread;
|
||||
std::thread mTCPThread;
|
||||
std::mutex mOpenIDMutex;
|
||||
|
||||
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
||||
void HandleDownload(TConnection&& TCPSock);
|
||||
@@ -45,7 +46,8 @@ private:
|
||||
void Looper(const std::weak_ptr<TClient>& c);
|
||||
int OpenID();
|
||||
void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr);
|
||||
void Parse(TClient& c, const std::vector<uint8_t>& Packet);
|
||||
ModMap GetClientMods(TClient& Client);
|
||||
void HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet);
|
||||
void SendFile(TClient& c, const std::string& Name);
|
||||
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
|
||||
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
|
||||
|
||||
+11
-10
@@ -1,21 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include <optional>
|
||||
|
||||
using ModMap = HashMap<std::string, size_t>;
|
||||
|
||||
class TResourceManager {
|
||||
public:
|
||||
TResourceManager();
|
||||
|
||||
[[nodiscard]] size_t MaxModSize() const { return mMaxModSize; }
|
||||
[[nodiscard]] std::string FileList() const { return mFileList; }
|
||||
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
|
||||
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
|
||||
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
|
||||
[[nodiscard]] size_t TotalModsSize() const { return mTotalModSize; }
|
||||
[[nodiscard]] ModMap FileMap() const { return mMods; }
|
||||
[[nodiscard]] static std::string FormatForBackend(const ModMap& mods);
|
||||
[[nodiscard]] static std::string FormatForClient(const ModMap& mods);
|
||||
[[nodiscard]] static std::optional<std::string> IsModValid(std::string& pathString, const ModMap& mods);
|
||||
[[nodiscard]] int LoadedModCount() const { return mMods.size(); }
|
||||
|
||||
private:
|
||||
size_t mMaxModSize = 0;
|
||||
std::string mFileSizes;
|
||||
std::string mFileList;
|
||||
std::string mTrimmedList;
|
||||
int mModsLoaded = 0;
|
||||
size_t mTotalModSize = 0; // size of all mods
|
||||
ModMap mMods; // map of mod names
|
||||
};
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
class TScopedTimer {
|
||||
public:
|
||||
TScopedTimer();
|
||||
@@ -11,7 +13,7 @@ public:
|
||||
TScopedTimer(std::function<void(size_t)> OnDestroy);
|
||||
~TScopedTimer();
|
||||
auto GetElapsedTime() const {
|
||||
auto EndTime = std::chrono::high_resolution_clock::now();
|
||||
auto EndTime = TimeType::now();
|
||||
auto Delta = EndTime - mStartTime;
|
||||
size_t TimeDelta = Delta / std::chrono::milliseconds(1);
|
||||
return TimeDelta;
|
||||
@@ -20,6 +22,6 @@ public:
|
||||
std::function<void(size_t /* time_ms */)> OnDestroy { nullptr };
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point mStartTime;
|
||||
TimeType::time_point mStartTime;
|
||||
std::string Name;
|
||||
};
|
||||
|
||||
+57
-3
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "IThreaded.h"
|
||||
#include "IterationDecision.h"
|
||||
#include "RWMutex.h"
|
||||
#include "TScopedTimer.h"
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "BoostAliases.h"
|
||||
@@ -14,6 +17,21 @@ class TClient;
|
||||
class TNetwork;
|
||||
class TPPSMonitor;
|
||||
|
||||
// clang-format doesn't know how to deal with concepts
|
||||
// clang-format off
|
||||
template <typename FnT>
|
||||
concept ForEachHandlerWithDecision = requires(FnT Fn, const std::shared_ptr<TClient>& Ptr) {
|
||||
requires std::invocable<FnT, const std::shared_ptr<TClient>&> ;
|
||||
{ std::invoke(Fn, Ptr) } -> std::convertible_to<IterationDecision>;
|
||||
};
|
||||
|
||||
template <typename FnT>
|
||||
concept ForEachHandler = requires(FnT Fn, const std::shared_ptr<TClient>& Ptr) {
|
||||
requires std::invocable <FnT, const std::shared_ptr<TClient>&> ;
|
||||
{ std::invoke(Fn, Ptr) } -> std::same_as<void>;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
class TServer final {
|
||||
public:
|
||||
using TClientSet = std::unordered_set<std::shared_ptr<TClient>>;
|
||||
@@ -22,11 +40,46 @@ public:
|
||||
|
||||
void InsertClient(const std::shared_ptr<TClient>& Ptr);
|
||||
void RemoveClient(const std::weak_ptr<TClient>&);
|
||||
void RemoveClientById(int Id);
|
||||
// in Fn, return true to continue, return false to break
|
||||
void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
|
||||
[[deprecated("use ForEachClient instead")]] void ForEachClientWeak(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
|
||||
// in Fn, return Break or Continue
|
||||
template <ForEachHandlerWithDecision FnT>
|
||||
void ForEachClient(FnT Fn) {
|
||||
decltype(mClients) Clients;
|
||||
{
|
||||
ReadLock lock(mClientsMutex);
|
||||
Clients = mClients;
|
||||
}
|
||||
for (auto& Client : Clients) {
|
||||
if (Client) [[likely]] {
|
||||
IterationDecision Decision = std::invoke(Fn, Client);
|
||||
if (Decision == IterationDecision::Break) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
beammp_assert_not_reachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
template <ForEachHandler FnT>
|
||||
void ForEachClient(FnT Fn) {
|
||||
decltype(mClients) Clients;
|
||||
{
|
||||
ReadLock lock(mClientsMutex);
|
||||
Clients = mClients;
|
||||
}
|
||||
for (auto& Client : Clients) {
|
||||
if (Client) [[likely]] {
|
||||
std::invoke(Fn, Client);
|
||||
} else {
|
||||
beammp_assert_not_reachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t ClientCount() const;
|
||||
|
||||
static void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network);
|
||||
static void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TNetwork& Network);
|
||||
static void HandleEvent(TClient& c, const std::string& Data);
|
||||
RWMutex& GetClientMutex() const { return mClientsMutex; }
|
||||
|
||||
@@ -43,7 +96,8 @@ private:
|
||||
static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID);
|
||||
static bool IsUnicycle(TClient& c, const std::string& CarJson);
|
||||
static void Apply(TClient& c, int VID, const std::string& pckt);
|
||||
static void HandlePosition(TClient& c, const std::string& Packet);
|
||||
static bool HandlePosition(TClient& c, const std::string& PacketStr);
|
||||
static bool HandleVehicleUpdate(const std::string& PacketStr, const int playerID);
|
||||
};
|
||||
|
||||
struct BufferView {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace uuid {
|
||||
|
||||
std::string GenerateUuid();
|
||||
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM archlinux
|
||||
|
||||
RUN pacman -Syu --noconfirm lua53 openssl curl git cmake gcc make zlib boost websocketpp
|
||||
RUN pacman -Syu --noconfirm lua53 openssl curl git cmake gcc make zlib boost websocketpp
|
||||
|
||||
+6
-6
@@ -13,7 +13,7 @@ void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
|
||||
ConsumeLongFlag(std::string(Arg));
|
||||
}
|
||||
} else {
|
||||
beammp_errorf("Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg);
|
||||
fmt::print(stderr, "Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,17 @@ bool ArgsParser::Verify() {
|
||||
bool Ok = true;
|
||||
for (const auto& RegisteredArg : mRegisteredArguments) {
|
||||
if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) {
|
||||
beammp_errorf("Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0));
|
||||
fmt::print(stderr, "Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0));
|
||||
Ok = false;
|
||||
continue;
|
||||
} else if (FoundArgument(RegisteredArg.Names)) {
|
||||
if (RegisteredArg.Flags & Flags::HAS_VALUE) {
|
||||
if (!GetValueOfArgument(RegisteredArg.Names).has_value()) {
|
||||
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' expects a value, but no value was given.");
|
||||
fmt::print(stderr, "Error in commandline arguments: Argument '{}' expects a value, but no value was given.", RegisteredArg.Names.at(0));
|
||||
Ok = false;
|
||||
}
|
||||
} else if (GetValueOfArgument(RegisteredArg.Names).has_value()) {
|
||||
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' does not expect a value, but one was given.");
|
||||
fmt::print(stderr, "Error in commandline arguments: Argument '{}' does not expect a value, but one was given.", RegisteredArg.Names.at(0));
|
||||
Ok = false;
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ void ArgsParser::ConsumeLongAssignment(const std::string& Arg) {
|
||||
auto Value = Arg.substr(Arg.rfind("=") + 1);
|
||||
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
|
||||
if (!IsRegistered(Name)) {
|
||||
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
|
||||
fmt::print(stdout, "Argument '{}' was supplied but isn't a known argument, so it is likely being ignored.", Name);
|
||||
}
|
||||
mFoundArgs.push_back({ Name, Value });
|
||||
}
|
||||
@@ -90,7 +90,7 @@ void ArgsParser::ConsumeLongFlag(const std::string& Arg) {
|
||||
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
|
||||
mFoundArgs.push_back({ Name, std::nullopt });
|
||||
if (!IsRegistered(Name)) {
|
||||
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
|
||||
fmt::print(stdout, "Argument '{}' was supplied but isn't a known argument, so it is likely being ignored.", Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-15
@@ -1,16 +1,20 @@
|
||||
#include "Client.h"
|
||||
|
||||
#include "CustomAssert.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TServer.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
void TClient::DeleteCar(int Ident) {
|
||||
// TODO: Send delete packets
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) {
|
||||
return Ident == elem.ID();
|
||||
});
|
||||
if (iter != mVehicleData.end()) {
|
||||
std::string Destroy = "Od:" + std::to_string(GetID()) + "-" + std::to_string(iter->ID());
|
||||
LuaAPI::MP::Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||
mVehicleData.erase(iter);
|
||||
} else {
|
||||
beammp_debug("tried to erase a vehicle that doesn't exist (not an error)");
|
||||
@@ -19,6 +23,10 @@ void TClient::DeleteCar(int Ident) {
|
||||
|
||||
void TClient::ClearCars() {
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
for (const auto& Car : mVehicleData) {
|
||||
std::string Destroy = "Od:" + std::to_string(GetID()) + "-" + std::to_string(Car.ID());
|
||||
LuaAPI::MP::Engine->Network().SendToAll(this, StringToVector(Destroy), false, true);
|
||||
}
|
||||
mVehicleData.clear();
|
||||
}
|
||||
|
||||
@@ -58,7 +66,12 @@ std::string TClient::GetCarPositionRaw(int Ident) {
|
||||
}
|
||||
|
||||
void TClient::Disconnect(std::string_view Reason) {
|
||||
// we need exclusivity here in case we can't
|
||||
std::unique_lock Lock(mDisconnectMtx);
|
||||
beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason);
|
||||
if (mSocket.is_open()) {
|
||||
beammp_debugf("Socket for client {} already closed", GetID());
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
mSocket.shutdown(socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
@@ -68,6 +81,7 @@ void TClient::Disconnect(std::string_view Reason) {
|
||||
if (ec) {
|
||||
beammp_debugf("Failed to close client socket: {}", ec.message());
|
||||
}
|
||||
mServer.RemoveClientById(GetID());
|
||||
}
|
||||
|
||||
void TClient::SetCarPosition(int Ident, const std::string& Data) {
|
||||
@@ -118,7 +132,7 @@ TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
|
||||
: mServer(Server)
|
||||
, mSocket(std::move(Socket))
|
||||
, mDownSocket(ip::tcp::socket(Server.IoCtx()))
|
||||
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
|
||||
, mLastPingTime(TimeType::now()) {
|
||||
}
|
||||
|
||||
TClient::~TClient() {
|
||||
@@ -126,27 +140,23 @@ TClient::~TClient() {
|
||||
}
|
||||
|
||||
void TClient::UpdatePingTime() {
|
||||
mLastPingTime = std::chrono::high_resolution_clock::now();
|
||||
mLastPingTime = TimeType::now();
|
||||
}
|
||||
int TClient::SecondsSinceLastPing() {
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::high_resolution_clock::now() - mLastPingTime)
|
||||
TimeType::now() - mLastPingTime)
|
||||
.count();
|
||||
return int(seconds);
|
||||
}
|
||||
|
||||
std::optional<std::weak_ptr<TClient>> GetClient(TServer& Server, int ID) {
|
||||
std::optional<std::weak_ptr<TClient>> MaybeClient { std::nullopt };
|
||||
Server.ForEachClient([&](std::weak_ptr<TClient> CPtr) -> bool {
|
||||
ReadLock Lock(Server.GetClientMutex());
|
||||
if (!CPtr.expired()) {
|
||||
auto C = CPtr.lock();
|
||||
if (C->GetID() == ID) {
|
||||
MaybeClient = CPtr;
|
||||
return false;
|
||||
}
|
||||
std::shared_ptr<TClient> GetClient(TServer& Server, int ID) {
|
||||
std::shared_ptr<TClient> Result {};
|
||||
Server.ForEachClient([&](const auto& Client) {
|
||||
if (Client->GetID() == ID) {
|
||||
Result = Client;
|
||||
return Break;
|
||||
}
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
return MaybeClient;
|
||||
return Result;
|
||||
}
|
||||
|
||||
+24
-8
@@ -34,19 +34,21 @@ Application::SettingsMap Application::mSettings = {
|
||||
{ StrHTTPServerIP, std::string("127.0.0.1") },
|
||||
{ StrHTTPServerUseSSL, false },
|
||||
{ StrHideUpdateMessages, false },
|
||||
{ StrAuthKey, std::string("") },
|
||||
{ StrIncludeSubdirectories, false },
|
||||
};
|
||||
|
||||
// global, yes, this is ugly, no, it cant be done another way
|
||||
TSentry Sentry {};
|
||||
|
||||
std::string Application::SettingToString(const Application::SettingValue& Value) {
|
||||
switch (Value.which()) {
|
||||
switch (Value.index()) {
|
||||
case 0:
|
||||
return fmt::format("{}", boost::get<std::string>(Value));
|
||||
return fmt::format("{}", std::get<std::string>(Value));
|
||||
case 1:
|
||||
return fmt::format("{}", boost::get<bool>(Value));
|
||||
return fmt::format("{}", std::get<bool>(Value));
|
||||
case 2:
|
||||
return fmt::format("{}", boost::get<int>(Value));
|
||||
return fmt::format("{}", std::get<int>(Value));
|
||||
default:
|
||||
return "<unknown type>";
|
||||
}
|
||||
@@ -54,7 +56,7 @@ std::string Application::SettingToString(const Application::SettingValue& Value)
|
||||
|
||||
std::string Application::GetSettingString(std::string_view Name) {
|
||||
try {
|
||||
return boost::get<std::string>(Application::mSettings.at(Name));
|
||||
return std::get<std::string>(Application::mSettings.at(Name));
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to get string setting '{}': {}", Name, e.what());
|
||||
return "";
|
||||
@@ -63,7 +65,7 @@ std::string Application::GetSettingString(std::string_view Name) {
|
||||
|
||||
int Application::GetSettingInt(std::string_view Name) {
|
||||
try {
|
||||
return boost::get<int>(Application::mSettings.at(Name));
|
||||
return std::get<int>(Application::mSettings.at(Name));
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to get int setting '{}': {}", Name, e.what());
|
||||
return 0;
|
||||
@@ -72,7 +74,7 @@ int Application::GetSettingInt(std::string_view Name) {
|
||||
|
||||
bool Application::GetSettingBool(std::string_view Name) {
|
||||
try {
|
||||
return boost::get<bool>(Application::mSettings.at(Name));
|
||||
return std::get<bool>(Application::mSettings.at(Name));
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to get bool setting '{}': {}", Name, e.what());
|
||||
return false;
|
||||
@@ -81,7 +83,7 @@ bool Application::GetSettingBool(std::string_view Name) {
|
||||
|
||||
void Application::SetSetting(std::string_view Name, const Application::SettingValue& Value) {
|
||||
if (mSettings.contains(Name)) {
|
||||
if (mSettings[Name].type() == Value.type()) {
|
||||
if (mSettings[Name].index() == Value.index()) {
|
||||
mSettings[Name] = Value;
|
||||
} else {
|
||||
beammp_errorf("Could not change value of setting '{}', because it has a different type.", Name);
|
||||
@@ -427,3 +429,17 @@ std::string GetPlatformAgnosticErrorString() {
|
||||
return "(no human-readable errors on this platform)";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ToHumanReadableSize(size_t Size) {
|
||||
if (Size > TB) {
|
||||
return fmt::format("{:.2f} TiB", double(Size) / TB);
|
||||
} else if (Size > GB) {
|
||||
return fmt::format("{:.2f} GiB", double(Size) / GB);
|
||||
} else if (Size > MB) {
|
||||
return fmt::format("{:.2f} MiB", double(Size) / MB);
|
||||
} else if (Size > KB) {
|
||||
return fmt::format("{:.2f} KiB", double(Size) / KB);
|
||||
} else {
|
||||
return fmt::format("{} B", Size);
|
||||
}
|
||||
}
|
||||
|
||||
+32
-29
@@ -119,15 +119,14 @@ static inline std::pair<bool, std::string> InternalTriggerClientEvent(int Player
|
||||
LuaAPI::MP::Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||
return { true, "" };
|
||||
} else {
|
||||
auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
|
||||
if (!MaybeClient || MaybeClient.value().expired()) {
|
||||
auto Client = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
|
||||
if (!Client) {
|
||||
beammp_lua_errorf("TriggerClientEvent invalid Player ID '{}'", PlayerID);
|
||||
return { false, "Invalid Player ID" };
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
||||
if (!LuaAPI::MP::Engine->Network().Respond(*Client, StringToVector(Packet), true)) {
|
||||
beammp_lua_errorf("Respond failed, dropping client {}", PlayerID);
|
||||
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
|
||||
LuaAPI::MP::Engine->Network().ClientKick(*Client, "Disconnected after failing to receive packets");
|
||||
return { false, "Respond failed, dropping client" };
|
||||
}
|
||||
return { true, "" };
|
||||
@@ -140,13 +139,12 @@ std::pair<bool, std::string> LuaAPI::MP::TriggerClientEvent(int PlayerID, const
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (!MaybeClient || MaybeClient.value().expired()) {
|
||||
auto Client = GetClient(Engine->Server(), ID);
|
||||
if (!Client) {
|
||||
beammp_lua_errorf("Tried to drop client with id {}, who doesn't exist", ID);
|
||||
return { false, "Player does not exist" };
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
LuaAPI::MP::Engine->Network().ClientKick(*c, MaybeReason.value_or("No reason"));
|
||||
LuaAPI::MP::Engine->Network().ClientKick(*Client, MaybeReason.value_or("No reason"));
|
||||
return { true, "" };
|
||||
}
|
||||
|
||||
@@ -158,16 +156,15 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
|
||||
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||
Result.first = true;
|
||||
} else {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->IsSynced()) {
|
||||
auto Client = GetClient(Engine->Server(), ID);
|
||||
if (Client) {
|
||||
if (!Client->IsSynced()) {
|
||||
Result.first = false;
|
||||
Result.second = "Player still syncing data";
|
||||
return Result;
|
||||
}
|
||||
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message);
|
||||
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
||||
LogChatMessage("<Server> (to \"" + Client->GetName() + "\")", -1, Message);
|
||||
if (!Engine->Network().Respond(*Client, StringToVector(Packet), true)) {
|
||||
beammp_errorf("Failed to send chat message back to sender (id {}) - did the sender disconnect?", ID);
|
||||
// TODO: should we return an error here?
|
||||
}
|
||||
@@ -184,18 +181,17 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
|
||||
|
||||
std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
|
||||
std::pair<bool, std::string> Result;
|
||||
auto MaybeClient = GetClient(Engine->Server(), PID);
|
||||
if (!MaybeClient || MaybeClient.value().expired()) {
|
||||
auto Client = GetClient(Engine->Server(), PID);
|
||||
if (!Client) {
|
||||
beammp_lua_error("RemoveVehicle invalid Player ID");
|
||||
Result.first = false;
|
||||
Result.second = "Invalid Player ID";
|
||||
return Result;
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->GetCarData(VID).empty()) {
|
||||
if (!Client->GetCarData(VID).empty()) {
|
||||
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
||||
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||
c->DeleteCar(VID);
|
||||
Client->DeleteCar(VID);
|
||||
Result.first = true;
|
||||
} else {
|
||||
Result.first = false;
|
||||
@@ -273,18 +269,18 @@ void LuaAPI::MP::Sleep(size_t Ms) {
|
||||
}
|
||||
|
||||
bool LuaAPI::MP::IsPlayerConnected(int ID) {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
return MaybeClient.value().lock()->IsConnected();
|
||||
auto Client = GetClient(Engine->Server(), ID);
|
||||
if (Client) {
|
||||
return Client->IsConnected();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool LuaAPI::MP::IsPlayerGuest(int ID) {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
return MaybeClient.value().lock()->IsGuest();
|
||||
auto Client = GetClient(Engine->Server(), ID);
|
||||
if (Client) {
|
||||
return Client->IsGuest();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -302,6 +298,7 @@ void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
|
||||
}
|
||||
|
||||
int LuaAPI::PanicHandler(lua_State* State) {
|
||||
// FIXME: unsafe operation, can cause stack overflow
|
||||
beammp_lua_error("PANIC: " + sol::stack::get<std::string>(State, 1));
|
||||
return 0;
|
||||
}
|
||||
@@ -572,7 +569,11 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
|
||||
value = right.as<std::string>();
|
||||
break;
|
||||
case sol::type::number:
|
||||
value = right.as<double>();
|
||||
if (right.is<int>()) {
|
||||
value = right.as<int>();
|
||||
} else {
|
||||
value = right.as<float>();
|
||||
}
|
||||
break;
|
||||
case sol::type::function:
|
||||
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
|
||||
@@ -582,6 +583,7 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
|
||||
for (const auto& pair : right.as<sol::table>()) {
|
||||
if (pair.first.get_type() != sol::type::number) {
|
||||
local_is_array = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& pair : right.as<sol::table>()) {
|
||||
@@ -606,10 +608,11 @@ std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
|
||||
for (const auto& pair : object.as<sol::table>()) {
|
||||
if (pair.first.get_type() != sol::type::number) {
|
||||
is_array = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& entry : object) {
|
||||
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
|
||||
for (const auto& [key, value] : object) {
|
||||
JsonEncodeRecursive(json, key, value, is_array);
|
||||
}
|
||||
return json.dump();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "SignalHandling.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include "Compat.h"
|
||||
|
||||
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
|
||||
#include <csignal>
|
||||
static void UnixSignalHandler(int sig) {
|
||||
|
||||
+5
-2
@@ -87,6 +87,8 @@ void TConfig::FlushToFile() {
|
||||
SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
|
||||
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::GetSettingBool(StrSendErrorsMessageEnabled.data());
|
||||
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
|
||||
data["Misc"][StrIncludeSubdirectories.data()] = Application::GetSettingBool(StrIncludeSubdirectories.data());
|
||||
SetComment(data["Misc"][StrIncludeSubdirectories.data()].comments(), " Whether or not to include subdirectories in General.ResourceFolder/Client when searching for `.zip`s");
|
||||
// HTTP
|
||||
data["HTTP"][StrSSLKeyPath.data()] = Application::GetSettingString(StrSSLKeyPath.data());
|
||||
data["HTTP"][StrSSLCertPath.data()] = Application::GetSettingString(StrSSLCertPath.data());
|
||||
@@ -158,6 +160,7 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
TryReadValue(data, "Misc", StrSendErrors);
|
||||
TryReadValue(data, "Misc", StrHideUpdateMessages);
|
||||
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled);
|
||||
TryReadValue(data, "Misc", StrIncludeSubdirectories);
|
||||
// HTTP
|
||||
TryReadValue(data, "HTTP", StrSSLKeyPath);
|
||||
TryReadValue(data, "HTTP", StrSSLCertPath);
|
||||
@@ -177,7 +180,7 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
FlushToFile();
|
||||
// all good so far, let's check if there's a key
|
||||
if (Application::GetSettingString(StrAuthKey).empty()) {
|
||||
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
|
||||
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server. Alternatively, if you only want a private server (one that doesn't show up in the server list), you may put anything as your AuthKey, like \"hello\".");
|
||||
Application::SetSubsystemStatus("Config", Application::Status::Bad);
|
||||
mFailed = true;
|
||||
return;
|
||||
@@ -191,7 +194,7 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
void TConfig::PrintDebug() {
|
||||
for (const auto& [k, v] : Application::mSettings) {
|
||||
if (k == StrAuthKey) {
|
||||
beammp_debugf("AuthKey: length {}", boost::get<std::string>(v).size());
|
||||
beammp_debugf("AuthKey: length {}", std::get<std::string>(v).size());
|
||||
continue;
|
||||
}
|
||||
beammp_debugf("{}: {}", k, Application::SettingToString(v));
|
||||
|
||||
+199
-61
@@ -7,18 +7,34 @@
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/spirit/home/qi/directive/lexeme.hpp>
|
||||
#include <boost/spirit/home/qi/parse.hpp>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <fmt/chrono.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/phoenix.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
|
||||
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
|
||||
}
|
||||
|
||||
static inline bool StringStartsWithLower(const std::string& Name1, const std::string& Name2) {
|
||||
std::string Name1Lower = boost::algorithm::to_lower_copy(Name1);
|
||||
return StringStartsWith(Name1Lower, Name2) || StringStartsWith(Name2, Name1Lower);
|
||||
};
|
||||
|
||||
static inline bool StringStartsWithLowerBoth(const std::string& Name1, const std::string& Name2) {
|
||||
std::string Name1Lower = boost::algorithm::to_lower_copy(Name1);
|
||||
std::string Name2Lower = boost::algorithm::to_lower_copy(Name2);
|
||||
return StringStartsWith(Name1Lower, Name2Lower) || StringStartsWith(Name2Lower, Name1Lower);
|
||||
};
|
||||
|
||||
TEST_CASE("StringStartsWith") {
|
||||
CHECK(StringStartsWith("Hello, World", "Hello"));
|
||||
CHECK(StringStartsWith("Hello, World", "H"));
|
||||
@@ -68,13 +84,13 @@ static inline void SplitString(std::string const& str, const char delim, std::ve
|
||||
}
|
||||
|
||||
static std::string GetDate() {
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
time_t tt = std::chrono::system_clock::to_time_t(now);
|
||||
TimeType::time_point now = TimeType::now();
|
||||
time_t tt = TimeType::to_time_t(now);
|
||||
auto local_tm = std::localtime(&tt);
|
||||
char buf[30];
|
||||
std::string date;
|
||||
if (Application::GetSettingBool(StrDebug)) {
|
||||
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
|
||||
std::strftime(buf, sizeof(buf), "[%Y/%m/%d %T.", local_tm);
|
||||
date += buf;
|
||||
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
||||
auto fraction = now - seconds;
|
||||
@@ -84,7 +100,7 @@ static std::string GetDate() {
|
||||
date += fracstr;
|
||||
date += "] ";
|
||||
} else {
|
||||
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T] ", local_tm);
|
||||
std::strftime(buf, sizeof(buf), "[%Y/%m/%d %T] ", local_tm);
|
||||
date += buf;
|
||||
}
|
||||
|
||||
@@ -237,14 +253,17 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
|
||||
static constexpr const char* sHelpString = R"(
|
||||
Commands:
|
||||
help displays this help
|
||||
exit shuts down the server
|
||||
exit
|
||||
quit shuts down the server
|
||||
kick <name> [reason] kicks specified player with an optional reason
|
||||
list lists all players and info about them
|
||||
say <message> sends the message to all players in chat
|
||||
lua [state id] switches to lua, optionally into a specific state id's lua
|
||||
settings [command] sets or gets settings for the server, run `settings help` for more info
|
||||
status how the server is doing and what it's up to
|
||||
clear clears the console window)";
|
||||
debug internal error and debug state of the server (for development)
|
||||
clear clears the console window
|
||||
version version info)";
|
||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||
}
|
||||
|
||||
@@ -264,33 +283,114 @@ void TConsole::Command_Clear(const std::string&, const std::vector<std::string>&
|
||||
mCommandline.write("\x1b[;H\x1b[2J");
|
||||
}
|
||||
|
||||
void TConsole::Command_Debug(const std::string&, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 0)) {
|
||||
return;
|
||||
}
|
||||
Application::Console().WriteRaw(fmt::format(R"(Debug info (for developers):
|
||||
UDP:
|
||||
Malformed packets: {}
|
||||
Invalid packets: {})",
|
||||
Application::MalformedUdpPackets,
|
||||
Application::InvalidUdpPackets));
|
||||
Application::Console().WriteRaw(fmt::format(R"( Clients:
|
||||
Note: All data/second rates are an average across the total time since
|
||||
connection and do not necessarily reflect the *current* data rate
|
||||
of that client.
|
||||
)"));
|
||||
mLuaEngine->Server().ForEachClient([&](const auto& Client) {
|
||||
std::string State = "";
|
||||
if (Client->IsSyncing()) {
|
||||
State += "Syncing";
|
||||
}
|
||||
if (Client->IsSynced()) {
|
||||
if (!State.empty()) {
|
||||
State += " & ";
|
||||
}
|
||||
State += "Synced";
|
||||
}
|
||||
if (Client->IsConnected()) {
|
||||
if (!State.empty()) {
|
||||
State += " & ";
|
||||
}
|
||||
State += "Connected";
|
||||
}
|
||||
if (Client->IsDisconnected()) {
|
||||
if (!State.empty()) {
|
||||
State += " & ";
|
||||
}
|
||||
State += "Disconnected";
|
||||
}
|
||||
auto Now = TimeType::now();
|
||||
auto Seconds = std::chrono::duration_cast<std::chrono::seconds>(Now - Client->ConnectionTime);
|
||||
std::string ConnectedSince = fmt::format("{:%Y/%m/%d %H:%M:%S}, {:%H:%M:%S} ago ({} seconds)",
|
||||
fmt::localtime(TimeType::to_time_t(Client->ConnectionTime)),
|
||||
Seconds,
|
||||
Seconds.count());
|
||||
Application::Console().WriteRaw(fmt::format(
|
||||
R"( {} ('{}'):
|
||||
Roles: {}
|
||||
Cars: {}
|
||||
Is guest: {}
|
||||
Has unicycle: {}
|
||||
TCP: {} (on port {})
|
||||
UDP: {} (on port {})
|
||||
Sent via TCP: {}
|
||||
Received via TCP: {}
|
||||
Sent via UDP: {} ({} packets)
|
||||
Received via UDP: {} ({} packets)
|
||||
Status: {}
|
||||
Queued packets: {}
|
||||
Latest packet: {}s ago
|
||||
Connected since: {}
|
||||
Average send: {}/s
|
||||
Average receive: {}/s)",
|
||||
Client->GetID(), Client->GetName(),
|
||||
Client->GetRoles(),
|
||||
Client->GetCarCount(),
|
||||
Client->IsGuest() ? "yes" : "no",
|
||||
Client->GetUnicycleID() == -1 ? "no" : "yes",
|
||||
Client->GetTCPSock().remote_endpoint().address() == ip::address::from_string("0.0.0.0") ? "not connected" : "connected", Client->GetTCPSock().remote_endpoint().port(),
|
||||
Client->GetUDPAddr().address() == ip::address::from_string("0.0.0.0") ? "NOT connected" : "connected", Client->GetUDPAddr().port(),
|
||||
ToHumanReadableSize(Client->TcpSent),
|
||||
ToHumanReadableSize(Client->TcpReceived),
|
||||
ToHumanReadableSize(Client->UdpSent), Client->UdpPacketsSent,
|
||||
ToHumanReadableSize(Client->UdpReceived), Client->UdpPacketsReceived,
|
||||
State.empty() ? "None (likely pre-sync)" : State,
|
||||
Client->MissedPacketQueueSize(),
|
||||
Client->SecondsSinceLastPing(),
|
||||
ConnectedSince,
|
||||
ToHumanReadableSize((Client->TcpSent + Client->UdpSent) / Seconds.count()),
|
||||
ToHumanReadableSize((Client->TcpReceived + Client->UdpReceived) / Seconds.count())));
|
||||
});
|
||||
}
|
||||
|
||||
void TConsole::Command_Version(const std::string&, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 0)) {
|
||||
return;
|
||||
}
|
||||
Application::Console().WriteRaw(fmt::format("BeamMP Server v{} ({})", Application::ServerVersionString(), BEAMMP_GIT_HASH));
|
||||
}
|
||||
|
||||
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 1, size_t(-1))) {
|
||||
return;
|
||||
}
|
||||
auto Name = args.at(0);
|
||||
auto Name = boost::algorithm::to_lower_copy(args.at(0));
|
||||
std::string Reason = "Kicked by server console";
|
||||
if (args.size() > 1) {
|
||||
Reason = ConcatArgs({ args.begin() + 1, args.end() });
|
||||
}
|
||||
beammp_trace("attempt to kick '" + Name + "' for '" + Reason + "'");
|
||||
bool Kicked = false;
|
||||
// TODO: this sucks, tolower is locale-dependent.
|
||||
auto NameCompare = [](std::string Name1, std::string Name2) -> bool {
|
||||
std::for_each(Name1.begin(), Name1.end(), [](char& c) { c = char(std::tolower(char(c))); });
|
||||
std::for_each(Name2.begin(), Name2.end(), [](char& c) { c = char(std::tolower(char(c))); });
|
||||
return StringStartsWith(Name1, Name2) || StringStartsWith(Name2, Name1);
|
||||
};
|
||||
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
if (!Client.expired()) {
|
||||
auto locked = Client.lock();
|
||||
if (NameCompare(locked->GetName(), Name)) {
|
||||
mLuaEngine->Network().ClientKick(*locked, Reason);
|
||||
Kicked = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
mLuaEngine->Server().ForEachClient([&](const auto& Client) -> IterationDecision {
|
||||
if (StringStartsWithLower(Client->GetName(), Name)) {
|
||||
mLuaEngine->Network().ClientKick(*Client, Reason);
|
||||
Kicked = true;
|
||||
return Break;
|
||||
}
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
if (!Kicked) {
|
||||
Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found.");
|
||||
@@ -403,16 +503,15 @@ void TConsole::Command_Settings(const std::string&, const std::vector<std::strin
|
||||
Application::SettingValue Value;
|
||||
qi::rule<std::string::iterator, std::string()> StringRule;
|
||||
StringRule
|
||||
%= qi::lexeme['"' >> *(qi::char_ - '"') >> '"' ]
|
||||
| +(qi::char_ - '"')
|
||||
;
|
||||
qi::rule<std::string::iterator, Application::SettingValue()> ValueRule
|
||||
%= qi::lexeme['"' >> *(qi::char_ - '"') >> '"']
|
||||
| +(qi::char_ - '"');
|
||||
qi::rule<std::string::iterator, Application::SettingValue()> ValueRule
|
||||
= qi::bool_
|
||||
| qi::int_
|
||||
| StringRule;
|
||||
auto It = ValueString.begin();
|
||||
if (qi::phrase_parse(It, ValueString.end(), ValueRule[boost::phoenix::ref(Value) = _1], ascii::space)
|
||||
&& It == ValueString.end()) {
|
||||
&& It == ValueString.end()) {
|
||||
Application::SetSetting(args.at(1), Value);
|
||||
Application::Console().WriteRaw(fmt::format("{} := {}", args.at(1), Application::SettingToString(Application::mSettings.at(args.at(1)))));
|
||||
} else {
|
||||
@@ -444,14 +543,10 @@ void TConsole::Command_List(const std::string&, const std::vector<std::string>&
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << std::left << std::setw(25) << "Name" << std::setw(6) << "ID" << std::setw(6) << "Cars" << std::endl;
|
||||
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
if (!Client.expired()) {
|
||||
auto locked = Client.lock();
|
||||
ss << std::left << std::setw(25) << locked->GetName()
|
||||
<< std::setw(6) << locked->GetID()
|
||||
<< std::setw(6) << locked->GetCarCount() << "\n";
|
||||
}
|
||||
return true;
|
||||
mLuaEngine->Server().ForEachClient([&](const auto& Client) {
|
||||
ss << std::left << std::setw(25) << Client->GetName()
|
||||
<< std::setw(6) << Client->GetID()
|
||||
<< std::setw(6) << Client->GetCarCount() << "\n";
|
||||
});
|
||||
auto Str = ss.str();
|
||||
Application::Console().WriteRaw(Str.substr(0, Str.size() - 1));
|
||||
@@ -471,20 +566,16 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
|
||||
size_t SyncingCount = 0;
|
||||
size_t MissedPacketQueueSum = 0;
|
||||
int LargestSecondsSinceLastPing = 0;
|
||||
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
if (!Client.expired()) {
|
||||
auto Locked = Client.lock();
|
||||
CarCount += Locked->GetCarCount();
|
||||
ConnectedCount += Locked->IsConnected() ? 1 : 0;
|
||||
GuestCount += Locked->IsGuest() ? 1 : 0;
|
||||
SyncedCount += Locked->IsSynced() ? 1 : 0;
|
||||
SyncingCount += Locked->IsSyncing() ? 1 : 0;
|
||||
MissedPacketQueueSum += Locked->MissedPacketQueueSize();
|
||||
if (Locked->SecondsSinceLastPing() < LargestSecondsSinceLastPing) {
|
||||
LargestSecondsSinceLastPing = Locked->SecondsSinceLastPing();
|
||||
}
|
||||
mLuaEngine->Server().ForEachClient([&](const auto& Client) {
|
||||
CarCount += Client->GetCarCount();
|
||||
ConnectedCount += Client->IsConnected() ? 1 : 0;
|
||||
GuestCount += Client->IsGuest() ? 1 : 0;
|
||||
SyncedCount += Client->IsSynced() ? 1 : 0;
|
||||
SyncingCount += Client->IsSyncing() ? 1 : 0;
|
||||
MissedPacketQueueSum += Client->MissedPacketQueueSize();
|
||||
if (Client->SecondsSinceLastPing() < LargestSecondsSinceLastPing) {
|
||||
LargestSecondsSinceLastPing = Client->SecondsSinceLastPing();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
size_t SystemsStarting = 0;
|
||||
@@ -548,17 +639,66 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
|
||||
<< "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n"
|
||||
<< "\tSubsystems:\n"
|
||||
<< "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n"
|
||||
<< "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n"
|
||||
<< "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n"
|
||||
<< "\t\tGood: [ " << SystemsGoodList << " ]\n"
|
||||
<< "\t\tStarting: [ " << SystemsStartingList << " ]\n"
|
||||
<< "\t\tBad: [ " << SystemsBadList << " ]\n"
|
||||
<< "\t\tShutting down: [ " << SystemsShuttingDownList << " ]\n"
|
||||
<< "\t\tShut down: [ " << SystemsShutdownList << " ]\n"
|
||||
<< "\t\tShut down: [ " << SystemsShutdownList << " ]\n"
|
||||
<< "";
|
||||
|
||||
Application::Console().WriteRaw(Status.str());
|
||||
}
|
||||
|
||||
void TConsole::Autocomplete_Lua(const std::string& stub, std::vector<std::string>& suggestions) {
|
||||
auto stateNames = mLuaEngine->GetLuaStateNames();
|
||||
|
||||
for (const auto& name : stateNames) {
|
||||
if (name.find(stub) == 0) {
|
||||
suggestions.push_back("lua " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TConsole::Autocomplete_Kick(const std::string& stub, std::vector<std::string>& suggestions) {
|
||||
std::string stub_lower = boost::algorithm::to_lower_copy(stub);
|
||||
|
||||
mLuaEngine->Server().ForEachClient([&](const auto& Client) {
|
||||
if (StringStartsWithLower(Client->GetName(), stub_lower)) {
|
||||
suggestions.push_back("kick " + Client->GetName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TConsole::Autocomplete_Settings(const std::string& stub, std::vector<std::string>& suggestions) {
|
||||
const std::string subcommands[] = { "help", "list", "set", "get" };
|
||||
|
||||
auto [command, args] = ParseCommand(stub);
|
||||
|
||||
std::string arg;
|
||||
if (!args.empty())
|
||||
arg = boost::algorithm::to_lower_copy(args.at(0));
|
||||
|
||||
// suggest setting names
|
||||
if (command == "set" || command == "get") {
|
||||
for (const auto& [k, v] : Application::mSettings) {
|
||||
std::string key = std::string(k);
|
||||
if (StringStartsWithLower(key, arg)) {
|
||||
suggestions.push_back("settings " + command + " " + key);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// suggest subcommands
|
||||
for (const auto& cmd : subcommands) {
|
||||
if (cmd.find(command) == 0) {
|
||||
suggestions.push_back("settings " + cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
|
||||
auto FutureIsNonNil =
|
||||
[](const std::shared_ptr<TLuaResult>& Future) {
|
||||
@@ -660,7 +800,6 @@ Commands
|
||||
TConsole::TConsole() {
|
||||
mCommandline.enable_history();
|
||||
mCommandline.set_history_limit(20);
|
||||
mCommandline.set_prompt("> ");
|
||||
BackupOldLog();
|
||||
mCommandline.on_command = [this](Commandline& c) {
|
||||
try {
|
||||
@@ -685,7 +824,7 @@ TConsole::TConsole() {
|
||||
} else {
|
||||
if (!mLuaEngine) {
|
||||
beammp_error("Attempted to run a command before Lua engine started. Please wait and try again.");
|
||||
} else if (cmd == "exit") {
|
||||
} else if (cmd == "exit" || cmd == "quit") {
|
||||
beammp_info("gracefully shutting down");
|
||||
Application::GracefullyShutdown();
|
||||
} else if (cmd == "say") {
|
||||
@@ -747,16 +886,15 @@ TConsole::TConsole() {
|
||||
}
|
||||
}
|
||||
} else { // if not lua
|
||||
if (stub.find("lua") == 0) { // starts with "lua" means we should suggest state names
|
||||
std::string after_prefix = TrimString(stub.substr(3));
|
||||
auto stateNames = mLuaEngine->GetLuaStateNames();
|
||||
|
||||
for (const auto& name : stateNames) {
|
||||
if (name.find(after_prefix) == 0) {
|
||||
suggestions.push_back("lua " + name);
|
||||
}
|
||||
for (const auto& [cmd_name, autocomplete_fn] : mCommandAutocompleteMap) {
|
||||
if (stub.find(cmd_name) == 0) { // input starts with a full command (that has autocomplete)
|
||||
std::size_t cmd_len = cmd_name.length();
|
||||
std::string trimmed = TrimString(stub.substr(cmd_len));
|
||||
autocomplete_fn(trimmed, suggestions);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (suggestions.empty()) {
|
||||
for (const auto& [cmd_name, cmd_fn] : mCommandMap) {
|
||||
if (cmd_name.find(stub) == 0) {
|
||||
suggestions.push_back(cmd_name);
|
||||
|
||||
@@ -17,14 +17,14 @@ void THeartbeatThread::operator()() {
|
||||
// these are "hot-change" related variables
|
||||
static std::string Last;
|
||||
|
||||
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
|
||||
static TimeType::time_point LastNormalUpdateTime = TimeType::now();
|
||||
bool isAuth = false;
|
||||
size_t UpdateReminderCounter = 0;
|
||||
while (!Application::IsShuttingDown()) {
|
||||
++UpdateReminderCounter;
|
||||
Body = GenerateCall();
|
||||
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
||||
auto Now = std::chrono::high_resolution_clock::now();
|
||||
auto Now = TimeType::now();
|
||||
bool Unchanged = Last == Body;
|
||||
auto TimePassed = (Now - LastNormalUpdateTime);
|
||||
auto Threshold = Unchanged ? 30 : 5;
|
||||
@@ -145,9 +145,9 @@ std::string THeartbeatThread::GenerateCall() {
|
||||
<< "&version=" << Application::ServerVersionString()
|
||||
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
||||
<< "&name=" << Application::GetSettingString(StrName)
|
||||
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||
<< "&modlist=" << TResourceManager::FormatForBackend(mResourceManager.FileMap())
|
||||
<< "&modstotalsize=" << mResourceManager.TotalModsSize()
|
||||
<< "&modstotal=" << mResourceManager.LoadedModCount()
|
||||
<< "&playerslist=" << GetPlayers()
|
||||
<< "&desc=" << Application::GetSettingString(StrDescription);
|
||||
return Ret.str();
|
||||
@@ -167,12 +167,8 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
|
||||
}
|
||||
std::string THeartbeatThread::GetPlayers() {
|
||||
std::string Return;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Return += ClientPtr.lock()->GetName() + ";";
|
||||
}
|
||||
return true;
|
||||
mServer.ForEachClient([&](const auto& Client) {
|
||||
Return += Client->GetName() + ";";
|
||||
});
|
||||
return Return;
|
||||
}
|
||||
|
||||
+127
-61
@@ -4,7 +4,8 @@
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaPlugin.h"
|
||||
#include "sol/object.hpp"
|
||||
#include "Uuid.h"
|
||||
#include "sol/types.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -71,13 +72,12 @@ void TLuaEngine::operator()() {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
|
||||
}
|
||||
mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(10));
|
||||
}
|
||||
});
|
||||
// event loop
|
||||
auto Before = std::chrono::high_resolution_clock::now();
|
||||
auto Before = TimeType::now();
|
||||
while (!Application::IsShuttingDown()) {
|
||||
{ // Timed Events Scope
|
||||
std::unique_lock Lock(mTimedEventsMutex);
|
||||
@@ -110,14 +110,14 @@ void TLuaEngine::operator()() {
|
||||
} else {
|
||||
constexpr double NsFactor = 1000000.0;
|
||||
constexpr double Expected = 10.0; // ms
|
||||
const auto Diff = (std::chrono::high_resolution_clock::now() - Before).count() / NsFactor;
|
||||
const auto Diff = (TimeType::now() - Before).count() / NsFactor;
|
||||
if (Diff < Expected) {
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(size_t((Expected - Diff) * NsFactor)));
|
||||
} else {
|
||||
beammp_tracef("Event loop cannot keep up! Running {}ms behind", Diff);
|
||||
}
|
||||
}
|
||||
Before = std::chrono::high_resolution_clock::now();
|
||||
Before = TimeType::now();
|
||||
}
|
||||
|
||||
if (ResultCheckThread.joinable()) {
|
||||
@@ -253,11 +253,15 @@ std::vector<std::string> TLuaEngine::StateThreadData::GetStateTableKeys(const st
|
||||
} else if (i == keys.size() - 1) {
|
||||
if (obj.get_type() == sol::type::table) {
|
||||
for (const auto& [key, value] : obj.as<sol::table>()) {
|
||||
std::string s = key.as<std::string>();
|
||||
if (value.get_type() == sol::type::function) {
|
||||
s += "(";
|
||||
if (key.get_type() == sol::type::string) {
|
||||
std::string s = key.as<std::string>();
|
||||
|
||||
if (value.get_type() == sol::type::function) {
|
||||
s += "(";
|
||||
}
|
||||
|
||||
Result.push_back(s);
|
||||
}
|
||||
Result.push_back(s);
|
||||
}
|
||||
} else {
|
||||
Result = { obj.as<std::string>() };
|
||||
@@ -281,7 +285,7 @@ std::vector<std::string> TLuaEngine::StateThreadData::GetStateTableKeys(const st
|
||||
|
||||
*/
|
||||
|
||||
void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<std::chrono::high_resolution_clock::duration>& Max) {
|
||||
void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<TimeType::duration>& Max) {
|
||||
for (const auto& Result : Results) {
|
||||
bool Cancelled = false;
|
||||
size_t ms = 0;
|
||||
@@ -330,7 +334,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const
|
||||
return mLuaStates.at(StateID)->EnqueueScript(Script);
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
|
||||
std::unique_lock Lock(mLuaStatesMutex);
|
||||
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args);
|
||||
}
|
||||
@@ -410,13 +414,55 @@ std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& Ev
|
||||
return mLuaEvents[EventName][StateId];
|
||||
}
|
||||
|
||||
std::vector<sol::object> TLuaEngine::StateThreadData::JsonStringToArray(JsonString Str) {
|
||||
auto LocalTable = Lua_JsonDecode(Str.value).as<std::vector<sol::object>>();
|
||||
for (auto& value : LocalTable) {
|
||||
if (value.is<std::string>() && value.as<std::string>() == BEAMMP_INTERNAL_NIL) {
|
||||
value = sol::object {};
|
||||
}
|
||||
}
|
||||
return LocalTable;
|
||||
}
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
||||
auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
|
||||
auto Table = mStateView.create_table();
|
||||
for (const sol::stack_proxy& Arg : EventArgs) {
|
||||
switch (Arg.get_type()) {
|
||||
case sol::type::none:
|
||||
case sol::type::userdata:
|
||||
case sol::type::lightuserdata:
|
||||
case sol::type::thread:
|
||||
case sol::type::function:
|
||||
case sol::type::poly:
|
||||
Table.add(BEAMMP_INTERNAL_NIL);
|
||||
beammp_warnf("Passed a value of type '{}' to TriggerGlobalEvent(\"{}\", ...). This type can not be serialized, and cannot be passed between states. It will arrive as <nil> in handlers.", sol::type_name(EventArgs.lua_state(), Arg.get_type()), EventName);
|
||||
break;
|
||||
case sol::type::lua_nil:
|
||||
Table.add(BEAMMP_INTERNAL_NIL);
|
||||
break;
|
||||
case sol::type::string:
|
||||
case sol::type::number:
|
||||
case sol::type::boolean:
|
||||
case sol::type::table:
|
||||
Table.add(Arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
JsonString Str { "" };
|
||||
if (!Table.empty()) {
|
||||
Str.value = LuaAPI::MP::JsonEncode(Table);
|
||||
}
|
||||
|
||||
auto Return = mEngine->TriggerEvent(EventName, mStateId, Str);
|
||||
mEngine->ReportErrors(Return);
|
||||
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
|
||||
|
||||
sol::variadic_results LocalArgs = Str.value.empty() ? sol::variadic_results {} : JsonStringToArray(Str);
|
||||
|
||||
for (const auto& Handler : MyHandlers) {
|
||||
auto Fn = mStateView[Handler];
|
||||
if (Fn.valid()) {
|
||||
auto LuaResult = Fn(EventArgs);
|
||||
auto LuaResult = LocalArgs.empty() ? Fn() : Fn(LocalArgs);
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
if (LuaResult.valid()) {
|
||||
Result->Error = false;
|
||||
@@ -455,31 +501,44 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
||||
}
|
||||
return Result;
|
||||
});
|
||||
AsyncEventReturn.set_function("Wait",
|
||||
[](const sol::table& Self, std::optional<float> TimeoutS) {
|
||||
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
|
||||
if (TimeoutS.has_value()) {
|
||||
TLuaEngine::WaitForAll(Vector, std::chrono::milliseconds(size_t(TimeoutS.value() * 1000.0f)));
|
||||
} else {
|
||||
TLuaEngine::WaitForAll(Vector);
|
||||
}
|
||||
});
|
||||
return AsyncEventReturn;
|
||||
}
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
|
||||
// TODO: make asynchronous?
|
||||
sol::table Result = mStateView.create_table();
|
||||
auto Result = mStateView.create_table();
|
||||
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
|
||||
auto Fn = mStateView[Handler];
|
||||
if (Fn.valid() && Fn.get_type() == sol::type::function) {
|
||||
auto FnRet = Fn(EventArgs);
|
||||
if (FnRet.valid()) {
|
||||
Result.add(FnRet);
|
||||
for (const auto& Res : FnRet) {
|
||||
Result.add(Res);
|
||||
}
|
||||
} else {
|
||||
sol::error Err = FnRet;
|
||||
beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
|
||||
}
|
||||
} else {
|
||||
beammp_lua_errorf("Handler '{}' for event '{}' in state '{}' is NOT a function, and will be ignored.", Handler, EventName, mStateId);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
|
||||
auto MaybeClient = GetClient(mEngine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto IDs = MaybeClient.value().lock()->GetIdentifiers();
|
||||
auto Client = GetClient(mEngine->Server(), ID);
|
||||
if (Client) {
|
||||
auto IDs = Client->GetIdentifiers();
|
||||
if (IDs.empty()) {
|
||||
return sol::lua_nil;
|
||||
}
|
||||
@@ -495,27 +554,20 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
|
||||
sol::table Result = mStateView.create_table();
|
||||
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
if (!Client.expired()) {
|
||||
auto locked = Client.lock();
|
||||
Result[locked->GetID()] = locked->GetName();
|
||||
}
|
||||
return true;
|
||||
mEngine->Server().ForEachClient([&](const auto& Client) {
|
||||
Result[Client->GetID()] = Client->GetName();
|
||||
});
|
||||
return Result;
|
||||
}
|
||||
|
||||
int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name) {
|
||||
int Id = -1;
|
||||
mEngine->mServer->ForEachClient([&Id, &Name](std::weak_ptr<TClient> Client) -> bool {
|
||||
if (!Client.expired()) {
|
||||
auto locked = Client.lock();
|
||||
if (locked->GetName() == Name) {
|
||||
Id = locked->GetID();
|
||||
return false;
|
||||
}
|
||||
mEngine->mServer->ForEachClient([&Id, &Name](const auto& Client) -> IterationDecision {
|
||||
if (Client->GetName() == Name) {
|
||||
Id = Client->GetID();
|
||||
return Break;
|
||||
}
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
return Id;
|
||||
}
|
||||
@@ -547,18 +599,17 @@ sol::table TLuaEngine::StateThreadData::Lua_FS_ListDirectories(const std::string
|
||||
}
|
||||
|
||||
std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) {
|
||||
auto MaybeClient = GetClient(mEngine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
return MaybeClient.value().lock()->GetName();
|
||||
auto Client = GetClient(mEngine->Server(), ID);
|
||||
if (Client) {
|
||||
return Client->GetName();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
|
||||
auto MaybeClient = GetClient(mEngine->Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto Client = MaybeClient.value().lock();
|
||||
auto Client = GetClient(mEngine->Server(), ID);
|
||||
if (Client) {
|
||||
TClient::TSetOfVehicleData VehicleData;
|
||||
{ // Vehicle Data Lock Scope
|
||||
auto LockedData = Client->GetAllCars();
|
||||
@@ -579,9 +630,8 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
|
||||
|
||||
std::pair<sol::table, std::string> TLuaEngine::StateThreadData::Lua_GetPositionRaw(int PID, int VID) {
|
||||
std::pair<sol::table, std::string> Result;
|
||||
auto MaybeClient = GetClient(mEngine->Server(), PID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto Client = MaybeClient.value().lock();
|
||||
auto Client = GetClient(mEngine->Server(), PID);
|
||||
if (Client) {
|
||||
std::string VehiclePos = Client->GetCarPositionRaw(VID);
|
||||
|
||||
if (VehiclePos.empty()) {
|
||||
@@ -726,14 +776,14 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
MPTable.set_function("CreateTimer", [&]() -> sol::table {
|
||||
sol::state_view StateView(mState);
|
||||
sol::table Result = StateView.create_table();
|
||||
Result["__StartTime"] = std::chrono::high_resolution_clock::now();
|
||||
Result["__StartTime"] = TimeType::now();
|
||||
Result.set_function("GetCurrent", [&](const sol::table& Table) -> float {
|
||||
auto End = std::chrono::high_resolution_clock::now();
|
||||
auto Start = Table.get<std::chrono::high_resolution_clock::time_point>("__StartTime");
|
||||
auto End = TimeType::now();
|
||||
auto Start = Table.get<TimeType::time_point>("__StartTime");
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(End - Start).count() / 1000000.0f;
|
||||
});
|
||||
Result.set_function("Start", [&](sol::table Table) {
|
||||
Table["__StartTime"] = std::chrono::high_resolution_clock::now();
|
||||
Table["__StartTime"] = TimeType::now();
|
||||
});
|
||||
return Result;
|
||||
});
|
||||
@@ -745,9 +795,10 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
MPTable.set_function("TriggerGlobalEvent", [&](const std::string& EventName, sol::variadic_args EventArgs) -> sol::table {
|
||||
return Lua_TriggerGlobalEvent(EventName, EventArgs);
|
||||
});
|
||||
MPTable.set_function("TriggerLocalEvent", [&](const std::string& EventName, sol::variadic_args EventArgs) -> sol::table {
|
||||
return Lua_TriggerLocalEvent(EventName, EventArgs);
|
||||
});
|
||||
MPTable.set_function(
|
||||
"TriggerLocalEvent", [&](const std::string& EventName, sol::variadic_args EventArgs) -> auto{
|
||||
return Lua_TriggerLocalEvent(EventName, EventArgs);
|
||||
});
|
||||
MPTable.set_function("TriggerClientEvent", &LuaAPI::MP::TriggerClientEvent);
|
||||
MPTable.set_function("TriggerClientEventJson", &LuaAPI::MP::TriggerClientEventJson);
|
||||
MPTable.set_function("GetPlayerCount", &LuaAPI::MP::GetPlayerCount);
|
||||
@@ -818,6 +869,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten);
|
||||
UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify);
|
||||
UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify);
|
||||
UtilTable.set_function("GenerateUUID", &uuid::GenerateUuid);
|
||||
UtilTable.set_function("Random", [this] {
|
||||
return mUniformRealDistribution01(mMersenneTwister);
|
||||
});
|
||||
@@ -874,7 +926,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLu
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy) {
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy) {
|
||||
// TODO: Document all this
|
||||
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
|
||||
if (Strategy == CallStrategy::BestEffort) {
|
||||
@@ -896,7 +948,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
Result->StateId = mStateId;
|
||||
Result->Function = FunctionName;
|
||||
@@ -981,19 +1033,24 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
continue;
|
||||
}
|
||||
switch (Arg.index()) {
|
||||
case TLuaArgTypes_String:
|
||||
case TLuaType::String:
|
||||
LuaArgs.push_back(sol::make_object(StateView, std::get<std::string>(Arg)));
|
||||
break;
|
||||
case TLuaArgTypes_Int:
|
||||
case TLuaType::Int:
|
||||
LuaArgs.push_back(sol::make_object(StateView, std::get<int>(Arg)));
|
||||
break;
|
||||
case TLuaArgTypes_VariadicArgs:
|
||||
LuaArgs.push_back(sol::make_object(StateView, std::get<sol::variadic_args>(Arg)));
|
||||
case TLuaType::Json: {
|
||||
auto Str = std::get<JsonString>(Arg);
|
||||
if (!Str.value.empty()) {
|
||||
auto LocalArgs = JsonStringToArray(Str);
|
||||
LuaArgs.insert(LuaArgs.end(), LocalArgs.begin(), LocalArgs.end());
|
||||
}
|
||||
break;
|
||||
case TLuaArgTypes_Bool:
|
||||
}
|
||||
case TLuaType::Bool:
|
||||
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
|
||||
break;
|
||||
case TLuaArgTypes_StringStringMap: {
|
||||
case TLuaType::StringStringMap: {
|
||||
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
|
||||
auto Table = StateView.create_table();
|
||||
for (const auto& [k, v] : Map) {
|
||||
@@ -1002,6 +1059,15 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
LuaArgs.push_back(sol::make_object(StateView, Table));
|
||||
break;
|
||||
}
|
||||
case TLuaType::StringSizeTMap: {
|
||||
auto Map = std::get<std::unordered_map<std::string, size_t>>(Arg);
|
||||
auto Table = StateView.create_table();
|
||||
for (const auto& [k, v] : Map) {
|
||||
Table[k] = v;
|
||||
}
|
||||
LuaArgs.push_back(sol::make_object(StateView, Table));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
beammp_error("Unknown argument type, passed as nil");
|
||||
break;
|
||||
@@ -1040,8 +1106,8 @@ std::vector<TLuaEngine::QueuedFunction> TLuaEngine::StateThreadData::Debug_GetSt
|
||||
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) {
|
||||
std::unique_lock Lock(mTimedEventsMutex);
|
||||
TimedEvent Event {
|
||||
std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) },
|
||||
std::chrono::high_resolution_clock::now(),
|
||||
TimeType::duration { std::chrono::milliseconds(IntervalMS) },
|
||||
TimeType::now(),
|
||||
EventName,
|
||||
StateId,
|
||||
Strategy
|
||||
@@ -1084,10 +1150,10 @@ TLuaChunk::TLuaChunk(std::shared_ptr<std::string> Content, std::string FileName,
|
||||
}
|
||||
|
||||
bool TLuaEngine::TimedEvent::Expired() {
|
||||
auto Waited = (std::chrono::high_resolution_clock::now() - LastCompletion);
|
||||
auto Waited = (TimeType::now() - LastCompletion);
|
||||
return Waited >= Duration;
|
||||
}
|
||||
|
||||
void TLuaEngine::TimedEvent::Reset() {
|
||||
LastCompletion = std::chrono::high_resolution_clock::now();
|
||||
LastCompletion = TimeType::now();
|
||||
}
|
||||
|
||||
+154
-129
@@ -1,6 +1,7 @@
|
||||
#include "TNetwork.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "IterationDecision.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <array>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
std::vector<uint8_t> StringToVector(const std::string& Str) {
|
||||
@@ -31,15 +33,6 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
|
||||
, mResourceManager(ResourceManager) {
|
||||
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Starting);
|
||||
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting);
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
beammp_debug("Kicking all players due to shutdown");
|
||||
Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool {
|
||||
if (!client.expired()) {
|
||||
ClientKick(*client.lock(), "Server shutdown");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
|
||||
if (mUDPThread.joinable()) {
|
||||
@@ -56,6 +49,14 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
|
||||
});
|
||||
mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
|
||||
mUDPThread = std::thread(&TNetwork::UDPServerMain, this);
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
|
||||
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(60));
|
||||
beammp_debug("Kicking all players due to shutdown");
|
||||
Server.ForEachClient([&](const auto& Client) {
|
||||
ClientKick(*Client, "Server shutdown");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void TNetwork::UDPServerMain() {
|
||||
@@ -81,29 +82,39 @@ void TNetwork::UDPServerMain() {
|
||||
try {
|
||||
ip::udp::endpoint client {};
|
||||
std::vector<uint8_t> Data = UDPRcvFromClient(client); // Receives any data from Socket
|
||||
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
||||
if (Data.empty() || Pos > Data.begin() + 2)
|
||||
// It has to be size>=2, because each UDP packet has a header of
|
||||
// <id+1>:
|
||||
// where id+1 is one byte.
|
||||
// We discard a UDP packet if it doesn't start like this.
|
||||
if (Data.size() < 2 || Data.at(1) != ':') {
|
||||
++Application::MalformedUdpPackets;
|
||||
continue;
|
||||
}
|
||||
// We do -1 here, because the launcher does +1
|
||||
// because it uses (or used to use) null-terminated strings
|
||||
// to represent packets. This would mean that player 0 would
|
||||
// cause empty packets.
|
||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
auto Client = GetClient(mServer, ID);
|
||||
if (Client) {
|
||||
try {
|
||||
if (Client->IsConnected() && Client->GetUDPAddr() != client) {
|
||||
beammp_debugf("Client at {}:{} tried to send UDP for client {}", client.address().to_string(), client.port(), Client->GetID());
|
||||
++Application::InvalidUdpPackets;
|
||||
continue;
|
||||
} else {
|
||||
Client->SetUDPAddr(client);
|
||||
Client->SetIsConnected(true);
|
||||
Client->mUDPCONNECTED = true;
|
||||
}
|
||||
Client->UdpReceived += Data.size();
|
||||
++Client->UdpPacketsReceived;
|
||||
Data.erase(Data.begin() + 0, Data.begin() + 2);
|
||||
TServer::GlobalParser(Client, std::move(Data), *this);
|
||||
} catch (const std::exception&) {
|
||||
++Application::InvalidUdpPackets;
|
||||
}
|
||||
|
||||
if (Client->GetID() == ID) {
|
||||
Client->SetUDPAddr(client);
|
||||
Client->SetIsConnected(true);
|
||||
Data.erase(Data.begin(), Data.begin() + 2);
|
||||
TServer::GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
beammp_error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
@@ -200,20 +211,18 @@ void TNetwork::HandleDownload(TConnection&& Conn) {
|
||||
return;
|
||||
}
|
||||
auto ID = uint8_t(D);
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
if (c->GetID() == ID) {
|
||||
c->SetDownSock(std::move(Conn.Socket));
|
||||
}
|
||||
mServer.ForEachClient([&](const auto& Client) -> IterationDecision {
|
||||
if (Client->GetID() == ID) {
|
||||
Client->SetDownSock(std::move(Conn.Socket));
|
||||
return Break;
|
||||
}
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
auto Client = CreateClient(std::move(RawConnection.Socket));
|
||||
Client->ConnectionTime = TimeType::now();
|
||||
Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string());
|
||||
beammp_tracef("This thread is ip {}", RawConnection.SockAddr.address().to_string());
|
||||
|
||||
@@ -265,7 +274,11 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
for (const auto& ID : AuthRes["identifiers"]) {
|
||||
auto Raw = std::string(ID);
|
||||
auto SepIndex = Raw.find(':');
|
||||
Client->SetIdentifier(Raw.substr(0, SepIndex), Raw.substr(SepIndex + 1));
|
||||
if (SepIndex != std::string::npos) {
|
||||
Client->SetIdentifier(Raw.substr(0, SepIndex), Raw.substr(SepIndex + 1));
|
||||
} else {
|
||||
beammp_errorf("Invalid response from auth servers: No ':' in identifier '{}', ignoring it", Raw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
beammp_error("Invalid authentication data received from authentication backend");
|
||||
@@ -280,24 +293,21 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
}
|
||||
|
||||
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Cl;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Cl = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
mServer.ForEachClient([&](const auto& ThisClient) -> IterationDecision {
|
||||
// FIXME: This doesn't respect forum ID, and it should :^)
|
||||
if (ThisClient->GetName() == Client->GetName() && ThisClient->IsGuest() == Client->IsGuest()) {
|
||||
ThisClient->Disconnect("Stale Client (replaced by new client)");
|
||||
return Break;
|
||||
}
|
||||
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
|
||||
Cl->Disconnect("Stale Client (not a real player)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||
Client->SetID(OpenID());
|
||||
beammp_info("Assigned ID " + std::to_string(Client->GetID()) + " to " + Client->GetName());
|
||||
|
||||
mServer.InsertClient(Client);
|
||||
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers(), Client->GetID());
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||
@@ -313,22 +323,24 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (NotAllowed) {
|
||||
ClientKick(*Client, "you are not allowed on the server!");
|
||||
return {};
|
||||
} else if (NotAllowedWithReason) {
|
||||
ClientKick(*Client, Reason);
|
||||
return {};
|
||||
bool fitsOnServer = mServer.ClientCount() < size_t(Application::GetSettingInt(StrMaxPlayers)); // || luaplayercountbypass;
|
||||
|
||||
if (!NotAllowed && !NotAllowedWithReason && fitsOnServer) {
|
||||
beammp_info("Identification success");
|
||||
TCPClient(Client);
|
||||
return Client;
|
||||
}
|
||||
|
||||
if (mServer.ClientCount() < size_t(Application::GetSettingInt(StrMaxPlayers))) {
|
||||
beammp_info("Identification success");
|
||||
mServer.InsertClient(Client);
|
||||
TCPClient(Client);
|
||||
if (NotAllowed) {
|
||||
ClientKick(*Client, "You are not allowed on the server!");
|
||||
} else if (NotAllowedWithReason) {
|
||||
ClientKick(*Client, Reason);
|
||||
} else {
|
||||
ClientKick(*Client, "Server full!");
|
||||
}
|
||||
return Client;
|
||||
|
||||
mServer.RemoveClient(Client);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<TClient> TNetwork::CreateClient(ip::tcp::socket&& TCPSock) {
|
||||
@@ -371,11 +383,12 @@ bool TNetwork::TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync
|
||||
return false;
|
||||
}
|
||||
c.UpdatePingTime();
|
||||
c.TcpSent += ToSend.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
||||
if (c.IsDisconnected()) {
|
||||
if (c.IsDisconnected() || (c.mUDPCONNECTED && !c.IsConnected())) {
|
||||
beammp_error("Client disconnected, cancelling TCPRcv");
|
||||
return {};
|
||||
}
|
||||
@@ -393,6 +406,12 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
||||
}
|
||||
Header = *reinterpret_cast<int32_t*>(HeaderData.data());
|
||||
|
||||
if (Header < 0) {
|
||||
ClientKick(c, "Invalid packet - header negative");
|
||||
beammp_errorf("Client {} send negative TCP header, ignoring packet", c.GetID());
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Data;
|
||||
// TODO: This is arbitrary, this needs to be handled another way
|
||||
if (Header < int32_t(100 * MB)) {
|
||||
@@ -413,6 +432,7 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
||||
beammp_errorf("Expected to read {} bytes, instead got {}", Header, N);
|
||||
}
|
||||
|
||||
c.TcpReceived += N + HeaderData.size();
|
||||
constexpr std::string_view ABG = "ABG:";
|
||||
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
||||
Data.erase(Data.begin(), Data.begin() + ABG.size());
|
||||
@@ -425,7 +445,7 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
||||
void TNetwork::ClientKick(TClient& c, const std::string& R) {
|
||||
beammp_info("Client kicked: " + R);
|
||||
if (!TCPSend(c, StringToVector("K" + R))) {
|
||||
beammp_debugf("tried to kick player '{}' (id {}), but was already connected", c.GetName(), c.GetID());
|
||||
beammp_debugf("tried to kick player '{}' (id {}), but was already disconnected", c.GetName(), c.GetID());
|
||||
}
|
||||
c.Disconnect("Kicked");
|
||||
}
|
||||
@@ -492,7 +512,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
|
||||
Client->Disconnect("TCPRcv failed");
|
||||
break;
|
||||
}
|
||||
TServer::GlobalParser(c, std::move(res), mPPSMonitor, *this);
|
||||
TServer::GlobalParser(c, std::move(res), *this);
|
||||
}
|
||||
|
||||
if (QueueSync.joinable())
|
||||
@@ -508,13 +528,8 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
|
||||
|
||||
void TNetwork::UpdatePlayer(TClient& Client) {
|
||||
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::GetSettingInt(StrMaxPlayers)) + ":";
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
Packet += c->GetName() + ",";
|
||||
}
|
||||
return true;
|
||||
mServer.ForEachClient([&](const auto& ThisClient) {
|
||||
Packet += ThisClient->GetName() + ",";
|
||||
});
|
||||
Packet = Packet.substr(0, Packet.length() - 1);
|
||||
Client.EnqueuePacket(StringToVector(Packet));
|
||||
@@ -522,8 +537,14 @@ void TNetwork::UpdatePlayer(TClient& Client) {
|
||||
}
|
||||
|
||||
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
|
||||
beammp_assert(!ClientPtr.expired());
|
||||
auto LockedClientPtr = ClientPtr.lock();
|
||||
std::shared_ptr<TClient> LockedClientPtr { nullptr };
|
||||
try {
|
||||
LockedClientPtr = ClientPtr.lock();
|
||||
} catch (const std::exception&) {
|
||||
beammp_warn("Client expired in OnDisconnect, this is unexpected");
|
||||
return;
|
||||
}
|
||||
beammp_assert(LockedClientPtr != nullptr);
|
||||
TClient& c = *LockedClientPtr;
|
||||
beammp_info(c.GetName() + (" Connection Terminated"));
|
||||
std::string Packet;
|
||||
@@ -540,26 +561,25 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
|
||||
SendToAll(&c, StringToVector(Packet), false, true);
|
||||
Packet.clear();
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID());
|
||||
LuaAPI::MP::Engine->ReportErrors(Futures);
|
||||
LuaAPI::MP::Engine->WaitForAll(Futures);
|
||||
c.Disconnect("Already Disconnected (OnDisconnect)");
|
||||
mServer.RemoveClient(ClientPtr);
|
||||
}
|
||||
|
||||
int TNetwork::OpenID() {
|
||||
// This lock ensures that each call to OpenID is exclusive.
|
||||
// If we didn't have this, two concurrent calls to this function may result
|
||||
// in the same ID.
|
||||
std::unique_lock Lock(mOpenIDMutex);
|
||||
int ID = 0;
|
||||
bool found;
|
||||
do {
|
||||
found = true;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
if (c->GetID() == ID) {
|
||||
found = false;
|
||||
ID++;
|
||||
}
|
||||
mServer.ForEachClient([&](const auto& Client) {
|
||||
if (Client->GetID() == ID) {
|
||||
found = false;
|
||||
ID++;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} while (!found);
|
||||
return ID;
|
||||
@@ -569,8 +589,6 @@ void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
|
||||
beammp_assert(!c.expired());
|
||||
beammp_info("Client connected");
|
||||
auto LockedClient = c.lock();
|
||||
LockedClient->SetID(OpenID());
|
||||
beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
|
||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", "", LockedClient->GetID()));
|
||||
SyncResources(*LockedClient);
|
||||
if (LockedClient->IsDisconnected())
|
||||
@@ -593,11 +611,39 @@ void TNetwork::SyncResources(TClient& c) {
|
||||
constexpr std::string_view Done = "Done";
|
||||
if (std::equal(Data.begin(), Data.end(), Done.begin(), Done.end()))
|
||||
break;
|
||||
Parse(c, Data);
|
||||
HandleResourcePackets(c, Data);
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
ModMap TNetwork::GetClientMods(TClient& Client) {
|
||||
auto AllMods = mResourceManager.FileMap();
|
||||
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerRequestMods", "", Client.GetName(), Client.GetRoles(), Client.IsGuest(), Client.GetIdentifiers(), Client.GetID(), AllMods);
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
|
||||
ModMap AllowedMods = AllMods;
|
||||
|
||||
for (const std::shared_ptr<TLuaResult>& Future : Futures) {
|
||||
sol::table Result;
|
||||
if (!Future->Error && Future->Result.is<sol::table>()) {
|
||||
Result = Future->Result.as<sol::table>();
|
||||
|
||||
for (const auto& [name, size] : AllMods) {
|
||||
auto val = Result.get<sol::optional<int>>(name);
|
||||
if (!val.has_value()) {
|
||||
AllowedMods.erase(name);
|
||||
beammp_debugf("Not sending mod '{}' to player '{}' (from state '{}')", name, Client.GetName(), Future->StateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Client.AllowedMods = AllowedMods;
|
||||
|
||||
return AllowedMods;
|
||||
}
|
||||
|
||||
void TNetwork::HandleResourcePackets(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
if (Packet.empty())
|
||||
return;
|
||||
char Code = Packet.at(0), SubCode = 0;
|
||||
@@ -610,7 +656,7 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
case 'S':
|
||||
if (SubCode == 'R') {
|
||||
beammp_debug("Sending Mod Info");
|
||||
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||
std::string ToSend = TResourceManager::FormatForClient(GetClientMods(c));
|
||||
if (ToSend.empty())
|
||||
ToSend = "-";
|
||||
if (!TCPSend(c, StringToVector(ToSend))) {
|
||||
@@ -624,28 +670,22 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
}
|
||||
|
||||
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
|
||||
beammp_infof("{} ({}) requesting mod: '{}'", c.GetName(), c.GetID(), UnsafeName);
|
||||
|
||||
if (!fs::path(UnsafeName).has_filename()) {
|
||||
if (!TCPSend(c, StringToVector("CO"))) {
|
||||
// TODO: handle
|
||||
}
|
||||
beammp_warn("File " + UnsafeName + " is not a file!");
|
||||
return;
|
||||
}
|
||||
auto FileName = fs::path(UnsafeName).filename().string();
|
||||
FileName = Application::GetSettingString(StrResourceFolder) + "/Client/" + FileName;
|
||||
auto FileName = UnsafeName;
|
||||
|
||||
if (!std::filesystem::exists(FileName)) {
|
||||
auto res = TResourceManager::IsModValid(FileName, c.AllowedMods);
|
||||
|
||||
if (res.has_value()) {
|
||||
if (!TCPSend(c, StringToVector("CO"))) {
|
||||
// TODO: handle
|
||||
c.Disconnect("TCP send failed in SendFile, when trying to cancel file transfer because " + res.value());
|
||||
}
|
||||
beammp_warn("File " + UnsafeName + " could not be accessed!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TCPSend(c, StringToVector("AG"))) {
|
||||
// TODO: handle
|
||||
c.Disconnect("TCP send failed in SendFile, when trying to send ");
|
||||
return;
|
||||
}
|
||||
|
||||
/// Wait for connections
|
||||
@@ -775,6 +815,7 @@ bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Da
|
||||
return false;
|
||||
}
|
||||
C.UpdatePingTime();
|
||||
C.TcpSent += Size;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -817,32 +858,23 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
||||
LockedClient->SetIsSyncing(true);
|
||||
bool Return = false;
|
||||
bool res = true;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
mServer.ForEachClient([&](const auto& Client) -> IterationDecision {
|
||||
TClient::TSetOfVehicleData VehicleData;
|
||||
{ // Vehicle Data Lock Scope
|
||||
auto LockedData = client->GetAllCars();
|
||||
auto LockedData = Client->GetAllCars();
|
||||
VehicleData = *LockedData.VehicleData;
|
||||
} // End Vehicle Data Lock Scope
|
||||
if (client != LockedClient) {
|
||||
if (Client != LockedClient) {
|
||||
for (auto& v : VehicleData) {
|
||||
if (LockedClient->IsDisconnected()) {
|
||||
Return = true;
|
||||
res = false;
|
||||
return false;
|
||||
return Break;
|
||||
}
|
||||
res = Respond(*LockedClient, StringToVector(v.Data()), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return Continue;
|
||||
});
|
||||
LockedClient->SetIsSyncing(false);
|
||||
if (Return) {
|
||||
@@ -858,15 +890,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
||||
beammp_assert(c);
|
||||
char C = Data.at(0);
|
||||
bool ret = true;
|
||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
mServer.ForEachClient([&](const auto& Client) {
|
||||
if (Self || Client.get() != c) {
|
||||
if (Client->IsSynced() || Client->IsSyncing()) {
|
||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||
@@ -888,7 +912,6 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!ret) {
|
||||
// TODO: handle
|
||||
@@ -916,6 +939,8 @@ bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
|
||||
Client.Disconnect("UDP send failed");
|
||||
return false;
|
||||
}
|
||||
Client.UdpSent += Data.size();
|
||||
++Client.UdpPacketsSent;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+6
-16
@@ -33,26 +33,16 @@ void TPPSMonitor::operator()() {
|
||||
Application::SetPPS("-");
|
||||
continue;
|
||||
}
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> c;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
c = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
if (c->GetCarCount() > 0) {
|
||||
mServer.ForEachClient([&](const auto& Client) {
|
||||
if (Client->GetCarCount() > 0) {
|
||||
C++;
|
||||
V += c->GetCarCount();
|
||||
V += Client->GetCarCount();
|
||||
}
|
||||
// kick on "no ping"
|
||||
if (c->SecondsSinceLastPing() > (20 * 60)) {
|
||||
beammp_debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
|
||||
TimedOutClients.push_back(c);
|
||||
if (Client->SecondsSinceLastPing() > (20 * 60)) {
|
||||
beammp_debugf("Client {} ({}) timing out: {}s since last contact", Client->GetName(), Client->GetID(), Client->SecondsSinceLastPing());
|
||||
TimedOutClients.push_back(Client);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
for (auto& ClientToKick : TimedOutClients) {
|
||||
Network().ClientKick(*ClientToKick, "Timeout (no ping for way too long)");
|
||||
|
||||
+68
-20
@@ -5,31 +5,79 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string TResourceManager::FormatForBackend(const ModMap& mods) {
|
||||
std::string monkey;
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += fs::path(name).filename().string() + ';';
|
||||
}
|
||||
return monkey;
|
||||
}
|
||||
|
||||
std::string TResourceManager::FormatForClient(const ModMap& mods) {
|
||||
std::string monkey;
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += '/' + name + ';';
|
||||
}
|
||||
for (const auto& [name, size] : mods) {
|
||||
monkey += std::to_string(size) + ';';
|
||||
}
|
||||
return monkey;
|
||||
}
|
||||
|
||||
/// @brief Sanitizes a requested mod string
|
||||
/// @param pathString Raw mod path string
|
||||
/// @param mods List of allowed mods for this client
|
||||
/// @return Error, if any
|
||||
std::optional<std::string> TResourceManager::IsModValid(std::string& pathString, const ModMap& mods) {
|
||||
auto path = fs::path(pathString);
|
||||
if (!path.has_filename()) {
|
||||
beammp_warn("File " + pathString + " is not a file!");
|
||||
return { "the requested file doesn't contain a valid filename" };
|
||||
}
|
||||
|
||||
auto BasePath = fs::path(Application::GetSettingString(StrResourceFolder) + "/Client");
|
||||
|
||||
auto CombinedPath = fs::path(BasePath.string() + pathString).lexically_normal();
|
||||
|
||||
// beammp_infof("path: {}, base: {}, combined: {}", pathString, BasePath.string(), CombinedPath.string());
|
||||
|
||||
if (!std::filesystem::exists(CombinedPath)) {
|
||||
beammp_warn("File " + pathString + " could not be accessed!");
|
||||
return { "the requested file doesn't exist or couldn't be accessed" };
|
||||
}
|
||||
|
||||
auto relative = fs::relative(CombinedPath, BasePath);
|
||||
|
||||
if (mods.count(relative.string()) == 0) {
|
||||
beammp_warn("File " + pathString + " is disallowed for this player!");
|
||||
return { "the requested file is disallowed for this player" };
|
||||
}
|
||||
|
||||
pathString = CombinedPath.string();
|
||||
return {};
|
||||
}
|
||||
|
||||
TResourceManager::TResourceManager() {
|
||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
||||
std::string Path = Application::GetSettingString(StrResourceFolder) + "/Client";
|
||||
if (!fs::exists(Path))
|
||||
fs::create_directories(Path);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
std::string File(entry.path().string());
|
||||
if (auto pos = File.find(".zip"); pos != std::string::npos) {
|
||||
if (File.length() - pos == 4) {
|
||||
std::replace(File.begin(), File.end(), '\\', '/');
|
||||
mFileList += File + ';';
|
||||
if (auto i = File.find_last_of('/'); i != std::string::npos) {
|
||||
++i;
|
||||
File = File.substr(i, pos - i);
|
||||
}
|
||||
mTrimmedList += "/" + fs::path(File).filename().string() + ';';
|
||||
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
|
||||
mMaxModSize += size_t(fs::file_size(entry.path()));
|
||||
mModsLoaded++;
|
||||
}
|
||||
std::string BasePath = Application::GetSettingString(StrResourceFolder) + "/Client";
|
||||
if (!fs::exists(BasePath))
|
||||
fs::create_directories(BasePath);
|
||||
std::vector<std::string> modNames;
|
||||
|
||||
auto iterator = fs::recursive_directory_iterator(BasePath, fs::directory_options::follow_directory_symlink | fs::directory_options::skip_permission_denied);
|
||||
for (const auto& entry : iterator) {
|
||||
if (iterator.depth() > 0 && !Application::GetSettingBool(StrIncludeSubdirectories))
|
||||
continue;
|
||||
if ((entry.is_regular_file() || entry.is_symlink()) && entry.path().extension() == ".zip") {
|
||||
auto relativePath = fs::relative(entry.path(), BasePath);
|
||||
|
||||
mMods[relativePath.string()] = entry.file_size();
|
||||
mTotalModSize += entry.file_size();
|
||||
}
|
||||
}
|
||||
|
||||
if (mModsLoaded) {
|
||||
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
|
||||
if (!mMods.empty()) {
|
||||
beammp_infof("Loaded {} mod{}", mMods.size(), mMods.size() != 1 ? 's' : ' ');
|
||||
}
|
||||
|
||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
#include "Common.h"
|
||||
|
||||
TScopedTimer::TScopedTimer()
|
||||
: mStartTime(std::chrono::high_resolution_clock::now()) {
|
||||
: mStartTime(TimeType::now()) {
|
||||
}
|
||||
|
||||
TScopedTimer::TScopedTimer(const std::string& mName)
|
||||
: mStartTime(std::chrono::high_resolution_clock::now())
|
||||
: mStartTime(TimeType::now())
|
||||
, Name(mName) {
|
||||
}
|
||||
|
||||
TScopedTimer::TScopedTimer(std::function<void(size_t)> OnDestroy)
|
||||
: OnDestroy(OnDestroy)
|
||||
, mStartTime(std::chrono::high_resolution_clock::now()) {
|
||||
, mStartTime(TimeType::now()) {
|
||||
}
|
||||
|
||||
TScopedTimer::~TScopedTimer() {
|
||||
auto EndTime = std::chrono::high_resolution_clock::now();
|
||||
auto EndTime = TimeType::now();
|
||||
auto Delta = EndTime - mStartTime;
|
||||
size_t TimeDelta = Delta / std::chrono::milliseconds(1);
|
||||
if (OnDestroy) {
|
||||
|
||||
+148
-54
@@ -1,14 +1,15 @@
|
||||
#include "TServer.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "IterationDecision.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TPPSMonitor.h"
|
||||
#include <TLuaPlugin.h>
|
||||
#include <algorithm>
|
||||
#include <any>
|
||||
#include <sstream>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#include "LuaAPI.h"
|
||||
|
||||
@@ -16,20 +17,66 @@
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static std::optional<std::pair<int, int>> GetPidVid(const std::string& str) {
|
||||
struct VehiclePacket {
|
||||
std::string Data;
|
||||
int Pid;
|
||||
int Vid;
|
||||
};
|
||||
|
||||
static Result<std::pair<int, int>> GetPidVid(const std::string& str) {
|
||||
auto IDSep = str.find('-');
|
||||
if (IDSep == std::string::npos) {
|
||||
return Error("Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{}'", str);
|
||||
}
|
||||
std::string pid = str.substr(0, IDSep);
|
||||
std::string vid = str.substr(IDSep + 1);
|
||||
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
try {
|
||||
int PID = stoi(pid);
|
||||
int VID = stoi(vid);
|
||||
int PID = std::stoi(pid);
|
||||
int VID = std::stoi(vid);
|
||||
return { { PID, VID } };
|
||||
} catch (const std::exception&) {
|
||||
return std::nullopt;
|
||||
return Error("Invalid packet: Could not parse pid/vid from packet, as one or both are not valid numbers: '{}'", str);
|
||||
}
|
||||
}
|
||||
return Error("Invalid packet: Could not parse pid/vid from packet: '{}'", str);
|
||||
}
|
||||
|
||||
static std::optional<VehiclePacket> ParseVehiclePacket(const std::string& Packet, const int playerID) {
|
||||
if (Packet.size() < 8) { // 2 for code, 3<= for pidvid, 1<= for data, 2 for dividers
|
||||
// invalid packet
|
||||
return std::nullopt;
|
||||
}
|
||||
// Zp:serverVehicleID:data
|
||||
// 0-0:data
|
||||
std::string withoutCode = Packet.substr(3);
|
||||
|
||||
auto NameDataSep = withoutCode.find(':', 3);
|
||||
if (NameDataSep == std::string::npos) {
|
||||
beammp_debugf("Invalid packet from {} in ParseVehiclePacket: No ':' separator, assuming corrupted packet. Packet: '{}'", playerID, Packet);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (NameDataSep + 1 > withoutCode.size()) {
|
||||
beammp_debugf("Invalid packet from {} in ParseVehiclePacket: Separator in unexpected place, assuming corrupted packet. Packet: '{}'", playerID, Packet);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string ServerVehicleID = withoutCode.substr(0, NameDataSep);
|
||||
|
||||
std::string Data = withoutCode.substr(NameDataSep + 1);
|
||||
|
||||
// parse veh ID
|
||||
auto MaybePidVid = GetPidVid(ServerVehicleID);
|
||||
if (MaybePidVid) {
|
||||
int PID, VID;
|
||||
std::tie(PID, VID) = MaybePidVid.value();
|
||||
|
||||
if (PID == playerID) {
|
||||
return { { Data, PID, VID } }; // std::vector<char>(Data.begin(), Data.end())
|
||||
}
|
||||
}
|
||||
beammp_debugf("Failed to parse packet from player {}", playerID);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -76,8 +123,40 @@ TEST_CASE("GetPidVid") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ParseVehiclePacket") {
|
||||
SUBCASE("Valid packet") {
|
||||
const auto valid = ParseVehiclePacket("Zp:0-0:{jsonstring}", 0);
|
||||
CHECK(valid.has_value());
|
||||
}
|
||||
SUBCASE("Valid packet 2") {
|
||||
const auto packet = ParseVehiclePacket("Zp:12-3:{jsonstring}", 12);
|
||||
CHECK(packet.has_value());
|
||||
CHECK_EQ(packet.value().Pid, 12);
|
||||
CHECK_EQ(packet.value().Vid, 3);
|
||||
}
|
||||
SUBCASE("Missing packet") {
|
||||
const auto valid = ParseVehiclePacket("", 0);
|
||||
CHECK(!valid.has_value());
|
||||
}
|
||||
SUBCASE("Missing ServerVehicleID") {
|
||||
const auto valid = ParseVehiclePacket("Zp:{jsonstring}", 0);
|
||||
CHECK(!valid.has_value());
|
||||
}
|
||||
SUBCASE("Missing data") {
|
||||
const auto valid = ParseVehiclePacket("Zp:0-0:", 0);
|
||||
CHECK(!valid.has_value());
|
||||
}
|
||||
SUBCASE("Incorrect Pid") {
|
||||
const auto valid = ParseVehiclePacket("Zp:0-0:{jsonstring}", 1);
|
||||
CHECK(!valid.has_value());
|
||||
}
|
||||
SUBCASE("Incorrect Pid 2") {
|
||||
const auto valid = ParseVehiclePacket("Zp:12-0:{jsonstring}", 13);
|
||||
CHECK(!valid.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
||||
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
||||
if (Arguments.size() > 1) {
|
||||
Application::SetSetting(StrCustomIP, std::string(Arguments[0]));
|
||||
@@ -95,16 +174,32 @@ TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||
}
|
||||
|
||||
void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
|
||||
if (!WeakClientPtr.expired()) {
|
||||
TClient& Client = *WeakClientPtr.lock();
|
||||
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
||||
Client.ClearCars();
|
||||
std::shared_ptr<TClient> LockedClientPtr { nullptr };
|
||||
try {
|
||||
LockedClientPtr = WeakClientPtr.lock();
|
||||
} catch (const std::exception&) {
|
||||
// silently fail, as there's nothing to do
|
||||
return;
|
||||
}
|
||||
beammp_assert(LockedClientPtr != nullptr);
|
||||
TClient& Client = *LockedClientPtr;
|
||||
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
||||
// TODO: Send delete packets for all cars
|
||||
Client.ClearCars();
|
||||
WriteLock Lock(mClientsMutex);
|
||||
mClients.erase(WeakClientPtr.lock());
|
||||
}
|
||||
|
||||
void TServer::RemoveClientById(int Id) {
|
||||
auto Client = GetClient(*this, Id);
|
||||
if (Client) {
|
||||
Client->ClearCars();
|
||||
WriteLock Lock(mClientsMutex);
|
||||
mClients.erase(WeakClientPtr.lock());
|
||||
mClients.erase(Client);
|
||||
}
|
||||
}
|
||||
|
||||
void TServer::ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn) {
|
||||
void TServer::ForEachClientWeak(const std::function<bool(std::weak_ptr<TClient>)>& Fn) {
|
||||
decltype(mClients) Clients;
|
||||
{
|
||||
ReadLock lock(mClientsMutex);
|
||||
@@ -122,7 +217,7 @@ size_t TServer::ClientCount() const {
|
||||
return mClients.size();
|
||||
}
|
||||
|
||||
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
|
||||
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TNetwork& Network) {
|
||||
constexpr std::string_view ABG = "ABG:";
|
||||
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
||||
Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
|
||||
@@ -144,8 +239,11 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
|
||||
// V to Y
|
||||
if (Code <= 89 && Code >= 86) {
|
||||
PPSMonitor.IncrementInternalPPS();
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
if (HandleVehicleUpdate(StringPacket, LockedClient->GetID())) {
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
} else {
|
||||
beammp_debugf("Invalid vehicle update packet received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (Code) {
|
||||
@@ -168,14 +266,20 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
}
|
||||
ParseVehicle(*LockedClient, StringPacket, Network);
|
||||
return;
|
||||
case 'J':
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||
return;
|
||||
case 'C': {
|
||||
if (Packet.size() < 4 || std::find(Packet.begin() + 3, Packet.end(), ':') == Packet.end())
|
||||
break;
|
||||
const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size());
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), PacketAsString.substr(PacketAsString.find(':', 3) + 2));
|
||||
std::string Message = "";
|
||||
const auto ColonPos = PacketAsString.find(':', 3);
|
||||
if (ColonPos != std::string::npos && ColonPos + 2 < PacketAsString.size()) {
|
||||
Message = PacketAsString.substr(ColonPos + 2);
|
||||
}
|
||||
if (Message.empty()) {
|
||||
beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
|
||||
return;
|
||||
}
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
|
||||
if (std::any_of(Futures.begin(), Futures.end(),
|
||||
@@ -186,7 +290,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
})) {
|
||||
break;
|
||||
}
|
||||
Network.SendToAll(nullptr, Packet, true, true);
|
||||
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
||||
Network.SendToAll(nullptr, StringToVector(SanitizedPacket), true, true);
|
||||
return;
|
||||
}
|
||||
case 'E':
|
||||
@@ -197,10 +302,11 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||
return;
|
||||
case 'Z': // position packet
|
||||
PPSMonitor.IncrementInternalPPS();
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
|
||||
HandlePosition(*LockedClient, StringPacket);
|
||||
if (HandlePosition(*LockedClient, StringPacket)) {
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
} else {
|
||||
beammp_debugf("Invalid vehicle position packet received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -209,6 +315,10 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
void TServer::HandleEvent(TClient& c, const std::string& RawData) {
|
||||
// E:Name:Data
|
||||
// Data is allowed to have ':'
|
||||
if (RawData.size() < 2) {
|
||||
beammp_debugf("Client '{}' ({}) tried to send an empty event, ignoring", c.GetName(), c.GetID());
|
||||
return;
|
||||
}
|
||||
auto NameDataSep = RawData.find(':', 2);
|
||||
if (NameDataSep == std::string::npos) {
|
||||
beammp_warn("received event in invalid format (missing ':'), got: '" + RawData + "'");
|
||||
@@ -415,35 +525,19 @@ void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
||||
(void)mClients.insert(NewClient);
|
||||
}
|
||||
|
||||
void TServer::HandlePosition(TClient& c, const std::string& Packet) {
|
||||
if (Packet.size() < 3) {
|
||||
// invalid packet
|
||||
return;
|
||||
}
|
||||
// Zp:serverVehicleID:data
|
||||
// Zp:0:data
|
||||
std::string withoutCode = Packet.substr(3);
|
||||
auto NameDataSep = withoutCode.find(':', 2);
|
||||
if (NameDataSep == std::string::npos || NameDataSep < 2) {
|
||||
// invalid packet
|
||||
return;
|
||||
}
|
||||
// FIXME: ensure that -2 does what it should... it seems weird.
|
||||
std::string ServerVehicleID = withoutCode.substr(2, NameDataSep - 2);
|
||||
if (NameDataSep + 1 > withoutCode.size()) {
|
||||
// invalid packet
|
||||
return;
|
||||
}
|
||||
std::string Data = withoutCode.substr(NameDataSep + 1);
|
||||
bool TServer::HandlePosition(TClient& c, const std::string& PacketStr) {
|
||||
auto MaybePacket = ParseVehiclePacket(PacketStr, c.GetID());
|
||||
|
||||
// parse veh ID
|
||||
auto MaybePidVid = GetPidVid(ServerVehicleID);
|
||||
if (MaybePidVid) {
|
||||
int PID = -1;
|
||||
int VID = -1;
|
||||
// FIXME: check that the VID and PID are valid, so that we don't waste memory
|
||||
std::tie(PID, VID) = MaybePidVid.value();
|
||||
|
||||
c.SetCarPosition(VID, Data);
|
||||
if (MaybePacket) {
|
||||
auto packet = MaybePacket.value();
|
||||
c.SetCarPosition(packet.Vid, packet.Data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TServer::HandleVehicleUpdate(const std::string& PacketStr, const int playerID) {
|
||||
auto MaybePacket = ParseVehiclePacket(PacketStr, playerID);
|
||||
|
||||
return MaybePacket.has_value();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "Uuid.h"
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
std::string uuid::GenerateUuid() {
|
||||
static thread_local boost::uuids::random_generator Generator {};
|
||||
boost::uuids::uuid Id { Generator() };
|
||||
return boost::uuids::to_string(Id);
|
||||
}
|
||||
+20
-20
@@ -73,6 +73,7 @@ int main(int argc, char** argv) {
|
||||
int BeamMPServerMain(MainArguments Arguments) {
|
||||
setlocale(LC_ALL, "C");
|
||||
Application::InitializeConsole();
|
||||
Application::Console().Internal().set_prompt("");
|
||||
ArgsParser Parser;
|
||||
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
|
||||
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
|
||||
@@ -83,16 +84,14 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
return 1;
|
||||
}
|
||||
if (Parser.FoundArgument({ "help" })) {
|
||||
Application::Console().Internal().set_prompt("");
|
||||
Application::Console().WriteRaw(sCommandlineArguments);
|
||||
return 0;
|
||||
}
|
||||
if (Parser.FoundArgument({ "version" })) {
|
||||
Application::Console().Internal().set_prompt("");
|
||||
Application::Console().WriteRaw("BeamMP-Server v" + Application::ServerVersionString());
|
||||
Application::Console().WriteRaw(fmt::format("BeamMP Server v{} ({})", Application::ServerVersionString(), BEAMMP_GIT_HASH));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
std::string ConfigPath = "ServerConfig.toml";
|
||||
if (Parser.FoundArgument({ "config" })) {
|
||||
auto MaybeConfigPath = Parser.GetValueOfArgument({ "config" });
|
||||
@@ -115,36 +114,37 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
|
||||
Application::SetSubsystemStatus("Main", Application::Status::Starting);
|
||||
|
||||
Application::Console().StartLoggingToFile();
|
||||
|
||||
SetupSignalHandlers();
|
||||
|
||||
beammp_infof("BeamMP Server v{} ({})", Application::ServerVersionString(), BEAMMP_GIT_HASH);
|
||||
|
||||
TConfig Config(ConfigPath);
|
||||
|
||||
if (Config.Failed()) {
|
||||
beammp_info("Closing in 10 seconds");
|
||||
// loop to make it possible to ctrl+c instead
|
||||
Application::SleepSafeSeconds(5);
|
||||
beammp_info("Closing in 5 seconds");
|
||||
Application::SleepSafeSeconds(5);
|
||||
Application::GracefullyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
Application::Console().Internal().set_prompt("> ");
|
||||
Application::Console().StartLoggingToFile();
|
||||
|
||||
bool Shutdown = false;
|
||||
Application::RegisterShutdownHandler([&Shutdown] {
|
||||
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
|
||||
Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown);
|
||||
Shutdown = true;
|
||||
});
|
||||
Application::RegisterShutdownHandler([] {
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
|
||||
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
|
||||
});
|
||||
|
||||
TServer Server(Arguments.List);
|
||||
TConfig Config(ConfigPath);
|
||||
auto LuaEngine = std::make_shared<TLuaEngine>();
|
||||
LuaEngine->SetServer(&Server);
|
||||
Application::Console().InitializeLuaConsole(*LuaEngine);
|
||||
|
||||
if (Config.Failed()) {
|
||||
beammp_info("Closing in 10 seconds");
|
||||
// loop to make it possible to ctrl+c instead
|
||||
for (size_t i = 0; i < 20; ++i) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
RegisterThread("Main");
|
||||
|
||||
beammp_trace("Running in debug mode on a debug build");
|
||||
|
||||
Reference in New Issue
Block a user