Routing Component
UCrimsonInventoryRoutingComponent is a server-authoritative router that owns every Server_ RPC the plugin needs. Attach it to your PlayerController; your UI calls the component's BlueprintCallable methods directly. There are no Server_ RPCs to write in your game project.
It also owns: per-player container storage (used by PerPlayer chest scope), the access-control virtuals, the party / dungeon-instance scope key resolvers, and a data-in / data-out save struct for personal containers.
Setup
// MyPlayerController.h#include "Routing/CrimsonInventoryRoutingComponent.h"UPROPERTY(VisibleAnywhere, BlueprintReadOnly)TObjectPtr<UCrimsonInventoryRoutingComponent> InventoryRouting;// MyPlayerController.cpp ctorInventoryRouting = CreateDefaultSubobject<UCrimsonInventoryRoutingComponent>(TEXT("InventoryRouting"));
Client-callable API
Every method here is safe to call from a client widget. The component forwards to a Server_ RPC internally, runs the access check, and dispatches to the authoritative manager component.
| Method | What it does |
|---|---|
MoveItem(Target, EntryId, T) | Move / swap / merge an entry within a target inventory |
TransferItem(From, To, EntryId, Count) | Atomic transfer between two inventories (player <-> chest) |
SplitStack(Target, EntryId, Amount) | Split off a sub-stack |
DropItem(Target, EntryId, Count, Location) | Drop to the world (spawns pickup actor if configured) |
LockEntry(Target, EntryId, bLocked) | Toggle the locked flag on an entry |
OpenContainer(Container) | Resolve scope + access, fire OnContainerOpened delegate on the client with the resolved UCrimsonInventoryManagerComponent* |
TakePickupSlot(Pickup, SlotType, Index, Qty, Destination) | Server-routed take of one slot from a ACrimsonPickupActorBase into Destination |
TakePickupAll(Pickup, Destination) | Server-routed take of every slot from a pickup; actor self-destroys when emptied |
Open container flow (server -> client)
When OpenContainer(Chest) is called, the routing component:
1. Sends Server_RequestOpenContainer(Chest) to the server.
2. Server runs CanOpenContainer(Chest) - if false, sends a failure result back.
3. Resolves the inventory based on Chest->GetScope():
- Shared -> returns the chest's own component
- PerPlayer -> lazy-creates a private component on the requesting PC's routing component, keyed by ContainerTag
- PerParty / PerDungeonInstance -> asks the instance subsystem for an ACrimsonInstancedInventoryActor keyed by (ContainerTag, ResolvePartyKey() | ResolveDungeonInstanceKey()), adds the player to its viewer set
4. Sends Client_OnContainerOpened(Result) back to the requesting client.
5. Client's OnContainerOpened delegate fires with the resolved inventory pointer for UI binding.
// On the client (e.g. inventory widget):InventoryRouting->OnContainerOpened.AddDynamic(this, &UMyInventoryWidget::HandleContainerOpened);InventoryRouting->OpenContainer(InteractingChestActor);void UMyInventoryWidget::HandleContainerOpened(const FCrimsonContainerOpenResult& Result){if (Result.bSuccess){BindContainerUI(Result.Inventory, Result.InstanceKey);}else{ShowError(Result.FailureReason);}}
Access control virtuals (override in BP or C++)
UFUNCTION(BlueprintNativeEvent, Category = "Routing|Access")bool CanAccessInventory(UCrimsonInventoryManagerComponent* Inventory) const;// default: owns own PC / PS / Pawn inventories + chest actors allowed.// Override for stricter rules (e.g. only the chest you're currently interacting with).UFUNCTION(BlueprintNativeEvent, Category = "Routing|Access")bool CanOpenContainer(ACrimsonContainerActorBase* Container) const;// default: allow. Override for proximity, faction, ownership, dungeon-membership checks.
Scope key resolvers (override for instanced scopes)
UFUNCTION(BlueprintNativeEvent, Category = "Routing|Resolvers")FName ResolvePartyKey() const;// default: NAME_None. Override to return the player's stable party id// (any value - game-defined hash, UUID, party name). NAME_None aborts a// PerParty open with a clear error message.UFUNCTION(BlueprintNativeEvent, Category = "Routing|Resolvers")FName ResolveDungeonInstanceKey() const;// default: NAME_None. Override for PerDungeonInstance chests.
Personal containers (PerPlayer storage)
For PerPlayer chests, the routing component lazy-creates a UCrimsonInventoryManagerComponent as a dynamic subcomponent on the PC, keyed by the chest's ContainerTag. Because the routing component lives on the PC, these components replicate only to the owning client - privacy is automatic.
// Server: explicit lookup / creationUCrimsonInventoryManagerComponent* GetOrCreatePersonalContainer(FGameplayTag ContainerTag, UCrimsonInventoryLayout* Layout);UCrimsonInventoryManagerComponent* FindPersonalContainer(FGameplayTag ContainerTag) const;
PerPlayer / PerParty / PerDungeonInstance scopes, the chest actor's OnScopedInstanceCreated(NewInventory, Key) BP event fires once per scoped instance. Override it to roll loot, populate starter contents, or apply per-player customizations.Save / Load - data-in / data-out
Snapshot the routing component's personal containers and hand the struct to your save system. No CrimsonSaveSystem dependency. Same flow works on dedicated server (server snapshots before disconnect), P2P / listen server (host snapshots), and standalone (client snapshots).
// Server: snapshot all personal containersFCrimsonInventoryRoutingSaveData Save = Routing->GetSaveData();MyGameSave->RoutingBlob = /* serialize Save */;// Server: restore (typically after a re-login or load)Routing->ApplySaveData(MyGameSave->RoutingBlob);
USTRUCT(BlueprintType)struct FCrimsonRoutingPersonalContainerSave{FGameplayTag ContainerTag;FCrimsonInventorySaveData Inventory; // reuses the per-component save format};USTRUCT(BlueprintType)struct FCrimsonInventoryRoutingSaveData{TArray<FCrimsonRoutingPersonalContainerSave> PersonalContainers;};
Server_ RPCs run CanAccessInventory / CanOpenContainer before any mutation. If you override those virtuals, keep the checks strict - never trust client-provided EntryIds without re-resolving them against the authoritative inventory.