diff --git a/CS2MultiplayerMod/Game/Sync/Channels/City/StatisticsStateChannel.cs b/CS2MultiplayerMod/Game/Sync/Channels/City/StatisticsStateChannel.cs
index d309aa5..a1c2aac 100644
--- a/CS2MultiplayerMod/Game/Sync/Channels/City/StatisticsStateChannel.cs
+++ b/CS2MultiplayerMod/Game/Sync/Channels/City/StatisticsStateChannel.cs
@@ -10,17 +10,31 @@ namespace CS2MultiplayerMod.Game.Sync.Channels
{
///
/// Replicates the cumulative life-event counters - deaths, births, move-ins,
- /// move-aways, crime, mail - host -> clients, so both players' statistics panels show
- /// the same numbers between full-world resyncs.
+ /// move-aways, crime, mail, transport passengers and cargo - host -> clients, so
+ /// both players' statistics panels show the same numbers between full-world resyncs.
/// Mechanism: the host snapshots each counter's lifetime value and the client feeds it
/// through the game's own event pipeline, the same path the deathcare/crime systems use,
/// so the statistics buffers stay internally consistent and serializable.
+ /// Only event-accumulated lifetime totals ride here. Gauges the simulation rewrites
+ /// itself (population, money, happiness, current tourists) are deliberately excluded:
+ /// forcing those through the event queue fights the writer, the same reason the
+ /// population channel was retired.
///
public sealed class StatisticsStateChannel : IStateChannel
{
public const byte Id = 10;
public byte ChannelId => Id;
+ ///
+ /// Statistic parameter holding the event-accumulated lifetime total. All
+ /// counters synced here are lifetime totals at this parameter, which is why
+ /// the generic delta mechanism applies to every entry unchanged.
+ ///
+ private const int LifetimeParameter = 0;
+
+ /// Upper bound for entries in one snapshot (the table holds 20).
+ private const int MaxEntriesPerSnapshot = 64;
+
private static readonly StatisticType[] Synced =
{
StatisticType.DeathRate, // "deaths" — cumulative count of citizen deaths
@@ -31,6 +45,23 @@ public sealed class StatisticsStateChannel : IStateChannel
StatisticType.EscapedArrestCount,
StatisticType.CollectedMail,
StatisticType.DeliveredMail,
+ // Transport ridership and cargo: lifetime boarding/load totals shown in the
+ // transport info summaries. Same event-accumulated shape as the counters
+ // above, read at parameter 0 (the total), so the generic delta mechanism
+ // applies unchanged. The payload stays self-describing (count + type +
+ // value), so peers without these entries simply exchange fewer of them.
+ StatisticType.PassengerCountBus,
+ StatisticType.PassengerCountSubway,
+ StatisticType.PassengerCountTram,
+ StatisticType.PassengerCountTrain,
+ StatisticType.PassengerCountTaxi,
+ StatisticType.PassengerCountAirplane,
+ StatisticType.PassengerCountShip,
+ StatisticType.PassengerCountFerry,
+ StatisticType.CargoCountTruck,
+ StatisticType.CargoCountTrain,
+ StatisticType.CargoCountShip,
+ StatisticType.CargoCountAirplane,
};
private CityStatisticsSystem _stats;
@@ -56,7 +87,7 @@ public bool Capture(EntityManager em, NetworkWriter writer)
for (int i = 0; i < Synced.Length; i++)
{
writer.WriteByte((byte)Synced[i]);
- writer.WriteLong(stats.GetStatisticValueLong(Synced[i], 0));
+ writer.WriteLong(stats.GetStatisticValueLong(Synced[i], LifetimeParameter));
}
return true;
}
@@ -71,13 +102,21 @@ public void Apply(EntityManager em, NetworkReader reader)
{
CityStatisticsSystem stats = Resolve(em);
int count = reader.ReadByte();
+ if (count < 0 || count > MaxEntriesPerSnapshot)
+ {
+ WarnOnce("apply", new System.IO.InvalidDataException(
+ "Implausible statistics entry count: " + count + "."));
+ return;
+ }
try
{
for (int i = 0; i < count; i++)
{
- var type = (StatisticType)reader.ReadByte();
+ byte rawType = reader.ReadByte();
long hostValue = reader.ReadLong();
- long localValue = stats.GetStatisticValueLong(type, 0);
+ if (!System.Enum.IsDefined(typeof(StatisticType), (int)rawType)) continue;
+ var type = (StatisticType)rawType;
+ long localValue = stats.GetStatisticValueLong(type, LifetimeParameter);
// Where this counter is headed: the value it will hold once the events already
// queued are processed. Once the local value has caught up to that target the
@@ -95,7 +134,7 @@ public void Apply(EntityManager em, NetworkReader reader)
queue.Enqueue(new StatisticsEvent
{
m_Statistic = type,
- m_Parameter = 0,
+ m_Parameter = LifetimeParameter,
m_Change = delta,
});
_inFlightTarget[type] = hostValue;