diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp index dd2d182..31372b1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp @@ -165,7 +165,7 @@ void UPS_AI_ConvAgent_InteractionComponent::TickComponent(float DeltaTime, ELeve // ───────────────────────────────────────────────────────────────────────────── // Selection evaluation // ───────────────────────────────────────────────────────────────────────────── -UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::EvaluateBestAgent() const +UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::EvaluateBestAgent() { UWorld* World = GetWorld(); if (!World) return nullptr; @@ -190,23 +190,25 @@ UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::Eva UPS_AI_ConvAgent_ElevenLabsComponent* CurrentAgent = SelectedAgent.Get(); // ── Conversation lock ────────────────────────────────────────────── - // While we're actively conversing with an agent, keep it selected as - // long as it's within interaction distance — ignore the view cone. - // This prevents deselect/reselect flicker when the player turns quickly - // (which would cause spurious OnAgentConnected re-broadcasts in - // persistent session mode). - if (CurrentAgent && CurrentAgent->bNetIsConversing) + // While we're actively conversing with an agent, keep it selected UNLESS + // the player is looking directly at a DIFFERENT agent within range. + // This allows switching between nearby agents by looking at them, while + // preventing deselect when looking at empty space (no agent in view cone). + // If no other agent is in the view cone, the current agent stays selected + // regardless of look direction — only distance can break the lock. + const bool bConversationLocked = CurrentAgent && CurrentAgent->bNetIsConversing; + bool bCurrentAgentInRange = false; + if (bConversationLocked) { if (AActor* AgentActor = CurrentAgent->GetOwner()) { const FVector AgentLoc = AgentActor->GetActorLocation() + FVector(0.0f, 0.0f, AgentEyeLevelOffset); const float DistSq = (AgentLoc - ViewLocation).SizeSquared(); - if (DistSq <= MaxDistSq) - { - return CurrentAgent; // Keep conversing agent selected. - } + bCurrentAgentInRange = (DistSq <= MaxDistSq); } + // If current agent is out of range, fall through to normal evaluation + // (which will select a new agent or nullptr). } for (UPS_AI_ConvAgent_ElevenLabsComponent* Agent : Agents) @@ -259,6 +261,51 @@ UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::Eva } } + // ── Conversation lock fallback ──────────────────────────────────── + // If we're in conversation and the current agent is still in range: + // - No other agent in view cone → keep current agent (don't deselect). + // - Different agent in view cone → switch after ConversationSwitchDelay. + if (bConversationLocked && bCurrentAgentInRange) + { + if (!BestCandidate || BestCandidate == CurrentAgent) + { + // Looking at current agent or empty space → keep current, reset switch timer. + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return CurrentAgent; + } + + // Player is looking at a different agent. Apply switch delay. + if (ConversationSwitchDelay <= 0.0f) + { + // Instant switch (delay = 0). + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return BestCandidate; + } + + // Start or continue the switch timer. + if (PendingSwitchAgent.Get() != BestCandidate) + { + // Player started looking at a new candidate — reset timer. + PendingSwitchAgent = BestCandidate; + PendingSwitchStartTime = FPlatformTime::Seconds(); + return CurrentAgent; // Not yet — keep current. + } + + // Same candidate as before — check if delay has elapsed. + const double Elapsed = FPlatformTime::Seconds() - PendingSwitchStartTime; + if (Elapsed < static_cast(ConversationSwitchDelay)) + { + return CurrentAgent; // Still waiting. + } + + // Delay elapsed → allow the switch. + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return BestCandidate; + } + return BestCandidate; } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h index e1caa6e..3294b78 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h @@ -117,6 +117,14 @@ public: // ── Conversation management ────────────────────────────────────────────── + /** How long (seconds) the player must look at a different agent before switching + * during an active conversation. Prevents accidental switches when glancing around. + * Only applies when a conversation is active (bNetIsConversing). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + meta = (ClampMin = "0.0", ClampMax = "5.0", + ToolTip = "Seconds the player must look at another agent before switching mid-conversation.\nPrevents accidental switches when glancing around.\n0 = instant switch.")) + float ConversationSwitchDelay = 1.0f; + /** Automatically start the WebSocket conversation when an agent is selected * (enters range + view cone). When false, selecting an agent only manages * gaze and visual awareness — the conversation must be started explicitly @@ -251,7 +259,7 @@ private: // ── Selection logic ────────────────────────────────────────────────────── /** Evaluate all registered agents, return the best candidate (or null). */ - UPS_AI_ConvAgent_ElevenLabsComponent* EvaluateBestAgent() const; + UPS_AI_ConvAgent_ElevenLabsComponent* EvaluateBestAgent(); /** Apply a new selection — fire events, reroute mic. */ void SetSelectedAgent(UPS_AI_ConvAgent_ElevenLabsComponent* NewAgent); @@ -296,4 +304,10 @@ private: FTimerHandle GazeAttachTimerHandle; FTimerHandle GazeDetachTimerHandle; + + // ── Conversation switch delay ──────────────────────────────────────── + // Tracks how long the player has been looking at a different agent + // while in an active conversation. Switch only happens after the delay. + TWeakObjectPtr PendingSwitchAgent; + double PendingSwitchStartTime = 0.0; };