World container actor
ACrimsonContainerActorBase is a replicating actor representing a chest, lootbox, vendor stash, or dungeon cache. Its Scope field selects who sees the contents.
ECrimsonContainerScope
| Scope | Storage | Replication | Use case |
|---|---|---|---|
Shared | Component on the chest actor | FastArray live to all relevant clients | Co-op shared loot, guild chests viewable by anyone in range |
PerPlayer | Dynamic component on the requesting player's PlayerController routing component | Owner-only - only that player sees it | Personal stash, instanced single-player loot, private vendor inventories |
PerParty | ACrimsonInstancedInventoryActor spawned by UCrimsonInventoryInstanceSubsystem | Restricted by IsNetRelevantFor to viewers in AllowedViewers | Party-shared dungeon loot, group expedition chests |
PerDungeonInstance | Same actor; keyed by dungeon-instance id | Same - viewer set is everyone in the instance | Per-run raid loot, dungeon-scoped trade caches |
-
ResolvePartyKey() -> FName - returns the player's stable party id (any value, including a hash). Used for PerParty containers.-
ResolveDungeonInstanceKey() -> FName - returns the current dungeon/instance id. Used for PerDungeonInstance.Return
NAME_None to opt out (the container open will fail with a clear error message rather than mis-route).ACrimsonContainerActorBase
UCLASS(Abstract, Blueprintable)class ACrimsonContainerActorBase : public AActor, public ICrimsonInteractableTarget{/** Stable identifier; required for non-Shared scopes (keys the per-scope instance). */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")FGameplayTag ContainerTag;UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")ECrimsonContainerScope Scope = ECrimsonContainerScope::Shared;/** Assigned to the Shared inventory at BeginPlay, or to scoped instances on first creation. */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")TObjectPtr<UCrimsonInventoryLayout> DefaultLayout;/** Only used in Shared scope - null at runtime in other scopes. */UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Container")TObjectPtr<UCrimsonInventoryManagerComponent> InventoryComponent;/** BP hook fired once when a scoped instance is first created (PerPlayer / PerParty / PerDungeonInstance).Override to roll loot or fill defaults. */UFUNCTION(BlueprintImplementableEvent)void OnScopedInstanceCreated(UCrimsonInventoryManagerComponent* NewInventory,const FCrimsonContainerInstanceKey& Key);UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container|Interaction")TSubclassOf<UGameplayAbility> OpenContainerUIAbility;UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container|Interaction")FText InteractionText;};
ACrimsonInstancedInventoryActor (PerParty / PerDungeonInstance backend)
Hidden server-spawned actor used as the storage for PerParty and PerDungeonInstance scopes. Wraps a UCrimsonInventoryManagerComponent and restricts replication to its AllowedViewers set via IsNetRelevantFor. UCrimsonInventoryInstanceSubsystem owns spawn/despawn - game code never deals with this actor directly; it's an implementation detail of the scope system.
// Server-only API (called by UCrimsonInventoryInstanceSubsystem):void InitializeInstance(const FCrimsonContainerInstanceKey& Key, UCrimsonInventoryLayout* Layout);void AddViewer(APlayerController* Viewer); // grants visibilityvoid RemoveViewer(APlayerController* Viewer); // revokes (close UI / leave party)bool HasViewer(APlayerController* Viewer) const;int32 GetViewerCount() const;// IsNetRelevantFor returns true iff the viewer's PC is in AllowedViewers.
UCrimsonInventoryInstanceSubsystem
UWorldSubsystem that exists only on server / standalone. Tracks all PerParty and PerDungeonInstance instance actors keyed by FCrimsonContainerInstanceKey { ContainerTag, ScopeKey }. Created lazily on first open, torn down with the world. Game code rarely calls this directly - the routing component does.
ACrimsonInstancedInventoryActor* GetOrCreateInstance(const FCrimsonContainerInstanceKey& Key, UCrimsonInventoryLayout* Layout,APlayerController* Viewer, bool& bWasCreated);ACrimsonInstancedInventoryActor* FindInstance(const FCrimsonContainerInstanceKey& Key) const;void RemoveViewer(const FCrimsonContainerInstanceKey& Key, APlayerController* Viewer);void RemoveViewerFromAll(APlayerController* Viewer); // call on disconnectvoid DestroyInstance(const FCrimsonContainerInstanceKey& Key); // party disbands, dungeon endsvoid GetAllInstanceKeys(TArray<FCrimsonContainerInstanceKey>& OutKeys) const;
ACrimsonContainerActorBase with Scope = Shared.Private personal stash (each player has their own) ->
Scope = PerPlayer. Storage on the PC routing component.Party-shared dungeon loot ->
Scope = PerParty + override ResolvePartyKey().Per-instance raid cache ->
Scope = PerDungeonInstance + override ResolveDungeonInstanceKey().Persistent virtual stash that lives outside the world (bank, mailbox) ->
UCrimsonContainerSubsystem (next section).Container subsystem (purely virtual)
UCrimsonContainerSubsystem manages named persistent inventories keyed by FGameplayTag. It is not replicated - it exists independently on the server and on each client. Use it for banks, guild vaults, and any stash that doesn't have a world actor.
Container accessibility scopes
| Scope | Default tag | When accessible |
|---|---|---|
| Global | TAG_Crimson_Container_Global | Always |
| Level | TAG_Crimson_Container_Local_Level | When the container tag contains the current level name |
| Zone | TAG_Crimson_Container_Local_Zone | When SetCurrentZone has been called with a matching tag |
UCrimsonContainerSubsystem* CS = GetGameInstance()->GetSubsystem<UCrimsonContainerSubsystem>();// Server onlyCS->RegisterContainer(TAG_Container_GuildBank, 0 /*unlimited*/);FCrimsonInventoryPickup* Data = CS->GetContainerData(TAG_Container_GuildBank);FCrimsonInventoryPickup Copy = CS->GetContainerDataBP(TAG_Container_GuildBank);// Move uses the player entry's stable EntryId (server-only)CS->MoveItemToContainer(PlayerInventory, SourceEntryId,TAG_Container_GuildBank, /*StackCount*/ 1);CS->MoveItemFromContainer(PlayerInventory, TAG_Container_GuildBank,ItemInstance, /*StackCount*/ 1);// Zone scoping (call before opening a crafting station or zone chest)CS->SetCurrentZone(TAG_Container_Local_Zone_Blacksmith);// ... interaction ...CS->ClearCurrentZone();
Cross-inventory consumption
TArray<FCrimsonItemConsumptionRequest> Ingredients = {{ UIronOreDefinition::StaticClass(), 3 },{ UWoodDefinition::StaticClass(), 2 }};bool bSuccess = CS->ConsumeItemsWithPriority(PlayerInventory,TAG_Crimson_Inventory_Player,Ingredients,{ TAG_Crimson_Inventory_Player, TAG_Container_Local_Zone_Blacksmith });