Concept: Multiplayer & Player Scoping
The save system is built for a dedicated server plus multiple clients. Saving is a server responsibility, save data is split into global and per-player fragments, and restored state reaches clients only through normal replication. This page is the canonical reference for all three rules.
Authority: server / standalone only
Server / Standalone Only
All save operations (
RequestSaveProgress, RequestAutoSave, RequestQuickSave, RequestNewGameSave, SaveGameToActiveSlotSynchronous) are guarded by authority checks. Calls received on NM_Client are rejected. Never trigger saves from client-side code.SaveGame is not Replicated
SaveGame and Replicated are independent flags
SaveGame controls persistence; Replicated controls networking. Restored values live on the server only. A client sees a restored value only if the property is also marked `Replicated` (Details -> Replication -> Replicated, or RepNotify if you apply it in an OnRep). Restore runs server-side and forces a net update, but a SaveGame-only property never reaches clients. Symptom: correct on the server / listen-host, stale on a client. Mark any restored gameplay state Replicated.Global vs player-scoped fragments
| Type | GetPlayerSaveKey() returns | File name in slot | Example use |
|---|---|---|---|
| Global | Empty string (default) | Inventory.sav | World state, game progress, difficulty settings |
| Player-scoped | UCrimsonSavePlayerIdentity::ResolvePlayerSaveKey(PC) | Inventory__PlayerKey.sav | Character inventory, player GAS attributes, quest log per character |
Resolving a player key
UCrimsonSavePlayerIdentity resolves a stable, unique save key for a player. Call it from GetPlayerSaveKey_Implementation() on any player-owned system. The key source depends on context:
| Context | Key source |
|---|---|
| Multiplayer with online subsystem | Serialized UniqueNetId from the player state - stable across reconnects and server restarts. |
| PIE (editor) | Named key from DeveloperSettings.PIEPlayerKeys[PIE instance index], or PIE_<InstanceID> if none is configured. Ensures PIE saves persist across editor restarts. |
| Offline / standalone | Deterministic key derived from the local player's controller ID. |
- On the player-owned system (component or actor), override the Get Player Save Key interface event.
- Get the owning Player Controller and feed it into Resolve Player Save Key - a pure node on
Crimson Save Player Identity. - Return that string. Returning empty instead makes the fragment global (shared by all players).
Dedicated server workflow
| Event | What happens |
|---|---|
| Server startup | Manager loads global fragments from DedicatedServerDefaultSlot (or the slot passed via -CrimsonSaveSlot=N). Player-scoped fragments are skipped. |
| Player PostLogin | Call NotifyPlayerLoggedIn(PC). Manager resolves the player key. If bAutoLoadOnPostLogin is true, loads fragments matching that key from the active slot. |
| Player Logout | Call NotifyPlayerLoggedOut(PC) before Super::Logout. If bAutoSaveOnLogout is true, synchronously flushes the player's fragments before their objects are destroyed. |
| Server shutdown | Save the active slot from AGameMode::EndPlay (before Super::EndPlay) to flush global fragments. |
GameMode wiring (canonical example)
Forward login, logout, and shutdown to the manager. Ordering rule: notify / save before the matching Super:: call, so the controller and player objects are still valid.
Or use CrimsonCore
ACrimsonGameMode already implements all three hooks (login, logout, save-on-exit) in the correct order. Use it and you write none of this - see How-To: Turnkey Save Setup with CrimsonCore.