mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2025-07-04 00:36:14 +00:00
possibly fix some issues with not disconnecting disconnected players
This commit is contained in:
parent
533c8c80e1
commit
fc201efa4b
@ -8,18 +8,18 @@ class TNetwork {
|
|||||||
public:
|
public:
|
||||||
TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager);
|
TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager);
|
||||||
|
|
||||||
bool TCPSend(TClient& c, const std::string& Data, bool IsSync = false);
|
[[nodiscard]] bool TCPSend(TClient& c, const std::string& Data, bool IsSync = false);
|
||||||
void SendLarge(TClient& c, std::string Data, bool isSync = false);
|
[[nodiscard]] bool SendLarge(TClient& c, std::string Data, bool isSync = false);
|
||||||
void Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync = false);
|
[[nodiscard]] bool Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync = false);
|
||||||
std::shared_ptr<TClient> CreateClient(SOCKET TCPSock);
|
std::shared_ptr<TClient> CreateClient(SOCKET TCPSock);
|
||||||
std::string TCPRcv(TClient& c);
|
std::string TCPRcv(TClient& c);
|
||||||
void ClientKick(TClient& c, const std::string& R);
|
void ClientKick(TClient& c, const std::string& R);
|
||||||
void SyncClient(const std::weak_ptr<TClient>& c);
|
[[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c);
|
||||||
void Identify(SOCKET TCPSock);
|
void Identify(SOCKET TCPSock);
|
||||||
void Authentication(SOCKET TCPSock);
|
void Authentication(SOCKET TCPSock);
|
||||||
bool CheckBytes(TClient& c, int32_t BytesRcv);
|
[[nodiscard]] bool CheckBytes(TClient& c, int32_t BytesRcv);
|
||||||
void SyncResources(TClient& c);
|
void SyncResources(TClient& c);
|
||||||
void UDPSend(TClient& Client, std::string Data) const;
|
[[nodiscard]] bool UDPSend(TClient& Client, std::string Data) const;
|
||||||
void SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel);
|
void SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel);
|
||||||
void UpdatePlayer(TClient& Client);
|
void UpdatePlayer(TClient& Client);
|
||||||
|
|
||||||
|
101
src/TNetwork.cpp
101
src/TNetwork.cpp
@ -255,7 +255,9 @@ void TNetwork::Authentication(SOCKET TCPSock) {
|
|||||||
ClientKick(*Client, "Invalid version header!");
|
ClientKick(*Client, "Invalid version header!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TCPSend(*Client, "S");
|
if (!TCPSend(*Client, "S")) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
|
|
||||||
Rc = TCPRcv(*Client);
|
Rc = TCPRcv(*Client);
|
||||||
|
|
||||||
@ -356,7 +358,12 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
|
|||||||
c.MissedPacketQueue().pop();
|
c.MissedPacketQueue().pop();
|
||||||
} // end locked context
|
} // end locked context
|
||||||
// debug("sending a missed packet: " + QData);
|
// debug("sending a missed packet: " + QData);
|
||||||
TCPSend(c, QData, true);
|
if (!TCPSend(c, QData, true)) {
|
||||||
|
if (c.GetStatus() > -1)
|
||||||
|
c.SetStatus(-1);
|
||||||
|
CloseSocketProper(c.GetTCPSock());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,14 +475,14 @@ std::string TNetwork::TCPRcv(TClient& c) {
|
|||||||
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
|
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
c.UpdatePingTime();
|
|
||||||
|
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::ClientKick(TClient& c, const std::string& R) {
|
void TNetwork::ClientKick(TClient& c, const std::string& R) {
|
||||||
info("Client kicked: " + R);
|
info("Client kicked: " + R);
|
||||||
TCPSend(c, "E" + R);
|
if (!TCPSend(c, "E" + R)) {
|
||||||
|
// TODO handle
|
||||||
|
}
|
||||||
c.SetStatus(-2);
|
c.SetStatus(-2);
|
||||||
CloseSocketProper(c.GetTCPSock());
|
CloseSocketProper(c.GetTCPSock());
|
||||||
}
|
}
|
||||||
@ -521,7 +528,7 @@ void TNetwork::UpdatePlayer(TClient& Client) {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
Packet = Packet.substr(0, Packet.length() - 1);
|
Packet = Packet.substr(0, Packet.length() - 1);
|
||||||
Respond(Client, Packet, true);
|
(void)Respond(Client, Packet, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) {
|
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) {
|
||||||
@ -582,7 +589,7 @@ void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
|
|||||||
SyncResources(*LockedClient);
|
SyncResources(*LockedClient);
|
||||||
if (LockedClient->GetStatus() < 0)
|
if (LockedClient->GetStatus() < 0)
|
||||||
return;
|
return;
|
||||||
Respond(*LockedClient, "M" + Application::Settings.MapName, true); //Send the Map on connect
|
(void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); //Send the Map on connect
|
||||||
info(LockedClient->GetName() + " : Connected");
|
info(LockedClient->GetName() + " : Connected");
|
||||||
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
||||||
}
|
}
|
||||||
@ -591,7 +598,9 @@ void TNetwork::SyncResources(TClient& c) {
|
|||||||
#ifndef DEBUG
|
#ifndef DEBUG
|
||||||
try {
|
try {
|
||||||
#endif
|
#endif
|
||||||
TCPSend(c, "P" + std::to_string(c.GetID()));
|
if (!TCPSend(c, "P" + std::to_string(c.GetID()))) {
|
||||||
|
// TODO handle
|
||||||
|
}
|
||||||
std::string Data;
|
std::string Data;
|
||||||
while (c.GetStatus() > -1) {
|
while (c.GetStatus() > -1) {
|
||||||
Data = TCPRcv(c);
|
Data = TCPRcv(c);
|
||||||
@ -623,7 +632,9 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
|
|||||||
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||||
if (ToSend.empty())
|
if (ToSend.empty())
|
||||||
ToSend = "-";
|
ToSend = "-";
|
||||||
TCPSend(c, ToSend);
|
if (!TCPSend(c, ToSend)) {
|
||||||
|
// TODO: error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
@ -635,11 +646,16 @@ void TNetwork::SendFile(TClient& c, const std::string& Name) {
|
|||||||
info(c.GetName() + " requesting : " + Name.substr(Name.find_last_of('/')));
|
info(c.GetName() + " requesting : " + Name.substr(Name.find_last_of('/')));
|
||||||
|
|
||||||
if (!std::filesystem::exists(Name)) {
|
if (!std::filesystem::exists(Name)) {
|
||||||
TCPSend(c, "CO");
|
if (!TCPSend(c, "CO")) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
warn("File " + Name + " could not be accessed!");
|
warn("File " + Name + " could not be accessed!");
|
||||||
return;
|
return;
|
||||||
} else
|
} else {
|
||||||
TCPSend(c, "AG");
|
if (!TCPSend(c, "AG")) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///Wait for connections
|
///Wait for connections
|
||||||
int T = 0;
|
int T = 0;
|
||||||
@ -727,41 +743,46 @@ bool TNetwork::TCPSendRaw(SOCKET C, char* Data, int32_t Size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::SendLarge(TClient& c, std::string Data, bool isSync) {
|
bool TNetwork::SendLarge(TClient& c, std::string Data, bool isSync) {
|
||||||
if (Data.length() > 400) {
|
if (Data.length() > 400) {
|
||||||
std::string CMP(Comp(Data));
|
std::string CMP(Comp(Data));
|
||||||
Data = "ABG:" + CMP;
|
Data = "ABG:" + CMP;
|
||||||
}
|
}
|
||||||
TCPSend(c, Data, isSync);
|
return TCPSend(c, Data, isSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync) {
|
bool TNetwork::Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync) {
|
||||||
char C = MSG.at(0);
|
char C = MSG.at(0);
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||||
if (C == 'O' || C == 'T' || MSG.length() > 1000) {
|
if (C == 'O' || C == 'T' || MSG.length() > 1000) {
|
||||||
SendLarge(c, MSG, isSync);
|
return SendLarge(c, MSG, isSync);
|
||||||
} else {
|
} else {
|
||||||
TCPSend(c, MSG, isSync);
|
return TCPSend(c, MSG, isSync);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
UDPSend(c, MSG);
|
return UDPSend(c, MSG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
||||||
if (c.expired()) {
|
if (c.expired()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
auto LockedClient = c.lock();
|
auto LockedClient = c.lock();
|
||||||
if (LockedClient->IsSynced())
|
if (LockedClient->IsSynced())
|
||||||
return;
|
return true;
|
||||||
// Syncing, later set isSynced
|
// Syncing, later set isSynced
|
||||||
// after syncing is done, we apply all packets they missed
|
// after syncing is done, we apply all packets they missed
|
||||||
Respond(*LockedClient, ("Sn") + LockedClient->GetName(), true);
|
if (!Respond(*LockedClient, ("Sn") + LockedClient->GetName(), true)) {
|
||||||
SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true);
|
return false;
|
||||||
|
}
|
||||||
|
// ignore error
|
||||||
|
(void)SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true);
|
||||||
|
|
||||||
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
||||||
LockedClient->SetIsSyncing(true);
|
LockedClient->SetIsSyncing(true);
|
||||||
bool Return = false;
|
bool Return = false;
|
||||||
|
bool res = true;
|
||||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
if (!ClientPtr.expired()) {
|
if (!ClientPtr.expired()) {
|
||||||
auto client = ClientPtr.lock();
|
auto client = ClientPtr.lock();
|
||||||
@ -774,9 +795,10 @@ void TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
|||||||
for (auto& v : VehicleData) {
|
for (auto& v : VehicleData) {
|
||||||
if (LockedClient->GetStatus() < 0) {
|
if (LockedClient->GetStatus() < 0) {
|
||||||
Return = true;
|
Return = true;
|
||||||
|
res = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Respond(*LockedClient, v.Data(), true, true);
|
res = Respond(*LockedClient, v.Data(), true, true);
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -785,42 +807,50 @@ void TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
|||||||
});
|
});
|
||||||
LockedClient->SetIsSyncing(false);
|
LockedClient->SetIsSyncing(false);
|
||||||
if (Return) {
|
if (Return) {
|
||||||
return;
|
return res;
|
||||||
}
|
}
|
||||||
LockedClient->SetIsSynced(true);
|
LockedClient->SetIsSynced(true);
|
||||||
info(LockedClient->GetName() + (" is now synced!"));
|
info(LockedClient->GetName() + (" is now synced!"));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
|
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
|
||||||
if (!Self)
|
if (!Self)
|
||||||
Assert(c);
|
Assert(c);
|
||||||
char C = Data.at(0);
|
char C = Data.at(0);
|
||||||
|
bool ret = true;
|
||||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||||
if (!ClientPtr.expired()) {
|
if (!ClientPtr.expired()) {
|
||||||
auto Client = ClientPtr.lock();
|
auto Client = ClientPtr.lock();
|
||||||
if (Self || Client.get() != c) {
|
if (Self || Client.get() != c) {
|
||||||
if (Client->IsSynced() || Client->IsSyncing()) {
|
if (Client->IsSynced() || Client->IsSyncing()) {
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||||
if (C == 'O' || C == 'T' || Data.length() > 1000)
|
if (C == 'O' || C == 'T' || Data.length() > 1000) {
|
||||||
SendLarge(*Client, Data);
|
ret = SendLarge(*Client, Data);
|
||||||
else
|
} else {
|
||||||
TCPSend(*Client, Data);
|
ret = TCPSend(*Client, Data);
|
||||||
} else
|
}
|
||||||
UDPSend(*Client, Data);
|
} else {
|
||||||
|
ret = UDPSend(*Client, Data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
if (!ret) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::UDPSend(TClient& Client, std::string Data) const {
|
bool TNetwork::UDPSend(TClient& Client, std::string Data) const {
|
||||||
if (!Client.IsConnected() || Client.GetStatus() < 0) {
|
if (!Client.IsConnected() || Client.GetStatus() < 0) {
|
||||||
// this can happen if we try to send a packet to a client that is either
|
// this can happen if we try to send a packet to a client that is either
|
||||||
// 1. not yet fully connected, or
|
// 1. not yet fully connected, or
|
||||||
// 2. disconnected and not yet fully removed
|
// 2. disconnected and not yet fully removed
|
||||||
// this is fine can can be ignored :^)
|
// this is fine can can be ignored :^)
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
sockaddr_in Addr = Client.GetUDPAddr();
|
sockaddr_in Addr = Client.GetUDPAddr();
|
||||||
auto AddrSize = sizeof(Client.GetUDPAddr());
|
auto AddrSize = sizeof(Client.GetUDPAddr());
|
||||||
@ -842,22 +872,27 @@ void TNetwork::UDPSend(TClient& Client, std::string Data) const {
|
|||||||
debug(("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
debug(("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
||||||
if (Client.GetStatus() > -1)
|
if (Client.GetStatus() > -1)
|
||||||
Client.SetStatus(-1);
|
Client.SetStatus(-1);
|
||||||
|
return false;
|
||||||
} else if (sendOk == 0) {
|
} else if (sendOk == 0) {
|
||||||
debug(("(UDP) sendto returned 0"));
|
debug(("(UDP) sendto returned 0"));
|
||||||
if (Client.GetStatus() > -1)
|
if (Client.GetStatus() > -1)
|
||||||
Client.SetStatus(-1);
|
Client.SetStatus(-1);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#else // unix
|
#else // unix
|
||||||
if (sendOk == -1) {
|
if (sendOk == -1) {
|
||||||
debug(("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
debug(("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
||||||
if (Client.GetStatus() > -1)
|
if (Client.GetStatus() > -1)
|
||||||
Client.SetStatus(-1);
|
Client.SetStatus(-1);
|
||||||
|
return false;
|
||||||
} else if (sendOk == 0) {
|
} else if (sendOk == 0) {
|
||||||
debug(("(UDP) sendto returned 0"));
|
debug(("(UDP) sendto returned 0"));
|
||||||
if (Client.GetStatus() > -1)
|
if (Client.GetStatus() > -1)
|
||||||
Client.SetStatus(-1);
|
Client.SetStatus(-1);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#endif // WIN32
|
#endif // WIN32
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TNetwork::UDPRcvFromClient(sockaddr_in& client) const {
|
std::string TNetwork::UDPRcvFromClient(sockaddr_in& client) const {
|
||||||
|
@ -91,11 +91,20 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
|
|||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
|
debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
|
||||||
#endif
|
#endif
|
||||||
Network.SyncClient(Client);
|
if (!Network.SyncClient(Client)) {
|
||||||
|
// TODO handle
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case 'p':
|
case 'p':
|
||||||
Network.Respond(*LockedClient, ("p"), false);
|
if (!Network.Respond(*LockedClient, ("p"), false)) {
|
||||||
|
// failed to send
|
||||||
|
if (LockedClient->GetStatus() > -1) {
|
||||||
|
LockedClient->SetStatus(-1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Network.UpdatePlayer(*LockedClient);
|
Network.UpdatePlayer(*LockedClient);
|
||||||
|
}
|
||||||
|
LockedClient->UpdatePingTime();
|
||||||
return;
|
return;
|
||||||
case 'O':
|
case 'O':
|
||||||
if (Packet.length() > 1000) {
|
if (Packet.length() > 1000) {
|
||||||
@ -171,9 +180,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
Packet = "Os:" + c.GetRoles() + ":" + c.GetName() + ":" + std::to_string(c.GetID()) + "-" + std::to_string(CarID) + Packet.substr(4);
|
Packet = "Os:" + c.GetRoles() + ":" + c.GetName() + ":" + std::to_string(c.GetID()) + "-" + std::to_string(CarID) + Packet.substr(4);
|
||||||
auto Res = TriggerLuaEvent(("onVehicleSpawn"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), CarID, Packet.substr(3) } }), true);
|
auto Res = TriggerLuaEvent(("onVehicleSpawn"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), CarID, Packet.substr(3) } }), true);
|
||||||
if (c.GetCarCount() >= Application::Settings.MaxCars || std::any_cast<int>(Res)) {
|
if (c.GetCarCount() >= Application::Settings.MaxCars || std::any_cast<int>(Res)) {
|
||||||
Network.Respond(c, Packet, true);
|
if (!Network.Respond(c, Packet, true)) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
||||||
Network.Respond(c, Destroy, true);
|
if (!Network.Respond(c, Destroy, true)) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
|
debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
|
||||||
} else {
|
} else {
|
||||||
c.AddNewCar(CarID, Packet);
|
c.AddNewCar(CarID, Packet);
|
||||||
@ -200,7 +213,9 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
Apply(c, VID, Packet);
|
Apply(c, VID, Packet);
|
||||||
} else {
|
} else {
|
||||||
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);
|
||||||
Network.Respond(c, Destroy, true);
|
if (!Network.Respond(c, Destroy, true)) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
c.DeleteCar(VID);
|
c.DeleteCar(VID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user