Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,31 @@ namespace CS2MultiplayerMod.Game.Sync.Channels
{
/// <summary>
/// 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.
/// </summary>
public sealed class StatisticsStateChannel : IStateChannel
{
public const byte Id = 10;
public byte ChannelId => Id;

/// <summary>
/// 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.
/// </summary>
private const int LifetimeParameter = 0;

/// <summary>Upper bound for entries in one snapshot (the table holds 20).</summary>
private const int MaxEntriesPerSnapshot = 64;

private static readonly StatisticType[] Synced =
{
StatisticType.DeathRate, // "deaths" — cumulative count of citizen deaths
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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;
Expand Down