Fix conversation handoff and server-side posture for network
- Auto-end conversation on deselection: when bAutoStartConversation is true and the player walks out of range, EndConversation() is called so the NPC becomes available for other players - Server-side posture: add ApplyConversationPosture() helper called from ServerRequestConversation, ServerReleaseConversation, EndConversation (Authority path), and HandleDisconnected — fixes NPC not tracking the client on the listen server (OnRep never fires on Authority) - Guard DetachPostureTarget: only clear TargetActor if it matches our pawn, preventing the server IC from overwriting posture set by the conversation system for a remote client Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
215cb398fd
commit
bf08bb67d9
@ -305,6 +305,7 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::EndConversation()
|
|||||||
|
|
||||||
// Reset replicated state so other players can talk to this NPC.
|
// Reset replicated state so other players can talk to this NPC.
|
||||||
bNetIsConversing = false;
|
bNetIsConversing = false;
|
||||||
|
ApplyConversationPosture();
|
||||||
NetConversatingPlayer = nullptr;
|
NetConversatingPlayer = nullptr;
|
||||||
NetConversatingPawn = nullptr;
|
NetConversatingPawn = nullptr;
|
||||||
}
|
}
|
||||||
@ -714,6 +715,7 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::HandleDisconnected(int32 StatusCode,
|
|||||||
if (GetOwnerRole() == ROLE_Authority)
|
if (GetOwnerRole() == ROLE_Authority)
|
||||||
{
|
{
|
||||||
bNetIsConversing = false;
|
bNetIsConversing = false;
|
||||||
|
ApplyConversationPosture();
|
||||||
NetConversatingPlayer = nullptr;
|
NetConversatingPlayer = nullptr;
|
||||||
NetConversatingPawn = nullptr;
|
NetConversatingPawn = nullptr;
|
||||||
}
|
}
|
||||||
@ -1445,6 +1447,9 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ServerRequestConversation_Implementat
|
|||||||
NetConversatingPlayer = RequestingPlayer;
|
NetConversatingPlayer = RequestingPlayer;
|
||||||
NetConversatingPawn = RequestingPlayer ? RequestingPlayer->GetPawn() : nullptr;
|
NetConversatingPawn = RequestingPlayer ? RequestingPlayer->GetPawn() : nullptr;
|
||||||
|
|
||||||
|
// Update NPC posture on the server (OnRep never fires on Authority).
|
||||||
|
ApplyConversationPosture();
|
||||||
|
|
||||||
StartConversation_Internal();
|
StartConversation_Internal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1460,7 +1465,10 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ServerReleaseConversation_Implementat
|
|||||||
WebSocketProxy = nullptr;
|
WebSocketProxy = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear posture before nullifying the pawn pointer (ApplyConversationPosture
|
||||||
|
// uses NetConversatingPawn to guard against clearing someone else's target).
|
||||||
bNetIsConversing = false;
|
bNetIsConversing = false;
|
||||||
|
ApplyConversationPosture();
|
||||||
NetConversatingPlayer = nullptr;
|
NetConversatingPlayer = nullptr;
|
||||||
NetConversatingPawn = nullptr;
|
NetConversatingPawn = nullptr;
|
||||||
}
|
}
|
||||||
@ -1694,3 +1702,33 @@ UPS_AI_ConvAgent_InteractionComponent* UPS_AI_ConvAgent_ElevenLabsComponent::Fin
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UPS_AI_ConvAgent_ElevenLabsComponent::ApplyConversationPosture()
|
||||||
|
{
|
||||||
|
// Same logic as OnRep_ConversationState's posture section, but callable
|
||||||
|
// from the server side where OnRep never fires (Authority).
|
||||||
|
AActor* Owner = GetOwner();
|
||||||
|
if (!Owner) return;
|
||||||
|
|
||||||
|
auto* Posture = Owner->FindComponentByClass<UPS_AI_ConvAgent_PostureComponent>();
|
||||||
|
if (!Posture) return;
|
||||||
|
|
||||||
|
if (bNetIsConversing && NetConversatingPawn)
|
||||||
|
{
|
||||||
|
Posture->bActive = true;
|
||||||
|
Posture->TargetActor = NetConversatingPawn;
|
||||||
|
Posture->ResetBodyTarget();
|
||||||
|
Posture->bEnableBodyTracking = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only clear if the posture is still pointing at the departing player.
|
||||||
|
// Another InteractionComponent may have already set a new TargetActor.
|
||||||
|
if (!Posture->TargetActor || Posture->TargetActor == NetConversatingPawn)
|
||||||
|
{
|
||||||
|
Posture->bActive = false;
|
||||||
|
Posture->TargetActor = nullptr;
|
||||||
|
Posture->bEnableBodyTracking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -249,20 +249,33 @@ void UPS_AI_ConvAgent_InteractionComponent::SetSelectedAgent(UPS_AI_ConvAgent_El
|
|||||||
OldAgent->GetOwner() ? *OldAgent->GetOwner()->GetName() : TEXT("(null)"));
|
OldAgent->GetOwner() ? *OldAgent->GetOwner()->GetName() : TEXT("(null)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Listening: stop ──────────────────────────────────────────────
|
// ── Conversation: end if auto-started ────────────────────────────
|
||||||
if (bAutoManageListening)
|
// If we auto-started the conversation on selection, end it now so the
|
||||||
|
// NPC becomes available for other players. EndConversation() also calls
|
||||||
|
// StopListening() internally, so we skip the separate StopListening below.
|
||||||
|
if (bAutoStartConversation && (OldAgent->IsConnected() || OldAgent->bNetIsConversing))
|
||||||
|
{
|
||||||
|
OldAgent->EndConversation();
|
||||||
|
}
|
||||||
|
else if (bAutoManageListening)
|
||||||
{
|
{
|
||||||
OldAgent->StopListening();
|
OldAgent->StopListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable body tracking on deselection.
|
// Disable body tracking on deselection — but only if we were the
|
||||||
|
// one who set the TargetActor. The conversation system (OnRep or
|
||||||
|
// server ApplyConversationPosture) may have set TargetActor to a
|
||||||
|
// different player; don't overwrite that.
|
||||||
if (bAutoManagePosture)
|
if (bAutoManagePosture)
|
||||||
{
|
{
|
||||||
if (UPS_AI_ConvAgent_PostureComponent* Posture = FindPostureOnAgent(OldAgent))
|
if (UPS_AI_ConvAgent_PostureComponent* Posture = FindPostureOnAgent(OldAgent))
|
||||||
|
{
|
||||||
|
if (Posture->TargetActor == GetOwner())
|
||||||
{
|
{
|
||||||
Posture->bEnableBodyTracking = false;
|
Posture->bEnableBodyTracking = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Posture: detach ──────────────────────────────────────────────
|
// ── Posture: detach ──────────────────────────────────────────────
|
||||||
if (bAutoManagePosture && World)
|
if (bAutoManagePosture && World)
|
||||||
@ -513,6 +526,11 @@ void UPS_AI_ConvAgent_InteractionComponent::DetachPostureTarget(
|
|||||||
if (!AgentPtr) return;
|
if (!AgentPtr) return;
|
||||||
|
|
||||||
if (UPS_AI_ConvAgent_PostureComponent* Posture = FindPostureOnAgent(AgentPtr))
|
if (UPS_AI_ConvAgent_PostureComponent* Posture = FindPostureOnAgent(AgentPtr))
|
||||||
|
{
|
||||||
|
// Only clear if we are the one who set the TargetActor.
|
||||||
|
// The conversation system (OnRep / ApplyConversationPosture on server)
|
||||||
|
// may have set TargetActor to a different player — don't overwrite that.
|
||||||
|
if (Posture->TargetActor == GetOwner())
|
||||||
{
|
{
|
||||||
Posture->TargetActor = nullptr;
|
Posture->TargetActor = nullptr;
|
||||||
|
|
||||||
@ -522,6 +540,7 @@ void UPS_AI_ConvAgent_InteractionComponent::DetachPostureTarget(
|
|||||||
AgentPtr->GetOwner() ? *AgentPtr->GetOwner()->GetName() : TEXT("(null)"));
|
AgentPtr->GetOwner() ? *AgentPtr->GetOwner()->GetName() : TEXT("(null)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -617,4 +617,9 @@ private:
|
|||||||
void StartConversation_Internal();
|
void StartConversation_Internal();
|
||||||
/** Find the InteractionComponent on the local player's pawn (for relay RPCs). */
|
/** Find the InteractionComponent on the local player's pawn (for relay RPCs). */
|
||||||
class UPS_AI_ConvAgent_InteractionComponent* FindLocalRelayComponent() const;
|
class UPS_AI_ConvAgent_InteractionComponent* FindLocalRelayComponent() const;
|
||||||
|
|
||||||
|
/** Update the NPC's PostureComponent from the current conversation state.
|
||||||
|
* Called on the server when bNetIsConversing / NetConversatingPawn change,
|
||||||
|
* because OnRep_ConversationState never fires on the Authority. */
|
||||||
|
void ApplyConversationPosture();
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user