// 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& 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(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(); 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(GetOwner()); AActor* Spawned = GetWorld()->SpawnActor(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(); }