Skip to content

Game.PSI.RichPresenceUpdateSystem

Assembly: Assembly-CSharp
Namespace: Game.PSI

Type: class

Base: GameSystemBase

Summary:
Provides Discord Rich Presence integration for Cities: Skylines 2. This system collects city/game state (population, money, happiness, health, milestone, weather, time of day, current tool/interaction, photo mode) and periodically updates a DiscordRichPresence PSI. Updates are throttled (every 5 seconds) and a shown sub-state cycles every 10 seconds. It also registers a set of localized rich-presence keys (e.g. "#StatusInGame_Population") with PlatformManager so other parts of the platform can resolve short status strings. The system depends on various game subsystems (CitySystem, ClimateSystem, TimeUISystem, etc.) and uses a lazy reference to the DiscordRichPresence PSI.


Fields

  • private const int kUpdateRate
    Throttling interval in seconds between rich-presence updates (constant value 5).

  • private const int kStateCycleInterval
    Interval in seconds used to advance the displayed state cycle (constant value 10).

  • private DateTime m_StartTime
    Capture time used as the "start time" for the presence (set on game load).

  • private DateTime m_LastRichUpdate
    Timestamp of the last time a rich-presence update was performed (used for throttling).

  • private DateTime m_LastCycleUpdate
    Timestamp of the last time the visible state index was cycled.

  • private PhotoModeRenderSystem m_PhotoModeRenderSystem
    Reference to the photo-mode rendering system (checked to determine if the player is in Photo Mode).

  • private ToolSystem m_ToolSystem
    Reference to the tool system (used to determine active tool / infoview state).

  • private CityConfigurationSystem m_CityConfigurationSystem
    Reference to the city configuration system (used e.g. to include city name in some keys).

  • private CitySystem m_CitySystem
    Reference to the city system (used to obtain money, city entity, etc).

  • private ClimateSystem m_ClimateSystem
    Reference to the climate/weather system (used to pick weather icon and temperature).

  • private TimeUISystem m_TimeUISystem
    Reference to the time UI system (used to determine lighting state/day/night).

  • private ClimateUISystem m_ClimateUISystem
    Reference to the climate UI system (present but not heavily used in this class).

  • private EntityQuery m_MilestoneLevelQuery
    EntityQuery used to obtain the singleton MilestoneLevel component and determine achieved milestone.

  • private int m_StateIndex
    Index used to cycle which detail line is shown in Discord presence.

  • private DiscordRichPresence m_DiscordRichPresence
    Cached reference to the DiscordRichPresence PSI obtained from PlatformManager (lazy-initialized by the discordRichPresence property).

  • private string[] m_DiscordState
    Array of state keys (rich-presence keys) that are cycled through (e.g. population, money, happiness...). Set on game load depending on game mode.

  • private static readonly string[] kHappinessEmoji
    Mapping table for happiness levels to emojis (5 levels).

  • private static readonly string[] kHealthEmoji
    Mapping table for health levels to heart emojis (3 levels).

  • private static readonly string[] kAltHealthEmoji
    Alternate set of heart/emoji markers for health (3 levels) — present but not used directly in current code paths.

  • private static readonly string[] kAltHealth2Emoji
    Second alternate set of emojis for health/status (3 levels) — present but not used directly in current code paths.

  • private static readonly string[] kRomanNumbers
    Roman numeral strings for milestone labels (I..XX). Used to build milestone text.

Properties

  • private DiscordRichPresence discordRichPresence { get; }
    Lazy getter that returns the cached DiscordRichPresence PSI instance or retrieves it from PlatformManager.instance.GetPSI("Discord") and caches it. Use this to send SetRichPresence calls without repeatedly performing the PSI lookup.

Constructors

  • public RichPresenceUpdateSystem()
    Default constructor. The system relies on the Unity/ECS world to perform initialization in OnCreate and OnGameLoaded.

Methods

  • private static int GetHappinessIndex(float happiness)
    Maps an average happiness (0..100) to an index 0..4 used to select an emoji from kHappinessEmoji. Thresholds: >70 -> 4, >55 -> 3, >40 -> 2, >25 -> 1, else 0.

  • private static int GetHealthIndex(float health)
    Maps average health (0..100) to an index 0..2 used to select an emoji from health arrays. Thresholds: >75 -> 2, >35 -> 1, else 0.

  • protected override void OnGameLoaded(Context serializationContext)
    Called when a game is loaded. Records m_StartTime and initializes m_DiscordState depending on whether the game mode is actual gameplay or editor. Sets up which rich-presence keys are cycled for in-game mode. If in editor it leaves the editor mode handling to be shown as "Authoring UGC..." via UpdateEditorRichPresence.

  • protected override void OnCreate()
    System initialization: calls base.OnCreate(), obtains references to required systems via base.World.GetOrCreateSystemManaged(), sets up the milestone EntityQuery, and calls RegisterKeys() to register platform rich-presence keys/lambdas.

  • protected override void OnUpdate()
    Main periodic driver. Uses DateTime.Now and:

  • Returns early if less than kUpdateRate (5s) since m_LastRichUpdate.
  • If game mode is Game or Editor, cycles m_StateIndex every kStateCycleInterval (10s).
  • Calls UpdateGameRichPresence() when in gameplay, or UpdateEditorRichPresence() when in editor.
  • Wraps logic in try/catch and logs exceptions via COSystemBase.baseLog.Warn.

  • private int GetAverageHealth()
    Returns the city's average health by reading the Population component on the city entity from the default World if available; otherwise returns 0.

  • private string GetMilestoneNumber(int i)
    Converts an integer milestone index to a Roman numeral string using kRomanNumbers (returns null when i == 0).

  • private string GetMilestoneKey(int i)
    Returns a key name for milestone assets/icon lookup in the form "milestone{i}". Used as the large image key when setting Discord presence.

  • private int GetAchievedMilestone()
    Reads the singleton MilestoneLevel via m_MilestoneLevelQuery; returns 0 if the query is empty, otherwise returns m_AchievedMilestone.

  • private int GetAverageHappiness()
    Returns the city's average happiness by reading the Population component on the city entity; returns 0 if unavailable.

  • private int GetPopulationCount()
    Returns the city population from the Population component on the city entity, or 0 if it is not available.

  • private string GetWeatherIconKey()
    Determines a combined weather + lighting key string used as a small image key for presence. Logic:

  • Base key is climate classification name lowercased.
  • If precipitation > 0.3, prefers "rain" or "snow".
  • If stormy classification + precipitation > 0.9 selects "hail" or "stormy".
  • Appends lighting state (day/night) determined via GetLightingState() to generate a final key like "rainnight" or "clearday". Returns that string for use by DiscordRichPresence.

  • private LightingSystem.State GetLightingState()
    Maps TimeUISystem.GetLightingState() to a simplified day/night mapping: treats Sunset and Dusk as Night, Dawn and Sunrise as Day, otherwise returns the lighting state unchanged.

  • private void RegisterKeys()
    Registers a set of rich-presence keys with PlatformManager.instance.RegisterRichPresenceKey. Each key is associated with a lambda that returns a short string describing the particular stat or action, e.g.:

  • "#StatusInGame_Building" => "Building {cityName}"
  • "#StatusInGame_Population" => "Population: {count}"
  • "#StatusInGame_Money" => "Money: {amount}¢"
  • "#StatusInGame_Happiness" => "Happiness: {emoji}"
  • "#StatusInGame_Health" => "Health: {emoji}"
  • "#StatusInGame_Milestone" => "Milestone {RomanNumeral}" These keys are used as the smaller/secondary text lines cycled in the presence.

  • private string GetActionKey()
    Choose an action key describing the player's current activity:

  • If Photo Mode is enabled -> "#StatusInGame_CapturingMemories"
  • If an infoview is active -> "#StatusInGame_Inspecting"
  • If active tool is a BulldozeToolSystem -> "#StatusInGame_Bulldozing"
  • Otherwise -> "#StatusInGame_Building"

  • private void UpdateEditorRichPresence()
    Sets a simple editor presence via discordRichPresence.SetRichPresence with title "In Editor" and details "Authoring UGC..." The StartTime and image keys reflect editor mode.

  • private void UpdateGameRichPresence()
    Builds and sets the in-game rich presence:

  • Computes achieved milestone and Roman numeral.
  • Selects which secondary state key from m_DiscordState to show based on m_StateIndex (cycles every 10s). If achievedMilestone == 0, the milestone key is excluded from the cycle.
  • Calls discordRichPresence.SetRichPresence(actionKey, stateKey, m_StartTime, milestoneImageKey, milestoneLabel, weatherIconKey, temperatureString)
  • Supplies a weather icon key and temperature string from climate data (rounded Celsius).

Usage Example

// Example: how the system sends an update to DiscordRichPresence.
// The system does this internally; this snippet mirrors the call used.
var discord = PlatformManager.instance.GetPSI<DiscordRichPresence>("Discord");
DateTime start = DateTime.Now;
string actionKey = "#StatusInGame_Building"; // e.g. from GetActionKey()
string stateKey = "#StatusInGame_Population"; // e.g. one of the registered keys
string milestoneImageKey = "milestoneIII";
string milestoneLabel = "Milestone III";
string weatherIconKey = "rainnight";
string temp = "12°C";

discord.SetRichPresence(actionKey, stateKey, start, milestoneImageKey, milestoneLabel, weatherIconKey, temp);

Notes and implementation remarks: - The system is intended to run inside the ECS/World; it resolves dependent systems in OnCreate. - All presence updates are throttled to at most once every 5 seconds. - The displayed secondary stat cycles every 10 seconds via m_StateIndex. - Several emoji tables and alternate emoji arrays are present; current code uses only the primary kHappinessEmoji and kHealthEmoji arrays. - Exceptions during OnUpdate are caught and logged via COSystemBase.baseLog.Warn to avoid crashing the update loop.