Pickup System
World pickups are AActor subclasses of ACrimsonPickupActorBase that implement ICrimsonPickupable and hold a FCrimsonInventoryPickup payload. The base provides auto-mesh resolution, single-item and multi-item ("bag") modes, and per-slot take logic. Concrete interaction + UI (the bag screen) lives in CrimsonCore; the inventory plugin itself stays UI-free.
ICrimsonPickupable
Any actor that holds pickup data implements this interface. The async pickup nodes call GetCrimsonPickupInventory() to read and SetCrimsonPickupInventory() to write back leftovers.
class ICrimsonPickupable{virtual FCrimsonInventoryPickup GetCrimsonPickupInventory() const = 0;virtual void SetCrimsonPickupInventory(const FCrimsonInventoryPickup& NewPickup) = 0;};struct FCrimsonPickupTemplate { TSubclassOf<UCrimsonInventoryItemDefinition> ItemDef; int32 StackCount = 1; };struct FCrimsonPickupInstance { UCrimsonInventoryItemInstance* Item = nullptr; int32 StackCount = 1; };struct FCrimsonCurrencyPickupTemplate { FDataTableRowHandle CurrencyDataTable; int64 StackCount = 1; };struct FCrimsonInventoryPickup{TArray<FCrimsonPickupInstance> Instances;TArray<FCrimsonPickupTemplate> Templates;TArray<FCrimsonCurrencyPickupTemplate> Currencies;};
ACrimsonPickupActorBase
Abstract base. Owns a UStaticMeshComponent root (visual only, NoCollision so subclasses can layer their own trigger volume), a BagMeshOverride property for multi-item bags, and the PickupInventory replicated payload. Two server entry points populate it:
| Method | When called | Use |
|---|---|---|
InitializeAsDroppedItem(Instance, StackCount) | Single-item drop path (DropItemByEntry, DropItemsByDefinition) | One FCrimsonPickupInstance row |
SetCrimsonPickupInventory(Payload) | Bulk fill (loot subsystem, DropEntriesAsBag) | Arbitrary mix of Instances / Templates / Currencies |
Both endpoints resolve and apply the visual mesh, broadcast OnPickupContentsChanged, and fire the OnPickupInitialized BP event (in InitializeAsDroppedItem only) for further per-actor customization.
Auto-mesh resolution
ResolveDisplayMesh() is called from InitializeAsDroppedItem, SetCrimsonPickupInventory, and OnRep_PickupInventory so server, client, and post-take re-resolutions all stay in sync. The rule:
| Condition | Mesh |
|---|---|
IsMultiItem() AND BagMeshOverride is set | BagMeshOverride |
| Single item OR no bag override | First item's UCrimsonInventoryItemFragment_Visuals::PickupMesh |
| No items at all | nullptr (mesh is cleared, component stays) |
IsMultiItem() returns true when GetTotalSlotCount() > 1 - i.e., total Instances + Templates + Currencies slots, NOT distinct ItemDefs. Three stacks of the same apple in three slots -> bag (they're three world objects). One stack of 99 apples -> single-item.Per-slot take API (server-only)
Clients drive these via UCrimsonInventoryRoutingComponent::TakePickupSlot / TakePickupAll (the routing component is player-owned, so its Server_ RPCs route correctly). The actor methods themselves run server-side only.
UENUM(BlueprintType)enum class ECrimsonPickupSlotType : uint8 { Instance, Template, Currency };// Server-only - called from Routing forwarders.void TakeSlot(ECrimsonPickupSlotType SlotType, int32 SlotIndex, int32 RequestedQuantity,UCrimsonInventoryManagerComponent* Destination);void TakeAll(UCrimsonInventoryManagerComponent* Destination);// Multicast - fires on server after a mutation AND on clients after OnRep.DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCrimsonPickupContentsChanged, const FCrimsonInventoryPickup&, NewPickup);FOnCrimsonPickupContentsChanged OnPickupContentsChanged;
TakeSlot clamps RequestedQuantity against the slot's stack and against Destination->CalculateAddableQuantity(...), so Fragment_Data.bUnique and every component-level constraint (StackLimit, IntTag, FloatTag, ...) gate the take before AddItemInstance / AddItemDefinition / AddCurrency runs. Per-stack MaxStackSize is honored inside Layout::FindPlacement when the add resolves. The actor self-destroys when emptied.
Drop entries as a single bag
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly)bool UCrimsonInventoryManagerComponent::DropEntriesAsBag(const TArray<int64>& EntryIds,FVector DropLocation,TSubclassOf<ACrimsonPickupActorBase> BagActorClass);
Server-only. Consumes every supplied EntryId (full stack each) from this inventory, builds one FCrimsonInventoryPickup with the consumed instances, spawns one BagActorClass actor at DropLocation, calls SetCrimsonPickupInventory(Payload), and hands subobject replication to the bag actor. Per-instance state (tag stacks, state fragments - durability, lifetime) is preserved; same-def stacks are NOT merged because merging would fabricate or destroy that state.
DropItemByEntry / DropItemsByDefinition / the loot subsystem still spawn one pickup actor per stack/instance. The bag flow is purely additive - invoke DropEntriesAsBag explicitly when you want bundling. With BagMeshOverride unset on a multi-item pickup, the actor falls back to the first item's mesh; with BagScreenClass unset on the CrimsonCore actor, the bag interaction silently no-ops. Degradation is graceful.Routing forwarders (client-callable)
Use these from any client widget or BP - they internally do the server RPC, run CanAccessInventory(Destination) access checks, then dispatch into the pickup actor's server-only methods.
| Method | What it does |
|---|---|
TakePickupSlot(PickupActor, SlotType, SlotIndex, RequestedQuantity, Destination) | Server-routed: pickup actor's TakeSlot(...) |
TakePickupAll(PickupActor, Destination) | Server-routed: pickup actor's TakeAll(...) |
Async pickup nodes (Blueprint)
| Node | Purpose |
|---|---|
UCrimsonInventoryAsync_AttemptPickup | Runs all constraints, adds as much as fits, returns leftover pickup. Used by 'pick up everything' flows. |
UCrimsonInventoryAsync_AttemptAddItem | Simpler add-by-definition variant with quantity-added / quantity-left-behind. |
ACrimsonCorePickupActor (a concrete subclass that implements ICrimsonInteractableTarget) and UCrimsonCorePickupBagScreen (the bag inspect/take UI) live in the CrimsonCore plugin. CrimsonInventory itself stays UI-free - see the CrimsonCore -> Pickup Actor & Bag UI wiki page for the full interaction + UI wiring.