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>
This commit is contained in:
150
Source/ItemWorld/Private/EquipmentComponent.cpp
Normal file
150
Source/ItemWorld/Private/EquipmentComponent.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
// 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();
|
||||
}
|
||||
Reference in New Issue
Block a user