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.