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