mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2025-07-03 08:15:35 +00:00
add MP.GetPositionRaw(pid, vid)
fix vehicles sometimes not deleting for all players
This commit is contained in:
parent
54e02abad1
commit
44b94c9e58
@ -6,6 +6,7 @@
|
|||||||
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
|
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
|
||||||
- ADDED FS.ListFiles and FS.ListDirectories
|
- ADDED FS.ListFiles and FS.ListDirectories
|
||||||
- ADDED onFileChanged event, triggered when a server plugin file changes
|
- 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 `ip` in MP.GetIdentifiers
|
||||||
- FIXED issue with client->server events which contain ':'
|
- FIXED issue with client->server events which contain ':'
|
||||||
- FIXED a fatal exception on LuaEngine startup if Resources/Server is a symlink
|
- 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 incorrect timing calculation of Lua EventTimer loop
|
||||||
- FIXED bug which caused hot-reload not to report syntax errors
|
- FIXED bug which caused hot-reload not to report syntax errors
|
||||||
- FIXED missing error messages on some event handler calls
|
- 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
|
# v3.0.2
|
||||||
|
|
||||||
|
@ -39,11 +39,13 @@ public:
|
|||||||
|
|
||||||
void AddNewCar(int Ident, const std::string& Data);
|
void AddNewCar(int Ident, const std::string& Data);
|
||||||
void SetCarData(int Ident, const std::string& Data);
|
void SetCarData(int Ident, const std::string& Data);
|
||||||
|
void SetCarPosition(int Ident, const std::string& Data);
|
||||||
TVehicleDataLockPair GetAllCars();
|
TVehicleDataLockPair GetAllCars();
|
||||||
void SetName(const std::string& Name) { mName = Name; }
|
void SetName(const std::string& Name) { mName = Name; }
|
||||||
void SetRoles(const std::string& Role) { mRole = Role; }
|
void SetRoles(const std::string& Role) { mRole = Role; }
|
||||||
void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; }
|
void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; }
|
||||||
std::string GetCarData(int Ident);
|
std::string GetCarData(int Ident);
|
||||||
|
std::string GetCarPositionRaw(int Ident);
|
||||||
void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; }
|
void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; }
|
||||||
void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; }
|
void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; }
|
||||||
void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; }
|
void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; }
|
||||||
@ -93,7 +95,9 @@ private:
|
|||||||
std::unordered_map<std::string, std::string> mIdentifiers;
|
std::unordered_map<std::string, std::string> mIdentifiers;
|
||||||
bool mIsGuest = false;
|
bool mIsGuest = false;
|
||||||
mutable std::mutex mVehicleDataMutex;
|
mutable std::mutex mVehicleDataMutex;
|
||||||
|
mutable std::mutex mVehiclePositionMutex;
|
||||||
TSetOfVehicleData mVehicleData;
|
TSetOfVehicleData mVehicleData;
|
||||||
|
SparseArray<std::string> mVehiclePosition;
|
||||||
std::string mName = "Unknown Client";
|
std::string mName = "Unknown Client";
|
||||||
SOCKET mSocket[2] { SOCKET(0), SOCKET(0) };
|
SOCKET mSocket[2] { SOCKET(0), SOCKET(0) };
|
||||||
sockaddr_in mUDPAddress {}; // is this initialization OK? yes it is
|
sockaddr_in mUDPAddress {}; // is this initialization OK? yes it is
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
extern TSentry Sentry;
|
extern TSentry Sentry;
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <unordered_map>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
@ -33,6 +34,9 @@ struct Version {
|
|||||||
std::string AsString();
|
std::string AsString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using SparseArray = std::unordered_map<size_t, T>;
|
||||||
|
|
||||||
// static class handling application start, shutdown, etc.
|
// static class handling application start, shutdown, etc.
|
||||||
// yes, static classes, singletons, globals are all pretty
|
// yes, static classes, singletons, globals are all pretty
|
||||||
// bad idioms. In this case we need a central way to access
|
// bad idioms. In this case we need a central way to access
|
||||||
|
@ -211,6 +211,7 @@ private:
|
|||||||
sol::table Lua_GetPlayers();
|
sol::table Lua_GetPlayers();
|
||||||
std::string Lua_GetPlayerName(int ID);
|
std::string Lua_GetPlayerName(int ID);
|
||||||
sol::table Lua_GetPlayerVehicles(int ID);
|
sol::table Lua_GetPlayerVehicles(int ID);
|
||||||
|
std::pair<sol::table, std::string> Lua_GetPositionRaw(int PID, int VID);
|
||||||
sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port);
|
sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port);
|
||||||
sol::table Lua_JsonDecode(const std::string& str);
|
sol::table Lua_JsonDecode(const std::string& str);
|
||||||
int Lua_GetPlayerIDByName(const std::string& Name);
|
int Lua_GetPlayerIDByName(const std::string& Name);
|
||||||
|
@ -38,4 +38,5 @@ private:
|
|||||||
static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID);
|
static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID);
|
||||||
static bool IsUnicycle(TClient& c, const std::string& CarJson);
|
static bool IsUnicycle(TClient& c, const std::string& CarJson);
|
||||||
static void Apply(TClient& c, int VID, const std::string& pckt);
|
static void Apply(TClient& c, int VID, const std::string& pckt);
|
||||||
|
static void HandlePosition(TClient& c, std::string Packet);
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,23 @@ TClient::TVehicleDataLockPair TClient::GetAllCars() {
|
|||||||
return { &mVehicleData, std::unique_lock(mVehicleDataMutex) };
|
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) {
|
std::string TClient::GetCarData(int Ident) {
|
||||||
{ // lock
|
{ // lock
|
||||||
std::unique_lock lock(mVehicleDataMutex);
|
std::unique_lock lock(mVehicleDataMutex);
|
||||||
|
@ -517,6 +517,35 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
|
|||||||
return sol::lua_nil;
|
return sol::lua_nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<sol::table, std::string> TLuaEngine::StateThreadData::Lua_GetPositionRaw(int PID, int VID) {
|
||||||
|
std::pair<sol::table, std::string> Result;
|
||||||
|
auto MaybeClient = GetClient(mEngine->Server(), PID);
|
||||||
|
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||||
|
auto Client = MaybeClient.value().lock();
|
||||||
|
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) {
|
sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::string& host, uint16_t port) {
|
||||||
auto table = mStateView.create_table();
|
auto table = mStateView.create_table();
|
||||||
constexpr const char* InternalClient = "__InternalClient";
|
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 {
|
MPTable.set_function("GetPlayerVehicles", [&](int ID) -> sol::table {
|
||||||
return Lua_GetPlayerVehicles(ID);
|
return Lua_GetPlayerVehicles(ID);
|
||||||
});
|
});
|
||||||
|
MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair<sol::table, std::string> {
|
||||||
|
return Lua_GetPositionRaw(PID, VID);
|
||||||
|
});
|
||||||
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
|
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
|
||||||
MPTable.set_function("GetPlayers", [&]() -> sol::table {
|
MPTable.set_function("GetPlayers", [&]() -> sol::table {
|
||||||
return Lua_GetPlayers();
|
return Lua_GetPlayers();
|
||||||
|
127
src/TServer.cpp
127
src/TServer.cpp
@ -15,6 +15,67 @@
|
|||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
|
static std::optional<std::pair<int, int>> 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<std::string_view>& Arguments) {
|
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||||
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
||||||
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
||||||
@ -86,8 +147,8 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
|
|||||||
std::any Res;
|
std::any Res;
|
||||||
char Code = Packet.at(0);
|
char Code = Packet.at(0);
|
||||||
|
|
||||||
// V to Z
|
// V to Y
|
||||||
if (Code <= 90 && Code >= 86) {
|
if (Code <= 89 && Code >= 86) {
|
||||||
PPSMonitor.IncrementInternalPPS();
|
PPSMonitor.IncrementInternalPPS();
|
||||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||||
return;
|
return;
|
||||||
@ -145,6 +206,11 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
|
|||||||
beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
|
beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
|
||||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||||
return;
|
return;
|
||||||
|
case 'Z': // position packet
|
||||||
|
PPSMonitor.IncrementInternalPPS();
|
||||||
|
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||||
|
|
||||||
|
HandlePosition(*LockedClient, Packet);
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -223,13 +289,11 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'c':
|
case 'c': {
|
||||||
beammp_trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
pid = Data.substr(0, Data.find('-'));
|
auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1)));
|
||||||
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1);
|
if (MaybePidVid) {
|
||||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
std::tie(PID, VID) = MaybePidVid.value();
|
||||||
PID = stoi(pid);
|
|
||||||
VID = stoi(vid);
|
|
||||||
}
|
}
|
||||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onVehicleEdited", "", c.GetID(), VID, Packet.substr(3));
|
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);
|
c.SetUnicycleID(-1);
|
||||||
}
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
||||||
if (!Network.Respond(c, Destroy, true)) {
|
Network.SendToAll(nullptr, Destroy, true, true);
|
||||||
// TODO: handle
|
|
||||||
}
|
|
||||||
c.DeleteCar(VID);
|
c.DeleteCar(VID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'd':
|
}
|
||||||
|
case 'd': {
|
||||||
beammp_trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
pid = Data.substr(0, Data.find('-'));
|
auto MaybePidVid = GetPidVid(Data);
|
||||||
vid = Data.substr(Data.find('-') + 1);
|
if (MaybePidVid) {
|
||||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
std::tie(PID, VID) = MaybePidVid.value();
|
||||||
PID = stoi(pid);
|
|
||||||
VID = stoi(vid);
|
|
||||||
}
|
}
|
||||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||||
if (c.GetUnicycleID() == VID) {
|
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));
|
beammp_debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'r':
|
}
|
||||||
|
case 'r': {
|
||||||
beammp_trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
Pos = int(Data.find('-'));
|
auto MaybePidVid = GetPidVid(Data);
|
||||||
pid = Data.substr(0, Pos++);
|
if (MaybePidVid) {
|
||||||
vid = Data.substr(Pos, Data.find(':') - Pos);
|
std::tie(PID, VID) = MaybePidVid.value();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
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);
|
Network.SendToAll(&c, Packet, false, true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
case 't':
|
case 't':
|
||||||
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
Network.SendToAll(&c, Packet, false, true);
|
Network.SendToAll(&c, Packet, false, true);
|
||||||
@ -365,3 +424,21 @@ void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
|||||||
WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here
|
WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here
|
||||||
(void)mClients.insert(NewClient);
|
(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user