Initial commit: Item Interaction Ecosystem plugin (UE5.7)
Server-authoritative, data-driven item/inventory/interaction/carry/equipment/ world-slot/placement/crafting framework. 8 modules, compiles against UE 5.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
195
Source/ItemInventory/Public/InventoryComponent.h
Normal file
195
Source/ItemInventory/Public/InventoryComponent.h
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "ReplicatedInventoryList.h"
|
||||
#include "InventoryContainer.h"
|
||||
#include "InventoryComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChangedSignature);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInventoryItemEventSignature, const FItemInstanceData&, Item);
|
||||
|
||||
/**
|
||||
* Owns and replicates a set of containers full of items (section 7).
|
||||
*
|
||||
* Server-authoritative: every mutating method must run on the server (the
|
||||
* interaction/transaction layer is the only intended caller). Clients receive
|
||||
* state through the FastArray and react via OnInventoryChanged / OnRep. The UI
|
||||
* never mutates this directly (section 25).
|
||||
*/
|
||||
UCLASS(ClassGroup = (Inventory), meta = (BlueprintSpawnableComponent))
|
||||
class ITEMINVENTORY_API UInventoryComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UInventoryComponent();
|
||||
|
||||
//~ UActorComponent
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
virtual void InitializeComponent() override;
|
||||
virtual void OnRegister() override;
|
||||
|
||||
// --- Events (UI binds here, never polls - section 32) ---
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Inventory")
|
||||
FOnInventoryChangedSignature OnInventoryChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Inventory")
|
||||
FOnInventoryItemEventSignature OnItemAdded;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Inventory")
|
||||
FOnInventoryItemEventSignature OnItemRemoved;
|
||||
|
||||
/**
|
||||
* When true (default) inventory replicates only to the owning client (player
|
||||
* inventory, section 25.9). Set false for shared world containers (loot chests,
|
||||
* vehicle cargo) so nearby clients can see the contents. Must be set before the
|
||||
* component registers replication (e.g. in the owning actor's constructor).
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory|Replication")
|
||||
bool bOwnerOnlyReplication = true;
|
||||
|
||||
// --- Container setup ---
|
||||
|
||||
/** Registers a container. Assigns a new ContainerId if unset. Server side. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
FGuid AddContainer(FInventoryContainer Container);
|
||||
|
||||
/** Registers a container from a DT_Containers template (section 6). Server. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
FGuid AddContainerByTag(FGameplayTag ContainerTag);
|
||||
|
||||
/** Creates a nested container owned by an item that has Feature.Container (section 7). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
FGuid AddNestedContainerForItem(FGuid OwnerItemInstanceId, FGameplayTag ContainerTag);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
bool GetContainer(FGuid ContainerId, FInventoryContainer& OutContainer) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
void GetContainers(TArray<FInventoryContainer>& OutContainers) const { OutContainers = Containers; }
|
||||
|
||||
/** First container whose tag matches, or an invalid guid. */
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
FGuid FindContainerByTag(FGameplayTag ContainerTag) const;
|
||||
|
||||
// --- Core operations (section 7.1) ---
|
||||
|
||||
/** Adds an item, stacking into existing compatible stacks then free slots. Server. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool AddItem(UPARAM(ref) FItemInstanceData& Item);
|
||||
|
||||
/** Adds an item into a specific container. Server. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool AddItemToContainer(UPARAM(ref) FItemInstanceData& Item, FGuid ContainerId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool RemoveItem(FGuid InstanceId);
|
||||
|
||||
/** Removes up to Amount from a stack; removes the entry when it hits zero. Server. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool RemoveQuantity(FGuid InstanceId, int32 Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool MoveItem(FGuid InstanceId, FGuid FromContainerId, FGuid ToContainerId, int32 TargetSlot);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool SplitStack(FGuid InstanceId, int32 Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool MergeStack(FGuid SourceInstanceId, FGuid TargetInstanceId);
|
||||
|
||||
// --- Queries ---
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
bool CanAcceptItem(const FItemInstanceData& Item, FGuid ContainerId) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
bool FindItem(FGuid InstanceId, FItemInstanceData& OutItem) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
void GetContainerItems(FGuid ContainerId, TArray<FItemInstanceData>& OutItems) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
void GetAllItems(TArray<FItemInstanceData>& OutItems) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
float GetContainerWeight(FGuid ContainerId) const;
|
||||
|
||||
/** Total quantity of a given ItemId across all containers. */
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
int32 GetItemCount(FName ItemId) const;
|
||||
|
||||
// --- Internal mutation used by the transaction layer ---
|
||||
|
||||
/** Writes a mutated copy back into the list and replicates it. Server. */
|
||||
bool UpdateItem(const FItemInstanceData& Item);
|
||||
|
||||
/** Server: place a soft lock on an item so it cannot be moved/taken (section 20). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool SetItemLock(FGuid InstanceId, FGameplayTag Reason, AActor* LockedBy, float ExpireWorldTime);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
bool ClearItemLock(FGuid InstanceId);
|
||||
|
||||
/** Reads a ServerOnly property kept off the wire (section 22). Server only. */
|
||||
UFUNCTION(BlueprintPure, Category = "Inventory")
|
||||
float GetServerOnlyFloat(FGuid InstanceId, FGameplayTag PropertyTag, float Fallback = 0.f) const;
|
||||
|
||||
/** Direct, unchecked write of an item into a slot (used by transactions). Server. */
|
||||
void SetItemInSlot(const FItemInstanceData& Item, FGuid ContainerId, int32 SlotIndex);
|
||||
|
||||
/** Server: remove every item (save load / debug clear). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Inventory")
|
||||
void ClearAllItems();
|
||||
|
||||
/** Server: replace the container set (save load). */
|
||||
void SetContainers(const TArray<FInventoryContainer>& InContainers);
|
||||
|
||||
/** Broadcasts OnInventoryChanged. Called by the FastArray on clients too. */
|
||||
void NotifyInventoryChanged();
|
||||
void NotifyItemAdded(const FItemInstanceData& Item);
|
||||
void NotifyItemRemoved(const FItemInstanceData& Item);
|
||||
|
||||
protected:
|
||||
UPROPERTY(Replicated)
|
||||
FReplicatedInventoryList ReplicatedItems;
|
||||
|
||||
UPROPERTY(Replicated)
|
||||
TArray<FInventoryContainer> Containers;
|
||||
|
||||
/** Default containers spawned in InitializeComponent (designer-configurable). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory|Setup")
|
||||
TArray<FInventoryContainer> DefaultContainers;
|
||||
|
||||
private:
|
||||
bool HasAuthorityChecked(const TCHAR* Op) const;
|
||||
|
||||
FInventoryContainer* GetContainerMutable(const FGuid& ContainerId);
|
||||
const FInventoryContainer* GetContainerConst(const FGuid& ContainerId) const;
|
||||
|
||||
/** Lowest free slot index in a slot-limited container, or INDEX_NONE if full/unlimited-ok. */
|
||||
int32 FindFreeSlot(const FGuid& ContainerId) const;
|
||||
|
||||
/** Grid-aware free slot for a specific item (uses its GridSize); falls back to FindFreeSlot. */
|
||||
int32 FindFreeSlotForItem(const FGuid& ContainerId, const FItemInstanceData& Item) const;
|
||||
|
||||
FIntPoint GetItemGridSize(FName ItemId) const;
|
||||
|
||||
/** True if the WxH rectangle at TopLeft is inside the grid and unoccupied (ignoring IgnoreInstance). */
|
||||
bool IsGridRegionFree(const FInventoryContainer& Container, int32 TopLeft, FIntPoint Size, const FGuid& IgnoreInstance) const;
|
||||
|
||||
bool IsSlotOccupied(const FGuid& ContainerId, int32 SlotIndex) const;
|
||||
|
||||
/** Tries to top up existing stacks of the same item; reduces Remaining accordingly. */
|
||||
void StackIntoExisting(const FGuid& ContainerId, FItemInstanceData& Item, int32& Remaining);
|
||||
|
||||
/** Moves an item's ServerOnly properties into the server-side side channel (section 22). */
|
||||
void StripServerOnlyProperties(FItemInstanceData& Item);
|
||||
|
||||
/** Server-only properties never replicated to clients, keyed by instance id. */
|
||||
TMap<FGuid, FItemRuntimeProperties> ServerOnlyProps;
|
||||
};
|
||||
74
Source/ItemInventory/Public/InventoryContainer.h
Normal file
74
Source/ItemInventory/Public/InventoryContainer.h
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ItemEnums.h"
|
||||
#include "InventoryContainer.generated.h"
|
||||
|
||||
/**
|
||||
* Metadata describing one container owned by an inventory (section 7.2).
|
||||
*
|
||||
* NOTE (design deviation from the TЗ sketch): the actual item entries do NOT live
|
||||
* inside the container struct. They live in the component's single
|
||||
* FReplicatedInventoryList, tagged with this ContainerId. A FastArraySerializer
|
||||
* only delta-replicates correctly as a direct UPROPERTY, so nesting one inside a
|
||||
* replicated TArray<FInventoryContainer> would silently fall back to full-array
|
||||
* replication. Keeping containers as lightweight metadata fixes that (section 32).
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINVENTORY_API FInventoryContainer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
FGuid ContainerId;
|
||||
|
||||
/** Logical identity, e.g. Container.Backpack / Container.Hotbar / Container.Vehicle.Cargo. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
FGameplayTag ContainerTag;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
EInventoryContainerType Type = EInventoryContainerType::SlotList;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
int32 Width = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
int32 Height = 0;
|
||||
|
||||
/** Used by SlotList/Equipment/Hotbar; for Grid types capacity is Width*Height. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
int32 MaxSlots = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
float MaxWeight = 0.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
FGameplayTagContainer AcceptedItemTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Container")
|
||||
FGameplayTagContainer BlockedItemTags;
|
||||
|
||||
/** Set when this container lives inside another item (a backpack/pouch item) - section 7. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Container")
|
||||
FGuid OwnerItemInstanceId;
|
||||
|
||||
FInventoryContainer() = default;
|
||||
|
||||
bool IsNested() const { return OwnerItemInstanceId.IsValid(); }
|
||||
|
||||
/** Number of addressable slots in this container (0 = unlimited weight-gated list). */
|
||||
int32 GetSlotCount() const
|
||||
{
|
||||
if (Type == EInventoryContainerType::Grid)
|
||||
{
|
||||
return FMath::Max(0, Width) * FMath::Max(0, Height);
|
||||
}
|
||||
return FMath::Max(0, MaxSlots);
|
||||
}
|
||||
|
||||
bool HasSlotLimit() const { return GetSlotCount() > 0; }
|
||||
bool HasWeightLimit() const { return MaxWeight > 0.f; }
|
||||
};
|
||||
38
Source/ItemInventory/Public/ItemCraftingLibrary.h
Normal file
38
Source/ItemInventory/Public/ItemCraftingLibrary.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ItemCraftingLibrary.generated.h"
|
||||
|
||||
class UInventoryComponent;
|
||||
|
||||
/**
|
||||
* Crafting + loot helpers (section 6). All server-authoritative: crafting consumes
|
||||
* inputs and produces the output through the inventory's validated operations;
|
||||
* loot rolls fresh instances from a loot table into a container.
|
||||
*/
|
||||
UCLASS()
|
||||
class ITEMINVENTORY_API UItemCraftingLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** True if Inventory holds every ingredient (and the station requirement is met). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Crafting", meta = (WorldContext = "WorldContext"))
|
||||
static bool CanCraft(const UObject* WorldContext, UInventoryComponent* Inventory, FName RecipeId, FGameplayTag AvailableStation);
|
||||
|
||||
/** Server: consume the recipe inputs and add its output. Returns true on success. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Crafting", meta = (WorldContext = "WorldContext"))
|
||||
static bool Craft(const UObject* WorldContext, UInventoryComponent* Inventory, FName RecipeId, FGameplayTag AvailableStation);
|
||||
|
||||
/** Server: roll a loot table and add the results into an inventory. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Loot", meta = (WorldContext = "WorldContext"))
|
||||
static void RollLootIntoInventory(const UObject* WorldContext, UInventoryComponent* Inventory, FName TableId, int32 RollCount);
|
||||
|
||||
private:
|
||||
/** Removes Count of ItemId across stacks. Returns true if the full amount was removed. */
|
||||
static bool ConsumeItem(UInventoryComponent* Inventory, FName ItemId, int32 Count);
|
||||
};
|
||||
56
Source/ItemInventory/Public/ItemFeatureLibrary.h
Normal file
56
Source/ItemInventory/Public/ItemFeatureLibrary.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "ItemFeatureLibrary.generated.h"
|
||||
|
||||
class UInventoryComponent;
|
||||
|
||||
/**
|
||||
* Feature processing (section 21). Everything is LAZY: nothing ticks per item.
|
||||
* Call these on events - container open, save load, interaction, world-time pulse.
|
||||
* State (freshness, last-update time, charge, fuel) lives in the item's runtime
|
||||
* properties so it travels with the instance.
|
||||
*/
|
||||
UCLASS()
|
||||
class ITEMINVENTORY_API UItemFeatureLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Recomputes time-based features (rotting) for one item up to "now".
|
||||
* Returns true if the item changed. If the item should transform (e.g. raw_meat
|
||||
* -> rotten_meat) OutTransformItemId is set to the replacement id.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features", meta = (WorldContext = "WorldContext"))
|
||||
static bool UpdateLazyFeatures(const UObject* WorldContext, UPARAM(ref) FItemInstanceData& Item, FName& OutTransformItemId);
|
||||
|
||||
/** Server: run lazy updates across a whole inventory, applying any transforms. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features", meta = (WorldContext = "WorldContext"))
|
||||
static void UpdateInventoryFeatures(const UObject* WorldContext, UInventoryComponent* Inventory);
|
||||
|
||||
// --- Direct feature operations (debug tools, gameplay) ---
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features", meta = (WorldContext = "WorldContext"))
|
||||
static void ForceRot(const UObject* WorldContext, UPARAM(ref) FItemInstanceData& Item, FName& OutTransformItemId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features")
|
||||
static void BreakItem(UPARAM(ref) FItemInstanceData& Item);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features")
|
||||
static void DrainBattery(UPARAM(ref) FItemInstanceData& Item, float DrainPerSecond, float Seconds);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features")
|
||||
static float AddFuel(UPARAM(ref) FItemInstanceData& Item, float Amount, float MaxFuel);
|
||||
|
||||
/** Moves up to Amount of fuel from one item to another, respecting ToMaxFuel. Returns moved. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features")
|
||||
static float TransferFuel(UPARAM(ref) FItemInstanceData& From, UPARAM(ref) FItemInstanceData& To, float Amount, float ToMaxFuel);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Features")
|
||||
static void SetWetness(UPARAM(ref) FItemInstanceData& Item, float Wetness);
|
||||
};
|
||||
8
Source/ItemInventory/Public/ItemInventoryLog.h
Normal file
8
Source/ItemInventory/Public/ItemInventoryLog.h
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
ITEMINVENTORY_API DECLARE_LOG_CATEGORY_EXTERN(LogItemInventory, Log, All);
|
||||
34
Source/ItemInventory/Public/ItemSaveGame.h
Normal file
34
Source/ItemInventory/Public/ItemSaveGame.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/SaveGame.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "InventoryContainer.h"
|
||||
#include "ItemSaveGame.generated.h"
|
||||
|
||||
/** Saved snapshot of one inventory (section 26). Only ids/state are kept. */
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINVENTORY_API FInventorySaveData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(SaveGame)
|
||||
TArray<FItemInstanceData> Items;
|
||||
|
||||
UPROPERTY(SaveGame)
|
||||
TArray<FInventoryContainer> Containers;
|
||||
};
|
||||
|
||||
/** SaveGame object holding any number of named inventory snapshots. */
|
||||
UCLASS()
|
||||
class ITEMINVENTORY_API UItemSaveGame : public USaveGame
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Keyed by a stable inventory id chosen by the game (e.g. "Player0", a chest guid). */
|
||||
UPROPERTY(SaveGame)
|
||||
TMap<FName, FInventorySaveData> Inventories;
|
||||
};
|
||||
38
Source/ItemInventory/Public/ItemSaveLibrary.h
Normal file
38
Source/ItemInventory/Public/ItemSaveLibrary.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "ItemSaveGame.h"
|
||||
#include "ItemSaveLibrary.generated.h"
|
||||
|
||||
class UInventoryComponent;
|
||||
|
||||
/**
|
||||
* Save / load of inventories with redirect + version migration (section 26).
|
||||
* Stores only ItemId/InstanceId/Quantity/RuntimeProperties/StateTags/container/slot
|
||||
* - never icons, meshes, names or actor pointers (section 26 "do not save").
|
||||
*/
|
||||
UCLASS()
|
||||
class ITEMINVENTORY_API UItemSaveLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Snapshots an inventory's items + containers (drops transient locks). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Save")
|
||||
static FInventorySaveData CaptureInventory(UInventoryComponent* Inventory);
|
||||
|
||||
/** Server: rebuilds an inventory from a snapshot, migrating each item. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Save", meta = (WorldContext = "WorldContext"))
|
||||
static void RestoreInventory(const UObject* WorldContext, UInventoryComponent* Inventory, const FInventorySaveData& Data);
|
||||
|
||||
/** Writes one inventory under Key into a save slot (loads/creates the slot file). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Save")
|
||||
static bool SaveInventoryToSlot(UInventoryComponent* Inventory, const FString& SlotName, FName Key);
|
||||
|
||||
/** Server: loads one inventory under Key from a save slot. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Item|Save", meta = (WorldContext = "WorldContext"))
|
||||
static bool LoadInventoryFromSlot(const UObject* WorldContext, UInventoryComponent* Inventory, const FString& SlotName, FName Key);
|
||||
};
|
||||
101
Source/ItemInventory/Public/ItemTransaction.h
Normal file
101
Source/ItemInventory/Public/ItemTransaction.h
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "ItemTransaction.generated.h"
|
||||
|
||||
class UInventoryComponent;
|
||||
|
||||
/** Kind of mutation a transaction step performs (section 10). */
|
||||
UENUM(BlueprintType)
|
||||
enum class EItemTransactionStepType : uint8
|
||||
{
|
||||
AddItem,
|
||||
RemoveItem,
|
||||
UpdateItem,
|
||||
MoveItem
|
||||
};
|
||||
|
||||
/** One atomic step. Carries enough to apply AND to roll back. */
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINVENTORY_API FItemTransactionStep
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
EItemTransactionStepType Type = EItemTransactionStepType::AddItem;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UInventoryComponent> Inventory;
|
||||
|
||||
/** Item payload for AddItem / UpdateItem. */
|
||||
UPROPERTY()
|
||||
FItemInstanceData Item;
|
||||
|
||||
UPROPERTY()
|
||||
FGuid InstanceId;
|
||||
|
||||
UPROPERTY()
|
||||
FGuid ToContainer;
|
||||
|
||||
UPROPERTY()
|
||||
int32 ToSlot = INDEX_NONE;
|
||||
|
||||
// --- runtime (rollback bookkeeping) ---
|
||||
UPROPERTY()
|
||||
bool bApplied = false;
|
||||
|
||||
UPROPERTY()
|
||||
FItemInstanceData UndoSnapshot;
|
||||
|
||||
UPROPERTY()
|
||||
FGuid UndoFromContainer;
|
||||
|
||||
UPROPERTY()
|
||||
int32 UndoFromSlot = INDEX_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic multi-step item operation (section 10). Build steps, then Apply():
|
||||
* if any step fails, every applied step is rolled back so the inventories are
|
||||
* left untouched. The single correct way to do multi-inventory moves.
|
||||
*
|
||||
* Server-only. Operates through UInventoryComponent's authoritative methods.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINVENTORY_API FItemTransaction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FGuid TransactionId;
|
||||
|
||||
UPROPERTY()
|
||||
FGameplayTag TransactionType;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<AActor> Instigator;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FItemTransactionStep> Steps;
|
||||
|
||||
FItemTransaction() { TransactionId = FGuid::NewGuid(); }
|
||||
|
||||
// --- Builders ---
|
||||
FItemTransaction& Add(UInventoryComponent* Inventory, const FItemInstanceData& Item);
|
||||
FItemTransaction& Remove(UInventoryComponent* Inventory, const FGuid& InstanceId);
|
||||
FItemTransaction& Update(UInventoryComponent* Inventory, const FItemInstanceData& Item);
|
||||
FItemTransaction& Move(UInventoryComponent* Inventory, const FGuid& InstanceId, const FGuid& ToContainerId, int32 ToSlotIndex);
|
||||
|
||||
/** Non-mutating feasibility check of every step. */
|
||||
bool Validate() const;
|
||||
|
||||
/** Applies all steps; on any failure rolls back and returns false. */
|
||||
bool Apply();
|
||||
|
||||
/** Undoes every applied step in reverse order. */
|
||||
void Rollback();
|
||||
};
|
||||
84
Source/ItemInventory/Public/ReplicatedInventoryList.h
Normal file
84
Source/ItemInventory/Public/ReplicatedInventoryList.h
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "ReplicatedInventoryList.generated.h"
|
||||
|
||||
class UInventoryComponent;
|
||||
|
||||
/**
|
||||
* One replicated item slot (section 7.3). The item carries its own ContainerId /
|
||||
* SlotIndex inside FItemInstanceData, but they are mirrored here so the FastArray
|
||||
* delta only needs the entry, and so clients can route OnRep callbacks per slot.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct ITEMINVENTORY_API FRepInventoryEntry : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FGuid ContainerId;
|
||||
|
||||
UPROPERTY()
|
||||
int32 SlotIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY()
|
||||
FItemInstanceData Item;
|
||||
|
||||
FRepInventoryEntry() = default;
|
||||
FRepInventoryEntry(const FGuid& InContainer, int32 InSlot, const FItemInstanceData& InItem)
|
||||
: ContainerId(InContainer), SlotIndex(InSlot), Item(InItem) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* FastArraySerializer carrying every item the inventory owns across all containers
|
||||
* (section 7.3 / 32). One list per component delta-replicates only changed entries.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct ITEMINVENTORY_API FReplicatedInventoryList : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FRepInventoryEntry> Entries;
|
||||
|
||||
/** Not replicated - set by the owning component so callbacks can broadcast. */
|
||||
UPROPERTY(NotReplicated)
|
||||
TObjectPtr<UInventoryComponent> OwnerComponent = nullptr;
|
||||
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FFastArraySerializer::FastArrayDeltaSerialize<FRepInventoryEntry, FReplicatedInventoryList>(Entries, DeltaParms, *this);
|
||||
}
|
||||
|
||||
//~ FFastArraySerializer client callbacks
|
||||
void PostReplicatedAdd(const TArrayView<int32>& AddedIndices, int32 FinalSize);
|
||||
void PostReplicatedChange(const TArrayView<int32>& ChangedIndices, int32 FinalSize);
|
||||
void PreReplicatedRemove(const TArrayView<int32>& RemovedIndices, int32 FinalSize);
|
||||
|
||||
// --- Server-side mutation helpers (mark dirty for delta replication) ---
|
||||
|
||||
/** Adds or replaces the entry occupying (Container, Slot). Returns the live entry. */
|
||||
FRepInventoryEntry& AddOrUpdate(const FGuid& Container, int32 Slot, const FItemInstanceData& Item);
|
||||
|
||||
/** Removes the entry with the given instance id. Returns true if one was removed. */
|
||||
bool RemoveByInstanceId(const FGuid& InstanceId);
|
||||
|
||||
/** Marks an existing entry dirty after its Item was mutated in place. */
|
||||
void MarkEntryDirtyByInstanceId(const FGuid& InstanceId);
|
||||
|
||||
FRepInventoryEntry* FindByInstanceId(const FGuid& InstanceId);
|
||||
const FRepInventoryEntry* FindByInstanceId(const FGuid& InstanceId) const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TStructOpsTypeTraits<FReplicatedInventoryList> : public TStructOpsTypeTraitsBase2<FReplicatedInventoryList>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithNetDeltaSerializer = true
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user