1174 lines
32 KiB
C++
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
|