How-To: Drop and Pick Up Items

Goal: drop an entry into the world as a pickup actor, author loot by hand, and pick it up slot-by-slot or all at once with partial-success feedback.

Prerequisites
How-To: Author an Item Definition (a PickupActorClass on the Visuals fragment) and How-To: Route UI Requests.

1. Create a pickup actor class

ACrimsonPickupActorBase replicates an FCrimsonInventoryPickup (Instances with live item objects, Templates as definition + count, Currencies) and resolves its mesh from the dropped item's PickupMesh, or from BagMeshOverride when it holds more than one slot. It destroys itself when emptied.

Blueprint Class -> `CrimsonPickupActorBase`, name it BP_Pickup. Assign Bag Mesh Override (used for multi-item drops). Override On Pickup Initialized for a spawn effect. Then on each item's Item Visuals fragment set Pickup Actor Class = BP_Pickup and Pickup Mesh.

Verify
Drop an item from Dev Tools -> Items: a BP_Pickup appears at the drop offset showing the item's mesh.
Set both Visuals fields, and give the mesh simple collision
A drop reads UCrimsonInventoryItemFragment_Visuals on the item definition. With PickupActorClass unset nothing spawns at all and the entry is still removed - the drop looks like it deleted the item. With PickupMesh unset the actor spawns invisible.

If your pickup actor simulates physics, that mesh also needs simple collision: a body carrying only complex (per-triangle) collision has nothing to rest on and falls through the world within a few frames, which looks identical to nothing having spawned. Check StaticMesh -> Collision -> Add Simple Collision when a dropped item vanishes.

2. Drop from an inventory (server)

CallResult
DropItemByEntry(EntryId, Quantity, Location)One pickup with one FCrimsonPickupInstance. A bag drops with its Contents subtree.
DropItemsByDefinition(ItemDef, Num, Location)Consumes by definition across entries, then drops.
DropEntriesAsBag(EntryIds, Location, BagActorClass)One pickup holding several entries (uses BagMeshOverride).
DropCurrency(CurrencyType, Amount, Location)Broadcasts Crimson.Inventory.Message.CurrencyDropped; spawning is up to the listener.

Every drop also broadcasts Crimson.Inventory.Message.ItemDropped (FCrimsonInventory_Message_ItemDropped: DroppedInstance, Quantity, DropLocation) for VFX or analytics. From the client use the routing component's DropItem, which lands in DropItemByEntry.

Client: Find Routing Component -> Drop Item (Target, Entry Id, Count, Drop Location = pawn location + forward 100). Server: Drop Item By Entry* directly.

Verify
Play As Client and drop 3 of a 5-stack: the entry shows x2, a pickup spawns, and OperationResult reports Operation = Drop, bSuccess = true.

3. Author loot by hand

Place BP_Pickup in the level and fill Pickup Inventory -> Templates (Item Def, Stack Count) and Currencies (Currency Data Table row, Stack Count). At runtime, Set Crimson Pickup Inventory On Actor (server) replaces the contents; Merge Pickup Inventories combines two.

Verify
The placed pickup shows BagMeshOverride when it has more than one slot and the single item's mesh otherwise.

4. Take from a pickup

TakeSlot(SlotType, SlotIndex, RequestedQuantity, Destination) and TakeAll(Destination) run on the server and return FCrimsonPickupTakeResult (bAllTaken, QuantityTaken, QuantityRemaining, FailureReason). A full inventory yields a partial take; what remains stays in the pickup. Template rows whose item carries UCrimsonInventoryItemFragment_GrantCurrency convert to currency on add instead of occupying a slot.

Client: Request Pickup (Player Controller, Pickup Actor) for take-all, or Find Routing Component -> Take Pickup Slot (Slot Type = Template, Slot Index, Requested Quantity, Destination) for one slot. Bind On Pickup Contents Changed on the actor to refresh a loot window.

Verify
Fill the inventory to one free slot and take-all from a two-slot pickup: OperationResult shows TakePickupAll with bSuccess = false and a reason, one slot moved, and the pickup remains with one slot.

5. Save and restore a dropped pickup (optional)

GetPickupSaveData flattens the carried contents into an FCrimsonInventoryPickupSaveData, and ApplyPickupSaveData rebuilds them. Data in, data out: this plugin never talks to a save system, so hand the struct to whichever one your project uses.

Why not just mark it SaveGame
FCrimsonInventoryPickup holds live item-instance pointers. A serialized pointer to a runtime subobject resolves to nothing on load, so the pickup comes back EMPTY - no mesh, and no interaction prompt either, because a pickup only offers interaction options while it holds something. The flattened struct exists for exactly that reason.

The same trap applies one level down: a save archive skips untagged properties recursively, so any struct you nest inside the snapshot needs SaveGame on its own members or it serializes as empty.

Get Pickup Save Data returns the struct and Apply Pickup Save Data takes it back, both on the pickup actor. Apply is authority-only - call it on the server after your save system has respawned the actor.

Verify
Drop a partial stack, capture it, then apply the snapshot to a freshly spawned pickup: same item, same count, same mesh, and nested bag rows keep their placement and lock flags. The rebuilt instances are new objects owned by the new actor, not shared with the original.

See also

  • How-To: Nest Bags
  • How-To: Route UI Requests
  • How-To: Manage Currency