Files
PHY/Plugins/GIS/Source/GenericInventorySystem/Private/Equipping/GIS_EquipmentSystemComponent.cpp
2026-03-03 01:23:02 +08:00

1174 lines
32 KiB
C++

// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIS_EquipmentSystemComponent.h"
#include "Engine/World.h"
#include "GameFramework/Controller.h"
#include "GIS_EquipmentInstance.h"
#include "GIS_InventorySystemComponent.h"
#include "GIS_ItemCollection.h"
#include "GIS_ItemDefinition.h"
#include "GIS_ItemFragment_Equippable.h"
#include "GIS_ItemInstance.h"
#include "GIS_ItemSlotCollection.h"
#include "GIS_LogChannels.h"
#include "Engine/ActorChannel.h"
#include "Net/UnrealNetwork.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_EquipmentSystemComponent)
UGIS_EquipmentSystemComponent::UGIS_EquipmentSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), Container(this)
{
PrimaryComponentTick.bCanEverTick = true;
SetIsReplicatedByDefault(true);
bReplicateUsingRegisteredSubObjectList = true;
bWantsInitializeComponent = true;
}
void UGIS_EquipmentSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, Container);
DOREPLIFETIME(ThisClass, bEquipmentSystemInitialized);
DOREPLIFETIME(ThisClass, TargetCollectionDefinition);
}
void UGIS_EquipmentSystemComponent::EquipItemToSlot(UGIS_ItemInstance* Item, const FGameplayTag& SlotTag)
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
if (!SlotTag.IsValid())
{
return;
}
UObject* EquipmentInstance = CreateEquipmentInstance(GetOwner(), Item);
if (!IsValid(EquipmentInstance))
{
return;
}
FGIS_EquipmentEntry NewEntry;
NewEntry.EquippedSlot = SlotTag;
NewEntry.Instance = EquipmentInstance;
NewEntry.ItemInstance = Item;
NewEntry.bActive = false;
NewEntry.bPrevActive = false;
AddEquipmentEntry(NewEntry);
if (auto Equippable = Item->FindFragmentByClass<UGIS_ItemFragment_Equippable>())
{
if (Equippable->bAutoActivate)
{
SetEquipmentActiveState(SlotTag, true);
}
}
}
void UGIS_EquipmentSystemComponent::UnequipBySlot(FGameplayTag SlotTag)
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
int32 Idx = Container.IndexOfBySlot(SlotTag);
if (Idx != INDEX_NONE)
{
RemoveEquipmentEntry(Idx);
}
}
void UGIS_EquipmentSystemComponent::UnequipByItem(const FGuid& ItemId)
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
int32 Idx = Container.IndexOfByItemId(ItemId);
if (Idx != INDEX_NONE)
{
RemoveEquipmentEntry(Idx);
}
}
bool UGIS_EquipmentSystemComponent::OwnerHasAuthority() const
{
AActor* Owner = GetOwner();
return IsValid(Owner) && Owner->HasAuthority();
}
TArray<UObject*> UGIS_EquipmentSystemComponent::GetEquipments(TSubclassOf<UObject> InstanceType, FGameplayTagQuery SlotQuery) const
{
TArray<UObject*> Results;
if (SlotQuery.IsEmpty())
{
return Results;
}
if (UClass* RealClass = InstanceType)
{
const TArray<FGIS_EquipmentEntry>& MatchedEntries = Container.Entries.FilterByPredicate([&SlotQuery, &RealClass](const FGIS_EquipmentEntry& Entry)
{
return Entry.Instance->IsA(RealClass) && SlotQuery.Matches(Entry.EquippedSlot.GetSingleTagContainer());
});
for (const FGIS_EquipmentEntry& Entry : MatchedEntries)
{
Results.AddUnique(Entry.Instance);
}
}
return Results;
}
TArray<UObject*> UGIS_EquipmentSystemComponent::GetActiveEquipments(TSubclassOf<UObject> InstanceType, FGameplayTagQuery SlotQuery) const
{
TArray<UObject*> Results;
if (SlotQuery.IsEmpty())
{
return Results;
}
if (UClass* RealClass = InstanceType)
{
const TArray<FGIS_EquipmentEntry>& MatchedEntries = Container.Entries.FilterByPredicate([&SlotQuery, &RealClass](const FGIS_EquipmentEntry& Entry)
{
return Entry.bActive && Entry.Instance->IsA(RealClass) && SlotQuery.Matches(Entry.EquippedSlot.GetSingleTagContainer());
});
for (const FGIS_EquipmentEntry& Entry : MatchedEntries)
{
Results.AddUnique(Entry.Instance);
}
}
return Results;
}
UObject* UGIS_EquipmentSystemComponent::GetEquipment(TSubclassOf<UObject> InstanceType, FGameplayTagQuery SlotQuery) const
{
if (UClass* RealClass = InstanceType)
{
const FGIS_EquipmentEntry* Found = Container.Entries.FindByPredicate([&SlotQuery, &RealClass](const FGIS_EquipmentEntry& Entry)
{
return Entry.Instance->IsA(RealClass) && SlotQuery.Matches(Entry.EquippedSlot.GetSingleTagContainer());
});
if (Found)
{
return Found->Instance;
}
}
return nullptr;
}
UObject* UGIS_EquipmentSystemComponent::GetActiveEquipment(TSubclassOf<UObject> InstanceType, FGameplayTagQuery SlotQuery) const
{
if (UClass* RealClass = InstanceType)
{
const FGIS_EquipmentEntry* Found = Container.Entries.FindByPredicate([&SlotQuery, &RealClass](const FGIS_EquipmentEntry& Entry)
{
return Entry.bActive && Entry.Instance->IsA(RealClass) && SlotQuery.Matches(Entry.EquippedSlot.GetSingleTagContainer());
});
if (Found)
{
return Found->Instance;
}
}
return nullptr;
}
UObject* UGIS_EquipmentSystemComponent::GetActiveEquipmentInGroup(FGameplayTag GroupTag, bool bExactMatch) const
{
if (!GroupTag.IsValid())
{
return nullptr;
}
int32 Idx = Container.Entries.IndexOfByPredicate([GroupTag,bExactMatch](const FGIS_EquipmentEntry& Entry)
{
return Entry.bActive && Entry.EquippedGroup.IsValid() && bExactMatch ? Entry.EquippedGroup.MatchesTagExact(GroupTag) : Entry.EquippedGroup.MatchesTag(GroupTag);
});
if (Idx != INDEX_NONE)
{
return Container.Entries[Idx].Instance;
}
return nullptr;
}
UGIS_EquipmentInstance* UGIS_EquipmentSystemComponent::GetEquipmentInstanceOfActor(AActor* EquipmentActor) const
{
if (IsValid(EquipmentActor))
{
for (const FGIS_EquipmentEntry& Entry : Container.Entries)
{
if (UGIS_EquipmentInstance* Instance = Cast<UGIS_EquipmentInstance>(Entry.Instance))
{
if (Instance->GetEquipmentActors().Contains(EquipmentActor))
{
return Instance;
}
}
}
}
return nullptr;
}
UGIS_EquipmentInstance* UGIS_EquipmentSystemComponent::GetTypedEquipmentInstanceOfActor(TSubclassOf<UGIS_EquipmentInstance> InstanceType, AActor* EquipmentActor) const
{
if (UClass* RealClass = InstanceType)
{
if (UGIS_EquipmentInstance* Instance = GetEquipmentInstanceOfActor(EquipmentActor))
{
if (Instance->GetClass()->IsChildOf(RealClass))
{
return Instance;
}
}
}
return nullptr;
}
bool UGIS_EquipmentSystemComponent::IsSlotEquipped(FGameplayTag SlotTag) const
{
return SlotToInstanceMap.Contains(SlotTag);
}
FGameplayTag UGIS_EquipmentSystemComponent::GetSlotByEquipment(UObject* Equipment) const
{
if (!IsValid(Equipment))
{
return FGameplayTag::EmptyTag;
}
int32 Idx = Container.IndexOfByInstance(Equipment);
if (Idx != INDEX_NONE)
{
return Container.Entries[Idx].EquippedSlot;
}
return FGameplayTag::EmptyTag;
}
FGameplayTag UGIS_EquipmentSystemComponent::GetSlotByItem(const UGIS_ItemInstance* Item) const
{
if (!IsValid(Item))
{
return FGameplayTag::EmptyTag;
}
int32 Idx = Container.IndexOfByItem(Item);
if (Idx != INDEX_NONE)
{
return Container.Entries[Idx].EquippedSlot;
}
return FGameplayTag::EmptyTag;
}
int32 UGIS_EquipmentSystemComponent::SlotTagToEquipmentInex(FGameplayTag InSlotTag) const
{
return Container.IndexOfBySlot(InSlotTag);
}
int32 UGIS_EquipmentSystemComponent::ItemIdToEquipmentInex(FGuid InItemId) const
{
return Container.IndexOfByItemId(InItemId);
}
UObject* UGIS_EquipmentSystemComponent::GetEquipmentInSlot(FGameplayTag SlotTag) const
{
int32 Idx = Container.IndexOfBySlot(SlotTag);
if (Idx != INDEX_NONE)
{
return Container.Entries[Idx].Instance;
}
return nullptr;
}
UObject* UGIS_EquipmentSystemComponent::GetEquipmentByItem(const UGIS_ItemInstance* Item)
{
if (Item == nullptr)
{
return nullptr;
}
int32 Idx = Container.IndexOfByItem(Item);
if (Idx != INDEX_NONE)
{
return Container.Entries[Idx].Instance;
}
return nullptr;
}
void UGIS_EquipmentSystemComponent::SetEquipmentActiveState(FGameplayTag SlotTag, bool NewActiveState)
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
int32 Idx = Container.IndexOfBySlot(SlotTag);
if (Idx == INDEX_NONE)
{
GIS_CLOG(Warning, "invalid equipment entry at slot:%s.", *SlotTag.ToString())
return;
}
const FGIS_EquipmentEntry& Entry = Container.Entries[Idx];
// same state, return early.
if (Entry.bActive == NewActiveState)
{
GIS_CLOG(Warning, "trying to set same active state to equipment entry at slot:%s.", *SlotTag.ToString())
return;
}
// check if the item is groupped.
FGameplayTag MatchingGroup = FindMatchingGroupForSlot(Entry.EquippedSlot);
// has group
if (MatchingGroup.IsValid())
{
//check occupied if trying to active.
if (NewActiveState)
{
int32 OccupiedIdx = Container.IndexOfByGroup(MatchingGroup);
// Already occupied and is not this one.
if (OccupiedIdx != INDEX_NONE && OccupiedIdx != Idx)
{
GIS_CLOG(Warning, "can't active equipment at idx(%d) due to target slot(%s) within group(%s) was occupied.", Idx, *SlotTag.ToString(), *MatchingGroup.ToString())
return;
}
}
UpdateEquipmentState(Idx, NewActiveState, MatchingGroup);
}
else
{
UpdateEquipmentState(Idx, NewActiveState, FGameplayTag::EmptyTag);
}
}
void UGIS_EquipmentSystemComponent::ServerSetEquipmentActiveState_Implementation(FGameplayTag SlotTag, bool NewActiveState)
{
SetEquipmentActiveState(SlotTag, NewActiveState);
}
void UGIS_EquipmentSystemComponent::UpdateEquipmentState(int32 Idx, bool NewActiveState, FGameplayTag NewGroup)
{
check(Container.Entries.IsValidIndex(Idx))
if (Container.Entries[Idx].bActive == NewActiveState)
{
// Same state, return.
return;
}
FGIS_EquipmentEntry& Entry = Container.Entries[Idx];
Entry.bActive = NewActiveState;
Entry.PrevEquippedGroup = Entry.EquippedGroup;
Entry.EquippedGroup = NewActiveState ? NewGroup : FGameplayTag::EmptyTag;
OnEquipmentEntryChanged(Entry, Idx);
Container.MarkItemDirty(Entry);
}
void UGIS_EquipmentSystemComponent::OnTargetCollectionChanged(const FGIS_InventoryStackUpdateMessage& Message)
{
// only handle equip/unequip on the server side.
if (!OwnerHasAuthority())
{
return;
}
// Make use this message comes from the inventory&&collection I care about.
bool bIsMyConcern = IsValid(Message.Inventory) && Message.Inventory == Inventory && Message.CollectionId == TargetCollection->GetCollectionId();
if (!bIsMyConcern)
{
return;
}
switch (Message.ChangeType)
{
case EGIS_ItemStackChangeType::WasAdded:
{
FGameplayTag SlotTag = TargetCollection->GetItemSlotName(Message.Instance);
if (!SlotTag.IsValid())
{
return;
}
EquipItemToSlot(Message.Instance, SlotTag);
return;
}
case EGIS_ItemStackChangeType::WasRemoved:
{
UnequipByItem(Message.Instance->GetItemId());
}
default:
break;
}
}
void UGIS_EquipmentSystemComponent::ProcessPendingEquipments()
{
if (!bEquipmentSystemInitialized || !HasBegunPlay() || GetOwner() == nullptr)
{
return;
}
TArray<int32> AddedEquipments;
for (auto& Pair : PendingEquipmentEntries)
{
if (Pair.Value.CheckClientDataReady())
{
AddedEquipments.Add(Pair.Key);
}
}
for (int32 i = 0; i < AddedEquipments.Num(); i++)
{
int32 idx = AddedEquipments[i];
if (PendingEquipmentEntries.Contains(idx))
{
const FGIS_EquipmentEntry& Entry = PendingEquipmentEntries[idx];
OnEquipmentEntryAdded(Entry, idx);
PendingEquipmentEntries.Remove(idx);
}
}
}
void UGIS_EquipmentSystemComponent::OnEquipmentSystemInitialized_Implementation()
{
OnEquipmentSystemInitializedEvent.Broadcast();
TArray<FGIS_EquipmentSystem_Initialized_DynamicEvent> Delegates = InitializedDelegates;
for (FGIS_EquipmentSystem_Initialized_DynamicEvent Delegate : Delegates)
{
Delegate.ExecuteIfBound();
}
InitializedDelegates.Empty();
}
void UGIS_EquipmentSystemComponent::OnTargetCollectionRemoved(UGIS_ItemCollection* Collection)
{
if (Collection && TargetCollection && TargetCollection == Collection)
{
ResetEquipmentSystem();
}
}
bool UGIS_EquipmentSystemComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
for (FGIS_EquipmentEntry& Entry : Container.Entries)
{
if (IsValid(Entry.Instance))
{
if (IGIS_EquipmentInterface::Execute_IsReplicationManaged(Entry.Instance))
{
WroteSomething |= Channel->ReplicateSubobject(Entry.Instance, *Bunch, *RepFlags);
}
}
}
return WroteSomething;
}
void UGIS_EquipmentSystemComponent::OnRegister()
{
Super::OnRegister();
}
void UGIS_EquipmentSystemComponent::InitializeComponent()
{
Super::InitializeComponent();
if (GetWorld() && !GetWorld()->IsGameWorld())
{
return;
}
Container.OwningComponent = this;
if (!GetOwner()->IsUsingRegisteredSubObjectList())
{
GIS_CLOG(Error, "requires enable bReplicateUsingRegisteredSubObjectList.")
}
}
void UGIS_EquipmentSystemComponent::ReadyForReplication()
{
Super::ReadyForReplication();
// Register existing Equipment Instance
if (IsUsingRegisteredSubObjectList())
{
for (const TObjectPtr<UObject>& PendingObject : PendingReplicatedEquipments)
{
if (!IsReplicatedSubObjectRegistered(PendingObject))
{
AddReplicatedSubObject(PendingObject);
}
}
PendingReplicatedEquipments.Empty();
}
}
// Called when the game starts
void UGIS_EquipmentSystemComponent::BeginPlay()
{
Super::BeginPlay();
if (bInitializeOnBeginPlay && OwnerHasAuthority())
{
InitializeEquipmentSystem();
}
}
void UGIS_EquipmentSystemComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGIS_EquipmentSystemComponent::TickComponent"), STAT_UGIS_EquipmentSystemComponent_TickComponent, STATGROUP_GIS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
ProcessPendingEquipments();
TickEquipmentEntries(DeltaTime);
}
void UGIS_EquipmentSystemComponent::UninitializeComponent()
{
Super::UninitializeComponent();
}
void UGIS_EquipmentSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (bInitializeOnBeginPlay)
{
ResetEquipmentSystem();
}
Super::EndPlay(EndPlayReason);
}
void UGIS_EquipmentSystemComponent::InitializeEquipmentSystem()
{
if (bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "already initialized or has no authority!")
return;
}
UGIS_InventorySystemComponent* InventorySystem = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
if (InventorySystem == nullptr)
{
InventorySystem = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetController<AController>());
}
if (!InventorySystem)
{
GIS_CLOG(Error, "doesn't have valid inventory system component!")
return;
}
InitializeEquipmentSystemWithInventory(InventorySystem);
}
void UGIS_EquipmentSystemComponent::InitializeEquipmentSystemWithInventory(UGIS_InventorySystemComponent* InventorySystem)
{
if (bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "already initialized or has no authority!")
return;
}
if (!IsValid(InventorySystem))
{
GIS_CLOG(Error, "the inventory is invalid!")
return;
}
if (!InventorySystem->IsInventoryInitialized())
{
GIS_CLOG(Error, "the inventory is not initialized!")
return;
}
if (!TargetCollectionTag.IsValid())
{
GIS_CLOG(Error, "doesn't have valid target collection tag!")
return;
}
UGIS_ItemSlotCollection* Collection = Cast<UGIS_ItemSlotCollection>(InventorySystem->GetCollectionByTag(GetTargetCollectionTag()));
if (Collection == nullptr)
{
GIS_CLOG(Error, "%s's inventory doesn't have valid item slot collection with name:%s", *InventorySystem->GetOwner()->GetName(), *TargetCollectionTag.ToString());
return;
}
Inventory = InventorySystem;
TargetCollection = Collection;
TargetCollectionDefinition = Collection->GetMyDefinition();
TArray<FGIS_EquipmentGroupEntry> Entries;
for (const TPair<FGameplayTag, FGIS_ItemSlotGroup>& Pair : TargetCollectionDefinition->SlotGroupMap)
{
FGIS_EquipmentGroupEntry Entry;
Entry.GroupTag = Pair.Key;
Entry.ActiveSlot = FGameplayTag::EmptyTag;
Entries.Add(Entry);
}
Inventory->OnInventoryStackUpdate.AddDynamic(this, &ThisClass::OnTargetCollectionChanged);
Inventory->OnCollectionRemovedEvent.AddDynamic(this, &ThisClass::OnTargetCollectionRemoved);
bEquipmentSystemInitialized = true;
OnEquipmentSystemInitialized();
for (const FGIS_ItemInfo& ItemInfo : TargetCollection->GetAllItemInfos())
{
FGameplayTag SlotTag = TargetCollection->GetItemSlotName(ItemInfo.Item);
EquipItemToSlot(ItemInfo.Item, SlotTag);
}
}
void UGIS_EquipmentSystemComponent::ResetEquipmentSystem()
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
RemoveAllEquipments();
if (IsValid(Inventory))
{
Inventory->OnCollectionRemovedEvent.RemoveDynamic(this, &ThisClass::OnTargetCollectionRemoved);
Inventory->OnInventoryStackUpdate.RemoveDynamic(this, &ThisClass::OnTargetCollectionChanged);
Inventory = nullptr;
TargetCollection = nullptr;
TargetCollectionDefinition = nullptr;
}
bEquipmentSystemInitialized = false;
OnEquipmentSystemInitialized();
}
bool UGIS_EquipmentSystemComponent::IsEquipmentSystemInitialized() const
{
return bEquipmentSystemInitialized;
}
void UGIS_EquipmentSystemComponent::BindToEquipmentSystemInitialized(FGIS_EquipmentSystem_Initialized_DynamicEvent Delegate)
{
if (bEquipmentSystemInitialized)
{
Delegate.ExecuteIfBound();
}
else
{
InitializedDelegates.Add(Delegate);
}
}
UGIS_EquipmentSystemComponent* UGIS_EquipmentSystemComponent::GetEquipmentSystemComponent(const AActor* Actor)
{
return IsValid(Actor) ? Actor->FindComponentByClass<UGIS_EquipmentSystemComponent>() : nullptr;
}
bool UGIS_EquipmentSystemComponent::FindEquipmentSystemComponent(const AActor* Actor, UGIS_EquipmentSystemComponent*& Component)
{
Component = (Actor ? Actor->FindComponentByClass<UGIS_EquipmentSystemComponent>() : nullptr);
return Component != nullptr;
}
bool UGIS_EquipmentSystemComponent::FindTypedEquipmentSystemComponent(AActor* Actor, TSubclassOf<UGIS_EquipmentSystemComponent> DesiredClass, UGIS_EquipmentSystemComponent*& Component)
{
if (UClass* RealClass = DesiredClass)
{
if (FindEquipmentSystemComponent(Actor, Component))
{
if (Component->GetClass()->IsChildOf(RealClass))
{
return true;
}
}
}
return false;
}
void UGIS_EquipmentSystemComponent::RemoveAllEquipments()
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
for (int32 i = 0; i < Container.Entries.Num(); i++)
{
RemoveEquipmentEntry(i);
}
}
void UGIS_EquipmentSystemComponent::AddEquipmentEntry(const FGIS_EquipmentEntry& NewEntry)
{
check(NewEntry.IsValidEntry())
int32 Idx = Container.Entries.AddDefaulted();
Container.Entries[Idx] = NewEntry;
AddReplicatedEquipmentObject(NewEntry.Instance);
OnEquipmentEntryAdded(NewEntry, Idx);
Container.MarkItemDirty(Container.Entries[Idx]);
}
void UGIS_EquipmentSystemComponent::TickEquipmentEntries(float DeltaTime)
{
if (!bEquipmentSystemInitialized)
{
return;
}
for (int32 i = 0; i < Container.Entries.Num(); i++)
{
if (Container.Entries[i].IsValidEntry())
{
IGIS_EquipmentInterface::Execute_OnEquipmentTick(Container.Entries[i].Instance, DeltaTime);
}
}
}
void UGIS_EquipmentSystemComponent::RemoveEquipmentEntry(int32 Idx)
{
check(Container.Entries.IsValidIndex(Idx));
const FGIS_EquipmentEntry& Entry = Container.Entries[Idx];
RemoveReplicatedEquipmentObject(Entry.Instance);
OnEquipmentEntryRemoved(Entry, Idx);
Container.Entries.RemoveAt(Idx);
Container.MarkArrayDirty();
}
UObject* UGIS_EquipmentSystemComponent::CreateEquipmentInstance_Implementation(AActor* Owner, UGIS_ItemInstance* ItemInstance) const
{
if (ItemInstance == nullptr)
{
GIS_CLOG(Error, "passed in invalid item instance.")
return nullptr;
}
const UGIS_ItemFragment_Equippable* EquippableItem = ItemInstance->FindFragmentByClass<UGIS_ItemFragment_Equippable>();
if (EquippableItem == nullptr)
{
GIS_CLOG(Error, "missing equippable fragment on item(%s)", *ItemInstance->GetDefinition()->GetName())
return nullptr;
}
TSubclassOf<UObject> InstanceType = !EquippableItem->InstanceType.IsNull() ? EquippableItem->InstanceType.LoadSynchronous() : nullptr;
if (InstanceType == nullptr)
{
GIS_CLOG(Error, "missing valid equipment instance type on item(%s)", *ItemInstance->GetDefinition()->GetName())
return nullptr;
}
if (!InstanceType->ImplementsInterface(UGIS_EquipmentInterface::StaticClass()))
{
GIS_CLOG(Error, "equipment instance type doesn't implement:%s", *UGIS_EquipmentInterface::StaticClass()->GetName())
return nullptr;
}
UObject* Instance;
if (EquippableItem->bActorBased)
{
FActorSpawnParameters SpawnParameters;
SpawnParameters.Owner = Owner;
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(InstanceType, FTransform::Identity, SpawnParameters);
if (SpawnedActor == nullptr)
{
GIS_CLOG(Error, "failed to create equipment instance of type:%s", *InstanceType->GetName())
return nullptr;
}
Instance = SpawnedActor;
}
else
{
UGIS_EquipmentInstance* EquipmentInstance = NewObject<UGIS_EquipmentInstance>(Owner, InstanceType); // Using the actor instead of component as the outer due to UE-127172
if (EquipmentInstance == nullptr)
{
GIS_CLOG(Error, "failed to create equipment instance of type:%s", *InstanceType->GetName())
return nullptr;
}
Instance = EquipmentInstance;
}
return Instance;
}
void UGIS_EquipmentSystemComponent::OnEquipmentEntryAdded(const FGIS_EquipmentEntry& Entry, int32 Idx)
{
APawn* OwningPawn = GetPawn<APawn>();
SlotToInstanceMap.Add(Entry.EquippedSlot, Entry.Instance);
// Initialize equipment instance
IGIS_EquipmentInterface::Execute_ReceiveOwningPawn(Entry.Instance, OwningPawn);
IGIS_EquipmentInterface::Execute_ReceiveSourceItem(Entry.Instance, Entry.ItemInstance);
IGIS_EquipmentInterface::Execute_OnEquipmentBeginPlay(Entry.Instance);
// Delay event broadcasting to ensure all initialization is complete
FGIS_EquipmentEntry AddedEntry = Entry;
GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateLambda([this, AddedEntry]()
{
OnEquipmentStateChangedEvent.Broadcast(AddedEntry.Instance, AddedEntry.EquippedSlot, true);
if (AddedEntry.bActive)
{
IGIS_EquipmentInterface::Execute_OnActiveStateChanged(AddedEntry.Instance, AddedEntry.bActive);
OnEquipmentActiveStateChangedEvent.Broadcast(AddedEntry.Instance, AddedEntry.EquippedSlot, AddedEntry.bActive);
}
if (AddedEntry.EquippedGroup.IsValid())
{
OnEquipmentGroupStateChangedEvent.Broadcast(AddedEntry.Instance, AddedEntry.EquippedSlot, AddedEntry.EquippedGroup);;
}
}));
}
void UGIS_EquipmentSystemComponent::OnEquipmentEntryChanged(const FGIS_EquipmentEntry& Entry, int32 Idx)
{
IGIS_EquipmentInterface::Execute_OnActiveStateChanged(Entry.Instance, Entry.bActive);
OnEquipmentActiveStateChangedEvent.Broadcast(Entry.Instance, Entry.EquippedSlot, Entry.bActive);
if (Entry.PrevEquippedGroup != Entry.EquippedGroup)
{
OnEquipmentGroupStateChangedEvent.Broadcast(Entry.Instance, Entry.EquippedSlot, Entry.EquippedGroup);
}
}
void UGIS_EquipmentSystemComponent::OnEquipmentEntryRemoved(const FGIS_EquipmentEntry& Entry, int32 Idx)
{
// remove but still active, so notify instance to do deactivate behavior.
SlotToInstanceMap.Remove(Entry.EquippedSlot);
if (IsValid(Entry.Instance)) // The instance may alreay in pending kill state, so no point to continues execution.
{
if (Entry.bActive)
{
IGIS_EquipmentInterface::Execute_OnActiveStateChanged(Entry.Instance, false);
}
IGIS_EquipmentInterface::Execute_OnEquipmentEndPlay(Entry.Instance);
IGIS_EquipmentInterface::Execute_ReceiveOwningPawn(Entry.Instance, nullptr);
IGIS_EquipmentInterface::Execute_ReceiveSourceItem(Entry.Instance, nullptr);
}
OnEquipmentStateChangedEvent.Broadcast(Entry.Instance, Entry.EquippedSlot, false);
OnEquipmentGroupStateChangedEvent.Broadcast(Entry.Instance, Entry.EquippedSlot, FGameplayTag::EmptyTag);
}
void UGIS_EquipmentSystemComponent::AddReplicatedEquipmentObject(TObjectPtr<UObject> Instance)
{
if (OwnerHasAuthority() && IsValid(Instance))
{
checkf(Instance->GetClass()->ImplementsInterface(UGIS_EquipmentInterface::StaticClass()), TEXT("%s doesn't implement GIS_EquipmentInterface"), *Instance->GetClass()->GetName())
bool IsReplicationManaged = IGIS_EquipmentInterface::Execute_IsReplicationManaged(Instance);
if (!IsReplicationManaged)
{
return;
}
if (IsReadyForReplication() && !IsReplicatedSubObjectRegistered(Instance))
{
AddReplicatedSubObject(Instance);
}
else
{
PendingReplicatedEquipments.AddUnique(Instance);
}
}
}
void UGIS_EquipmentSystemComponent::RemoveReplicatedEquipmentObject(TObjectPtr<UObject> Instance)
{
if (OwnerHasAuthority() && IsValid(Instance))
{
bool IsReplicationManaged = IGIS_EquipmentInterface::Execute_IsReplicationManaged(Instance);
if (IsReplicationManaged && IsReplicatedSubObjectRegistered(Instance))
{
RemoveReplicatedSubObject(Instance);
}
}
}
#pragma region Equipment Groups
TMap<int32, FGameplayTag> UGIS_EquipmentSystemComponent::GetLayoutOfGroup(FGameplayTag GroupTag) const
{
TMap<int32, FGameplayTag> GroupLayout;
if (TargetCollectionDefinition == nullptr)
{
return GroupLayout;
}
if (TargetCollectionDefinition->SlotGroupMap.Contains(GroupTag))
{
GroupLayout = TargetCollectionDefinition->SlotGroupMap[GroupTag].IndexToSlotMap;
}
return GroupLayout;
}
TMap<FGameplayTag, int32> UGIS_EquipmentSystemComponent::GetSlottedLayoutOfGroup(FGameplayTag GroupTag) const
{
TMap<FGameplayTag, int32> GroupLayout;
if (TargetCollectionDefinition == nullptr)
{
return GroupLayout;
}
if (TargetCollectionDefinition->SlotGroupMap.Contains(GroupTag))
{
GroupLayout = TargetCollectionDefinition->SlotGroupMap[GroupTag].SlotToIndexMap;
}
return GroupLayout;
}
FGameplayTag UGIS_EquipmentSystemComponent::FindMatchingGroupForSlot(FGameplayTag SlotTag) const
{
if (TargetCollectionDefinition->SlotGroupMap.IsEmpty())
{
return FGameplayTag::EmptyTag;
}
for (const TPair<FGameplayTag, FGIS_ItemSlotGroup>& Pair : TargetCollectionDefinition->SlotGroupMap)
{
if (Pair.Value.SlotToIndexMap.Contains(SlotTag))
{
return Pair.Key;
}
}
return FGameplayTag::EmptyTag;
}
TMap<FGameplayTag, UObject*> UGIS_EquipmentSystemComponent::GetSlottedEquipmentsOfGroup(FGameplayTag GroupTag) const
{
TMap<FGameplayTag, int32> GroupLayout = GetSlottedLayoutOfGroup(GroupTag);
TMap<FGameplayTag, UObject*> GroupedEquipments;
GroupedEquipments.Reserve(GroupLayout.Num());
for (auto& Pair : GroupLayout)
{
int32 Idx = Container.IndexOfBySlot(Pair.Key);
if (Idx != INDEX_NONE)
{
GroupedEquipments.Emplace(Pair.Key, Container.Entries[Idx].Instance);
}
else
{
GroupedEquipments.Emplace(Pair.Key, nullptr);
}
}
return GroupedEquipments;
}
TMap<int32, UObject*> UGIS_EquipmentSystemComponent::GetEquipmentsOfGroup(FGameplayTag GroupTag) const
{
TMap<int32, FGameplayTag> GroupLayout = GetLayoutOfGroup(GroupTag);
TMap<int32, UObject*> GroupedEquipments;
GroupedEquipments.Reserve(GroupLayout.Num());
for (auto& Pair : GroupLayout)
{
int32 Idx = Container.IndexOfBySlot(Pair.Value);
if (Idx != INDEX_NONE)
{
GroupedEquipments.Emplace(Pair.Key, Container.Entries[Idx].Instance);
}
else
{
GroupedEquipments.Emplace(Pair.Key, nullptr);
}
}
return GroupedEquipments;
}
void UGIS_EquipmentSystemComponent::SetGroupActiveSlot(FGameplayTag GroupTag, FGameplayTag NewSlot)
{
if (!bEquipmentSystemInitialized || !TargetCollectionDefinition || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
if (TargetCollectionDefinition->SlotGroupMap.IsEmpty())
{
GIS_CLOG(Warning, "no equipment groups setup within TargetCollectionDefinition(%s)!", *TargetCollectionDefinition->GetName())
return;
}
if (!GroupTag.IsValid())
{
GIS_CLOG(Warning, "requested invalid equipment group!")
return;
}
if (!TargetCollectionDefinition->SlotGroupMap.Contains(GroupTag))
{
GIS_CLOG(Warning, "requested equipment group doesn't exists within TargetCollectionDefinition(%s)!", *TargetCollectionDefinition->GetName())
return;
}
int32 IdxToReset = Container.IndexOfByGroup(GroupTag);
if (IdxToReset != INDEX_NONE)
{
const FGIS_EquipmentEntry& ExistingEntry = Container.Entries[IdxToReset];
if (ExistingEntry.EquippedGroup == GroupTag && ExistingEntry.EquippedSlot == NewSlot)
{
// Same state, return.
return;
}
// Deactivate the equipment for prev active slot.
if (Container.Entries[IdxToReset].bActive)
{
UpdateEquipmentState(IdxToReset, false, FGameplayTag::EmptyTag);
}
}
int32 IdxToSet = NewSlot.IsValid() ? Container.IndexOfBySlot(NewSlot) : INDEX_NONE;
if (IdxToSet != INDEX_NONE)
{
if (!Container.Entries[IdxToSet].bActive)
{
UpdateEquipmentState(IdxToSet, true, GroupTag);
}
}
}
void UGIS_EquipmentSystemComponent::ServerSetGroupActiveSlot_Implementation(FGameplayTag GroupTag, FGameplayTag NewSlot)
{
SetGroupActiveSlot(GroupTag, NewSlot);
}
void UGIS_EquipmentSystemComponent::CycleGroupActiveSlot(FGameplayTag GroupTag, bool bDirection)
{
if (!bEquipmentSystemInitialized || !OwnerHasAuthority())
{
GIS_CLOG(Error, "not initialized or has no authority!")
return;
}
if (!TargetCollectionDefinition->SlotGroupMap.Contains(GroupTag))
{
GIS_CLOG(Warning, "has no equipment group named:%s.", *GroupTag.ToString())
return;
}
FGameplayTag PrevSlot = FGameplayTag::EmptyTag;
int32 Idx = Container.IndexOfByGroup(GroupTag);
if (Idx != INDEX_NONE)
{
PrevSlot = Container.Entries[Idx].EquippedSlot;
}
FGameplayTag NewSlot = CycleGroupNextSlot(GroupTag, PrevSlot, bDirection);
if (NewSlot != PrevSlot)
{
SetGroupActiveSlot(GroupTag, NewSlot);
}
}
FGameplayTag UGIS_EquipmentSystemComponent::CycleGroupNextSlot(FGameplayTag GroupTag, FGameplayTag PrevSlot, bool bDirection)
{
TMap<FGameplayTag, UObject*> GroupedEquipments = GetSlottedEquipmentsOfGroup(GroupTag);
if (GroupedEquipments.IsEmpty())
{
// No equipments in the group, nothing to cycle
return FGameplayTag::EmptyTag;
}
if (!TargetCollectionDefinition || !TargetCollectionDefinition->SlotGroupMap.Contains(GroupTag))
{
return FGameplayTag::EmptyTag;
}
const FGIS_ItemSlotGroup& SlotGroup = TargetCollectionDefinition->SlotGroupMap[GroupTag];
// Get all slots in the group
TArray<FGameplayTag> AllSlots;
SlotGroup.IndexToSlotMap.GenerateValueArray(AllSlots);
if (AllSlots.IsEmpty())
{
return FGameplayTag::EmptyTag;
}
// Starting index for the search
int32 StartIndex;
if (!PrevSlot.IsValid())
{
StartIndex = bDirection ? 0 : AllSlots.Num() - 1;
}
else
{
StartIndex = AllSlots.Find(PrevSlot);
if (StartIndex == INDEX_NONE)
{
StartIndex = bDirection ? 0 : AllSlots.Num() - 1;
}
else
{
StartIndex = bDirection ? StartIndex + 1 : StartIndex - 1;
if (bDirection && StartIndex >= AllSlots.Num())
{
StartIndex = 0;
}
if (!bDirection && StartIndex < 0)
{
StartIndex = AllSlots.Num() - 1;
}
}
}
// Now, search for the next valid slot starting from StartIndex, wrapping around
int32 CurrentIndex = StartIndex;
bool bWrappedAround = false;
do
{
FGameplayTag CurrentSlot = AllSlots[CurrentIndex];
if (GroupedEquipments.Contains(CurrentSlot) && IsValid(GroupedEquipments[CurrentSlot]))
{
// Found a valid equipment at this slot
return CurrentSlot;
}
// Move to the next index
if (bDirection)
{
CurrentIndex++;
if (CurrentIndex >= AllSlots.Num())
{
CurrentIndex = 0;
}
}
else
{
CurrentIndex--;
if (CurrentIndex < 0)
{
CurrentIndex = AllSlots.Num() - 1;
}
}
// Check if we've wrapped around fully
if (CurrentIndex == StartIndex)
{
bWrappedAround = true;
}
}
while (!bWrappedAround);
// If no valid slot found after full cycle, return empty tag
return FGameplayTag::EmptyTag;
}
void UGIS_EquipmentSystemComponent::ServerCycleGroupActiveSlot_Implementation(FGameplayTag GroupTag, bool bDirection)
{
CycleGroupActiveSlot(GroupTag, bDirection);
}
#pragma endregion