UMIPItemPiece¶
Overview¶
UMIPItemPiece is the abstract base class for a modular composition system that attaches data and behaviour to items. Instead of putting all item logic in one monolithic class, each concern (durability, equipment stats, cooldowns, expiration, etc.) lives in its own isolated piece object.
Pieces come in two fundamental flavours:
| Flavour | Base Class | Lifetime | Saved per instance? |
|---|---|---|---|
| Static | UMIPStaticItemPiece |
Shared across all instances (CDO) | No — lives on the definition |
| Dynamic | UMIPItemPieceDynamic |
Created per item instance | Yes (unless DynamicTemp) |
{% hint style="info" %}
Static pieces are configuration. Dynamic pieces are runtime state. A static piece's primary job is often to declare which dynamic pieces should be created when an instance of that item is spawned — via IAdditionalDynamicPieceInterface.
{% endhint %}
Key Classes & Files¶
All paths are relative to Plugins/ModularInventoryPlus/Source/ModularInventoryPlus/.
| Class | File |
|---|---|
UMIPItemPiece |
Public/Inventory/Pieces/MIPItemPiece.h |
UMIPStaticItemPiece |
Public/Inventory/Pieces/Static/MIPStaticItemPiece.h |
UMIPItemPieceDynamic |
Public/Inventory/Pieces/MIPItemPieceDynamic.h |
UMIPItemPieceDynamicTemp |
Public/Inventory/Pieces/MIPItemPieceDynamicTemp.h |
IAdditionalDynamicPieceInterface |
Public/Inventory/Interfaces/AdditionalDynamicPieceInterface.h |
UMIPItemInstance |
Public/Inventory/Instances/MIPItemInstance.h/.cpp |
UMIPBaseInventoryManagerComponent |
Public/Inventory/MIPBaseInventoryManagerComponent.h/.cpp |
Class Hierarchy¶
UObject
└── UMIPItemPiece (abstract)
│ • Holds UMIPItemInstance* Instance
│ • OnInstanceCreated(Instance)
│
├── UMIPStaticItemPiece (abstract)
│ Shared definition-time config. Never replicated per instance.
│
│ ├── UItemPiece_DurabilityDefinition
│ │ Configures max durability. Spawns UItemPiece_Durability.
│ ├── UItemPiece_ExpiringItemDefinition
│ │ Configures expiration duration. Spawns UItemPiece_ExpiringItem.
│ ├── UItemPiece_EquipmentDefinition
│ │ References UMIPEquipmentPieceAsset. Spawns UItemPiece_DynamicEquipmentItemStats.
│ ├── UItemPiece_StackableDefinition
│ ├── UItemPiece_EnhanceDefinition
│ ├── UItemPiece_UsableDefinition
│ ├── UItemPiece_ClassRestrictDefinition
│ └── UItemPiece_FeeOverrideDefinition
│
└── UMIPItemPieceDynamic (abstract)
Per-instance runtime state. Replicated + saveable.
• OnInstanceCreatedDynamic(Instance, CreatorPiece, bIsLoaded)
• PostAllPiecesCreated()
• MarkPieceForSaveAndDirty()
• CanSavePiece() = true (default)
│
├── UItemPiece_Durability
│ Tracks CurrentDurability / MaxDurability. Replicated.
├── UItemPiece_DynamicEquipmentItemStats
│ Generates and stores randomized equipment stat rolls.
├── UItemPiece_Enhanceable
│ Tracks enhancement level.
├── UItemPiece_UniquePropertyItem
│ Assigns a unique FGuid. Always added to every instance.
│
└── UMIPItemPieceDynamicTemp (CanSavePiece = false)
Volatile pieces — not persisted across sessions.
│
└── UMIPItemPieceWithTempDuration (abstract)
Base for time-limited pieces. Self-removes when timer expires.
│
├── UItemPiece_ExpiringItem — item expiration countdown
├── UItemPiece_Cooldown — usage cooldown
└── UItemPiece_SoldToVendorItem — vendor buyback window
IAdditionalDynamicPieceInterface¶
Static pieces implement this interface to declare which dynamic pieces the inventory manager should create alongside them when an item instance is built.
class IAdditionalDynamicPieceInterface
{
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
TArray<TSubclassOf<UMIPItemPieceDynamic>> GetAdditionalDynamicPieceClasses();
};
Example — UItemPiece_DurabilityDefinition:
TArray<TSubclassOf<UMIPItemPieceDynamic>> UItemPiece_DurabilityDefinition::GetAdditionalDynamicPieceClasses_Implementation()
{
return { UItemPiece_Durability::StaticClass() };
}
Example — UItemPiece_EquipmentDefinition:
TArray<TSubclassOf<UMIPItemPieceDynamic>> UItemPiece_EquipmentDefinition::GetAdditionalDynamicPieceClasses_Implementation()
{
return { UItemPiece_DynamicEquipmentItemStats::StaticClass() };
}
Instance Creation Flow¶
When CreateNewItemEntry() is called on the inventory manager, the following sequence runs:
CreateNewItemEntry(AssetId, Count)
│
├─ Construct UMIPItemInstance
│
├─ Always add UItemPiece_UniquePropertyItem (every item gets a unique GUID)
│
├─ For each UMIPStaticItemPiece in ItemDefinition.Pieces:
│ │
│ ├─ Piece->OnInstanceCreated(ItemInstance)
│ │
│ └─ If Piece implements IAdditionalDynamicPieceInterface:
│ └─ Classes = Piece->GetAdditionalDynamicPieceClasses()
│ └─ For each Class:
│ ├─ DynPiece = NewObject<UMIPItemPieceDynamic>(Owner, Class)
│ ├─ DynPiece->OnInstanceCreatedDynamic(ItemInstance, CreatorPiece, bIsLoaded=false)
│ └─ ItemInstance->AddDynamicPiece(DynPiece)
│
└─ For each dynamic piece now on the instance:
└─ DynPiece->PostAllPiecesCreated()
Called after all siblings exist — use this for cross-piece init.
{% hint style="warning" %}
PostAllPiecesCreated() is the correct place for a dynamic piece to read data from sibling pieces. OnInstanceCreatedDynamic may fire before other pieces are attached.
{% endhint %}
Accessing Pieces on an Instance¶
UMIPItemInstance exposes lazy-cached accessors generated by a macro pair:
// Macro generates the cached field + accessor on UMIPItemInstance
GENERATE_GET_OR_SET_PIECE(UItemPiece_Durability, DurabilityItemPiece)
GENERATE_GET_OR_SET_PIECE(UItemPiece_DurabilityDefinition, DurabilityDefinitionItemPiece)
GENERATE_GET_OR_SET_PIECE(UItemPiece_EquipmentDefinition, EquipmentDefinitionPiece)
GENERATE_GET_OR_SET_PIECE(UItemPiece_DynamicEquipmentItemStats, DynamicEquipmentItemStatsPiece)
GENERATE_GET_OR_SET_PIECE(UItemPiece_Enhanceable, EnhanceablePiece)
// ... and more
What the macro generates:
// Lazy-cached mutable pointer (null on first access)
mutable UItemPiece_Durability* DurabilityItemPiece = nullptr;
// Accessor: returns cache, or searches then caches on first call
UItemPiece_Durability* GetOrSetDurabilityItemPiece() const
{
if (DurabilityItemPiece) return DurabilityItemPiece;
DurabilityItemPiece = FindPieceByClassCasted<UItemPiece_Durability>();
return DurabilityItemPiece;
}
Usage in code:
// First call searches + caches. Subsequent calls are free.
float HP = ItemInstance->GetOrSetDurabilityItemPiece()->GetCurrentDurability();
For types without a generated accessor, use the generic template directly:
FindPieceByClassCasted checks dynamic pieces first, then falls back to the static pieces on the item definition.
Dynamic Piece Storage & Replication¶
Dynamic pieces are stored on the item instance in a FFastArraySerializer container for efficient delta replication:
USTRUCT(BlueprintType)
struct FDynamicPieceEntry : public FFastArraySerializerItem
{
UPROPERTY(BlueprintReadOnly)
UMIPItemPieceDynamic* DynamicPiece = nullptr;
};
USTRUCT(BlueprintType)
struct FDynamicPieceList : public FFastArraySerializer
{
UPROPERTY(BlueprintReadOnly)
TArray<FDynamicPieceEntry> DynamicPieceEntries;
UPROPERTY(BlueprintReadOnly)
AActor* Owner;
};
Individual piece properties (e.g. CurrentDurability) replicate via standard DOREPLIFETIME on each piece class.
Saving & Loading¶
Saving — a dynamic piece calls MarkPieceForSaveAndDirty() whenever its state changes:
void UMIPItemPieceDynamic::MarkPieceForSaveAndDirty(bool bShouldMarkDirty)
{
SavePiece(); // serializes this piece to the save system
if (bShouldMarkDirty) MarkPieceDirty(); // marks FastArray dirty for replication
}
CanSavePiece() returns true by default. UMIPItemPieceDynamicTemp overrides it to false — temp pieces are never written to disk.
Loading — the inventory manager deserializes each piece from JSON, reconstructs it by class path, then calls PreLoadPiece(Instance) before invoking LoadThisUObject:
// Simplified load loop in MIPBaseInventoryManagerComponent
for each piece in saved JSON:
UClass* PieceClass = LoadObject<UClass>(ClassPath)
UMIPItemPieceDynamic* Piece = NewObject<>(Owner, PieceClass)
Piece->PreLoadPiece(ItemInstance)
ISaveableLoadableObjectInterface::Execute_LoadThisUObject(Piece, JsonString)
ItemInstance->AddDynamicPiece_Client(Piece)
Pieces implement ISaveableLoadableObjectInterface and ISaveIdentifierInterface to participate in this system.
Temp Pieces & Self-Removal¶
UMIPItemPieceDynamicTemp (and subclasses via UMIPItemPieceWithTempDuration) remove themselves from the instance when their condition expires:
void UMIPItemPieceWithTempDuration::OnTemporalDurationDecoratorRemoved_Implementation(bool bCancelled)
{
if (Instance)
Instance->RemoveDynamicPiece(this);
TemporalDurationDecorator = nullptr;
}
This means cooldown and expiration pieces clean up after themselves without external coordination.
End-to-End Data Flow¶
┌──────────────────────────────────────────────────────────────────┐
│ UMIPItemDefinition (asset) │
│ │
│ Pieces[] │
│ ├── UItemPiece_DurabilityDefinition ──► spawns Durability │
│ ├── UItemPiece_EquipmentDefinition ──► spawns DynEquipStats │
│ └── UItemPiece_UsableDefinition │
└──────────────────────────────────────────────────────────────────┘
│
CreateNewItemEntry()
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ UMIPItemInstance │
│ │
│ DynamicPieceList │
│ ├── UItemPiece_UniquePropertyItem { FGuid } │
│ ├── UItemPiece_Durability { Current, Max } │
│ └── UItemPiece_DynamicEquipmentItemStats { rolled stats } │
│ │
│ Cached accessors (lazy, mutable): │
│ ├── GetOrSetDurabilityItemPiece() │
│ ├── GetOrSetEquipmentDefinitionPiece() │
│ └── GetOrSetDynamicEquipmentItemStatsPiece() │
└──────────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Replicated Saveable Accessible
(FastArray + (SavePiece / (GetOrSet* /
DOREPLIFETIME) CanSavePiece) FindByClass)
Summary¶
| Question | Answer |
|---|---|
| What is a piece? | A self-contained data/behaviour unit attached to an item |
| Static vs Dynamic? | Static = definition config (shared). Dynamic = per-instance state (replicated + saved) |
| How are dynamic pieces created? | Static piece declares them via IAdditionalDynamicPieceInterface |
| How do I access a piece? | ItemInstance->GetOrSetXxxPiece() or FindPieceByClassCasted<T>() |
| Are temp pieces saved? | No — UMIPItemPieceDynamicTemp::CanSavePiece() returns false |
| How does a temp piece clean up? | It calls Instance->RemoveDynamicPiece(this) on expiry |
When is PostAllPiecesCreated called? |
After all pieces on the instance are attached — safe for cross-piece reads |