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

TypeGetPlayerSaveKey() returnsFile name in slotExample use
GlobalEmpty string (default)Inventory.savWorld state, game progress, difficulty settings
Player-scopedUCrimsonSavePlayerIdentity::ResolvePlayerSaveKey(PC)Inventory__PlayerKey.savCharacter 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:

ContextKey source
Multiplayer with online subsystemSerialized 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 / standaloneDeterministic key derived from the local player's controller ID.
  1. On the player-owned system (component or actor), override the Get Player Save Key interface event.
  2. Get the owning Player Controller and feed it into Resolve Player Save Key - a pure node on Crimson Save Player Identity.
  3. Return that string. Returning empty instead makes the fragment global (shared by all players).

Dedicated server workflow

EventWhat happens
Server startupManager loads global fragments from DedicatedServerDefaultSlot (or the slot passed via -CrimsonSaveSlot=N). Player-scoped fragments are skipped.
Player PostLoginCall NotifyPlayerLoggedIn(PC). Manager resolves the player key. If bAutoLoadOnPostLogin is true, loads fragments matching that key from the active slot.
Player LogoutCall NotifyPlayerLoggedOut(PC) before Super::Logout. If bAutoSaveOnLogout is true, synchronously flushes the player's fragments before their objects are destroyed.
Server shutdownSave 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.

In your Game Mode: **Event On Post Login** -> **Notify Player Logged In**, and **Event On Logout** -> Cast to Player Controller -> **Notify Player Logged Out**. For save-on-shutdown, call **Save Game To Active Slot Synchronous** from **Event EndPlay** - see **How-To: Configure Auto-Saving**.
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.