Files
inventory-system-plugin/Source/ItemInteraction/Private/InteractionAction.cpp
Bonchellon 7f7e043a88 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>
2026-06-22 20:49:56 +03:00

163 lines
4.9 KiB
C++

// 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;
}