134 lines
4.6 KiB
C#
134 lines
4.6 KiB
C#
|
|
using System.Collections;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using UnityEngine;
|
||
|
|
using System;
|
||
|
|
|
||
|
|
namespace Hallucinate.AI
|
||
|
|
{
|
||
|
|
public class ConversationManager : MonoBehaviour
|
||
|
|
{
|
||
|
|
public static ConversationManager Instance { get; private set; }
|
||
|
|
|
||
|
|
[Header("Settings")]
|
||
|
|
public int maxSimultaneousConversations = 3;
|
||
|
|
public float maxConversationDuration = 120f; // 2 minutes
|
||
|
|
|
||
|
|
private List<ConversationSession> activeSessions = new List<ConversationSession>();
|
||
|
|
|
||
|
|
private void Awake()
|
||
|
|
{
|
||
|
|
if (Instance == null) Instance = this;
|
||
|
|
else Destroy(gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool CanStartConversation()
|
||
|
|
{
|
||
|
|
return activeSessions.Count < maxSimultaneousConversations;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void StartConversation(EnemyAI initiator, EnemyAI responder)
|
||
|
|
{
|
||
|
|
if (!CanStartConversation()) return;
|
||
|
|
|
||
|
|
ConversationSession session = new ConversationSession(initiator, responder, maxConversationDuration);
|
||
|
|
activeSessions.Add(session);
|
||
|
|
StartCoroutine(RunConversation(session));
|
||
|
|
}
|
||
|
|
|
||
|
|
private IEnumerator RunConversation(ConversationSession session)
|
||
|
|
{
|
||
|
|
Debug.Log($"<color=cyan>[ConvManager]</color> Starting: {session.initiator.npcName} & {session.responder.npcName}");
|
||
|
|
|
||
|
|
// Phase 1: Initiator speaks
|
||
|
|
bool phase1Complete = false;
|
||
|
|
session.RequestDialogue(session.initiator, (success) => phase1Complete = true);
|
||
|
|
|
||
|
|
float startTime = Time.time;
|
||
|
|
while (!phase1Complete && Time.time < startTime + 10f) yield return null;
|
||
|
|
|
||
|
|
if (phase1Complete && !session.isInterrupted)
|
||
|
|
{
|
||
|
|
yield return new WaitForSeconds(4f); // Reading time
|
||
|
|
|
||
|
|
// Phase 2: Responder speaks
|
||
|
|
bool phase2Complete = false;
|
||
|
|
session.RequestDialogue(session.responder, (success) => phase2Complete = true);
|
||
|
|
|
||
|
|
float phase2StartTime = Time.time;
|
||
|
|
while (!phase2Complete && Time.time < phase2StartTime + 10f) yield return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
yield return new WaitForSeconds(4f);
|
||
|
|
EndConversation(session);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void EndConversation(ConversationSession session)
|
||
|
|
{
|
||
|
|
if (activeSessions.Contains(session))
|
||
|
|
{
|
||
|
|
session.Cleanup();
|
||
|
|
activeSessions.Remove(session);
|
||
|
|
Debug.Log($"<color=cyan>[ConvManager]</color> Ended session. Active: {activeSessions.Count}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void InterruptConversation(EnemyAI npc)
|
||
|
|
{
|
||
|
|
ConversationSession session = activeSessions.Find(s => s.initiator == npc || s.responder == npc);
|
||
|
|
if (session != null)
|
||
|
|
{
|
||
|
|
session.isInterrupted = true;
|
||
|
|
EndConversation(session);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public class ConversationSession
|
||
|
|
{
|
||
|
|
public EnemyAI initiator;
|
||
|
|
public EnemyAI responder;
|
||
|
|
public float durationLimit;
|
||
|
|
public bool isInterrupted;
|
||
|
|
|
||
|
|
public ConversationSession(EnemyAI initiator, EnemyAI responder, float limit)
|
||
|
|
{
|
||
|
|
this.initiator = initiator;
|
||
|
|
this.responder = responder;
|
||
|
|
this.durationLimit = limit;
|
||
|
|
|
||
|
|
initiator.isTalking = true;
|
||
|
|
responder.isTalking = true;
|
||
|
|
|
||
|
|
// Set references for Gizmos and Facing
|
||
|
|
initiator.SetTalkingPartner(responder);
|
||
|
|
responder.SetTalkingPartner(initiator);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void RequestDialogue(EnemyAI speaker, Action<bool> callback)
|
||
|
|
{
|
||
|
|
if (isInterrupted) { callback?.Invoke(false); return; }
|
||
|
|
|
||
|
|
EnemyAI listener = (speaker == initiator) ? responder : initiator;
|
||
|
|
|
||
|
|
// Face each other
|
||
|
|
speaker.FaceTarget(listener.transform.position);
|
||
|
|
listener.FaceTarget(speaker.transform.position);
|
||
|
|
|
||
|
|
string prompt = $"You are {speaker.npcName} talking to {listener.npcName}. Previous context: None. " +
|
||
|
|
"Keep it natural and short.";
|
||
|
|
|
||
|
|
GeminiService.Instance.GetResponse(speaker.persona, prompt, (json) => {
|
||
|
|
if (isInterrupted) { callback?.Invoke(false); return; }
|
||
|
|
speaker.ProcessDialogueResult(json);
|
||
|
|
callback?.Invoke(true);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Cleanup()
|
||
|
|
{
|
||
|
|
if (initiator != null) initiator.isTalking = false;
|
||
|
|
if (responder != null) responder.isTalking = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|