diff --git a/Changelog.md b/Changelog.md index e8f3a6a..f719f10 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ - ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa - ADDED FS.ListFiles and FS.ListDirectories - ADDED onFileChanged event, triggered when a server plugin file changes +- ADDED MP.GetPositionRaw(), which can be used to retrieve the latest position packet per player, per vehicle - FIXED `ip` in MP.GetIdentifiers - FIXED issue with client->server events which contain ':' - FIXED a fatal exception on LuaEngine startup if Resources/Server is a symlink @@ -13,6 +14,7 @@ - FIXED incorrect timing calculation of Lua EventTimer loop - FIXED bug which caused hot-reload not to report syntax errors - FIXED missing error messages on some event handler calls +- FIXED vehicles not deleting for all players if an edit was cancelled by Lua # v3.0.2 diff --git a/include/Client.h b/include/Client.h index d513659..114ff3e 100644 --- a/include/Client.h +++ b/include/Client.h @@ -39,11 +39,13 @@ public: void AddNewCar(int Ident, const std::string& Data); void SetCarData(int Ident, const std::string& Data); + void SetCarPosition(int Ident, const std::string& Data); TVehicleDataLockPair GetAllCars(); void SetName(const std::string& Name) { mName = Name; } void SetRoles(const std::string& Role) { mRole = Role; } void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; } std::string GetCarData(int Ident); + std::string GetCarPositionRaw(int Ident); void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; } void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; } void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; } @@ -93,7 +95,9 @@ private: std::unordered_map mIdentifiers; bool mIsGuest = false; mutable std::mutex mVehicleDataMutex; + mutable std::mutex mVehiclePositionMutex; TSetOfVehicleData mVehicleData; + SparseArray mVehiclePosition; std::string mName = "Unknown Client"; SOCKET mSocket[2] { SOCKET(0), SOCKET(0) }; sockaddr_in mUDPAddress {}; // is this initialization OK? yes it is diff --git a/include/Common.h b/include/Common.h index c77ffd1..37c891f 100644 --- a/include/Common.h +++ b/include/Common.h @@ -4,6 +4,7 @@ extern TSentry Sentry; #include +#include #include #include #include @@ -33,6 +34,9 @@ struct Version { std::string AsString(); }; +template +using SparseArray = std::unordered_map; + // static class handling application start, shutdown, etc. // yes, static classes, singletons, globals are all pretty // bad idioms. In this case we need a central way to access diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 2041687..b3cf883 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -211,6 +211,7 @@ private: sol::table Lua_GetPlayers(); std::string Lua_GetPlayerName(int ID); sol::table Lua_GetPlayerVehicles(int ID); + std::pair Lua_GetPositionRaw(int PID, int VID); 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); diff --git a/include/TServer.h b/include/TServer.h index 2e51e12..698835a 100644 --- a/include/TServer.h +++ b/include/TServer.h @@ -38,4 +38,5 @@ private: static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID); static bool IsUnicycle(TClient& c, const std::string& CarJson); static void Apply(TClient& c, int VID, const std::string& pckt); + static void HandlePosition(TClient& c, std::string Packet); }; diff --git a/src/Client.cpp b/src/Client.cpp index 410d966..f93f825 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -47,6 +47,23 @@ TClient::TVehicleDataLockPair TClient::GetAllCars() { return { &mVehicleData, std::unique_lock(mVehicleDataMutex) }; } +std::string TClient::GetCarPositionRaw(int Ident) { + std::unique_lock lock(mVehiclePositionMutex); + try + { + return mVehiclePosition.at(Ident); + } + catch (const std::out_of_range& oor) { + return ""; + } + return ""; +} + +void TClient::SetCarPosition(int Ident, const std::string& Data) { + std::unique_lock lock(mVehiclePositionMutex); + mVehiclePosition[Ident] = Data; +} + std::string TClient::GetCarData(int Ident) { { // lock std::unique_lock lock(mVehicleDataMutex); diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 1ac7fe3..303f8bf 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -517,6 +517,35 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) { return sol::lua_nil; } +std::pair TLuaEngine::StateThreadData::Lua_GetPositionRaw(int PID, int VID) { + std::pair Result; + auto MaybeClient = GetClient(mEngine->Server(), PID); + if (MaybeClient && !MaybeClient.value().expired()) { + auto Client = MaybeClient.value().lock(); + std::string VehiclePos = Client->GetCarPositionRaw(VID); + + if (VehiclePos.empty()) { + //return std::make_tuple(sol::lua_nil, sol::make_object(StateView, "Vehicle not found")); + Result.second = "Vehicle not found"; + return Result; + } + + sol::table t = Lua_JsonDecode(VehiclePos); + if (t == sol::lua_nil){ + Result.second = "Packet decode failed"; + } + //return std::make_tuple(Result, sol::make_object(StateView, sol::lua_nil)); + Result.first = t; + return Result; + } + else { + //return std::make_tuple(sol::lua_nil, sol::make_object(StateView, "Client expired")); + Result.second = "Client expired"; + return Result; + } +} + + sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::string& host, uint16_t port) { auto table = mStateView.create_table(); constexpr const char* InternalClient = "__InternalClient"; @@ -673,6 +702,9 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI MPTable.set_function("GetPlayerVehicles", [&](int ID) -> sol::table { return Lua_GetPlayerVehicles(ID); }); + MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair { + return Lua_GetPositionRaw(PID, VID); + }); MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage); MPTable.set_function("GetPlayers", [&]() -> sol::table { return Lua_GetPlayers(); diff --git a/src/TServer.cpp b/src/TServer.cpp index 48b2bbb..92e9061 100644 --- a/src/TServer.cpp +++ b/src/TServer.cpp @@ -15,6 +15,67 @@ #include "Json.h" +static std::optional> GetPidVid(std::string str) { + auto IDSep = str.find('-'); + std::string pid = str.substr(0, IDSep); + std::string vid = str.substr(IDSep + 1); + + if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) { + try { + int PID = stoi(pid); + int VID = stoi(vid); + return {{ PID, VID }}; + } catch(const std::exception&) { + return std::nullopt; + } + } + return std::nullopt; +} + +TEST_CASE("GetPidVid") { + SUBCASE("Valid singledigit") { + const auto MaybePidVid = GetPidVid("0-1"); + CHECK(MaybePidVid); + auto [pid, vid] = MaybePidVid.value(); + + CHECK_EQ(pid, 0); + CHECK_EQ(vid, 1); + } + SUBCASE("Valid doubledigit") { + const auto MaybePidVid = GetPidVid("10-12"); + CHECK(MaybePidVid); + auto [pid, vid] = MaybePidVid.value(); + + CHECK_EQ(pid, 10); + CHECK_EQ(vid, 12); + } + SUBCASE("Empty string") { + const auto MaybePidVid = GetPidVid(""); + CHECK(!MaybePidVid); + } + SUBCASE("Invalid separator") { + const auto MaybePidVid = GetPidVid("0x0"); + CHECK(!MaybePidVid); + } + SUBCASE("Missing pid") { + const auto MaybePidVid = GetPidVid("-0"); + CHECK(!MaybePidVid); + } + SUBCASE("Missing vid") { + const auto MaybePidVid = GetPidVid("0-"); + CHECK(!MaybePidVid); + } + SUBCASE("Invalid pid") { + const auto MaybePidVid = GetPidVid("x-0"); + CHECK(!MaybePidVid); + } + SUBCASE("Invalid vid") { + const auto MaybePidVid = GetPidVid("0-x"); + CHECK(!MaybePidVid); + } +} + + TServer::TServer(const std::vector& Arguments) { beammp_info("BeamMP Server v" + Application::ServerVersionString()); Application::SetSubsystemStatus("Server", Application::Status::Starting); @@ -86,8 +147,8 @@ void TServer::GlobalParser(const std::weak_ptr& Client, std::string Pac std::any Res; char Code = Packet.at(0); - // V to Z - if (Code <= 90 && Code >= 86) { + // V to Y + if (Code <= 89 && Code >= 86) { PPSMonitor.IncrementInternalPPS(); Network.SendToAll(LockedClient.get(), Packet, false, false); return; @@ -145,6 +206,11 @@ void TServer::GlobalParser(const std::weak_ptr& Client, std::string Pac beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")"); Network.SendToAll(LockedClient.get(), Packet, false, true); return; + case 'Z': // position packet + PPSMonitor.IncrementInternalPPS(); + Network.SendToAll(LockedClient.get(), Packet, false, false); + + HandlePosition(*LockedClient, Packet); default: return; } @@ -223,13 +289,11 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ } } return; - case 'c': + case 'c': { beammp_trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); - pid = Data.substr(0, Data.find('-')); - vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1); - if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) { - PID = stoi(pid); - VID = stoi(vid); + auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1))); + if (MaybePidVid) { + std::tie(PID, VID) = MaybePidVid.value(); } if (PID != -1 && VID != -1 && PID == c.GetID()) { auto Futures = LuaAPI::MP::Engine->TriggerEvent("onVehicleEdited", "", c.GetID(), VID, Packet.substr(3)); @@ -250,20 +314,17 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ c.SetUnicycleID(-1); } std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID); - if (!Network.Respond(c, Destroy, true)) { - // TODO: handle - } + Network.SendToAll(nullptr, Destroy, true, true); c.DeleteCar(VID); } } return; - case 'd': + } + case 'd': { beammp_trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); - pid = Data.substr(0, Data.find('-')); - vid = Data.substr(Data.find('-') + 1); - if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) { - PID = stoi(pid); - VID = stoi(vid); + auto MaybePidVid = GetPidVid(Data); + if (MaybePidVid) { + std::tie(PID, VID) = MaybePidVid.value(); } if (PID != -1 && VID != -1 && PID == c.GetID()) { if (c.GetUnicycleID() == VID) { @@ -276,15 +337,12 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ beammp_debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID)); } return; - case 'r': + } + case 'r': { beammp_trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); - Pos = int(Data.find('-')); - pid = Data.substr(0, Pos++); - vid = Data.substr(Pos, Data.find(':') - Pos); - - if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) { - PID = stoi(pid); - VID = stoi(vid); + auto MaybePidVid = GetPidVid(Data); + if (MaybePidVid) { + std::tie(PID, VID) = MaybePidVid.value(); } if (PID != -1 && VID != -1 && PID == c.GetID()) { @@ -293,6 +351,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ Network.SendToAll(&c, Packet, false, true); } return; + } case 't': beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); Network.SendToAll(&c, Packet, false, true); @@ -365,3 +424,21 @@ void TServer::InsertClient(const std::shared_ptr& NewClient) { WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here (void)mClients.insert(NewClient); } + +void TServer::HandlePosition(TClient& c, std::string Packet) { + // Zp:serverVehicleID:data + std::string withoutCode = Packet.substr(3); + auto NameDataSep = withoutCode.find(':', 2); + std::string ServerVehicleID = withoutCode.substr(2, NameDataSep - 2); + std::string Data = withoutCode.substr(NameDataSep + 1); + + // parse veh ID + auto MaybePidVid = GetPidVid(ServerVehicleID); + if (MaybePidVid) { + int PID = -1; + int VID = -1; + std::tie(PID, VID) = MaybePidVid.value(); + + c.SetCarPosition(VID, Data); + } +}