feat(stats): send UpdatePlayerStatistic to the player as live did

Live sent UpdatePlayerStatistic (1481) server -> client for every statistic
the server counted (59,933 messages in the captures, none client -> server);
DLU only counted them, so the passport stayed stale until the next login.
CharacterComponent::UpdatePlayerStatistic now tells the player's client what
it counts (not what a client reported), with the amount left out when it is
1, byte for byte as the live samples.

MetersTraveled: whole meters now count (each position update's distance was
cut to a whole number before, so short steps never counted) and go to the
client once 25 have gathered (live's values were nearly all 25-33 while
moving) and the rest, 0 included, when the player is taken down on leaving
the world (live sent one more after TRANSFER_TO_WORLD). DistanceDriven goes
out every 10 seconds of racing (inferred: live sent it every few hundred
packets, about 1300 units at race speed).

Check in game: open the passport's statistics page, walk around, collect a
power-up, complete a mission; the numbers go up without relogging. Walk
25+ meters; Meters Traveled matches what the passport shows after
relogging. Change worlds and relog: the meters were kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 02:11:41 -05:00
parent 5b1be32e06
commit 752f079100
6 changed files with 196 additions and 4 deletions

View File

@@ -25,6 +25,7 @@
#include "eReplicaPacketType.h"
#include "PlayerManager.h"
#include "GhostComponent.h"
#include "CharacterComponent.h"
#include <ranges>
// Configure which zones have ghosting disabled, mostly small worlds.
@@ -452,6 +453,12 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
Game::server->Send(stream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
// A player taken down for everyone is leaving the world: live then sent the meters its client hadn't been told
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS && entity->IsPlayer()) {
auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->FlushMovementStatistics();
}
// Players still loading were to get it once loaded: only the ones it was taken down for (all of them, or the one
// player; taking it down for one player, e.g. out of their ghosting range, leaves the others' queue alone)
for (auto* player : PlayerManager::GetAllPlayers()) {