Compare commits

..
6 Commits
Author SHA1 Message Date
Tixx f55931da73 Player ping packet (#505)
Extracts self reported ve - ge - launcher - server ping from time packet
and broadcasts it via new `Sp` packet.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2026-09-23 17:37:29 +02:00
Tixx 7ece770eb7 Fix JsonEncode treating tables with index 0 as arrays (#454)
## Summary
- Fix `Util.JsonEncode` incorrectly encoding Lua tables with index 0 as
JSON arrays
- Added `IsLuaArray()` helper function that validates all keys are
integers >= 1
- Tables with non-standard indices (like 0) are now correctly encoded as
JSON objects

  Closes #348

  ## Before
  ```lua
  Util.JsonEncode({[0]="a", [1]=true, [2]=0.1})
  -- Output: ["a",true,0.1]  (incorrect - data loss)
  ```
  ## After
  ```lua
  Util.JsonEncode({[0]="a", [1]=true, [2]=0.1})
  -- Output: {"0":"a","1":true,"2":0.1}  (correct)
  ```
2026-09-09 11:42:16 +02:00
Tixx 312e991814 Remove self explanatory comment 2026-09-09 10:28:57 +02:00
Tixx 2585affd80 Player ping packet 2026-09-08 23:44:37 +02:00
Kipstz 38de5be365 Merge branch 'BeamMP:minor' into fix-jsonencode-index-zero 2025-12-28 00:56:18 +01:00
Kipstz 14c69893e8 Fix JsonEncode treating tables with index 0 as arrays
Tables with index 0 should be encoded as JSON objects, not arrays.
Lua arrays start at index 1, so any table with keys outside 1-N range
should be treated as an object.

Added IsLuaArray() helper that checks all keys are integers >= 1.

Closes #348
2025-12-24 16:35:54 +01:00
4 changed files with 62 additions and 14 deletions

No files matched your search

+3
View File
@@ -85,10 +85,12 @@ public:
[[nodiscard]] std::string GetName() const { return mName; }
void SetUnicycleID(int ID) { mUnicycleID = ID; }
void SetID(int ID) { mID = ID; }
void SetSelfPing(uint16_t ping ) { mSelfPing = ping; }
[[nodiscard]] int GetOpenCarID() const;
[[nodiscard]] int GetCarCount() const;
void ClearCars();
[[nodiscard]] int GetID() const { return mID; }
[[nodiscard]] uint16_t GetSelfPing() const { return mSelfPing; }
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
[[nodiscard]] bool IsUDPConnected() const { return mIsUDPConnected; }
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
@@ -139,6 +141,7 @@ private:
std::string mRole;
std::string mDID;
int mID = -1;
uint16_t mSelfPing = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now();
std::vector<uint8_t> mMagic;
};
+18 -12
View File
@@ -693,6 +693,22 @@ std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
return Result;
}
static bool IsLuaArray(const sol::table& table) {
for (const auto& pair : table) {
if (pair.first.get_type() != sol::type::number) {
return false;
}
if (!pair.first.is<int>()) {
return false;
}
int key = pair.first.as<int>();
if (key < 1) {
return false;
}
}
return true;
}
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");
@@ -762,12 +778,7 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
if (right.as<sol::table>().empty()) {
value = nlohmann::json::object();
} else {
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;
}
}
bool local_is_array = IsLuaArray(right.as<sol::table>());
for (const auto& pair : right.as<sol::table>()) {
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
}
@@ -790,12 +801,7 @@ std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
if (object.as<sol::table>().empty()) {
json = nlohmann::json::object();
} else {
bool is_array = true;
for (const auto& pair : object.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
is_array = false;
}
}
bool is_array = IsLuaArray(object.as<sol::table>());
for (const auto& entry : object) {
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
}
+33
View File
@@ -819,6 +819,39 @@ void TNetwork::UpdatePlayer(TClient& Client) {
});
Packet = Packet.substr(0, Packet.length() - 1);
Client.EnqueuePacket(StringToVector(Packet));
std::vector<uint8_t> PingsPacket = StringToVector("Sp");
PingsPacket.push_back(0);
uint8_t amount = 0;
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
if (amount == UINT8_MAX) {
beammp_debugf("Client ping packet overflow");
return false;
}
ReadLock Lock(mServer.GetClientMutex());
if (const auto c = ClientPtr.lock()) {
int ID = c->GetID();
if (ID < 0 || ID > UINT8_MAX) {
beammp_debugf("Invalid ID found: {}", ID);
return true;
}
PingsPacket.push_back(static_cast<uint8_t>(ID));
const uint16_t ping = c->GetSelfPing();
PingsPacket.push_back(static_cast<uint8_t>(ping));
PingsPacket.push_back(static_cast<uint8_t>(ping >> 8));
amount++;
}
return true;
});
PingsPacket[2] = amount;
Client.EnqueuePacket(PingsPacket);
//(void)Respond(Client, Packet, true);
}
+8 -2
View File
@@ -217,12 +217,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
}
switch (Code) {
case 't':
if (!udp && Packet.size() == 9) {
auto Time = GetServerTimeMS();
if (!udp && Packet.size() == 11) {
uint64_t Time = GetServerTimeMS();
std::vector<uint8_t> ServerTime(sizeof(uint64_t));
std::memcpy(ServerTime.data(), &Time, sizeof(uint64_t));
uint16_t selfPing = 0;
std::memcpy(&selfPing, &Packet[Packet.size() - 2], sizeof(uint16_t));
LockedClient->SetSelfPing(selfPing);
Packet.erase(Packet.end() - 2,Packet.end());
auto Data = Packet;
beammp_debugf("Sending server time {} to client '{}' ({}), client time: {}", Time, LockedClient->GetName(), LockedClient->GetID(), reinterpret_cast<uint64_t>(Data.data()));