Layouts
UCrimsonInventoryLayout is the topology strategy. It is a UDataAsset so a single layout asset (e.g. BP_PlayerGrid_20x8) can be referenced by many components - every player, every chest of a given type. Layouts hold no per-component state and no caps. All caps come from the constraint stack at runtime, all per-component derived state lives in FCrimsonLayoutRuntimeState on the component.
Order of veto
Every placement goes through three stages:
1. Layout `FindPlacement` - geometric feasibility ("is there a 2x2 rectangle anywhere?")
2. Layout `CanAcceptAt` - geometric validity of the chosen target ("is cell (3,1) actually in bounds?")
3. Constraint stack - gameplay vetoes (slot cap, weight, type filter, custom rules)
If any stage fails, the failure reason is broadcast via BroadcastOnConstraintFailed on the owning client.
Virtual surface
UCLASS(Abstract, Blueprintable, BlueprintType)class UCrimsonInventoryLayout : public UDataAsset{UFUNCTION(BlueprintNativeEvent)FCrimsonPlacementResult FindPlacement(const FCrimsonPlacementContext& Context,const FCrimsonPlacementQuery& Query) const;UFUNCTION(BlueprintNativeEvent)bool CanAcceptAt(const FCrimsonPlacementContext& Context,const FCrimsonPlacementQuery& Query,const FCrimsonPlacementTarget& Target,FText& OutFailureReason) const;UFUNCTION(BlueprintNativeEvent)FCrimsonMoveResult TryResolveMove(const FCrimsonPlacementContext& Context,const FCrimsonMoveQuery& Query) const;UFUNCTION(BlueprintNativeEvent)int32 GetCapacityHint(const FCrimsonPlacementContext& Context) const;UFUNCTION(BlueprintNativeEvent)FCrimsonItemFootprint GetItemFootprint(const UCrimsonInventoryItemDefinition* Definition) const;UFUNCTION(BlueprintNativeEvent)bool SupportsRotation() const;UFUNCTION(BlueprintNativeEvent)bool SupportsStacking() const;virtual void RebuildOccupancy(const UCrimsonInventoryManagerComponent* Component,const TArray<FCrimsonInventoryEntry>& Entries,FCrimsonLayoutRuntimeState& OutState) const;};
Built-in layouts
| Class | Topology | Reads from constraints |
|---|---|---|
UCrimsonInventoryLayout_FlatSlot | Indexed slot list | Slot count -> tag Crimson.Inventory.Constraint.Slots |
UCrimsonInventoryLayout_Grid | 2D cell grid | Width / Height -> tags .GridWidth / .GridHeight. Reads footprint Dimensions + Rotation. |
UCrimsonInventoryLayout_Weight | Ordered list, no spatial | Cap enforced via _FloatTag weight constraint (not by the layout) |
Placement types (FCrimsonInventoryLayoutTypes.h)
UENUM(BlueprintType)enum class ECrimsonItemRotation : uint8{None, CW90, Rot180, CCW90};UENUM(BlueprintType)enum class ECrimsonPlacementPolicy : uint8{MergeFirst, // stack into existing, then take first free slot for overflowFirstFree, // skip stacking; lowest-index free slotLastFree, // skip stacking; highest-index free slotMergeOnly, // stack into existing only; reject overflowExplicitTarget // use Query.Hint exactly};UENUM(BlueprintType)enum class ECrimsonMoveAction : uint8{None, Move, Swap, Merge};USTRUCT(BlueprintType)struct FCrimsonItemFootprint{FIntPoint Dimensions = FIntPoint(1, 1);float Weight = 0.f;int32 SlotCost = 1;bool bAllowRotation = false;};USTRUCT(BlueprintType)struct FCrimsonPlacementTarget{int32 SlotIndex = -1;FIntPoint GridPosition = FIntPoint(-1, -1);ECrimsonItemRotation Rotation = ECrimsonItemRotation::None;int32 LayoutGroupId = 0;};USTRUCT(BlueprintType)struct FCrimsonPlacementQuery{TSubclassOf<UCrimsonInventoryItemDefinition> ItemDef;int32 RequestedStackCount = 1;ECrimsonPlacementPolicy Policy = ECrimsonPlacementPolicy::MergeFirst;FCrimsonPlacementTarget Hint;int32 LayoutGroupId = 0;};USTRUCT(BlueprintType)struct FCrimsonPlacementResult{bool bSuccess = false;FText FailureReason;FCrimsonPlacementTarget Target;int32 QuantityPlaced = 0;int32 QuantityRemaining = 0;bool bMergedIntoExisting = false;int64 MergedTargetEntryId = 0;};
Runtime occupancy cache
FCrimsonLayoutRuntimeState is a transient struct owned by the component. The layout populates it in RebuildOccupancy from the current entry list. Clients rebuild from FastArray callbacks (PostReplicatedAdd/Change/Remove) and from OnRep_InventoryList.
USTRUCT(BlueprintType)struct FCrimsonLayoutRuntimeState{/** Shared: stable EntryId -> its current target. */TMap<int64, FCrimsonPlacementTarget> EntryIdToTarget;/** FlatSlot / Weight: slot index -> EntryId. */TMap<int32, int64> SlotIndexToEntryId;/** Grid layouts: cell (X + Y*Width) -> EntryId, 0 if empty. */TArray<int64> GridOccupancy;/** Cached grid bounds; (0,0) for non-grid layouts. */FIntPoint GridSize = FIntPoint::ZeroValue;};
UCrimsonInventoryLayout (BP or C++). Override FindPlacement_Implementation to decide where items go; optionally override CanAcceptAt, TryResolveMove, GetCapacityHint, RebuildOccupancy. Read parameters (caps, dimensions) from the constraint stack via Context.Component, never bake them into the layout asset.