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>
151 lines
3.6 KiB
C++
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();
|
|
}
|