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:
48
Source/ItemInteraction/Public/InteractableComponent.h
Normal file
48
Source/ItemInteraction/Public/InteractableComponent.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "InteractableComponent.generated.h"
|
||||
|
||||
class UInteractionAction;
|
||||
|
||||
/**
|
||||
* Marks an actor as interactable and lists the actions it offers (section 8.1).
|
||||
* Attach to pickups, chests, vehicles, generators, doors, workbenches, corpses,
|
||||
* placed items, quest objects, etc.
|
||||
*/
|
||||
UCLASS(ClassGroup = (Interaction), meta = (BlueprintSpawnableComponent))
|
||||
class ITEMINTERACTION_API UInteractableComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UInteractableComponent();
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
FGameplayTag InteractableType;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
FText DisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
float InteractionDistance = 250.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
bool bRequiresLineOfSight = true;
|
||||
|
||||
/** Free-form world tags exposed to the interaction context (e.g. Vehicle, Generator). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
FGameplayTagContainer InteractionTags;
|
||||
|
||||
/** Explicit action classes this object offers (in addition to data-driven item rows). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category = "Interaction")
|
||||
TArray<TObjectPtr<UInteractionAction>> AvailableActions;
|
||||
|
||||
/** Server-side soft claim so two players don't interact with the same object at once. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bIsBusy = false;
|
||||
};
|
||||
65
Source/ItemInteraction/Public/InteractionAction.h
Normal file
65
Source/ItemInteraction/Public/InteractionAction.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ItemEnums.h"
|
||||
#include "InteractionTypes.h"
|
||||
#include "InteractionAction.generated.h"
|
||||
|
||||
/**
|
||||
* A single interaction verb, authored as an instanced object on an
|
||||
* InteractableComponent (section 16). This is the Blueprint-extensible path:
|
||||
* built-in modes are also handled directly by the interaction component, but
|
||||
* any custom verb can be added here without C++.
|
||||
*
|
||||
* CanExecute/Execute must be safe to run on the server. CanExecute may also run
|
||||
* on the client for predictive UI, so it must not mutate state.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced, CollapseCategories)
|
||||
class ITEMINTERACTION_API UInteractionAction : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
FGameplayTag ActionTag;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
FText DisplayText;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
EItemInteractionMode Mode = EItemInteractionMode::Use;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
int32 Priority = 300;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
bool bRequiresHold = false;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
float HoldDuration = 0.f;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
FInteractionExecutionData Execution;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Action")
|
||||
TArray<FInteractionRequirement> Requirements;
|
||||
|
||||
/** Pure check: are all requirements satisfied? Writes the first failure reason. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Action")
|
||||
bool CanExecute(const FInteractionContext& Context, FText& OutDisabledReason) const;
|
||||
virtual bool CanExecute_Implementation(const FInteractionContext& Context, FText& OutDisabledReason) const;
|
||||
|
||||
/** Server-authoritative effect. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Action")
|
||||
void Execute(const FInteractionContext& Context);
|
||||
virtual void Execute_Implementation(const FInteractionContext& Context);
|
||||
|
||||
/** Turns this action into a UI option for the given context. */
|
||||
FInteractionOption BuildOption(const FInteractionContext& Context) const;
|
||||
|
||||
virtual UWorld* GetWorld() const override;
|
||||
};
|
||||
44
Source/ItemInteraction/Public/InteractionResolver.h
Normal file
44
Source/ItemInteraction/Public/InteractionResolver.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "InteractionTypes.h"
|
||||
#include "InteractionResolver.generated.h"
|
||||
|
||||
class APawn;
|
||||
|
||||
/**
|
||||
* Central, stateless resolver (section 8.3). Given who is interacting and what
|
||||
* they look at, it produces the ordered list of available options. Run on the
|
||||
* client for predictive UI AND re-run on the server to authorize a request, so
|
||||
* it must be deterministic and side-effect free.
|
||||
*/
|
||||
UCLASS()
|
||||
class ITEMINTERACTION_API UInteractionResolver : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Assembles the interaction context for an instigator/target pair. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction", meta = (WorldContext = "Instigator"))
|
||||
static FInteractionContext BuildContext(APawn* Instigator, AActor* TargetActor);
|
||||
|
||||
/** All options available, highest priority first (section 8.6). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||
static void GetAvailableInteractions(APawn* Instigator, AActor* TargetActor, TArray<FInteractionOption>& OutOptions);
|
||||
|
||||
/** Convenience: the single best (highest priority, enabled) option. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||
static bool GetPrimaryInteraction(APawn* Instigator, AActor* TargetActor, FInteractionOption& OutOption);
|
||||
|
||||
/** Finds the held item on an instigator via IHeldItemInterface (actor or component). */
|
||||
static bool ResolveHeldItem(APawn* Instigator, FItemInstanceData& OutItem);
|
||||
|
||||
private:
|
||||
static void GatherCapabilityOptions(const FInteractionContext& Ctx, TArray<FInteractionOption>& Out);
|
||||
static void GatherContainerOptions(const FInteractionContext& Ctx, TArray<FInteractionOption>& Out);
|
||||
static void GatherDataDrivenOptions(const FInteractionContext& Ctx, TArray<FInteractionOption>& Out);
|
||||
static void GatherActionClassOptions(const FInteractionContext& Ctx, TArray<FInteractionOption>& Out);
|
||||
};
|
||||
60
Source/ItemInteraction/Public/InteractionTraceComponent.h
Normal file
60
Source/ItemInteraction/Public/InteractionTraceComponent.h
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "InteractionTraceComponent.generated.h"
|
||||
|
||||
class UInteractableComponent;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInteractionTargetChanged, AActor*, NewTarget, UInteractableComponent*, NewInteractable);
|
||||
|
||||
/**
|
||||
* Local-only aim trace that finds the best interactable under the crosshair
|
||||
* (section 8.2). Runs only on the owning client; the result drives the prompt UI
|
||||
* and is what the player confirms - never trusted by the server.
|
||||
*/
|
||||
UCLASS(ClassGroup = (Interaction), meta = (BlueprintSpawnableComponent))
|
||||
class ITEMINTERACTION_API UInteractionTraceComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UInteractionTraceComponent();
|
||||
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
/** Fires the trace once and updates CurrentTarget. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Interaction")
|
||||
void TickTrace();
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
float TraceDistance = 300.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
float TraceRadius = 12.f;
|
||||
|
||||
/** Trace channel; defaults to Visibility. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<AActor> CurrentTarget = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<UInteractableComponent> CurrentInteractable = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FVector LastHitLocation = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FVector LastHitNormal = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Interaction")
|
||||
FOnInteractionTargetChanged OnTargetChanged;
|
||||
|
||||
private:
|
||||
bool GetViewPoint(FVector& OutLocation, FRotator& OutRotation) const;
|
||||
void SetTarget(AActor* NewTarget, UInteractableComponent* NewInteractable);
|
||||
};
|
||||
215
Source/ItemInteraction/Public/InteractionTypes.h
Normal file
215
Source/ItemInteraction/Public/InteractionTypes.h
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "ItemEnums.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "InteractionTypes.generated.h"
|
||||
|
||||
class APlayerController;
|
||||
class ACharacter;
|
||||
class UInteractableComponent;
|
||||
class UInventoryComponent;
|
||||
class UTexture2D;
|
||||
|
||||
/**
|
||||
* Client -> server intent (section 9). The client never mutates state; it sends
|
||||
* this and the server re-validates everything before executing.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINTERACTION_API FInteractionRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGuid RequestId;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
EItemInteractionMode Mode = EItemInteractionMode::None;
|
||||
|
||||
/** The verb that produced this request; lets the server pick the matching action. */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGameplayTag ActionTag;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
TObjectPtr<AActor> TargetActor = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGuid ItemInstanceId;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGuid SourceContainerId;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGuid TargetContainerId;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
int32 TargetSlotIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FGameplayTag TargetSlotTag;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
FTransform DesiredTransform = FTransform::Identity;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
int32 Quantity = 1;
|
||||
|
||||
/** Last inventory revision the client had; lets the server detect desync. */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Interaction")
|
||||
int32 ClientRevision = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A single interaction the player could perform on the current target (section 8.4).
|
||||
* Built by the resolver, shown by the UI, and carries the ready-to-send Request.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINTERACTION_API FInteractionOption
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FGameplayTag ActionTag;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
EItemInteractionMode Mode = EItemInteractionMode::None;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FText DisplayText;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<UTexture2D> Icon = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
int32 Priority = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bRequiresHold = false;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
float HoldDuration = 0.f;
|
||||
|
||||
/** Duration of the action itself once started (section 19). */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
float ActionDuration = 0.f;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bIsEnabled = true;
|
||||
|
||||
/** Why the option is greyed out, e.g. "Inventory Full", "Requires Wrench". */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FText DisabledReason;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FInteractionRequest Request;
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything an action/requirement needs to make a decision (section 17).
|
||||
* Assembled by the resolver on whichever machine is evaluating (client preview
|
||||
* or server validation).
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINTERACTION_API FInteractionContext
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<APlayerController> PlayerController = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<ACharacter> Character = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<AActor> TargetActor = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<UInteractableComponent> TargetInteractable = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
TObjectPtr<UInventoryComponent> PlayerInventory = nullptr;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bHasHeldItem = false;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FItemInstanceData HeldItem;
|
||||
|
||||
/** The item the target itself represents (for pickups), if any. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
bool bHasTargetItem = false;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FItemInstanceData TargetItem;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FGameplayTagContainer WorldTags;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FVector HitLocation = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
|
||||
FVector HitNormal = FVector::ZeroVector;
|
||||
};
|
||||
|
||||
/** Requirement type verbs (section 18). */
|
||||
UENUM(BlueprintType)
|
||||
enum class EInteractionRequirementType : uint8
|
||||
{
|
||||
None,
|
||||
HasInventorySpace,
|
||||
HasItemWithTag,
|
||||
HasHeldItemWithTag,
|
||||
PropertyGreaterThan,
|
||||
PropertyLessThan,
|
||||
TargetHasFreeSlotTag,
|
||||
NotWet,
|
||||
NotBroken
|
||||
};
|
||||
|
||||
/** One requirement gate on an interaction (section 18). */
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINTERACTION_API FInteractionRequirement
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
EInteractionRequirementType RequirementType = EInteractionRequirementType::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FGameplayTag RequiredTag;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FName RequiredItemId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
float RequiredValue = 0.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
FText FailureText;
|
||||
};
|
||||
|
||||
/** Timing / interruption data for an action (section 19). */
|
||||
USTRUCT(BlueprintType)
|
||||
struct ITEMINTERACTION_API FInteractionExecutionData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
bool bHasDuration = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
float Duration = 0.f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
bool bCanBeInterrupted = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
bool bLockItemDuringAction = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
|
||||
bool bLockTargetDuringAction = false;
|
||||
};
|
||||
72
Source/ItemInteraction/Public/ItemInteractionActions.h
Normal file
72
Source/ItemInteraction/Public/ItemInteractionActions.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "InteractionAction.h"
|
||||
#include "ItemInteractionActions.generated.h"
|
||||
|
||||
/**
|
||||
* Concrete interaction actions (section 16). These are the Blueprint-droppable
|
||||
* authoring units: add one to an InteractableComponent's AvailableActions and the
|
||||
* resolver/executor will surface and run it. Built-in modes are also dispatched
|
||||
* directly by the interaction component, so both paths reach the same effect.
|
||||
*/
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Instant Pickup"))
|
||||
class ITEMINTERACTION_API UInteractionAction_InstantPickup : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_InstantPickup();
|
||||
virtual void Execute_Implementation(const FInteractionContext& Context) override;
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Open Loot UI"))
|
||||
class ITEMINTERACTION_API UInteractionAction_OpenLootUI : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_OpenLootUI();
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Inspect"))
|
||||
class ITEMINTERACTION_API UInteractionAction_Inspect : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_Inspect();
|
||||
virtual void Execute_Implementation(const FInteractionContext& Context) override;
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Combine"))
|
||||
class ITEMINTERACTION_API UInteractionAction_Combine : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_Combine();
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Transfer Container Item"))
|
||||
class ITEMINTERACTION_API UInteractionAction_TransferContainerItem : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_TransferContainerItem();
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Split Stack"))
|
||||
class ITEMINTERACTION_API UInteractionAction_SplitStack : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_SplitStack();
|
||||
};
|
||||
|
||||
UCLASS(meta = (DisplayName = "Action: Merge Stack"))
|
||||
class ITEMINTERACTION_API UInteractionAction_MergeStack : public UInteractionAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UInteractionAction_MergeStack();
|
||||
};
|
||||
178
Source/ItemInteraction/Public/ItemInteractionComponent.h
Normal file
178
Source/ItemInteraction/Public/ItemInteractionComponent.h
Normal file
@ -0,0 +1,178 @@
|
||||
// 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<FLifetimeProperty>& 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;
|
||||
};
|
||||
84
Source/ItemInteraction/Public/ItemInteractionInterfaces.h
Normal file
84
Source/ItemInteraction/Public/ItemInteractionInterfaces.h
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "ItemInstanceData.h"
|
||||
#include "ItemInteractionInterfaces.generated.h"
|
||||
|
||||
/**
|
||||
* Implemented by world actors that represent a concrete item (e.g. APickupItemActor).
|
||||
* Lets the interaction layer read/take the item without ItemInteraction depending on
|
||||
* ItemWorld - the dependency only flows World -> Interaction.
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType)
|
||||
class UWorldItemProvider : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class ITEMINTERACTION_API IWorldItemProvider
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Returns the item this actor carries. */
|
||||
virtual bool GetProvidedItem(FItemInstanceData& OutItem) const = 0;
|
||||
|
||||
/** True if the provider is currently locked/claimed by another interaction. */
|
||||
virtual bool IsItemAvailable() const { return true; }
|
||||
|
||||
/** Server: the item was taken into an inventory/hands - tear down the world actor. */
|
||||
virtual void NotifyItemTaken(AActor* Taker) {}
|
||||
|
||||
/** Server: only part of the stack was taken; write back the remaining item. */
|
||||
virtual void UpdateProvidedItem(const FItemInstanceData& RemainingItem) {}
|
||||
|
||||
/** Server: mark this world item as being physically carried by Holder (or released). */
|
||||
virtual void SetCarried(AActor* Holder, bool bCarried) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implemented by whatever tracks the pawn's currently held/carried item
|
||||
* (the character or its UHeldItemComponent). The resolver queries it to build
|
||||
* "held + target" interactions without depending on ItemWorld.
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType)
|
||||
class UHeldItemInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class ITEMINTERACTION_API IHeldItemInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual bool GetHeldItem(FItemInstanceData& OutItem) const = 0;
|
||||
virtual bool HasHeldItem() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implemented by world objects that react to a held item being used on them
|
||||
* (section 15): pour fuel into a generator, barricade a door, place bait, etc.
|
||||
* Found on the target actor or any of its components.
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType)
|
||||
class UItemUseTarget : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class ITEMINTERACTION_API IItemUseTarget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Server: apply the held item to this target for the given result verb (e.g.
|
||||
* "TransferFuel"). May mutate HeldItem (reduce fuel/charge). Returns true if it
|
||||
* did something - the caller then writes HeldItem back to the held component.
|
||||
*/
|
||||
virtual bool ReceiveHeldUse(FName ResultVerb, FItemInstanceData& HeldItem, AActor* Instigator) = 0;
|
||||
};
|
||||
8
Source/ItemInteraction/Public/ItemInteractionLog.h
Normal file
8
Source/ItemInteraction/Public/ItemInteractionLog.h
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
ITEMINTERACTION_API DECLARE_LOG_CATEGORY_EXTERN(LogItemInteraction, Log, All);
|
||||
Reference in New Issue
Block a user