Merge branch 'rc-v3.1.0' into feature-9

This commit is contained in:
Lion Kortlepel
2022-05-23 17:26:32 +02:00
22 changed files with 928 additions and 256 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "deps/commandline"]
path = deps/commandline
url = https://github.com/lionkor/commandline
[submodule "deps/fmt"]
path = deps/fmt
url = https://github.com/fmtlib/fmt

View File

@@ -1,16 +1,5 @@
cmake_minimum_required(VERSION 3.0)
if (WIN32)
set(VCPKG_TARGET_TRIPLET "x64-windows-static")
elseif(UNIX)
set(VCPKG_TARGET_TRIPLET "x64-linux")
endif()
option(USE_VCPKG "USE_VCPKG" ON)
if(USE_VCPKG)
include(vcpkg.cmake)
endif()
# 3.4 is required for imported targets.
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")
@@ -20,42 +9,57 @@ project(BeamMP-Server
HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C)
find_package(Git REQUIRED)
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
set(HTTPLIB_REQUIRE_OPENSSL ON)
set(SENTRY_BUILD_SHARED_LIBS OFF)
include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib")
include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include")
include_directories("${PROJECT_SOURCE_DIR}/deps")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
# ------------------------ APPLE ---------------------------------
if(APPLE)
set(LUA_INCLUDE_DIR /usr/local/Cellar/lua@5.3/5.3.6/include/lua5.3)
set(LUA_LIBRARIES lua)
include_directories(/usr/local/opt/openssl@1.1/include)
link_directories(/usr/local/Cellar/lua@5.3/5.3.6/lib)
link_directories(/usr/local/opt/openssl@1.1/lib)
endif()
if (WIN32)
# ------------------------ WINDOWS ---------------------------------
elseif (WIN32)
# this has to happen before sentry, so that crashpad on windows links with these settings.
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})
endif()
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif ()
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
message(STATUS "Setting compiler flags")
if (WIN32)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif ()
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
# ------------------------ LINUX ---------------------------------
elseif (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++ -static-libgcc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
if (SANITIZE)
@@ -64,6 +68,9 @@ elseif (UNIX)
endif (SANITIZE)
endif ()
include_directories("include/sentry-native/include")
# ------------------------ SENTRY ---------------------------------
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
@@ -73,14 +80,19 @@ if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
set(BEAMMP_SECRET_SENTRY_URL "")
set(SENTRY_BACKEND none)
else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN)
message(STATUS "Sentry URL is length ${URL_LEN}")
set(SENTRY_BACKEND breakpad)
endif()
add_subdirectory("deps/sentry-native")
# ------------------------ C++ SETUP ---------------------------------
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
# ------------------------ DEPENDENCIES ------------------------------
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
# ------------------------ BEAMMP SERVER -----------------------------
add_executable(BeamMP-Server
src/main.cpp
@@ -108,34 +120,33 @@ add_executable(BeamMP-Server
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline")
include(FindLua)
include(FindOpenSSL)
include(FindThreads)
include(FindZLIB)
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(CURL CONFIG REQUIRED)
find_package(Lua REQUIRED 5.3)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(RapidJSON CONFIG REQUIRED)
find_package(sentry CONFIG REQUIRED)
find_package(sol2 CONFIG REQUIRED)
find_package(toml11 CONFIG REQUIRED)
find_path(CPP_HTTPLIB_INCLUDE_DIRS "httplib.h")
target_include_directories(BeamMP-Server PUBLIC
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include")
target_include_directories(BeamMP-Server PRIVATE
${CPP_HTTPLIB_INCLUDE_DIRS}
${LUA_INCLUDE_DIR}
)
target_link_libraries(BeamMP-Server PRIVATE OpenSSL::SSL OpenSSL::Crypto
ZLIB::ZLIB
CURL::libcurl
${LUA_LIBRARIES}
Threads::Threads
commandline
nlohmann_json::nlohmann_json
rapidjson
sentry::sentry
target_link_libraries(BeamMP-Server
OpenSSL::SSL
OpenSSL::Crypto
sol2::sol2
toml11::toml11
)
fmt::fmt
Threads::Threads
ZLIB::ZLIB
${LUA_LIBRARIES}
commandline
sentry)
if (WIN32)
target_link_libraries(BeamMP-Server wsock32 ws2_32)
endif ()

View File

@@ -1,3 +1,11 @@
# v3.1.0
- ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console
- ADDED lua debug facilities (type `:help` when attached to lua via `lua`)
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
- ADDED FS.ListFiles and FS.ListDirectories
- FIXED issue with client->server events which contain ':'
# v3.0.2
- ADDED Periodic update message if a new server is released

2
deps/CMakeLists.txt vendored
View File

@@ -1,4 +1,6 @@
include_directories("${PROJECT_SOURCE_DIR}/deps")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt")

1
deps/fmt vendored Submodule

Submodule deps/fmt added at 17dda58391

View File

@@ -8,6 +8,7 @@ extern TSentry Sentry;
#include <cstring>
#include <deque>
#include <filesystem>
#include <fmt/format.h>
#include <functional>
#include <memory>
#include <mutex>
@@ -48,6 +49,7 @@ public:
bool DebugModeEnabled { false };
int Port { 30814 };
std::string CustomIP {};
bool LogChat { true };
bool SendErrors { true };
bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 };
@@ -120,7 +122,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 0, 2 };
static inline Version mVersion { 3, 1, 0 };
};
std::string ThreadName(bool DebugModeOverride = false);
@@ -146,6 +148,10 @@ void RegisterThread(const std::string& str);
#define _function_name std::string(__func__)
#endif
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
@@ -207,6 +213,13 @@ void RegisterThread(const std::string& str);
#define beammp_trace(x)
#endif // defined(DEBUG)
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__))
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
void LogChatMessage(const std::string& name, int id, const std::string& msg);
#define Biggest 30000

View File

@@ -6,10 +6,10 @@
#ifdef BEAMMP_LINUX
#include <arpa/inet.h>
#include <errno.h>
#include <sys/socket.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
using SOCKET = int;
using DWORD = unsigned long;
using PDWORD = unsigned long*;
@@ -25,10 +25,10 @@ inline void CloseSocketProper(int TheSocket) {
#ifdef BEAMMP_APPLE
#include <arpa/inet.h>
#include <errno.h>
#include <sys/socket.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
using SOCKET = int;
using DWORD = unsigned long;
using PDWORD = unsigned long*;
@@ -48,6 +48,11 @@ inline void CloseSocketProper(int TheSocket) {
inline void CloseSocketProper(SOCKET TheSocket) {
shutdown(TheSocket, 2); // 2 == SD_BOTH
closesocket(TheSocket);
}
#endif // WIN32
#ifdef INVALID_SOCKET
static inline constexpr int BEAMMP_INVALID_SOCKET = INVALID_SOCKET;
#else
static inline constexpr int BEAMMP_INVALID_SOCKET = -1;
#endif

View File

@@ -12,7 +12,8 @@ namespace MP {
std::string GetOSName();
std::tuple<int, int, int> GetServerVersion();
bool TriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data);
bool TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& Data);
bool TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data);
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
void DropPlayer(int ID, std::optional<std::string> MaybeReason);
void SendChatMessage(int ID, const std::string& Message);
@@ -22,7 +23,15 @@ namespace MP {
bool IsPlayerConnected(int ID);
void Sleep(size_t Ms);
void PrintRaw(sol::variadic_args);
std::string JsonEncode(const sol::table& object);
std::string JsonDiff(const std::string& a, const std::string& b);
std::string JsonDiffApply(const std::string& data, const std::string& patch);
std::string JsonPrettify(const std::string& json);
std::string JsonMinify(const std::string& json);
std::string JsonFlatten(const std::string& json);
std::string JsonUnflatten(const std::string& json);
}
namespace FS {
std::pair<bool, std::string> CreateDirectory(const std::string& Path);
std::pair<bool, std::string> Remove(const std::string& Path);

View File

@@ -3,6 +3,7 @@
#include "Common.h"
#include <atomic>
#include <filesystem>
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
#include <toml.hpp> // header-only version of TOML++

View File

@@ -4,6 +4,11 @@
#include "commandline.h"
#include <atomic>
#include <fstream>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
class TLuaEngine;
@@ -22,13 +27,31 @@ private:
void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false);
void ChangeToLuaConsole(const std::string& LuaStateId);
void ChangeToRegularConsole();
void HandleLuaInternalCommand(const std::string& cmd);
void Command_Lua(const std::string& cmd);
void Command_Help(const std::string& cmd);
void Command_Kick(const std::string& cmd);
void Command_Say(const std::string& cmd);
void Command_List(const std::string& cmd);
void Command_Status(const std::string& cmd);
void Command_Lua(const std::string& cmd, const std::vector<std::string>& args);
void Command_Help(const std::string& cmd, const std::vector<std::string>& args);
void Command_Kick(const std::string& cmd, const std::vector<std::string>& args);
void Command_List(const std::string& cmd, const std::vector<std::string>& args);
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_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t min, size_t max);
static std::tuple<std::string, std::vector<std::string>> ParseCommand(const std::string& cmd);
static std::string ConcatArgs(const std::vector<std::string>& args, char space = ' ');
std::unordered_map<std::string, std::function<void(const std::string&, const std::vector<std::string>&)>> mCommandMap = {
{ "lua", [this](const auto& a, const auto& b) { Command_Lua(a, b); } },
{ "help", [this](const auto& a, const auto& b) { Command_Help(a, b); } },
{ "kick", [this](const auto& a, const auto& b) { Command_Kick(a, b); } },
{ "list", [this](const auto& a, const auto& b) { Command_List(a, b); } },
{ "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } },
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
};
Commandline mCommandline;
std::vector<std::string> mCachedLuaHistory;

View File

@@ -11,6 +11,7 @@
#include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <set>
#include <toml.hpp>
#include <unordered_map>
@@ -33,8 +34,8 @@ static constexpr size_t TLuaArgTypes_Bool = 3;
class TLuaPlugin;
struct TLuaResult {
std::atomic_bool Ready;
std::atomic_bool Error;
bool Ready;
bool Error;
std::string ErrorMessage;
sol::object Result { sol::lua_nil };
TLuaStateId StateId;
@@ -102,6 +103,7 @@ public:
std::unique_lock Lock(mResultsToCheckMutex);
return mResultsToCheck.size();
}
size_t GetLuaStateCount() {
std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.size();
@@ -166,6 +168,14 @@ public:
static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND";
std::vector<std::string> GetStateGlobalKeysForState(TLuaStateId StateId);
// Debugging functions (slow)
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
private:
void CollectAndInitPlugins();
void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
@@ -185,6 +195,12 @@ private:
void operator()() override;
sol::state_view State() { return sol::state_view(mState); }
std::vector<std::string> GetStateGlobalKeys();
// Debug functions, slow
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueue();
std::vector<TLuaEngine::QueuedFunction> Debug_GetStateFunctionQueue();
private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
@@ -193,7 +209,10 @@ private:
std::string Lua_GetPlayerName(int ID);
sol::table Lua_GetPlayerVehicles(int ID);
sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port);
sol::table Lua_JsonDecode(const std::string& str);
int Lua_GetPlayerIDByName(const std::string& Name);
sol::table Lua_FS_ListFiles(const std::string& Path);
sol::table Lua_FS_ListDirectories(const std::string& Path);
std::string mName;
std::atomic_bool& mShutdown;
@@ -209,6 +228,8 @@ private:
sol::state_view mStateView { mState };
std::queue<fs::path> mPaths;
std::recursive_mutex mPathsMutex;
std::mt19937 mMersenneTwister;
std::uniform_real_distribution<double> mUniformRealDistribution01;
};
struct TimedEvent {

View File

@@ -12,7 +12,7 @@ void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
ConsumeLongFlag(std::string(Arg));
}
} else {
beammp_error("Error parsing commandline arguments: Supplied argument '" + std::string(Arg) + "' is not a valid argument and was ignored.");
beammp_errorf("Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg);
}
}
}
@@ -21,7 +21,7 @@ bool ArgsParser::Verify() {
bool Ok = true;
for (const auto& RegisteredArg : mRegisteredArguments) {
if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' is required but wasn't found.");
beammp_errorf("Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0));
Ok = false;
continue;
} else if (FoundArgument(RegisteredArg.Names)) {

View File

@@ -5,8 +5,6 @@
#include <memory>
#include <optional>
// FIXME: add debug prints
void TClient::DeleteCar(int Ident) {
std::unique_lock lock(mVehicleDataMutex);
auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) {

View File

@@ -43,6 +43,7 @@ void Application::GracefullyShutdown() {
beammp_info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down");
mShutdownHandlers[i]();
}
// std::exit(-1);
}
std::string Application::ServerVersionString() {
@@ -60,7 +61,6 @@ std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
return Version;
}
// FIXME: This should be used by operator< on Version
bool Application::IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
@@ -162,7 +162,7 @@ void RegisterThread(const std::string& str) {
ThreadId = std::to_string(gettid());
#endif
if (Application::Settings.DebugModeEnabled) {
std::ofstream ThreadFile("Threads.log", std::ios::app);
std::ofstream ThreadFile(".Threads.log", std::ios::app);
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
}
auto Lock = std::unique_lock(ThreadNameMapMutex);
@@ -179,22 +179,22 @@ Version::Version(const std::array<uint8_t, 3>& v)
}
std::string Version::AsString() {
std::stringstream ss {};
ss << int(major) << "." << int(minor) << "." << int(patch);
return ss.str();
return fmt::format("{:d}.{:d}.{:d}", major, minor, patch);
}
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
std::stringstream ss;
ss << ThreadName();
ss << "[CHAT] ";
if (id != -1) {
ss << "(" << id << ") <" << name << "> ";
} else {
ss << name << "";
if (Application::Settings.LogChat) {
std::stringstream ss;
ss << ThreadName();
ss << "[CHAT] ";
if (id != -1) {
ss << "(" << id << ") <" << name << "> ";
} else {
ss << name << "";
}
ss << msg;
Application::Console().Write(ss.str());
}
ss << msg;
Application::Console().Write(ss.str());
}
std::string GetPlatformAgnosticErrorString() {

View File

@@ -3,6 +3,8 @@
#include "Common.h"
#include "TLuaEngine.h"
#include <nlohmann/json.hpp>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
@@ -100,26 +102,31 @@ void LuaAPI::Print(sol::variadic_args Args) {
luaprint(ToPrint);
}
bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) {
static inline bool InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) {
std::string Packet = "E:" + EventName + ":" + Data;
if (PlayerID == -1)
Engine->Network().SendToAll(nullptr, Packet, true, true);
LuaAPI::MP::Engine->Network().SendToAll(nullptr, Packet, true, true);
else {
auto MaybeClient = GetClient(Engine->Server(), PlayerID);
auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
if (!MaybeClient || MaybeClient.value().expired()) {
beammp_lua_error("TriggerClientEvent invalid Player ID");
return false;
}
auto c = MaybeClient.value().lock();
if (!Engine->Network().Respond(*c, Packet, true)) {
if (!LuaAPI::MP::Engine->Network().Respond(*c, Packet, true)) {
beammp_lua_error("Respond failed, dropping client " + std::to_string(PlayerID));
Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
return false;
}
}
return true;
}
bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& DataObj) {
std::string Data = DataObj.as<std::string>();
return InternalTriggerClientEvent(PlayerID, EventName, Data);
}
void LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (!MaybeClient || MaybeClient.value().expired()) {
@@ -352,3 +359,157 @@ std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
auto Result = Path.lexically_normal().string();
return Result;
}
static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, const sol::object& right, bool is_array, size_t depth = 0) {
if (depth > 100) {
beammp_lua_error("json serialize will not go deeper than 100 nested tables, internal references assumed, aborted this path");
return;
}
std::string key;
switch (left.get_type()) {
case sol::type::lua_nil:
case sol::type::none:
case sol::type::poly:
case sol::type::boolean:
case sol::type::lightuserdata:
case sol::type::userdata:
case sol::type::thread:
case sol::type::function:
case sol::type::table:
beammp_lua_error("JsonEncode: left side of table field is unexpected type");
return;
case sol::type::string:
key = left.as<std::string>();
break;
case sol::type::number:
key = std::to_string(left.as<double>());
break;
}
nlohmann::json value;
switch (right.get_type()) {
case sol::type::lua_nil:
case sol::type::none:
return;
case sol::type::poly:
beammp_lua_warn("unsure what to do with poly type in JsonEncode, ignoring");
return;
case sol::type::boolean:
value = right.as<bool>();
break;
case sol::type::lightuserdata:
beammp_lua_warn("unsure what to do with lightuserdata in JsonEncode, ignoring");
return;
case sol::type::userdata:
beammp_lua_warn("unsure what to do with userdata in JsonEncode, ignoring");
return;
case sol::type::thread:
beammp_lua_warn("unsure what to do with thread in JsonEncode, ignoring");
return;
case sol::type::string:
value = right.as<std::string>();
break;
case sol::type::number:
value = right.as<double>();
break;
case sol::type::function:
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
return;
case sol::type::table: {
bool local_is_array = true;
for (const auto& pair : right.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
local_is_array = false;
}
}
for (const auto& pair : right.as<sol::table>()) {
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
}
break;
}
}
if (is_array) {
json.push_back(value);
} else {
json[key] = value;
}
}
std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
nlohmann::json json;
// table
bool is_array = true;
for (const auto& pair : object.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
is_array = false;
}
}
for (const auto& entry : object) {
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
}
return json.dump();
}
std::string LuaAPI::MP::JsonDiff(const std::string& a, const std::string& b) {
if (!nlohmann::json::accept(a)) {
beammp_lua_error("JsonDiff first argument is not valid json: `" + a + "`");
return "";
}
if (!nlohmann::json::accept(b)) {
beammp_lua_error("JsonDiff second argument is not valid json: `" + b + "`");
return "";
}
auto a_json = nlohmann::json::parse(a);
auto b_json = nlohmann::json::parse(b);
return nlohmann::json::diff(a_json, b_json).dump();
}
std::string LuaAPI::MP::JsonDiffApply(const std::string& data, const std::string& patch) {
if (!nlohmann::json::accept(data)) {
beammp_lua_error("JsonDiffApply first argument is not valid json: `" + data + "`");
return "";
}
if (!nlohmann::json::accept(patch)) {
beammp_lua_error("JsonDiffApply second argument is not valid json: `" + patch + "`");
return "";
}
auto a_json = nlohmann::json::parse(data);
auto b_json = nlohmann::json::parse(patch);
a_json.patch(b_json);
return a_json.dump();
}
std::string LuaAPI::MP::JsonPrettify(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonPrettify argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).dump(4);
}
std::string LuaAPI::MP::JsonMinify(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonMinify argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).dump(-1);
}
std::string LuaAPI::MP::JsonFlatten(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonFlatten argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).flatten().dump(-1);
}
std::string LuaAPI::MP::JsonUnflatten(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonUnflatten argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).unflatten().dump(-1);
}
bool LuaAPI::MP::TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data) {
return InternalTriggerClientEvent(PlayerID, EventName, JsonEncode(Data));
}

View File

@@ -17,6 +17,7 @@ static constexpr std::string_view StrName = "Name";
static constexpr std::string_view StrDescription = "Description";
static constexpr std::string_view StrResourceFolder = "ResourceFolder";
static constexpr std::string_view StrAuthKey = "AuthKey";
static constexpr std::string_view StrLogChat = "LogChat";
// Misc
static constexpr std::string_view StrSendErrors = "SendErrors";
@@ -63,6 +64,8 @@ void TConfig::FlushToFile() {
auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
data["General"][StrAuthKey.data()] = Application::Settings.Key;
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
data["General"][StrLogChat.data()] = Application::Settings.LogChat;
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
data["General"][StrPrivate.data()] = Application::Settings.Private;
data["General"][StrPort.data()] = Application::Settings.Port;
@@ -161,6 +164,7 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc);
TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource);
TryReadValue(data, "General", StrAuthKey, Application::Settings.Key);
TryReadValue(data, "General", StrLogChat, Application::Settings.LogChat);
// Misc
TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors);
TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages);
@@ -201,6 +205,7 @@ void TConfig::PrintDebug() {
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\"");
beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\"");

View File

@@ -118,10 +118,10 @@ void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
mStateId = LuaStateId;
mIsLuaConsole = true;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Entered Lua console for state '" + mStateId + "'. To exit, type `exit()`");
Application::Console().WriteRaw("Attached to Lua state '" + mStateId + "'. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua @" + LuaStateId + "> ");
} else {
Application::Console().WriteRaw("Entered Lua console. To exit, type `exit()`");
Application::Console().WriteRaw("Attached to Lua. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua> ");
}
mCachedRegularHistory = mCommandline.history();
@@ -133,9 +133,9 @@ void TConsole::ChangeToRegularConsole() {
if (mIsLuaConsole) {
mIsLuaConsole = false;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Left Lua console for state '" + mStateId + "'.");
Application::Console().WriteRaw("Detached from Lua state '" + mStateId + "'.");
} else {
Application::Console().WriteRaw("Left Lua console.");
Application::Console().WriteRaw("Detached from Lua.");
}
mCachedLuaHistory = mCommandline.history();
mCommandline.set_history(mCachedRegularHistory);
@@ -144,21 +144,54 @@ void TConsole::ChangeToRegularConsole() {
}
}
void TConsole::Command_Lua(const std::string& cmd) {
if (cmd.size() > 3) {
auto NewStateId = cmd.substr(4);
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (n == 0 && args.size() != 0) {
Application::Console().WriteRaw("This command expects no arguments.");
return false;
} else if (args.size() != n) {
Application::Console().WriteRaw("Expected " + std::to_string(n) + " argument(s), instead got " + std::to_string(args.size()));
return false;
} else {
return true;
}
}
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t min, size_t max) {
if (min == max) {
return EnsureArgsCount(args, min);
} else {
if (args.size() > max) {
Application::Console().WriteRaw("Too many arguments. At most " + std::to_string(max) + " arguments expected, got " + std::to_string(args.size()) + " instead.");
return false;
} else if (args.size() < min) {
Application::Console().WriteRaw("Too few arguments. At least " + std::to_string(max) + " arguments expected, got " + std::to_string(args.size()) + " instead.");
return false;
}
}
return true;
}
void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0, 1)) {
return;
}
if (args.size() == 1) {
auto NewStateId = args.at(0);
beammp_assert(!NewStateId.empty());
if (mLuaEngine->HasState(NewStateId)) {
ChangeToLuaConsole(NewStateId);
} else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
}
} else {
} else if (args.size() == 0) {
ChangeToLuaConsole(mDefaultStateId);
}
}
void TConsole::Command_Help(const std::string&) {
void TConsole::Command_Help(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
static constexpr const char* sHelpString = R"(
Commands:
help displays this help
@@ -167,53 +200,127 @@ void TConsole::Command_Help(const std::string&) {
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)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
}
void TConsole::Command_Kick(const std::string& cmd) {
if (cmd.size() > 4) {
auto Name = cmd.substr(5);
std::string Reason = "Kicked by server console";
auto SpacePos = Name.find(' ');
if (SpacePos != Name.npos) {
Reason = Name.substr(SpacePos + 1);
Name = cmd.substr(5, cmd.size() - Reason.size() - 5 - 1);
std::string TConsole::ConcatArgs(const std::vector<std::string>& args, char space) {
std::string Result;
for (const auto& arg : args) {
Result += arg + space;
}
Result = Result.substr(0, Result.size() - 1); // strip trailing space
return Result;
}
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);
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;
auto NameCompare = [](std::string Name1, std::string Name2) -> bool {
std::for_each(Name1.begin(), Name1.end(), [](char& c) { c = tolower(c); });
std::for_each(Name2.begin(), Name2.end(), [](char& c) { c = tolower(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;
}
}
beammp_trace("attempt to kick '" + Name + "' for '" + Reason + "'");
bool Kicked = false;
auto NameCompare = [](std::string Name1, std::string Name2) -> bool {
std::for_each(Name1.begin(), Name1.end(), [](char& c) { c = tolower(c); });
std::for_each(Name2.begin(), Name2.end(), [](char& c) { c = tolower(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;
return true;
});
if (!Kicked) {
Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found.");
} else {
Application::Console().WriteRaw("Kicked player '" + Name + "' for reason: '" + Reason + "'.");
}
}
std::tuple<std::string, std::vector<std::string>> TConsole::ParseCommand(const std::string& CommandWithArgs) {
// Algorithm designed and implemented by Lion Kortlepel (c) 2022
// It correctly splits arguments, including respecting single and double quotes, as well as backticks
auto End_i = CommandWithArgs.find_first_of(' ');
std::string Command = CommandWithArgs.substr(0, End_i);
std::string ArgsStr {};
if (End_i != std::string::npos) {
ArgsStr = CommandWithArgs.substr(End_i);
}
std::vector<std::string> Args;
char* PrevPtr = ArgsStr.data();
char* Ptr = ArgsStr.data();
const char* End = ArgsStr.data() + ArgsStr.size();
while (Ptr != End) {
std::string Arg = "";
// advance while space
while (Ptr != End && std::isspace(*Ptr))
++Ptr;
PrevPtr = Ptr;
// advance while NOT space, also handle quotes
while (Ptr != End && !std::isspace(*Ptr)) {
// TODO: backslash escaping quotes
for (char Quote : { '"', '\'', '`' }) {
if (*Ptr == Quote) {
// seek if there's a closing quote
// if there is, go there and continue, otherwise ignore
char* Seeker = Ptr + 1;
while (Seeker != End && *Seeker != Quote)
++Seeker;
if (Seeker != End) {
// found closing quote
Ptr = Seeker;
}
break; // exit for loop
}
}
return true;
});
if (!Kicked) {
Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found.");
} else {
Application::Console().WriteRaw("Kicked player '" + Name + "' for reason: '" + Reason + "'.");
++Ptr;
}
Arg = std::string(PrevPtr, Ptr - PrevPtr);
// remove quotes if enclosed in quotes
for (char Quote : { '"', '\'', '`' }) {
if (!Arg.empty() && Arg.at(0) == Quote && Arg.at(Arg.size() - 1) == Quote) {
Arg = Arg.substr(1, Arg.size() - 2);
break;
}
}
if (!Arg.empty()) {
Args.push_back(Arg);
}
}
return { Command, Args };
}
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
}
void TConsole::Command_Say(const std::string& FullCmd) {
if (FullCmd.size() > 3) {
auto Message = FullCmd.substr(4);
LuaAPI::MP::SendChatMessage(-1, Message);
if (!Application::Settings.LogChat) {
Application::Console().WriteRaw("Chat message sent!");
}
}
}
void TConsole::Command_Say(const std::string& cmd) {
if (cmd.size() > 3) {
auto Message = cmd.substr(4);
LuaAPI::MP::SendChatMessage(-1, Message);
void TConsole::Command_List(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
}
void TConsole::Command_List(const std::string&) {
if (mLuaEngine->Server().ClientCount() == 0) {
Application::Console().WriteRaw("No players online.");
} else {
@@ -233,7 +340,10 @@ void TConsole::Command_List(const std::string&) {
}
}
void TConsole::Command_Status(const std::string&) {
void TConsole::Command_Status(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
std::stringstream Status;
size_t CarCount = 0;
@@ -318,12 +428,12 @@ void TConsole::Command_Status(const std::string&) {
<< "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n"
<< "\tSubsystems:\n"
<< "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n"
<< "\t\tShutting down/Shutdown: " << 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\tShutdown: [ " << SystemsShutdownList << " ]\n"
<< "\t\tShut down: [ " << SystemsShutdownList << " ]\n"
<< "";
Application::Console().WriteRaw(Status.str());
@@ -375,6 +485,58 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
}
}
void TConsole::HandleLuaInternalCommand(const std::string& cmd) {
if (cmd == "exit") {
ChangeToRegularConsole();
} else if (cmd == "queued") {
auto QueuedFunctions = LuaAPI::MP::Engine->Debug_GetStateFunctionQueueForState(mStateId);
Application::Console().WriteRaw("Pending functions in State '" + mStateId + "'");
std::unordered_map<std::string, size_t> FunctionsCount;
std::vector<std::string> FunctionsInOrder;
while (!QueuedFunctions.empty()) {
auto Tuple = QueuedFunctions.front();
QueuedFunctions.erase(QueuedFunctions.begin());
FunctionsInOrder.push_back(Tuple.FunctionName);
FunctionsCount[Tuple.FunctionName] += 1;
}
std::set<std::string> Uniques;
for (const auto& Function : FunctionsInOrder) {
if (Uniques.count(Function) == 0) {
Uniques.insert(Function);
if (FunctionsCount.at(Function) > 1) {
Application::Console().WriteRaw(" " + Function + " (" + std::to_string(FunctionsCount.at(Function)) + "x)");
} else {
Application::Console().WriteRaw(" " + Function);
}
}
}
Application::Console().WriteRaw("Executed functions waiting to be checked in State '" + mStateId + "'");
for (const auto& Function : LuaAPI::MP::Engine->Debug_GetResultsToCheckForState(mStateId)) {
Application::Console().WriteRaw(" '" + Function.Function + "' (Ready? " + (Function.Ready ? "Yes" : "No") + ", Error? " + (Function.Error ? "Yes: '" + Function.ErrorMessage + "'" : "No") + ")");
}
} else if (cmd == "events") {
auto Events = LuaAPI::MP::Engine->Debug_GetEventsForState(mStateId);
Application::Console().WriteRaw("Registered Events + Handlers for State '" + mStateId + "'");
for (const auto& EventHandlerPair : Events) {
Application::Console().WriteRaw(" Event '" + EventHandlerPair.first + "'");
for (const auto& Handler : EventHandlerPair.second) {
Application::Console().WriteRaw(" " + Handler);
}
}
} else if (cmd == "help") {
Application::Console().WriteRaw(R"(BeamMP Lua Debugger
All commands must be prefixed with a `:`. Non-prefixed commands are interpreted as Lua.
Commands
:exit detaches (exits) from this Lua console
:help displays this help
:events shows a list of currently registered events
:queued shows a list of all pending and queued functions)");
} else {
beammp_error("internal command '" + cmd + "' is not known");
}
}
TConsole::TConsole() {
mCommandline.enable_history();
mCommandline.set_history_limit(20);
@@ -382,21 +544,22 @@ TConsole::TConsole() {
BackupOldLog();
mCommandline.on_command = [this](Commandline& c) {
try {
auto cmd = c.get_command();
cmd = TrimString(cmd);
mCommandline.write(mCommandline.prompt() + cmd);
auto TrimmedCmd = c.get_command();
TrimmedCmd = TrimString(TrimmedCmd);
auto [cmd, args] = ParseCommand(TrimmedCmd);
mCommandline.write(mCommandline.prompt() + TrimmedCmd);
if (mIsLuaConsole) {
if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second");
} else if (cmd == "exit()") {
ChangeToRegularConsole();
} else if (!cmd.empty() && cmd.at(0) == ':') {
HandleLuaInternalCommand(cmd.substr(1));
} else {
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(cmd), "", "" });
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" });
while (!Future->Ready) {
std::this_thread::yield(); // TODO: Add a timeout
}
if (Future->Error) {
beammp_lua_error(Future->ErrorMessage);
beammp_lua_error("error in " + mStateId + ": " + Future->ErrorMessage);
}
}
} else {
@@ -405,31 +568,63 @@ TConsole::TConsole() {
} else if (cmd == "exit") {
beammp_info("gracefully shutting down");
Application::GracefullyShutdown();
} else if (StringStartsWith(cmd, "lua")) {
Command_Lua(cmd);
} else if (StringStartsWith(cmd, "help")) {
RunAsCommand(cmd, true);
Command_Help(cmd);
} else if (StringStartsWith(cmd, "kick")) {
RunAsCommand(cmd, true);
Command_Kick(cmd);
} else if (StringStartsWith(cmd, "say")) {
RunAsCommand(cmd, true);
Command_Say(cmd);
} else if (StringStartsWith(cmd, "list")) {
RunAsCommand(cmd, true);
Command_List(cmd);
} else if (StringStartsWith(cmd, "status")) {
RunAsCommand(cmd, true);
Command_Status(cmd);
} else if (!cmd.empty()) {
RunAsCommand(cmd);
} else if (cmd == "say") {
RunAsCommand(TrimmedCmd, true);
Command_Say(TrimmedCmd);
} else {
if (mCommandMap.find(cmd) != mCommandMap.end()) {
mCommandMap.at(cmd)(cmd, args);
RunAsCommand(TrimmedCmd, true);
} else {
RunAsCommand(TrimmedCmd);
}
}
}
} catch (const std::exception& e) {
beammp_error("Console died with: " + std::string(e.what()) + ". This could be a fatal error and could cause the server to terminate.");
}
};
mCommandline.on_autocomplete = [this](Commandline&, std::string stub, int) {
std::vector<std::string> suggestions;
try {
auto cmd = TrimString(stub);
// beammp_error("yes 1");
// beammp_error(stub);
if (mIsLuaConsole) { // if lua
if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second");
} else {
std::string prefix {};
for (size_t i = stub.length(); i > 0; i--) {
if (!std::isalnum(stub[i - 1]) && stub[i - 1] != '_') {
prefix = stub.substr(0, i);
stub = stub.substr(i);
break;
}
}
auto keys = mLuaEngine->GetStateGlobalKeysForState(mStateId);
for (const auto& key : keys) {
std::string::size_type n = key.find(stub);
if (n == 0) {
suggestions.push_back(prefix + key);
// beammp_warn(cmd_name);
}
}
}
} else { // if not lua
for (const auto& [cmd_name, cmd_fn] : mCommandMap) {
std::string::size_type n = cmd_name.find(stub);
if (n == 0) {
suggestions.push_back(cmd_name);
// beammp_warn(cmd_name);
}
}
}
} catch (const std::exception& e) {
beammp_error("Console died with: " + std::string(e.what()) + ". This could be a fatal error and could cause the server to terminate.");
}
return suggestions;
};
}
void TConsole::Write(const std::string& str) {

View File

@@ -64,8 +64,10 @@ void THeartbeatThread::operator()() {
beammp_trace(T);
Doc.Parse(T.data(), T.size());
if (Doc.HasParseError() || !Doc.IsObject()) {
beammp_debug("Failed to contact backend at " + Url + " (this is not an error).");
beammp_trace("Response was: " + T);
if (!Application::Settings.Private) {
beammp_error("Backend response failed to parse as valid json");
beammp_trace("Response was: `" + T + "`");
}
Sentry.SetContext("JSON Response", { { "reponse", T } });
SentryReportError(Url + Target, ResponseCode);
} else if (ResponseCode != 200) {
@@ -113,21 +115,21 @@ void THeartbeatThread::operator()() {
}
}
if (Ok && !isAuth) {
if (Ok && !isAuth && !Application::Settings.Private) {
if (Status == "2000") {
beammp_info(("Authenticated!"));
beammp_info(("Authenticated! " + Message));
isAuth = true;
} else if (Status == "200") {
beammp_info(("Resumed authenticated session!"));
beammp_info(("Resumed authenticated session! " + Message));
isAuth = true;
} else {
if (Message.empty()) {
Message = "Backend didn't provide a reason";
Message = "Backend didn't provide a reason.";
}
beammp_error("Backend REFUSED the auth key. " + Message);
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
}
}
if (isAuth) {
if (isAuth || Application::Settings.Private) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {

View File

@@ -8,6 +8,7 @@
#include <chrono>
#include <condition_variable>
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <random>
#include <thread>
#include <tuple>
@@ -154,10 +155,70 @@ void TLuaEngine::AddResultToCheck(const std::shared_ptr<TLuaResult>& Result) {
mResultsToCheckCond.notify_one();
}
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> TLuaEngine::Debug_GetEventsForState(TLuaStateId StateId) {
std::unordered_map<std::string, std::vector<std::string>> Result;
std::unique_lock Lock(mLuaEventsMutex);
for (const auto& EventNameToEventMap : mLuaEvents) {
for (const auto& IdSetOfHandlersPair : EventNameToEventMap.second) {
if (IdSetOfHandlersPair.first == StateId) {
for (const auto& Handler : IdSetOfHandlersPair.second) {
Result[EventNameToEventMap.first].push_back(Handler);
}
}
}
}
return Result;
}
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) {
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue();
return Result;
}
std::vector<TLuaEngine::QueuedFunction> TLuaEngine::Debug_GetStateFunctionQueueForState(TLuaStateId StateId) {
std::vector<TLuaEngine::QueuedFunction> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateFunctionQueue();
return Result;
}
std::vector<TLuaResult> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
std::unique_lock Lock(mResultsToCheckMutex);
auto ResultsToCheckCopy = mResultsToCheck;
Lock.unlock();
std::vector<TLuaResult> Result;
while (!ResultsToCheckCopy.empty()) {
auto ResultToCheck = std::move(ResultsToCheckCopy.front());
ResultsToCheckCopy.pop_front();
if (ResultToCheck->StateId == StateId) {
Result.push_back(*ResultToCheck);
}
}
return Result;
}
std::vector<std::string> TLuaEngine::GetStateGlobalKeysForState(TLuaStateId StateId) {
std::unique_lock Lock(mLuaStatesMutex);
auto Result = mLuaStates.at(StateId)->GetStateGlobalKeys();
return Result;
}
std::vector<std::string> TLuaEngine::StateThreadData::GetStateGlobalKeys() {
auto globals = mStateView.globals();
std::vector<std::string> Result;
for (const auto& [key, value] : globals) {
Result.push_back(key.as<std::string>());
}
return Result;
}
void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<std::chrono::high_resolution_clock::duration>& Max) {
for (const auto& Result : Results) {
bool Cancelled = false;
size_t ms = 0;
std::set<std::string> WarnedResults;
while (!Result->Ready && !Cancelled) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
ms += 10;
@@ -165,7 +226,11 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
Cancelled = true;
} else if (ms > 1000 * 60) {
beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
auto ResultId = Result->StateId + "_" + Result->Function;
if (WarnedResults.count(ResultId) == 0) {
WarnedResults.insert(ResultId);
beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
}
}
}
if (Cancelled) {
@@ -277,7 +342,6 @@ std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& Ev
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
// TODO Synchronous call to the event handlers
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler];
@@ -335,7 +399,7 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
Result.add(FnRet);
} else {
sol::error Err = FnRet;
beammp_lua_error(Err.what());
beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
}
}
}
@@ -386,6 +450,32 @@ int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name)
return Id;
}
sol::table TLuaEngine::StateThreadData::Lua_FS_ListFiles(const std::string& Path) {
if (!std::filesystem::exists(Path)) {
return sol::lua_nil;
}
auto table = mStateView.create_table();
for (const auto& entry : std::filesystem::directory_iterator(Path)) {
if (entry.is_regular_file() || entry.is_symlink()) {
table[table.size() + 1] = entry.path().lexically_relative(Path).string();
}
}
return table;
}
sol::table TLuaEngine::StateThreadData::Lua_FS_ListDirectories(const std::string& Path) {
if (!std::filesystem::exists(Path)) {
return sol::lua_nil;
}
auto table = mStateView.create_table();
for (const auto& entry : std::filesystem::directory_iterator(Path)) {
if (entry.is_directory()) {
table[table.size() + 1] = entry.path().lexically_relative(Path).string();
}
}
return table;
}
std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient && !MaybeClient.value().expired()) {
@@ -439,6 +529,83 @@ sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::stri
return table;
}
template <typename T>
static void AddToTable(sol::table& table, const std::string& left, const T& value) {
if (left.empty()) {
table[table.size() + 1] = value;
} else {
table[left] = value;
}
}
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
switch (right.type()) {
case nlohmann::detail::value_t::null:
return;
case nlohmann::detail::value_t::object: {
auto value = table.create();
value.clear();
for (const auto& entry : right.items()) {
JsonDecodeRecursive(StateView, value, entry.key(), entry.value());
}
AddToTable(table, left, value);
break;
}
case nlohmann::detail::value_t::array: {
auto value = table.create();
value.clear();
for (const auto& entry : right.items()) {
JsonDecodeRecursive(StateView, value, "", entry.value());
}
AddToTable(table, left, value);
break;
}
case nlohmann::detail::value_t::string:
AddToTable(table, left, right.get<std::string>());
break;
case nlohmann::detail::value_t::boolean:
AddToTable(table, left, right.get<bool>());
break;
case nlohmann::detail::value_t::number_integer:
AddToTable(table, left, right.get<int64_t>());
break;
case nlohmann::detail::value_t::number_unsigned:
AddToTable(table, left, right.get<uint64_t>());
break;
case nlohmann::detail::value_t::number_float:
AddToTable(table, left, right.get<double>());
break;
case nlohmann::detail::value_t::binary:
beammp_lua_error("JsonDecode can't handle binary blob in json, ignoring");
return;
case nlohmann::detail::value_t::discarded:
return;
}
}
sol::table TLuaEngine::StateThreadData::Lua_JsonDecode(const std::string& str) {
sol::state_view StateView(mState);
auto table = StateView.create_table();
if (!nlohmann::json::accept(str)) {
beammp_lua_error("string given to JsonDecode is not valid json: `" + str + "`");
return sol::lua_nil;
}
nlohmann::json json = nlohmann::json::parse(str);
if (json.is_object()) {
for (const auto& entry : json.items()) {
JsonDecodeRecursive(StateView, table, entry.key(), entry.value());
}
} else if (json.is_array()) {
for (const auto& entry : json) {
JsonDecodeRecursive(StateView, table, "", entry);
}
} else {
beammp_lua_error("JsonDecode expected array or object json, instead got " + std::string(json.type_name()));
return sol::lua_nil;
}
return table;
}
TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine)
: mName(Name)
, mShutdown(Shutdown)
@@ -484,6 +651,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
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);
MPTable.set_function("IsPlayerConnected", &LuaAPI::MP::IsPlayerConnected);
MPTable.set_function("GetPlayerIDByName", [&](const std::string& Name) -> int {
@@ -538,6 +706,27 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
mEngine->CancelEventTimers(EventName, mStateId);
});
MPTable.set_function("Set", &LuaAPI::MP::Set);
auto UtilTable = StateView.create_named_table("Util");
UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
UtilTable.set_function("JsonDecode", [this](const std::string& str) {
return Lua_JsonDecode(str);
});
UtilTable.set_function("JsonDiff", &LuaAPI::MP::JsonDiff);
UtilTable.set_function("JsonFlatten", &LuaAPI::MP::JsonFlatten);
UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten);
UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify);
UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify);
UtilTable.set_function("Random", [this] {
return mUniformRealDistribution01(mMersenneTwister);
});
UtilTable.set_function("RandomRange", [this](double min, double max) -> double {
return std::uniform_real_distribution(min, max)(mMersenneTwister);
});
UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
return std::uniform_int_distribution(min, max)(mMersenneTwister);
});
auto HttpTable = StateView.create_named_table("Http");
HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
return Lua_HttpCreateConnection(host, port);
@@ -568,6 +757,12 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
FSTable.set_function("IsDirectory", &LuaAPI::FS::IsDirectory);
FSTable.set_function("IsFile", &LuaAPI::FS::IsFile);
FSTable.set_function("ConcatPaths", &LuaAPI::FS::ConcatPaths);
FSTable.set_function("ListFiles", [this](const std::string& Path) {
return Lua_FS_ListFiles(Path);
});
FSTable.set_function("ListDirectories", [this](const std::string& Path) {
return Lua_FS_ListDirectories(Path);
});
Start();
}
@@ -722,6 +917,16 @@ void TLuaEngine::StateThreadData::operator()() {
}
}
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() {
std::unique_lock Lock(mStateExecuteQueueMutex);
return mStateExecuteQueue;
}
std::vector<TLuaEngine::QueuedFunction> TLuaEngine::StateThreadData::Debug_GetStateFunctionQueue() {
std::unique_lock Lock(mStateFunctionQueueMutex);
return mStateFunctionQueue;
}
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) {
std::unique_lock Lock(mTimedEventsMutex);
TimedEvent Event {
@@ -798,38 +1003,47 @@ void TPluginMonitor::operator()() {
beammp_info("PluginMonitor started");
while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::vector<std::string> ToRemove;
for (const auto& Pair : mFileTimes) {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime != Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_info("File \"" + Pair.first + "\" changed, reloading");
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine.EnqueueScript(StateID, Chunk);
// TODO: call onInit
mEngine.AddResultToCheck(Res);
} else {
// TODO: trigger onFileChanged event
beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet");
/*
// is in subfolder, dont reload, just trigger an event
auto Results = mEngine.TriggerEvent("onFileChanged", "", Pair.first);
mEngine.WaitForAll(Results);
for (const auto& Result : Results) {
if (Result->Error) {
beammp_lua_error(Result->ErrorMessage);
}
}*/
try {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime != Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_info("File \"" + Pair.first + "\" changed, reloading");
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine.EnqueueScript(StateID, Chunk);
// TODO: call onInit
mEngine.AddResultToCheck(Res);
} else {
// TODO: trigger onFileChanged event
beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet");
/*
// is in subfolder, dont reload, just trigger an event
auto Results = mEngine.TriggerEvent("onFileChanged", "", Pair.first);
mEngine.WaitForAll(Results);
for (const auto& Result : Results) {
if (Result->Error) {
beammp_lua_error(Result->ErrorMessage);
}
}*/
}
}
} catch (const std::exception& e) {
ToRemove.push_back(Pair.first);
}
}
for (const auto& File : ToRemove) {
mFileTimes.erase(File);
beammp_warn("file '" + File + "' couldn't be accessed, so it was removed from plugin hot reload monitor (probably got deleted)");
}
}
}

View File

@@ -108,40 +108,48 @@ void TNetwork::TCPServerMain() {
#if defined(BEAMMP_WINDOWS)
WSADATA wsaData;
if (WSAStartup(514, &wsaData)) {
beammp_error("Can't start Winsock!");
return;
beammp_error("Can't start Winsock! Shutting down");
Application::GracefullyShutdown();
}
#endif // WINDOWS
TConnection client {};
SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int optval = 1;
if (Listener == BEAMMP_INVALID_SOCKET) {
beammp_error("Failed to create socket: " + GetPlatformAgnosticErrorString()
+ ". This is a fatal error, as a socket is needed for the server to operate. Shutting down.");
Application::GracefullyShutdown();
}
#if defined(BEAMMP_WINDOWS)
const char* optval_ptr = reinterpret_cast<const char*>(&optval);
const char optval = 0;
int ret = ::setsockopt(Listener, SOL_SOCKET, SO_DONTLINGER, &optval, sizeof(optval));
#elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
void* optval_ptr = reinterpret_cast<void*>(&optval);
int optval = true;
int ret = ::setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void*>(&optval), sizeof(optval));
#endif
setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, optval_ptr, sizeof(optval));
// TODO: check optval or return value idk
// not a fatal error
if (ret < 0) {
beammp_error("Failed to set up listening socket to not linger / reuse address. "
"This may cause the socket to refuse to bind(). Error: "
+ GetPlatformAgnosticErrorString());
}
sockaddr_in addr {};
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_family = AF_INET;
addr.sin_port = htons(uint16_t(Application::Settings.Port));
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) {
beammp_error("bind() failed: " + GetPlatformAgnosticErrorString());
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(-1); // TODO: Wtf.
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) < 0) {
beammp_error("bind() failed, the server cannot operate and will shut down now. "
"Error: "
+ GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
}
if (Listener == -1) {
beammp_error("Invalid listening socket");
return;
}
if (listen(Listener, SOMAXCONN)) {
beammp_error("listen() failed: " + GetPlatformAgnosticErrorString());
// FIXME leak Listener
return;
if (listen(Listener, SOMAXCONN) < 0) {
beammp_error("listen() failed, which is needed for the server to operate. "
"Shutting down. Error: "
+ GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
}
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
beammp_info(("Vehicle event network online"));
beammp_info("Vehicle event network online");
do {
try {
if (mShutdown) {
@@ -154,15 +162,15 @@ void TNetwork::TCPServerMain() {
beammp_warn(("Got an invalid client socket on connect! Skipping..."));
continue;
}
// set timeout (DWORD, aka uint32_t)
uint32_t SendTimeoutMS = 30 * 1000;
// set timeout
size_t SendTimeoutMS = 30 * 1000;
#if defined(BEAMMP_WINDOWS)
int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&SendTimeoutMS), sizeof(SendTimeoutMS));
#else // POSIX
struct timeval optval;
optval.tv_sec = (int)(SendTimeoutMS / 1000);
optval.tv_usec = (SendTimeoutMS % 1000) * 1000;
int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<void*>(&optval), sizeof(optval));
ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<void*>(&optval), sizeof(optval));
#endif
if (ret < 0) {
throw std::runtime_error("setsockopt recv timeout: " + GetPlatformAgnosticErrorString());
@@ -170,9 +178,9 @@ void TNetwork::TCPServerMain() {
std::thread ID(&TNetwork::Identify, this, client);
ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) {
beammp_error(("fatal: ") + std::string(e.what()));
beammp_error("fatal: " + std::string(e.what()));
}
} while (client.Socket);
} while (client.Socket != BEAMMP_INVALID_SOCKET);
beammp_debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
@@ -477,7 +485,7 @@ std::string TNetwork::TCPRcv(TClient& c) {
void TNetwork::ClientKick(TClient& c, const std::string& R) {
beammp_info("Client kicked: " + R);
if (!TCPSend(c, "K" + R)) {
// TODO handle
beammp_warn("tried to kick player '" + c.GetName() + "' (id " + std::to_string(c.GetID()) + "), but was already disconnected");
}
c.SetStatus(-2);
@@ -755,7 +763,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) {
std::ifstream f(Name.c_str(), std::ios::binary);
uint32_t Split = 0x7735940; // 125MB
uint32_t Split = 125 * MB;
char* Data;
if (Size > Split)
Data = new char[Split];

View File

@@ -125,7 +125,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
break;
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 2));
TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1));
if (std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) {
return !Elem->Error
@@ -150,26 +150,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
}
}
void TServer::HandleEvent(TClient& c, const std::string& Data) {
std::stringstream ss(Data);
std::string t, Name;
int a = 0;
while (std::getline(ss, t, ':')) {
switch (a) {
case 1:
Name = t;
break;
case 2:
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), t));
break;
default:
break;
}
if (a == 2)
break;
a++;
void TServer::HandleEvent(TClient& c, const std::string& RawData) {
// E:Name:Data
// Data is allowed to have ':'
auto NameDataSep = RawData.find(':', 2);
if (NameDataSep == std::string::npos) {
beammp_warn("received event in invalid format (missing ':'), got: '" + RawData + "'");
}
std::string Name = RawData.substr(2, NameDataSep - 2);
std::string Data = RawData.substr(NameDataSep + 1);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data));
}
bool TServer::IsUnicycle(TClient& c, const std::string& CarJson) {
try {
auto Car = nlohmann::json::parse(CarJson);

View File

@@ -113,7 +113,7 @@ int BeamMPServerMain(MainArguments Arguments) {
try {
fs::current_path(fs::path(MaybeWorkingDirectory.value()));
} catch (const std::exception& e) {
beammp_error("Could not set working directory to '" + MaybeWorkingDirectory.value() + "': " + e.what());
beammp_errorf("Could not set working directory to '{}': {}", MaybeWorkingDirectory.value(), e.what());
}
}
}