An attribute is changed an unnecessary number of times.
First, a gameplay effect is applied on a client, and it changes the attribute.
Then, the effect is removed on a client, and it removes modifiers from AttributeModeAggregator.
Then, an update attribute set is replicated from the server, and the “on dirty” event is broadcast for the aggregator, which causes the effect recalculation without modifiers already.
Then, the attribute value is updated with server’s value.
We did a workaround in our project and committed an ability only on authority, while manually faking prediction on the locally controlled client. Because we need to play a gameplay cue on the client with a new attribute value, and we need to do it on ability activation so a gameplay cue has a prediction key and can actually be executed.
So it has quite a few layers to it. Maybe you can suggest a better solution for this? And is it even an intended behavior of the gameplay effects?
Apologies for the delayed response and thanks for the repro project and detailed steps.
This behavior is intended in GAS as Instant Gameplay Effects applied under prediction will trigger attribute changes multiple times on the autonomous client as part of the prediction and reconciliation cycle. This is described in GameplayPrediction.h:
“We treat predictive instant gameplay effects as infinite-duration gameplay effects… When the prediction key is caught up, the predictive GameplayEffect is removed and we will return to the server-given value.”Client first applies the effect predictively, then removes the temporary modifier when the prediction key is acknowledged, and finally updates the attribute once more when the server’s authoritative value replicates.
If your goal is to react once when the ability activates, you could use Gameplay Cues Notifies instead of binding directly to OnAttributeChange.
Using your repro project as reference, you can do the following:
In GE_ChangeMyAttribute, add a Gameplay Cue Tag such as GameplayCue.Change.
Create a GameplayCueNotify_Burst Blueprint (e.g: GCN_MyAttrChange) and assign it the same tag.
In the Notify’s OnBurst event, add a Print String to verify when it fires.
Once configured, the cue will fire predictively once when the ability is commited. The log output will look like this: