mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-04-03 06:16:04 +00:00
Merge branch 'rc-v3.1.0' into feature-9
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
131
CMakeLists.txt
131
CMakeLists.txt
@@ -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 ()
|
||||
|
||||
@@ -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
2
deps/CMakeLists.txt
vendored
@@ -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
1
deps/fmt
vendored
Submodule
Submodule deps/fmt added at 17dda58391
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
171
src/LuaAPI.cpp
171
src/LuaAPI.cpp
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 + "\"");
|
||||
|
||||
341
src/TConsole.cpp
341
src/TConsole.cpp
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user