GameplayAbilities and you

Don’t beat yourself up over not being quite getting into the system’s flow. It’s difficult to grasp what’s the way to do each thing, and if it were all obvious and easy, I wouldn’t have arsed myself to write so much about it.

Hm, how come you can’t figure out how to apply multiple stacks of an effect in a single call within an ability? Just use the ApplyGameplayEffect functions within the Ability blueprints, you can either just apply a regular GameplayEffectTemplate with some details on the level/cooldown of it, or create GameplayEffectSpec first, change up some more specific parameters(such as duration, added tags) and apply that.

That being said, this solution strikes me as a tad odd anyway. I mean, I guess you could represent aggro via some hidden buff stacks that builds up depending on healing/damage done, but then I’d probably put that logic right into the GameplayEffectExecutionCalculation responsible for damage/healing, because then each time you deal damage/heal it applies these automatically. Could even have the damage execution apply aggro counts to monsters that entice them to target characters that recently healed them. I’m not sure how performant the trick may be depending on how many individual GameplayEffect stacks you’d have to govern, but that’s more because I don’t try to juggle with active gameplayeffect structs yet so I don’t know how well the system deals with excessive amounts of stacks. You’d probably have to do such a thing for UI purposes anyway, so I would think someone had the foresight to make iterating through the buffs somewhat efficient, though. You’d have to try it, actually. FGameplayEffectQuery might help with that, seems to be a struct you can use to filter your active GameplayEffects by. Use it with GetActiveEffects(const FGameplayEffectQuery& Query) and you should receive all active effect handles that pass through the filter.

Getting who applied the effect is fairly easy, GameplayEffects tend to keep their EffectContext around while they’re applied, which pretty much describe where they came from. GetEffectContextFromActiveGEHandle should give you a handle containing the context when you use the active gameplay effect handle in question as parameter.

You can actually use a delegate provided by RegisterGameplayAttributeEvent(YourAttribute) to make it so that you can call a function every time an attribute is changed. This not only cleans up the process and lets you avoid using the Tick function like a scrub, but it also provides you an FGameplayEffectModCallbackData as parameter to use in the function you wish to bind into that delegate. The CallBackData struct contains the spec of the GameplayEffect(and as such all the contextual info you’d need to derive the instigator/causer/whatever of the damage from) and some other useful info such as what kind of modifier is being applied(additive, multiplicative, by how much?) and which Ability System is being targetted(which granted is usually obvious, because you only bind yourself to that one ability system with that function).
Do be careful though that the FGameplayEffectModCallbackData is a pointer, and not always valid. I know modifiers without a GameplayEffect attached to them will fire the function without a CallBackData, but there may or may not be more instances. You should always check if the Data is valid before you access it, but then again, you should pretty much always check if pointers are valid anyway.

Binding anything gameplay-related to a GameplayCue is a nice way to screw yourself over, because they don’t really replicate in a way that’d be desireable for actual gameplay logic. In fact I’m not even sure if they replicate at all, the game may actually just replicate/multicast the tag you wish to call a GameplayCue from and every client’s kinda responsible for their own cue. It’s also just kind of a weird detour to take for gameplay logic, you’re pretty much calling an unknown function that’s assigned to the tag you put in as parameter, and you only have limited options in what to put in as parameter.

There’s an AbilityTask that listens for Gameplay Events, so that usually means that by extension there is a delegate you can use for listening to them, too. This system loves its delegates(not that I’m complaining). Poking about reveals that GenericGameplayEventCallbacks seems to be it, it’s a public TMap in AbilitySystemComponent that maps a tag to a FGameplayEventMulticastDelegate, which is a multicast delegate that offers the FGameplayEventData itself as variable. Just remember to use FindOrAdd with the map, because you don’t actually know if there’s a fitting delegate in the map already, and you don’t want to try to bind to an invalid delegate/accidentally reset a delegate other functions may already be bound to.

I’d argue you’d ideally want your AI to be able to stand on its own feet, so I wouldn’t want to add an extra ability concerned with listening to GameplayEvents just to make it work. That being said, if something is stupid but works, it probably isn’t that stupid. I don’t think there’s a big difference between binding a manager ability to your GameplayEvent of choice, making it wait for one using an AbilityTask or just cutting the middleman and binding the delegate to whatever is supposed to control your Ai.

I gave you some options now. Bottomline is that binding it to GameplayEffectExecution may probably be the easiest and most ideal solution, however you can either combine this with other solutions to spare yourself from writing up an extra system just for that, or you can just use delegates, some of which I’ve mentioned here, for a more global but perhaps less controllable solution.

Good luck my dude!