Gameplay Tag Stack Container

Here is a very simple container which allows you to have replicated Gameplay Tags as a Stack Count. Useful for say things like Weapon Ammo, Inventory item count, stat points, etc.

Here is the header file:

USTRUCT(BlueprintType)
struct FKaosGameplayTagStack : public FFastArraySerializerItem
{
	GENERATED_BODY()

	FKaosGameplayTagStack()
	{}

	FKaosGameplayTagStack(FGameplayTag InTag, int32 InCount)
		: Tag(InTag)
	{
		Tag = InTag;
		Count = InCount;
	}

private:
	friend FKaosGameplayTagStackContainer;

	UPROPERTY()
	FGameplayTag Tag;

	UPROPERTY()
	int32 Count = 0;

};


USTRUCT(BlueprintType)
struct FKaosGameplayTagStackContainer : public FFastArraySerializer
{
	GENERATED_BODY()

	FKaosGameplayTagStackContainer() { }

public:

	void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
	void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
	void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
	
	bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
	{
		return FastArrayDeltaSerialize<FKaosGameplayTagStack, FKaosGameplayTagStackContainer>(Stacks, DeltaParms, *this);
	}

	/* 
	 * Adds count to the tag's stack
	 */
	void AddStackCount(FGameplayTag Tag, int32 Count);

	/*
	 * Removes count from the tag's stack
	 */
	void RemoveStackCount(FGameplayTag Tag, int32 Count);
	
	/* 
	 * Returns true if we have a one or more of the tag in the stack.
	 */
	bool HasTag(FGameplayTag Tag) const
	{
		return TagToCountMap.Contains(Tag);
	}
	
	/*
	 * Returns the amount of the stack we have for the supplied tag
	 */
	int32 GetStackCount(FGameplayTag Tag) const
	{
		return TagToCountMap.FindRef(Tag);
	}
	

private:
	// List of gameplay tag stacks. Use the accelerated TagCountMap for checking tag count, etc.
	UPROPERTY()
	TArray<FKaosGameplayTagStack> Stacks;
	
	// Accelerated list for queries
	TMap<FGameplayTag, int32> TagCountMap;
};

template<>
struct TStructOpsTypeTraits<FKaosGameplayTagStackContainer> : public TStructOpsTypeTraitsBase2<FKaosGameplayTagStackContainer>
{
	enum
	{
		WithNetDeltaSerializer = true,
	};
};

And the cpp file part:

void FKaosGameplayTagStackContainer::AddStackCount(FGameplayTag Tag, int32 Count)
{
	if (Count > 0)
	{
		for (FKaosGameplayTagStack& Stack : Stacks)
		{
			if (Stack.Tag == Tag)
			{
				const int32 NewCount = Stack.Count + Count;
				Stack.Count = NewCount;
				TagCountMap[Tag] = NewCount;
				MarkItemDirty(Stack);
				return;
			}
		}

		FKaosGameplayTagStack& NewStack = Stacks.Emplace_GetRef(Tag, Count);
		MarkItemDirty(NewStack);
		TagCountMap.Add(Tag, Count);
	}
}

void FKaosGameplayTagStackContainer::RemoveStackCount(FGameplayTag Tag, int32 Count)
{
	if (Count > 0)
	{
		for (auto It = Stacks.CreateIterator(); It; ++It)
		{
			FKaosGameplayTagStack& Stack = *It;
			if (Stack.Tag == Tag)
			{
				if (Stack.Count <= Count)
				{
					It.RemoveCurrent();
					TagCountMap.Remove(Tag);
					MarkArrayDirty();
				}
				else
				{
					const int32 NewCount = Stack.Count - Count;
					Stack.Count = NewCount;
					TagCountMap[Tag] = NewCount;
					MarkItemDirty(Stack);
				}
				return;
			}
		}
	}
}

void FKaosGameplayTagStackContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
{
	for (const int32 Index : RemovedIndices)
	{
		const FGameplayTag Tag = Stacks[Index].Tag;
		TagCountMap.Remove(Tag);
	}
}

void FKaosGameplayTagStackContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
{
	for (const int32 Index : AddedIndices)
	{
		const FKaosGameplayTagStack& Stack = Stacks[Index];
		TagCountMap.Add(Stack.Tag, Stack.Count);
	}
}

void FKaosGameplayTagStackContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
{
	for (const int32 Index : ChangedIndices)
	{
		const FKaosGameplayTagStack& Stack = Stacks[Index];
		TagCountMap[Stack.Tag] = Stack.Count;
	}
}

To use it, simply add a replicated property somewhere, say for example your player

UPROPERTY(Replicated)
FKaosGameplayTagStackContainer GameplayStats;

And you can use it like so:

void AMyCharacter::AddStatTagCount(FGameplayTag Tag, int32 Count)
{
   GameplayStats.AddStack(Tag, Count);
}

void AMyCharacter::RemoveStatTagCount(FGameplayTag Tag, int32 Count)
{
   GameplayStats.RemoveStack(Tag, Count);
}

int32 AMyCharacter::GetStatTagStackCount(FGameplayTag Tag) const
{
	return GameplayStats.GetStackCount(Tag);
}

bool AMyCharacter::HasStatTag(FGameplayTag Tag) const
{
	return GameplayStats.ContainsTag(Tag);
}

Hopefully people find this helpful and useful.

,