// 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& 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& 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& OutItems) const; UFUNCTION(BlueprintPure, Category = "Inventory") void GetAllItems(TArray& 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& 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 Containers; /** Default containers spawned in InitializeComponent (designer-configurable). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory|Setup") TArray 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 ServerOnlyProps; };