Files
inventory-system-plugin/Source/ItemWorld/Private/WorldItemSlotComponent.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

130 lines
3.4 KiB
C++

// Copyright ExByte Studios. Item Interaction Ecosystem.
#include "WorldItemSlotComponent.h"
#include "PickupItemActor.h"
#include "ItemWorldLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "Net/UnrealNetwork.h"
UWorldItemSlotComponent::UWorldItemSlotComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
}
void UWorldItemSlotComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UWorldItemSlotComponent, InstalledItem);
DOREPLIFETIME(UWorldItemSlotComponent, InstalledVisual);
}
bool UWorldItemSlotComponent::HasAuthority() const
{
return GetOwner() && GetOwner()->HasAuthority();
}
USceneComponent* UWorldItemSlotComponent::GetAttachTarget() const
{
return GetOwner() ? GetOwner()->GetRootComponent() : nullptr;
}
bool UWorldItemSlotComponent::CanInstall(const FItemInstanceData& Item) const
{
if (InstalledItem.bHasItem || !Item.IsValid())
{
return false;
}
const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
if (!Def || !Def->bCanInstallIntoWorldSlot)
{
return false;
}
// The item must declare this slot as compatible.
if (!Def->CompatibleWorldSlots.HasTag(SlotTag))
{
return false;
}
// Optional extra tag gate.
if (!AcceptedItemTags.IsEmpty())
{
FGameplayTagContainer ItemTags = Def->ItemTags;
ItemTags.AddTag(Def->ItemType);
if (!ItemTags.HasAny(AcceptedItemTags))
{
return false;
}
}
return true;
}
bool UWorldItemSlotComponent::InstallItem(const FItemInstanceData& Item, AActor* ProvidedVisual)
{
if (!HasAuthority() || !CanInstall(Item))
{
return false;
}
InstalledItem.bHasItem = true;
InstalledItem.Item = Item;
InstalledItem.Item.LocationType = EItemLocationType::Installed;
if (ProvidedVisual)
{
// Reuse the carried actor as the visual.
if (USceneComponent* Target = GetAttachTarget())
{
ProvidedVisual->AttachToComponent(Target, FAttachmentTransformRules::SnapToTargetIncludingScale, AttachSocketName);
}
if (IWorldItemProvider* Provider = Cast<IWorldItemProvider>(ProvidedVisual))
{
Provider->SetCarried(GetOwner(), true); // keep collision off, locked
}
InstalledVisual = ProvidedVisual;
}
else
{
// Spawn a fresh visual pickup and attach it.
const FTransform Xf = GetAttachTarget() ? GetAttachTarget()->GetComponentTransform() : GetOwner()->GetActorTransform();
if (APickupItemActor* Visual = APickupItemActor::SpawnPickupFromInstance(this, Item, Xf))
{
Visual->SetCarried(GetOwner(), true);
if (USceneComponent* Target = GetAttachTarget())
{
Visual->AttachToComponent(Target, FAttachmentTransformRules::SnapToTargetIncludingScale, AttachSocketName);
}
InstalledVisual = Visual;
}
}
OnSlotChanged.Broadcast();
UE_LOG(LogItemWorld, Log, TEXT("[Interaction] installed %s into %s"), *Item.ItemId.ToString(), *SlotTag.ToString());
return true;
}
bool UWorldItemSlotComponent::RemoveItem(FItemInstanceData& OutItem)
{
if (!HasAuthority() || !InstalledItem.bHasItem)
{
return false;
}
OutItem = InstalledItem.Item;
if (InstalledVisual)
{
InstalledVisual->Destroy();
InstalledVisual = nullptr;
}
InstalledItem = FWorldSlotInstalledState();
OnSlotChanged.Broadcast();
return true;
}
void UWorldItemSlotComponent::OnRep_InstalledItem()
{
OnSlotChanged.Broadcast();
}