Skip to content

Game.Simulation.ServiceFeeSystem

Assembly: Game (Game.dll)
Namespace: Game.Simulation

Type: class (public)

Base: GameSystemBase, IServiceFeeSystem, IDefaultSerializable, ISerializable, IPostDeserialize

Summary:
ServiceFeeSystem is responsible for collecting, aggregating and reporting city service fees in Cities: Skylines 2. It: - Collects per-update service fees from service-producing buildings (patients at healthcare, students at education, etc.), queues fee events and deducts money from households. - Aggregates queued fee events into per-city CollectedCityServiceFeeData buffers. - Sends trigger notifications based on trade/balance of service resources. - Provides static helpers to query and modify service fees, calculate multipliers and estimate income. - Handles serialization/deserialization of the queued fee events and supports new-game initialization.

Notes: - Much of the runtime work is performed in Burst-compiled jobs (PayFeeJob, FeeToCityJob, TriggerJob) to run on worker threads. The system manages a NativeQueue of FeeEvent and a cached NativeList of CollectedCityServiceFeeData for quick access. - The system updates infrequently (GetUpdateInterval returns 2048), and distributes work to an EndFrameBarrier to synchronize with end-of-frame writers.


Fields

  • private const int kUpdatesPerDay
    Used as an internal constant (value 128) representing the number of fee updates per in-game day. Drives normalization of per-tick amounts.

  • private CitySystem m_CitySystem
    Reference to the global CitySystem. Used to obtain the city entity (m_City) used by jobs and lookups.

  • private TriggerSystem m_TriggerSystem
    Reference to TriggerSystem used to enqueue triggers/notifications produced by service fee aggregation.

  • private EndFrameBarrier m_EndFrameBarrier
    Reference to the EndFrameBarrier system. Used to register writer dependencies for jobs that enqueue into managed buffers at end of frame.

  • private EntityQuery m_FeeCollectorGroup
    EntityQuery selecting entities that can produce fee events (e.g., buildings with Patient or Student buffers and a ServiceFeeCollector component). Used to schedule the PayFeeJob.

  • private EntityQuery m_CollectedFeeGroup
    EntityQuery selecting entities that contain CollectedCityServiceFeeData buffers (city-level storage). Used to aggregate fees.

  • private NativeQueue<FeeEvent> m_FeeQueue
    Thread-safe queue where jobs enqueue FeeEvent instances describing collected fees. Serialized/deserialized when saving/loading.

  • private NativeList<CollectedCityServiceFeeData> m_CityServiceFees
    Cached list of all CollectedCityServiceFeeData across the city. Used for quick, CPU-side queries (e.g., GetServiceFees, income estimates).

  • private JobHandle m_Writers
    Tracks job dependencies for writers that write into city buffers; combined with other dependencies to ensure proper synchronization.

  • private TypeHandle __TypeHandle
    Internal struct containing cached Component/Buffer lookup type handles used by jobs. Populated in OnCreateForCompiler.

Nested types (not fields but important): - FeeEvent (struct) — represents a single fee event and implements ISerializable. - PayFeeJob (Burst-compiled IJobChunk) — collects fees from entities and enqueues FeeEvent into m_FeeQueue; deducts household money. - FeeToCityJob (Burst-compiled IJob) — drains m_FeeQueue and accumulates values into CollectedCityServiceFeeData buffers. - TriggerJob (Burst-compiled IJob) — computes balances and enqueues trigger actions (e.g., notifications for electricity, healthcare). - TypeHandle (struct) — holds BufferTypeHandle/ComponentLookup/BufferLookup instances used by the jobs.


Properties

  • (No public properties)
    This system exposes functionality via methods rather than public properties. Use GetServiceFees(), GetFeeQueue(out JobHandle), AddQueueWriter(JobHandle) and other methods below.

Constructors

  • public ServiceFeeSystem()
    Default constructor. The system uses OnCreate to initialize references, queries and native containers.

Methods

  • public override int GetUpdateInterval(SystemUpdatePhase phase)
    Returns the update interval for the system. This system uses a large interval (2048) to control how often it is scheduled in the ECS update pipeline.

  • protected override void OnCreate()
    Initializes entity queries, acquires references to CitySystem, TriggerSystem and EndFrameBarrier, and allocates native containers:

  • Creates m_FeeQueue (NativeQueue) and m_CityServiceFees (NativeList).
  • Sets up m_FeeCollectorGroup and m_CollectedFeeGroup queries.
  • Calls RequireForUpdate on m_CollectedFeeGroup so the system only runs if collected fee buffers exist.

  • public void PostDeserialize(Context context)
    Called after deserialization. If context.purpose == NewGame it resets cached fees (CacheFees(reset: true)), otherwise just caches fees. Ensures fresh starting state for new games.

  • protected override void OnDestroy()
    Completes outstanding m_Writers JobHandle, disposes m_FeeQueue and m_CityServiceFees, and calls base.OnDestroy().

  • public void Serialize<TWriter>(TWriter writer) where TWriter : IWriter
    Serializes the pending fee queue to the writer. Ensures m_Writers is completed, copies the queue to a NativeArray and writes length + each FeeEvent.

  • public void Deserialize<TReader>(TReader reader) where TReader : IReader
    Deserializes and restores the fee queue. Completes m_Writers and clears existing m_FeeQueue before enqueuing read FeeEvent items.

  • public void SetDefaults(Context context)
    Resets state for new game defaults: completes writers and clears the fee queue.

  • public NativeList<CollectedCityServiceFeeData> GetServiceFees()
    Returns the cached m_CityServiceFees NativeList containing collected fee data (internal, export, import amounts) for all resources. Useful for UI, previews and plugin queries. Caller should not dispose the returned list (owned by system).

  • public NativeQueue<FeeEvent> GetFeeQueue(out JobHandle deps)
    Returns a reference to the internal fee queue and outputs current writer dependencies via deps = m_Writers. Used by jobs that will enqueue fee events; callers must combine dependencies and call AddQueueWriter to register their job handle so the system waits correctly.

  • public void AddQueueWriter(JobHandle deps)
    Combine a job handle that writes to the queue into the system's writer dependency chain: m_Writers = JobHandle.CombineDependencies(m_Writers, deps).

  • public int3 GetServiceFees(PlayerResource resource)
    Convenience wrapper calling the static GetServiceFees(resource, m_CityServiceFees). Returns rounded int3 (internal, export, import) amounts for the requested resource.

  • public int GetServiceFeeIncomeEstimate(PlayerResource resource, float fee)
    Convenience wrapper calling the static GetServiceFeeIncomeEstimate(resource, fee, m_CityServiceFees). Estimates income generated by the specified fee using collected counts.

  • public static PlayerResource GetEducationResource(int level)
    Maps education level (1..4) to respective PlayerResource enum: BasicEducation, SecondaryEducation, HigherEducation, otherwise PlayerResource.Count.

  • public static float GetFee(PlayerResource resource, DynamicBuffer<ServiceFee> fees)
    Returns the fee value for the given resource from a ServiceFee buffer. Returns 0f if not found.

  • public static bool TryGetFee(PlayerResource resource, DynamicBuffer<ServiceFee> fees, out float fee)
    Tries to find a ServiceFee for resource in the buffer; outputs fee and returns true if present.

  • public static void SetFee(PlayerResource resource, DynamicBuffer<ServiceFee> fees, float value)
    Sets or adds a ServiceFee entry for resource in the provided DynamicBuffer.

  • public static float GetConsumptionMultiplier(PlayerResource resource, float relativeFee, in ServiceFeeParameterData feeParameters)
    Returns consumption multiplier for a resource relative to fee. Currently delegates to specialized systems for Electricity and Water; returns 1f for others.

  • public static float GetEfficiencyMultiplier(PlayerResource resource, float relativeFee, in BuildingEfficiencyParameterData efficiencyParameters)
    Returns building efficiency multiplier for the resource based on fees. Delegates to electricity/water adjust systems, 1f otherwise.

  • public static int GetHappinessEffect(PlayerResource resource, float relativeFee, in CitizenHappinessParameterData happinessParameters)
    Returns integer happiness effect due to fee for certain resources (electricity/water); defaults to 1 for others.

  • public static int3 GetServiceFees(PlayerResource resource, NativeList<CollectedCityServiceFeeData> fees)
    Static version that aggregates CollectedCityServiceFeeData entries in a provided list and returns rounded int3 {internal, export, import} totals for the resource.

  • public static int GetServiceFeeIncomeEstimate(PlayerResource resource, float fee, NativeList<CollectedCityServiceFeeData> fees)
    Static estimator: multiplies internal counts by fee to estimate periodic income; returns rounded int.

  • private void CacheFees(bool reset = false)
    Reads all CollectedCityServiceFeeData buffers from the entities matching m_CollectedFeeGroup and populates the m_CityServiceFees cache. If reset==true, buffer entries are reset to zero except resource id. Called on PostDeserialize(new game) and each OnUpdate to refresh cache.

  • protected override void OnUpdate()
    Main update orchestrator:

  • Calls CacheFees() to refresh cache.
  • Constructs and schedules PayFeeJob (IJobChunk) over m_FeeCollectorGroup — job collects fees from Patients and Students and enqueues FeeEvent items, and deducts household money.
  • Adds base.Dependency to the EndFrameBarrier as a producer.
  • Schedules FeeToCityJob (IJob) to drain the NativeQueue and write into CollectedCityServiceFeeData buffers, combining dependencies with writer jobs.
  • Stores the combined dependency in m_Writers for later synchronization.
  • Schedules TriggerJob (IJob) to compute aggregated balances and enqueue triggers via TriggerSystem's action buffer.

  • protected override void OnCreateForCompiler()
    Internal initialization used by generated/compiled code path: assigns queries and TypeHandle handles.

  • private void __AssignQueries(ref SystemState state)
    Compiler-internal; used during OnCreateForCompiler.

Additional behavior details: - PayFeeJob: multiplies stored per-fee values to produce per-tick monetary adjustments. It uses GetFee(...) and normalizes by 128 (kUpdatesPerDay). Households are charged using EconomyUtils.AddResources with negative money adjustments. - FeeEvent serialization: FeeEvent implements ISerializable for persistent queue saving/loading.

Threading and safety: - Native containers (m_FeeQueue, m_CityServiceFees) are allocated with persistent lifetime and must be disposed in OnDestroy. - Jobs updating the queue must pass their JobHandle to AddQueueWriter so the system can combine dependencies. The system completes m_Writers before serializing or destroying.


Usage Example

// Example: modify a city's electricity service fee and estimate income.
public void Example(ServiceFeeSystem feeSystem, Entity cityEntity)
{
    // Set fee on a building's ServiceFee buffer (example context):
    // DynamicBuffer<ServiceFee> fees = entityManager.GetBuffer<ServiceFee>(someBuildingEntity);
    // ServiceFeeSystem.SetFee(PlayerResource.Electricity, fees, 0.12f);

    // Query aggregated service fees for the city
    int3 electricityTotals = feeSystem.GetServiceFees(PlayerResource.Electricity);
    // electricityTotals.x == internal, .y == export, .z == import (rounded ints)

    // Estimate income for a proposed fee (per update period)
    int estimatedIncome = feeSystem.GetServiceFeeIncomeEstimate(PlayerResource.Electricity, 0.12f);

    UnityEngine.Debug.Log($"Electricity fees: internal={electricityTotals.x}, export={electricityTotals.y}, import={electricityTotals.z}");
    UnityEngine.Debug.Log($"Estimated income at fee 0.12: {estimatedIncome}");
}

Notes: - When writing jobs that enqueue FeeEvent into the system queue, obtain the queue via GetFeeQueue(out JobHandle deps) to get the current m_Writers and ensure you combine dependencies and call AddQueueWriter(...) so the system knows about your writer job. - Use PostDeserialize to reset state for new games (the system does this automatically via the IPostDeserialize implementation).