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

151 lines
3.6 KiB
C++

// Copyright ExByte Studios. Item Interaction Ecosystem.
#include "EquipmentComponent.h"
#include "ItemWorldLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "InventoryComponent.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"
#include "Net/UnrealNetwork.h"
UEquipmentComponent::UEquipmentComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
}
void UEquipmentComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UEquipmentComponent, Equipped);
}
void UEquipmentComponent::OnUnregister()
{
if (HasAuthority())
{
for (const FEquippedItemEntry& Entry : Equipped)
{
if (Entry.EquippedActor)
{
Entry.EquippedActor->Destroy();
}
}
}
Super::OnUnregister();
}
bool UEquipmentComponent::HasAuthority() const
{
return GetOwner() && GetOwner()->HasAuthority();
}
USceneComponent* UEquipmentComponent::GetAttachTarget() const
{
if (const ACharacter* Char = Cast<ACharacter>(GetOwner()))
{
return Char->GetMesh();
}
return GetOwner() ? GetOwner()->GetRootComponent() : nullptr;
}
FName UEquipmentComponent::ResolveSocket(const FGameplayTag& SlotTag) const
{
if (const FName* Found = SlotSockets.Find(SlotTag))
{
return *Found;
}
return DefaultEquipSocket;
}
bool UEquipmentComponent::IsSlotEquipped(FGameplayTag SlotTag) const
{
return Equipped.ContainsByPredicate([&SlotTag](const FEquippedItemEntry& E) { return E.SlotTag == SlotTag; });
}
bool UEquipmentComponent::ServerEquip(FGuid InstanceId, FGameplayTag SlotTag)
{
if (!HasAuthority())
{
return false;
}
UInventoryComponent* Inv = GetOwner()->FindComponentByClass<UInventoryComponent>();
if (!Inv)
{
return false;
}
FItemInstanceData Item;
if (!Inv->FindItem(InstanceId, Item))
{
return false;
}
const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
if (!Def || !Def->bCanEquip)
{
return false;
}
// Slot compatibility (if the item restricts equipment slots).
if (!Def->AllowedEquipmentSlots.IsEmpty() && !Def->AllowedEquipmentSlots.HasTag(SlotTag))
{
return false;
}
// Free the slot first.
ServerUnequip(SlotTag);
FEquippedItemEntry Entry;
Entry.SlotTag = SlotTag;
Entry.InstanceId = InstanceId;
Entry.ItemId = Item.ItemId;
if (!Def->EquippedActorClass.IsNull())
{
if (UClass* ActorClass = Def->EquippedActorClass.LoadSynchronous())
{
FActorSpawnParameters Params;
Params.Owner = GetOwner();
Params.Instigator = Cast<APawn>(GetOwner());
AActor* Spawned = GetWorld()->SpawnActor<AActor>(ActorClass, FTransform::Identity, Params);
if (Spawned)
{
Spawned->AttachToComponent(GetAttachTarget(), FAttachmentTransformRules::SnapToTargetIncludingScale, ResolveSocket(SlotTag));
Entry.EquippedActor = Spawned;
}
}
}
Equipped.Add(Entry);
OnEquipmentChanged.Broadcast();
UE_LOG(LogItemWorld, Log, TEXT("[Inventory] equipped %s in %s"), *Item.ItemId.ToString(), *SlotTag.ToString());
return true;
}
bool UEquipmentComponent::ServerUnequip(FGameplayTag SlotTag)
{
if (!HasAuthority())
{
return false;
}
const int32 Index = Equipped.IndexOfByPredicate([&SlotTag](const FEquippedItemEntry& E) { return E.SlotTag == SlotTag; });
if (Index == INDEX_NONE)
{
return false;
}
if (Equipped[Index].EquippedActor)
{
Equipped[Index].EquippedActor->Destroy();
}
Equipped.RemoveAt(Index);
OnEquipmentChanged.Broadcast();
return true;
}
void UEquipmentComponent::OnRep_Equipped()
{
OnEquipmentChanged.Broadcast();
}