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:
162
Source/ItemInteraction/Private/InteractionAction.cpp
Normal file
162
Source/ItemInteraction/Private/InteractionAction.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
||||
|
||||
#include "InteractionAction.h"
|
||||
#include "InteractableComponent.h"
|
||||
#include "ItemInteractionLog.h"
|
||||
#include "ItemNativeTags.h"
|
||||
#include "ItemDatabaseSubsystem.h"
|
||||
#include "ItemDefinitionRow.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Picks the item a requirement should inspect: held first, else the target item. */
|
||||
const FItemInstanceData* ResolveSubjectItem(const FInteractionContext& Ctx)
|
||||
{
|
||||
if (Ctx.bHasHeldItem) { return &Ctx.HeldItem; }
|
||||
if (Ctx.bHasTargetItem) { return &Ctx.TargetItem; }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ItemHasTag(const UObject* WorldCtx, const FItemInstanceData& Item, const FGameplayTag& Tag)
|
||||
{
|
||||
if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldCtx))
|
||||
{
|
||||
if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
|
||||
{
|
||||
return Def->ItemTags.HasTag(Tag) || Def->ItemType.MatchesTag(Tag);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EvaluateRequirement(const FInteractionRequirement& Req, const FInteractionContext& Ctx)
|
||||
{
|
||||
switch (Req.RequirementType)
|
||||
{
|
||||
case EInteractionRequirementType::None:
|
||||
return true;
|
||||
|
||||
case EInteractionRequirementType::HasInventorySpace:
|
||||
{
|
||||
if (!Ctx.PlayerInventory) { return false; }
|
||||
const FItemInstanceData* Subject = Ctx.bHasTargetItem ? &Ctx.TargetItem : ResolveSubjectItem(Ctx);
|
||||
if (!Subject) { return true; }
|
||||
TArray<FInventoryContainer> Containers;
|
||||
Ctx.PlayerInventory->GetContainers(Containers);
|
||||
for (const FInventoryContainer& C : Containers)
|
||||
{
|
||||
if (Ctx.PlayerInventory->CanAcceptItem(*Subject, C.ContainerId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case EInteractionRequirementType::HasItemWithTag:
|
||||
{
|
||||
if (!Ctx.PlayerInventory) { return false; }
|
||||
TArray<FItemInstanceData> Items;
|
||||
Ctx.PlayerInventory->GetAllItems(Items);
|
||||
for (const FItemInstanceData& It : Items)
|
||||
{
|
||||
if (ItemHasTag(Ctx.PlayerInventory.Get(), It, Req.RequiredTag)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case EInteractionRequirementType::HasHeldItemWithTag:
|
||||
return Ctx.bHasHeldItem && ItemHasTag(Ctx.Character.Get(), Ctx.HeldItem, Req.RequiredTag);
|
||||
|
||||
case EInteractionRequirementType::PropertyGreaterThan:
|
||||
{
|
||||
const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
|
||||
return Subject && Subject->RuntimeProperties.GetFloat(Req.RequiredTag) > Req.RequiredValue;
|
||||
}
|
||||
|
||||
case EInteractionRequirementType::PropertyLessThan:
|
||||
{
|
||||
const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
|
||||
return Subject && Subject->RuntimeProperties.GetFloat(Req.RequiredTag) < Req.RequiredValue;
|
||||
}
|
||||
|
||||
case EInteractionRequirementType::TargetHasFreeSlotTag:
|
||||
// Re-validated against the world slot component at execution time (stage 8).
|
||||
return Ctx.TargetActor != nullptr;
|
||||
|
||||
case EInteractionRequirementType::NotWet:
|
||||
{
|
||||
const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
|
||||
return !Subject || Subject->RuntimeProperties.GetFloat(ItemEcosystemTags::Property_Wetness) < 0.5f;
|
||||
}
|
||||
|
||||
case EInteractionRequirementType::NotBroken:
|
||||
{
|
||||
const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
|
||||
return !Subject || !Subject->StateTags.HasTag(ItemEcosystemTags::State_Broken);
|
||||
}
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UInteractionAction::CanExecute_Implementation(const FInteractionContext& Context, FText& OutDisabledReason) const
|
||||
{
|
||||
for (const FInteractionRequirement& Req : Requirements)
|
||||
{
|
||||
if (!EvaluateRequirement(Req, Context))
|
||||
{
|
||||
OutDisabledReason = Req.FailureText.IsEmpty()
|
||||
? NSLOCTEXT("ItemInteraction", "RequirementFailed", "Requirement not met")
|
||||
: Req.FailureText;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UInteractionAction::Execute_Implementation(const FInteractionContext& Context)
|
||||
{
|
||||
// Base does nothing; concrete actions / the interaction component handle effects.
|
||||
UE_LOG(LogItemInteraction, Verbose, TEXT("UInteractionAction '%s' base Execute (no-op)."), *ActionTag.ToString());
|
||||
}
|
||||
|
||||
FInteractionOption UInteractionAction::BuildOption(const FInteractionContext& Context) const
|
||||
{
|
||||
FInteractionOption Option;
|
||||
Option.ActionTag = ActionTag;
|
||||
Option.Mode = Mode;
|
||||
Option.DisplayText = DisplayText;
|
||||
Option.Priority = Priority;
|
||||
Option.bRequiresHold = bRequiresHold;
|
||||
Option.HoldDuration = HoldDuration;
|
||||
Option.ActionDuration = Execution.bHasDuration ? Execution.Duration : 0.f;
|
||||
|
||||
FText Reason;
|
||||
Option.bIsEnabled = CanExecute(Context, Reason);
|
||||
Option.DisabledReason = Reason;
|
||||
|
||||
Option.Request.Mode = Mode;
|
||||
Option.Request.ActionTag = ActionTag;
|
||||
Option.Request.TargetActor = Context.TargetActor;
|
||||
if (Context.bHasTargetItem) { Option.Request.ItemInstanceId = Context.TargetItem.InstanceId; }
|
||||
else if (Context.bHasHeldItem) { Option.Request.ItemInstanceId = Context.HeldItem.InstanceId; }
|
||||
return Option;
|
||||
}
|
||||
|
||||
UWorld* UInteractionAction::GetWorld() const
|
||||
{
|
||||
if (HasAllFlags(RF_ClassDefaultObject))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (const UObject* Outer = GetOuter())
|
||||
{
|
||||
return Outer->GetWorld();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user