ElevenLabs: guard null gaze target on late join (replicated ref not resolved)

A client that joins while a conversation is already active can run OnRep with
NetConnectedPawns/NetActiveSpeakerPawn entries whose replicated pawn references
haven't resolved yet (actor channel not open). NetConnectedPawns.Num() > 0 did
not guarantee a non-null element, so OnRep_ConversationState dereferenced a null
pawn in its log (crash in Dev/PIE) and ApplyConversationGaze set TargetActor to
null (gaze glitch). Both now skip until the reference resolves — OnRep fires
again once it's patched in. More likely with 3+ clients.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 18:16:35 +02:00
parent fee7d0c679
commit efd9921fde

View File

@@ -2060,12 +2060,18 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnRep_ConversationState()
{
Gaze->bActive = true;
// Use active speaker if set, otherwise first connected pawn.
// GazeTarget can be null on a late joiner if the replicated pawn
// reference hasn't resolved yet (actor channel not open). Skip until
// it resolves — OnRep fires again once the reference is patched in.
APawn* GazeTarget = NetActiveSpeakerPawn ? NetActiveSpeakerPawn : NetConnectedPawns.Last();
Gaze->TargetActor = GazeTarget;
Gaze->ResetBodyTarget();
Gaze->bEnableBodyTracking = true;
UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log,
TEXT("[NET-REP] Gaze ACTIVATED, TargetActor set to %s"), *GazeTarget->GetName());
if (GazeTarget)
{
Gaze->TargetActor = GazeTarget;
Gaze->ResetBodyTarget();
Gaze->bEnableBodyTracking = true;
UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log,
TEXT("[NET-REP] Gaze ACTIVATED, TargetActor set to %s"), *GazeTarget->GetName());
}
}
else
{
@@ -2704,10 +2710,12 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ApplyConversationGaze()
{
Gaze->bActive = true;
// Look at active speaker if set, otherwise last connected pawn.
// GazeTarget can be null if a replicated pawn ref hasn't resolved yet
// (late joiner). Don't overwrite the target with null in that case.
AActor* GazeTarget = NetActiveSpeakerPawn
? static_cast<AActor*>(NetActiveSpeakerPawn)
: static_cast<AActor*>(NetConnectedPawns.Last());
if (Gaze->TargetActor != GazeTarget)
if (GazeTarget && Gaze->TargetActor != GazeTarget)
{
Gaze->TargetActor = GazeTarget;
Gaze->ResetBodyTarget();