Pickup Actor & Loot Window

Requires every Crimson plugin
CrimsonCore is free to download, but compiles only with all Crimson plugins installed. The UI-free bases (ACrimsonPickupActorBase, ACrimsonContainerActorBase, UCrimsonInventoryRoutingComponent) live in CrimsonInventory; the interaction contract (ICrimsonInteractableTarget) lives in CrimsonInteraction; ACrimsonInteractableContainerActor (CrimsonCore) joins them. This page documents the drop-in actors and screens CrimsonCore builds on top of them.

Two world actors bring items into the inventory UI. ACrimsonCorePickupActor is a dropped item or loot bag: one item is taken on interact, several open the loot window. ACrimsonCoreContainerActor is a chest or stash: interact opens the two-pane container screen and asks the routing component for a scoped session (shared, per player, per party, per map instance).

What ships

TypeBase (plugin)Shipped assetRole
ACrimsonCorePickupActorACrimsonPickupActorBase (CrimsonInventory) + ICrimsonInteractableTarget (CrimsonInteraction) + ICrimsonSaveableActor (CrimsonSaveSystem)/CrimsonCore/Inventory/B_PickupActor_Base, /CrimsonCore/Inventory/Actors/B_LootBagWorld pickup with an InteractionSphere, interaction options, secondary actions, and the loot-window push.
UCrimsonPickupAction + _Examine, _OpenBag, _PickUp, _TakeAllUObject (EditInlineNew, DefaultToInstanced)authored inline on the pickupExtra interaction options (Label, SubText, HandlerAbility, IsAvailable(Pickup)) that show in the interaction context menu.
UCrimsonCoreLootWindowUCrimsonCoreInventoryScreenBase/CrimsonCore/UserInterface/Inventory/Screens/W_Loot_WindowLoot list plus an optional player pane. BindToPickup(SourceActor, Destination) is called by the pickup by name.
ACrimsonCoreContainerActorACrimsonContainerActorBase (CrimsonInventory)/CrimsonCore/Inventory/Actors/B_Chest_Shared, B_Stash_Personal (subclasses: B_LootChest, B_OwnedStash, B_ItemVendingMachine)Container with a Mesh and an InteractionSphere; pushes ContainerScreenClass and calls Routing->OpenContainer.
UCrimsonCoreContainerScreenUCrimsonCoreInventoryScreenBase/CrimsonCore/UserInterface/Inventory/Screens/W_Container_ScreenPlayer pane + container pane; binds from the routing OnContainerOpened result and closes on OnContainerClosed.

ACrimsonCorePickupActor

PropertyCategoryMeaning
InteractionSphereCrimson|PickupUSphereComponent using the Interactable_OverlapDynamic profile - the range the interaction scan sees.
BagScreenClassCrimson|PickupActivatable widget pushed for a multi-item pickup. Set it to W_Loot_Window (or your UCrimsonCoreLootWindow subclass). Unset = multi-item interact does nothing.
BagScreenLayerTagCrimson|PickupLayer the loot window is pushed onto (UI.Layer.*).
SingleItemOptionTextCrimson|Pickup|DisplayInteraction option text for a single item; {0} = item name, {1} = stack count.
BagOptionTextCrimson|Pickup|DisplayInteraction option text when IsMultiItem(); {0} = slot count.
PickupHandlerAbilityCrimson|PickupAbility granted to the instigator to run the interaction; defaults to UCrimsonCoreGameplayAbility_InteractionHandler.
SecondaryActionsCrimson|PickupInstanced UCrimsonPickupAction list. Shipped actions: Examine, Open Bag, Pick Up, Take All. They appear as extra options in the interaction context menu, never on the quick press.
ConditionOption textOn success
One item (instance, template, or currency)SingleItemOptionTextTakeAllItems -> Routing->TakePickupAll(this, Destination): the server moves everything into the instigator's inventory and the actor destroys itself when empty.
IsMultiItem()BagOptionTextOpenBagScreen pushes BagScreenClass on BagScreenLayerTag for the local controller, then calls BindToPickup(SourceActor, Destination) on the new widget by name.

Both OpenBagScreen and TakeAllItems are BlueprintCallable, resolve the instigator's UCrimsonInventoryManagerComponent and UCrimsonInventoryRoutingComponent themselves, and gate on IsLocalController() - the interaction ability runs on the owning client, so the UI appears on the right machine and the take goes through the routing Server RPC.

Reference implementation
Open B_LootBag (/CrimsonCore/Inventory/Actors/), a child of B_PickupActor_Base (/CrimsonCore/Inventory/). Study the Crimson|Pickup category on the parent and the BagMeshOverride / PickupMeshComponent from ACrimsonPickupActorBase. Duplicate B_LootBag for your own loot bags and pre-fill its pickup inventory for placed treasure.

Add a pickup to your game

Create a Blueprint child of `B_PickupActor_Base` (or of ACrimsonCorePickupActor directly). In Class Defaults: set BagScreenClass = W_Loot_Window, BagScreenLayerTag = UI.Layer.Menu, add the secondary actions you want (+ on SecondaryActions -> Pickup Action - Examine, ...). Drop items at runtime with the CrimsonInventory drop path (the Drop verb, or Drop Entries As Bag on the inventory manager) and pass your class as the pickup class.

Verify
Interact with a single Apple pickup: it goes straight into the bag and a toast shows. Interact with B_LootBag: W_Loot_Window opens, rows list the contents, A takes one row, Y takes all, and the window closes when the bag is empty.

UCrimsonCoreLootWindow

BindToPickup(SourceActor, Destination) creates a UCrimsonCoreLootViewModel bound to the ACrimsonPickupActorBase and the destination inventory. Activating a row calls RequestTake(Entry); the Take All verb calls RequestTakeAll(); both go through Routing->TakePickupSlot / TakePickupAll. bCloseWhenEmpty (default on) deactivates the window when bIsEmpty becomes true or the pickup is destroyed. Optional widgets: Text_Title, ListView_Loot (a UCrimsonListView whose entry class is a UCrimsonCoreLootEntryWidget), and Panel_Player (a UCrimsonCoreInventoryPanelWidget so the player can make room while looting).

Reference implementation
Open the shipped W_Loot_Window (/CrimsonCore/UserInterface/Inventory/Screens/) - a complete UCrimsonCoreLootWindow with all three optional widgets bound, so it shows exactly what to name yours: Text_Title, ListView_Loot (its entry class is W_Loot_Entry, /CrimsonCore/UserInterface/Inventory/Widgets/), and Panel_Player. B_LootBag (/CrimsonCore/Inventory/Actors/) already sets it as BagScreenClass, so placing the bag and interacting with it is the fastest way to see the pair working. Duplicate the widget as your starting point and keep the bound-widget names.
Keep the BindToPickup signature
ACrimsonCorePickupActor::OpenBagScreen finds BindToPickup by name with (AActor* SourceActor, UCrimsonInventoryManagerComponent* Destination). Subclass the window freely, but do not rename or re-sign that function.

ACrimsonCoreContainerActor

Inherits the CrimsonInventory base: ContainerTag, Scope (ECrimsonContainerScope::Shared / PerPlayer / PerParty / PerMapInstance), DefaultLayout, DefaultIntConstraints, DefaultFloatConstraints, InteractionText, and OpenContainerUIAbility (defaults to UCrimsonCoreGameplayAbility_InteractionHandler). CrimsonCore adds:

PropertyCategoryMeaning
MeshCrimson|ContainerThe chest's UStaticMeshComponent (GetMesh()).
InteractionSphereCrimson|ContainerInteraction range, like the pickup.
ContainerScreenClassCrimson|Container|UIWidget pushed on interact; the shipped chests use W_Container_Screen.
ContainerScreenLayerTagCrimson|Container|UILayer for the screen (UI.Layer.*).
ContainerTitleCrimson|Container|UITitle of the container pane. Empty = InteractionText.

OpenContainerScreen(Instigator) runs on the local controller: it pushes ContainerScreenClass, calls SetContainerTitle on the new screen, and once the screen is active calls Routing->OpenContainer(this). The routing component resolves the scope, runs the access check, creates the scoped instance if needed, and fires OnContainerOpened with an FCrimsonContainerOpenResult; the screen is already subscribed and binds its right pane from it (BindToOpenResult). Closing the screen calls CloseContainer (bCloseContainerOnDeactivate), and a session closed from elsewhere fires OnContainerClosed, which closes the screen.

Reference implementation
Open the shipped W_Container_Screen (/CrimsonCore/UserInterface/Inventory/Screens/) - a complete UCrimsonCoreContainerScreen. Its two bound panes, Panel_Player and Panel_Container (both UCrimsonCoreInventoryPanelWidget), are each registered as a panel root, which is what lets the Next / Previous panel verbs move focus - and a carried item - between them. Duplicate it as your starting point and keep the bound-widget names.

Then open B_Chest_Shared and B_Stash_Personal (/CrimsonCore/Inventory/Actors/); both already set it as their ContainerScreenClass, so placing one and interacting is the fastest way to see the pair live. Both also share DefaultLayout = DA_CrimsonInventoryLayout_FlatSlot and DefaultIntConstraints = [Crimson.Inventory.Constraint.Slots = 12]; they differ only in Scope. Duplicate one and change Scope and ContainerTitle. B_LootChest (ACrimsonCoreLootChest) is the one-time, take-only variant: authored InitialContents, one slot per remaining item, self-destroys once opened and emptied, and the world save keeps its remaining contents and opened state. B_OwnedStash (ACrimsonCoreOwnedStash) is the player-owned variant: spawned at runtime with SpawnOwnedStash (or the SpawnOwnedStash cheat) or claimed by its first opener, with an Access mode of Private, Party or Public that the owner sets from the interaction prompt's more-options menu (an Access rotator row, UCrimsonCoreContextAction_StashAccess); a refused player sees No Access (Owner: <Name>) on the prompt through an interaction condition (UCrimsonInteractionCondition_StashAccess on the container's OpenConditions); the server saves it with the world like any Shared container.

Add a container to your game

Duplicate `B_Chest_Shared` (/CrimsonCore/Inventory/Actors/), set Scope, ContainerTitle, and the constraints, then place it in the level. Nothing else is required: the player's routing component (from CrimsonGameFeatureAction_AddInventory) and W_Container_Screen handle the session.

Verify (Play As Client)
Two players, Play As Client. Both open B_Chest_Shared: items placed by one appear for the other. Both open B_Stash_Personal: each sees only their own contents. Close the screen on one client; the other's stays open.

Saving dropped pickups

ACrimsonCorePickupActor implements ICrimsonSaveableActor (CrimsonSaveSystem), so a dropped item survives a save and load with its contents intact - and a collected one stays collected.

HookWhat it does
GatherActorSaveDataReturns GetPickupSaveData() (CrimsonInventory) as the actor's custom payload.
RestoreFromSaveDataFeeds it back through ApplyPickupSaveData, which rebuilds the item instances, re-registers them as replicated subobjects and re-resolves the mesh.
EndPlayReports an explicit Destroy() - the pickup being taken - to UCrimsonSaveWorldManagerSubsystem, which drops the entry so it never respawns. Streaming out or level travel does not count as taken.
Contents are custom data, not a SaveGame property
Marking PickupInventory as SaveGame does not work: it holds live item-instance POINTERS, and a saved pointer to a runtime subobject resolves to nothing on load. The pickup would return EMPTY - no mesh, and no interaction prompt either, since a pickup only offers options while it holds something. That is why the contents travel as a flattened FCrimsonInventoryPickupSaveData. See CrimsonInventory -> How-To: Drop and Pick Up Items.
Verify
Drop a partial stack, save, reload: the pickup is back with the same item and count, and the interaction prompt works. Take it, save, reload: it is gone for good.

Multiplayer

  • Server: pickup contents replicate from ACrimsonPickupActorBase; takes and container mutations run through UCrimsonInventoryRoutingComponent Server RPCs and are validated there. Only the server spawns pickups.
  • Owning client: OpenBagScreen, OpenContainerScreen, and every screen. Both actors gate on IsLocalController().
  • All clients: the pickup mesh (BagMeshOverride when multi-item) and the chest mesh.

See also

  • Inventory UI
  • Inventory Showcases
  • Interaction Integration
  • Context Menu
  • CrimsonInventory -> Overview
  • CrimsonSaveSystem -> Quick Start: World Actors