Compare commits

..

No commits in common. "0124de8b53a371de34af51fca9f6a152b207b719" and "fc728454d030201c45963ca903fd2fd47a6270d2" have entirely different histories.

2 changed files with 21 additions and 38 deletions

View File

@ -225,7 +225,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::StartConversation()
APlayerController* PC = GetWorld()->GetFirstPlayerController(); APlayerController* PC = GetWorld()->GetFirstPlayerController();
bNetIsConversing = true; bNetIsConversing = true;
NetConversatingPlayer = PC; NetConversatingPlayer = PC;
NetConversatingPawn = PC ? PC->GetPawn() : nullptr;
} }
StartConversation_Internal(); StartConversation_Internal();
} }
@ -299,7 +298,6 @@ 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;
NetConversatingPlayer = nullptr; NetConversatingPlayer = nullptr;
NetConversatingPawn = nullptr;
} }
else else
{ {
@ -610,7 +608,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::HandleDisconnected(int32 StatusCode,
{ {
bNetIsConversing = false; bNetIsConversing = false;
NetConversatingPlayer = nullptr; NetConversatingPlayer = nullptr;
NetConversatingPawn = nullptr;
} }
OnAgentDisconnected.Broadcast(StatusCode, Reason); OnAgentDisconnected.Broadcast(StatusCode, Reason);
@ -1035,26 +1032,20 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::EnqueueAgentAudio(const TArray<uint8>
else if (bPreBuffering) else if (bPreBuffering)
{ {
// Second (or later) audio chunk arrived during pre-buffer period. // Second (or later) audio chunk arrived during pre-buffer period.
// On Authority: this means a genuine second TTS chunk arrived from the // We now have both chunks buffered — start playback immediately.
// WebSocket, so we have enough data buffered — start playback immediately. bPreBuffering = false;
// On Clients: sub-chunks from network splitting arrive nearly simultaneously, if (bDebug)
// which would defeat the pre-buffer. Let the timer in TickComponent handle it.
if (GetOwnerRole() == ROLE_Authority)
{ {
bPreBuffering = false; const double NowPb = FPlatformTime::Seconds();
if (bDebug) const double BufferedMs = (NowPb - PreBufferStartTime) * 1000.0;
{ const double Tpb3 = NowPb - SessionStartTime;
const double NowPb = FPlatformTime::Seconds(); UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log,
const double BufferedMs = (NowPb - PreBufferStartTime) * 1000.0; TEXT("[T+%.2fs] [Turn %d] Pre-buffer: second chunk arrived (%.0fms buffered). Starting playback."),
const double Tpb3 = NowPb - SessionStartTime; Tpb3, LastClosedTurnIndex, BufferedMs);
UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, }
TEXT("[T+%.2fs] [Turn %d] Pre-buffer: second chunk arrived (%.0fms buffered). Starting playback."), if (AudioPlaybackComponent && !AudioPlaybackComponent->IsPlaying())
Tpb3, LastClosedTurnIndex, BufferedMs); {
} AudioPlaybackComponent->Play();
if (AudioPlaybackComponent && !AudioPlaybackComponent->IsPlaying())
{
AudioPlaybackComponent->Play();
}
} }
SilentTickCount = 0; SilentTickCount = 0;
} }
@ -1204,7 +1195,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::GetLifetimeReplicatedProps(
Super::GetLifetimeReplicatedProps(OutLifetimeProps); Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, bNetIsConversing); DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, bNetIsConversing);
DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, NetConversatingPlayer); DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, NetConversatingPlayer);
DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, NetConversatingPawn);
DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, CurrentEmotion); DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, CurrentEmotion);
DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, CurrentEmotionIntensity); DOREPLIFETIME(UPS_AI_ConvAgent_ElevenLabsComponent, CurrentEmotionIntensity);
} }
@ -1220,14 +1210,14 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnRep_ConversationState()
// on the local pawn, but remote clients never run that code path. // on the local pawn, but remote clients never run that code path.
if (UPS_AI_ConvAgent_PostureComponent* Posture = Owner->FindComponentByClass<UPS_AI_ConvAgent_PostureComponent>()) if (UPS_AI_ConvAgent_PostureComponent* Posture = Owner->FindComponentByClass<UPS_AI_ConvAgent_PostureComponent>())
{ {
// Use NetConversatingPawn (replicated to ALL clients) instead of if (bNetIsConversing && NetConversatingPlayer)
// NetConversatingPlayer->GetPawn() — PlayerControllers are only
// replicated to their owning client (bOnlyRelevantToOwner=true).
if (bNetIsConversing && NetConversatingPawn)
{ {
Posture->TargetActor = NetConversatingPawn; if (APawn* PlayerPawn = NetConversatingPlayer->GetPawn())
Posture->ResetBodyTarget(); {
Posture->bEnableBodyTracking = true; Posture->TargetActor = PlayerPawn;
Posture->ResetBodyTarget();
Posture->bEnableBodyTracking = true;
}
} }
else else
{ {
@ -1281,7 +1271,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ServerRequestConversation_Implementat
bNetIsConversing = true; bNetIsConversing = true;
NetConversatingPlayer = RequestingPlayer; NetConversatingPlayer = RequestingPlayer;
NetConversatingPawn = RequestingPlayer ? RequestingPlayer->GetPawn() : nullptr;
StartConversation_Internal(); StartConversation_Internal();
} }
@ -1300,7 +1289,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ServerReleaseConversation_Implementat
bNetIsConversing = false; bNetIsConversing = false;
NetConversatingPlayer = nullptr; NetConversatingPlayer = nullptr;
NetConversatingPawn = nullptr;
} }
void UPS_AI_ConvAgent_ElevenLabsComponent::ServerSendMicAudio_Implementation( void UPS_AI_ConvAgent_ElevenLabsComponent::ServerSendMicAudio_Implementation(

View File

@ -296,15 +296,10 @@ public:
bool bNetIsConversing = false; bool bNetIsConversing = false;
/** The player controller currently in conversation with this NPC (null if free). /** The player controller currently in conversation with this NPC (null if free).
* Only valid on server and owning client (PlayerControllers are not replicated to other clients). */ * Replicated so each client knows who is speaking (used for posture target, LOD). */
UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "PS AI ConvAgent|Network") UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "PS AI ConvAgent|Network")
TObjectPtr<APlayerController> NetConversatingPlayer = nullptr; TObjectPtr<APlayerController> NetConversatingPlayer = nullptr;
/** The pawn of the conversating player. Replicated to ALL clients (unlike PlayerController).
* Used by remote clients for posture target (head/eye tracking) and LOD distance checks. */
UPROPERTY(ReplicatedUsing = OnRep_ConversationState, BlueprintReadOnly, Category = "PS AI ConvAgent|Network")
TObjectPtr<APawn> NetConversatingPawn = nullptr;
// ── Network LOD ────────────────────────────────────────────────────────── // ── Network LOD ──────────────────────────────────────────────────────────
/** Distance (cm) beyond which remote clients stop receiving agent audio entirely. /** Distance (cm) beyond which remote clients stop receiving agent audio entirely.