// Copyright ExByte Studios. Item Interaction Ecosystem. #include "WorldInteractionComponent.h" #include "HeldItemComponent.h" #include "EquipmentComponent.h" #include "WorldItemSlotComponent.h" #include "PickupItemActor.h" #include "ItemWorldLog.h" #include "InventoryComponent.h" #include "ItemDatabaseSubsystem.h" #include "ItemDefinitionRow.h" #include "ItemAuxiliaryRows.h" #include "ItemInteractionInterfaces.h" #include "GameFramework/Pawn.h" bool UWorldInteractionComponent::ExecuteAuthorized(const FInteractionContext& Context, const FInteractionOption& Option, const FInteractionRequest& Request) { switch (Option.Mode) { case EItemInteractionMode::CarryAttached: return ExecuteCarry(Context, /*bPhysics*/ false); case EItemInteractionMode::CarryPhysicsHandle: return ExecuteCarry(Context, /*bPhysics*/ true); case EItemInteractionMode::DropHeldItem: return ExecuteDropHeld(Context); case EItemInteractionMode::InstallIntoWorldSlot: return ExecuteInstall(Context); case EItemInteractionMode::RemoveFromWorldSlot: return ExecuteRemoveFromSlot(Context); case EItemInteractionMode::EquipFromInventory: return ExecuteEquip(Context, Request); case EItemInteractionMode::UseHeldOnTarget: return ExecuteUseHeldOnTarget(Context, Option); default: return Super::ExecuteAuthorized(Context, Option, Request); } } bool UWorldInteractionComponent::ExecuteCarry(const FInteractionContext& Context, bool bPhysics) { UHeldItemComponent* Held = GetOwner()->FindComponentByClass(); if (!Held || !Context.TargetActor) { return false; } return bPhysics ? Held->ServerHoldWorldItemPhysics(Context.TargetActor) : Held->ServerHoldWorldItem(Context.TargetActor); } bool UWorldInteractionComponent::ExecuteDropHeld(const FInteractionContext& Context) { UHeldItemComponent* Held = GetOwner()->FindComponentByClass(); return Held && Held->ServerDropHeldItem(); } bool UWorldInteractionComponent::ExecuteInstall(const FInteractionContext& Context) { UHeldItemComponent* Held = GetOwner()->FindComponentByClass(); if (!Held || !Held->HasHeldItem() || !Context.TargetActor) { return false; } FItemInstanceData HeldItem; Held->GetHeldItem(HeldItem); // Find a compatible, free slot on the target. UWorldItemSlotComponent* Chosen = nullptr; TArray Slots; Context.TargetActor->GetComponents(Slots); for (UWorldItemSlotComponent* Slot : Slots) { if (Slot && Slot->CanInstall(HeldItem)) { Chosen = Slot; break; } } if (!Chosen) { UE_LOG(LogItemWorld, Warning, TEXT("[ServerReject] Install failed: no compatible free slot.")); return false; } // Hand the carried actor over to the slot as its visual. FItemInstanceData Item; AActor* Visual = nullptr; if (!Held->ServerHandOffHeldItem(Item, Visual)) { return false; } if (!Chosen->InstallItem(Item, Visual)) { // Roll back: drop the item back to the world so it is not lost. const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 100.f); if (APickupItemActor* Pickup = Cast(Visual)) { Pickup->ReleaseToWorld(Xf); } else { APickupItemActor::SpawnPickupFromInstance(this, Item, Xf); if (Visual) { Visual->Destroy(); } } return false; } return true; } bool UWorldInteractionComponent::ExecuteEquip(const FInteractionContext& Context, const FInteractionRequest& Request) { UEquipmentComponent* Equip = GetOwner()->FindComponentByClass(); if (!Equip) { return false; } FGameplayTag SlotTag = Request.TargetSlotTag; if (!SlotTag.IsValid() && Context.PlayerInventory) { // Fall back to the item's first allowed equipment slot. FItemInstanceData Item; if (Context.PlayerInventory->FindItem(Request.ItemInstanceId, Item)) { if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this)) { if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId)) { if (!Def->AllowedEquipmentSlots.IsEmpty()) { SlotTag = Def->AllowedEquipmentSlots.First(); } } } } } return Equip->ServerEquip(Request.ItemInstanceId, SlotTag); } bool UWorldInteractionComponent::ExecuteUseHeldOnTarget(const FInteractionContext& Context, const FInteractionOption& Option) { UHeldItemComponent* Held = GetOwner()->FindComponentByClass(); if (!Held || !Held->HasHeldItem() || !Context.TargetActor) { return false; } FItemInstanceData HeldItem; Held->GetHeldItem(HeldItem); // Find the result verb from the data-driven interaction row for this held item. FName ResultVerb = NAME_None; if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this)) { TArray Rows; DB->GetInteractionRows(HeldItem.ItemId, Rows); for (const FItemInteractionRow& Row : Rows) { if (Row.ActionTag == Option.ActionTag) { ResultVerb = Row.Result; break; } } } if (ResultVerb.IsNone()) { return false; } // Locate an IItemUseTarget on the target actor or its components. IItemUseTarget* UseTarget = Cast(Context.TargetActor); if (!UseTarget) { for (UActorComponent* Comp : Context.TargetActor->GetComponents()) { if (IItemUseTarget* AsTarget = Cast(Comp)) { UseTarget = AsTarget; break; } } } if (!UseTarget) { return false; } if (UseTarget->ReceiveHeldUse(ResultVerb, HeldItem, GetOwner())) { Held->ServerUpdateHeldItem(HeldItem); return true; } return false; } bool UWorldInteractionComponent::ExecuteRemoveFromSlot(const FInteractionContext& Context) { if (!Context.TargetActor) { return false; } // Find the first occupied slot on the target. UWorldItemSlotComponent* Occupied = nullptr; TArray Slots; Context.TargetActor->GetComponents(Slots); for (UWorldItemSlotComponent* Slot : Slots) { if (Slot && Slot->IsOccupied()) { Occupied = Slot; break; } } if (!Occupied) { return false; } FItemInstanceData Item; if (!Occupied->RemoveItem(Item)) { return false; } // Prefer the player's inventory; otherwise drop it at their feet. UInventoryComponent* Inv = Context.PlayerInventory; if (Inv && Inv->AddItem(Item) && Item.Quantity <= 0) { return true; } const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 120.f + FVector(0, 0, 30.f)); APickupItemActor::SpawnPickupFromInstance(this, Item, Xf); return true; } bool UWorldInteractionComponent::HandleDropFromInventory(FGuid InstanceId) { APawn* Pawn = Cast(GetOwner()); UInventoryComponent* Inv = Pawn ? Pawn->FindComponentByClass() : nullptr; if (!Inv) { return false; } FItemInstanceData Item; if (!Inv->FindItem(InstanceId, Item) || Item.Lock.bLocked) { return false; } const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this); const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr; if (Def && !Def->bCanDrop) { UE_LOG(LogItemWorld, Warning, TEXT("[ServerReject] Drop failed: %s cannot be dropped."), *Item.ItemId.ToString()); return false; } const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 120.f + FVector(0, 0, 20.f)); if (APickupItemActor::SpawnPickupFromInstance(this, Item, Xf)) { Inv->RemoveItem(InstanceId); UE_LOG(LogItemWorld, Log, TEXT("[Inventory] dropped %s"), *Item.ItemId.ToString()); return true; } return false; }