// Copyright ExByte Studios. Item Interaction Ecosystem. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "InteractionTypes.h" #include "ItemInteractionComponent.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInteractionResult, FGuid, RequestId, bool, bSuccess); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnOpenContainer, AActor*, ContainerActor); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActionStarted, FGameplayTag, ActionTag, float, Duration); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnActionFinished, FGameplayTag, ActionTag, bool, bCompleted); /** Replicated snapshot of a long/hold action in progress (section 19), for UI. */ USTRUCT(BlueprintType) struct FActiveInteractionState { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "Interaction") bool bActive = false; UPROPERTY(BlueprintReadOnly, Category = "Interaction") FGameplayTag ActionTag; UPROPERTY(BlueprintReadOnly, Category = "Interaction") float Duration = 0.f; }; /** * Player-side hub that turns a chosen option into a server request and executes * the authorized result (sections 9, 25). Attach to the player pawn alongside * an InventoryComponent and (optionally) an InteractionTraceComponent. * * Flow: UI/input picks an FInteractionOption -> RequestInteraction() -> Server RPC * -> server re-resolves and re-validates -> executes the transaction -> state * replicates back. The client never changes item state itself. */ UCLASS(ClassGroup = (Interaction), meta = (BlueprintSpawnableComponent)) class ITEMINTERACTION_API UItemInteractionComponent : public UActorComponent { GENERATED_BODY() public: UItemInteractionComponent(); virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; /** Client entry point: send the chosen option's request to the server. */ UFUNCTION(BlueprintCallable, Category = "Interaction") void RequestInteraction(const FInteractionOption& Option); /** Client: signal that a hold action's button was released (cancels if mid-action). */ UFUNCTION(BlueprintCallable, Category = "Interaction") void ReleaseHold(); /** Server: cancel any action in progress (bind to damage/stun for interruption). */ UFUNCTION(BlueprintCallable, Category = "Interaction") void CancelActiveAction(); UFUNCTION(BlueprintPure, Category = "Interaction") const FActiveInteractionState& GetActiveAction() const { return ActiveAction; } /** Lower-level entry: send a hand-built request. */ UFUNCTION(BlueprintCallable, Category = "Interaction") void SendRequest(const FInteractionRequest& Request); // --- Inventory UI operations (section 23.3). Client calls, server validates. --- /** * Move/transfer an item. StorageActor == null -> rearrange within the player's * own inventory. Otherwise transfer between the player and the storage actor's * inventory (loot). ToContainerId == invalid -> first accepting container. */ UFUNCTION(BlueprintCallable, Category = "Inventory") void RequestMoveItem(AActor* StorageActor, FGuid InstanceId, FGuid ToContainerId, int32 ToSlot); UFUNCTION(BlueprintCallable, Category = "Inventory") void RequestSplitStack(FGuid InstanceId, int32 Amount); UFUNCTION(BlueprintCallable, Category = "Inventory") void RequestMergeStack(FGuid SourceInstanceId, FGuid TargetInstanceId); UFUNCTION(BlueprintCallable, Category = "Inventory") void RequestDropFromInventory(FGuid InstanceId); UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnInteractionResult OnInteractionResult; /** Fired locally when the player chooses to open a container - UI opens the loot window. */ UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnOpenContainer OnOpenContainer; UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnActionStarted OnActionStarted; UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnActionFinished OnActionFinished; /** Max slack (cm) added to the interactable's own distance when server-validating. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction") float ServerDistanceTolerance = 75.f; protected: UFUNCTION(Server, Reliable) void Server_RequestInteraction(const FInteractionRequest& Request); UFUNCTION(Server, Reliable) void Server_MoveItem(AActor* StorageActor, FGuid InstanceId, FGuid ToContainerId, int32 ToSlot); UFUNCTION(Server, Reliable) void Server_SplitStack(FGuid InstanceId, int32 Amount); UFUNCTION(Server, Reliable) void Server_MergeStack(FGuid SourceInstanceId, FGuid TargetInstanceId); UFUNCTION(Server, Reliable) void Server_DropFromInventory(FGuid InstanceId); UFUNCTION(Server, Reliable) void Server_ReleaseHold(); UFUNCTION(Client, Reliable) void Client_InteractionResult(FGuid RequestId, bool bSuccess); /** Server hook for dropping an item into the world. Overridden in ItemWorld. */ virtual bool HandleDropFromInventory(FGuid InstanceId); private: /** Server: full validation pipeline (section 9). Returns the authorized option. */ bool AuthorizeRequest(const FInteractionRequest& Request, FInteractionContext& OutContext, FInteractionOption& OutOption) const; public: /** * Server: run a mode directly from an already-built context (used by the named * UInteractionAction_* classes so they share the one execution path). Builds a * minimal option/request and dispatches through ExecuteAuthorized (virtual, so * the ItemWorld subclass handles carry/equip/install/use). */ bool ServerExecuteMode(const FInteractionContext& Context, EItemInteractionMode Mode, FGameplayTag ActionTag); protected: APawn* GetPawn() const; /** * Server: run the authorized option's effect. Virtual so ItemWorld can add the * carry/drop/equip/install/use-on-target modes (which need ItemWorld types) * without ItemInteraction depending on ItemWorld. Base handles pickup + transfer. */ virtual bool ExecuteAuthorized(const FInteractionContext& Context, const FInteractionOption& Option, const FInteractionRequest& Request); bool ExecutePickup(const FInteractionContext& Context); bool ExecuteTransfer(const FInteractionContext& Context, const FInteractionRequest& Request); bool ExecuteActionClass(const FInteractionContext& Context, const FInteractionOption& Option); // --- Timed / hold action plumbing (section 19) --- void BeginTimedAction(const FInteractionContext& Context, const FInteractionOption& Option, const FInteractionRequest& Request); void CompleteTimedAction(); void AbortTimedAction(); void LockActionItem(bool bLock); UPROPERTY(ReplicatedUsing = OnRep_ActiveAction) FActiveInteractionState ActiveAction; UFUNCTION() void OnRep_ActiveAction(); // Server-side pending state. bool bHasPending = false; bool bPendingHold = false; float PendingEndTime = 0.f; float PendingMaxDistanceSq = 0.f; FInteractionContext PendingContext; FInteractionOption PendingOption; FInteractionRequest PendingRequest; };