Locally Predicted AI Abilities for HitReaction

Hello,

We have a dedicated server PvPvE game and are using GAS mainly for characters.

We have an issue with Network Latency upon player melee attacking AI which triggers an HitReaction ability.

There’s a noticeable delay when testing on Bad network connection between the Hit and Reaction.

Since Abilities can only be predicted on Locally Owned Actor and AI’s are owned by Server I guess here its not doable to predict this ability.

We have very varied HitReactions based on HitData like Hit body part, strengh of attack etc. Most of the animations are full body and many of them has Root motion.

We are thinking how can we solve it using GAS or should we do it manually?

With Gameplay Cues I believe they were meant for lighter less important visuals like VFX/SFX, but for root motion HitReaction it might cause some issues with roll back character movement caused by it?

How was it done in Fortnite or maybe other projects to achieve Responsive and Precise Melee Combat HitReactions?

We also thought about maybe doing HitDetection earlier at the beginning of the Attack so it would make up the roundtrip time from the server, but then player can turn mid attack and the HitData would be completely different.

Doing it sideways of GAS and Playing predicted HitReaction locally, I believe we would need to also somehow store AttackID/PredictionKey so then we can avoid double reactions on client, doing some interrupts or rollbacks?

But still when doing predicted HitReaction we would need to trigger ability alongside to handle some gameplay logic like Pausing AI Logic or maybe using AI Task to manage the AI resources?

Any Insights would be greatly appreciated, since I’m not familiar with replication that much yet and sorry if my questions doesn’t make sense :slight_smile:

Thank you

[Attachment Removed]

Hi there. The article and presentation I gave on Best Practices for Networked Movement Abilities touches on the known challenges combining GAS and Character Movement Component.

You are correct about this:

“Since Abilities can only be predicted on Locally Owned Actor and AI’s are owned by Server I guess here its not doable to predict this ability.”

GAS does have a setting for UGameplayAbilitiesDeveloperSettings::PredictTargetGameplayEffects (default: true) which allows a local predicted ability to predictively apply GEs on targets via ApplyGameplayEffectToTarget, but that wouldn’t be the same as a target predictively activating a (hit reaction) GA. You can use it to play an animation through a GameplayCue. In our own games we actually set PredictTargetGameplayEffects to false.

“We are thinking how can we solve it using GAS or should we do it manually?”

Predicting hit reactions with root motion I feel is opening a can of worms. Since the server is authoritative over NPC location, if your client predicts a hit reaction you will then have this situation:

  • Client shows the NPC at a location that’s behind (on time) compared to the server, plus root motion ahead of the server
  • Server has the NPC at a location that’s ahead of the client, plus root motion behind the client
  • Other clients show the NPC at a location that’s behind the server, with root motion 2x latency steps behind the acting client

The potential error between (1) and (2) will be the bigger now. Since you are considering predicting on targets, I take it this is something you’re okay with and willing to mask with more smoothing?

I don’t believe that in Fortnite BR, we do attempt to predict any root motion on targets. Certainly not for players, but I’ll ask the dev team if we do anything for NPCs. I think primarily you will have to find your own solution. When using GAS and CMC, the method I would experiment with is manipulating the ACharacter’s RepRootMotion (replicated) and RootMotionRepMoves (client-side kept history) on the predicting client. For example:

  • Predictively play an anim montage with root motion on a sim proxy, by calling ACharacter::PlayAnimMontage() directly (not via an NPC ability, since those don’t run predictively).
  • Calling ACharacter::SetRepRootMotion manually and call OnRep_RootMotion(), faking as if you’ve received from the server that you’re playing a root motion montage. See ACharacter::PreReplication() for how to populate that struct. ACharacter keeps a buffer ‘RootMotionRepMoves’ that gets updated on every OnRep_RootMotion() call.
  • When RepRootMotion is replicated down from the server authoritatively, it also triggers OnRep_RootMotion(). Somehow detect this and fiddle with timestamps so that you stay ahead of the server. The goal with this is that your predicted motion and place in the animation doesn’t get teleported back. The RootMotionRepMoves buffer should contain snapshots based on your predicted root motion.
  • Undo these edits to the rep root motion history if the player’s predicted ability gets rejected.
  • Be more lenient on sim proxy corrections while this particular root motion is active.

This is untested, since we don’t predict movement effects on sim proxies. So these recommendations are primarily “if I would try it, I would consider this approach”.

This article describes movement replication in general and dedicates some sections to sim proxies. That should help with covering the relevant knowledge and with tweaking positional error tolerance and smoothing on sim proxies.

Hopefully the info helps!

[Attachment Removed]

“What we are missing right now is to identify what animations are HitReactions, since in OnRep_ReplicatedAnimMontage we have access to FGameplayAbilityRepAnimMontage”

You’ll need a custom solution. Consider:

  • Asset name or path substring matching
  • Asset registry tags (i.e. custom metadata). For example, in an editor module bind to UObject::FAssetRegistryTag::OnGetExtraObjectTagsWithContext (search engine codebase for examples), to add extra tags to specific animation assets at save-time or cook-time. Then back in OnRep_AnimMontageInfo, you can get the FAssetData from the UAnimSequenceBase* package and call GetTagValue on it (unrelated article, but example usage of GetTagValue). Note: I haven’t tested this myself, but I believe this idea is feasible.

“Also how do you handle the case in which the local predicted ability is triggered twice in quick succesion (let say 2 quick melee attacks) and then during the second attack, you get a replicated info from the first attack, will the client cancel his second attack and transition into first one and then he gets a rep data from second attack and switches back to second attack?”

The autonomous proxy will not play any montages from RepAnimMontageInfo, see how ASC::OnRep_ReplicatedAnimMontage() there is a guard: if (!AbilityActorInfo->IsLocallyControlled()) for playing the montage locally. For autonomous proxy, playing montages is completely reliant on local prediction. For server-initiated abilities on player pawns, this works because GAS will send an RPC from server to client to activate the ability locally, predictively.

[Attachment Removed]

Thank you for your answer, it helped a lot.

We decided to maybe stay away from Root Motion hit reactions, but managed to solve predicting the montage by fiddling with Local PlayInstanceID in UAbilitySystemComponent::PlayMontageSimulated and handling it in OnRep_ReplicatedAnimMontage.

What we do right now is we increase the local PlayInstanceID mannually in our “predicted” play montage function and then we call PlayMontageSimulated on client.

On the server we trigger Gameplay ability normally with PlayMontageAndWait with FGameplayAbilityRepAnimMontage RepAnimPositionmethod set to CurrentSectionID (to skip the teleporting back)

Then in OnRep it will handle to not replay the animation if we are already playing that id.

What we are missing right now is to identify what animations are HitReactions, since in OnRep_ReplicatedAnimMontage we have access to FGameplayAbilityRepAnimMontage

I guess we can check from the Animation montage to see if it is in our chooser table, or just filter it using the name (but then we would have to stick to some certain naming convention), or maybe tag them somehow using MetaData?

[Image Removed]We also considered storing those predicted LocalPlayInstanceID’s in some array ex PredictedHitReactIds and then checking it in OnRep. But then we would need to manage it. In case like we play montage predictevely on client manually, then the server rejected or couldnt activate the ability, hence OnRep_ would never be called and we wouldnt be able to remove the index from the Array causing next different HitReact to not play.

Also how do you handle the case in which the local predicted ability is triggered twice in quick succesion (let say 2 quick melee attacks) and then during the second attack, you get a replicated info from the first attack, will the client cancel his second attack and transition into first one and then he gets a rep data from second attack and switches back to second attack?

Or you have it handled somehow using the prediction keys to know that you are already ahead on client and no need to rollback?

Because right now with our local playInstanceID approach we have this case that the AI

on client is getting 2 quick HitReactions and during the second one he gets a data from the first one and because of Local and repIds mismatch it switch back to first hit reaction and then back to second when it receive it.

How would you solve that? maybe just storing last Id and comparing it to RepID and just ignore the smaller one? but then there is a wraparound at 255 so we would need to handle it also :smiley:

[Attachment Removed]