using System; using System.Collections; using System.Text; using UnityEngine; using UnityEngine.Networking; namespace Hallucinate.AI { [Serializable] public class Part { public string text; } [Serializable] public class Content { public Part[] parts; } [Serializable] public class Candidate { public Content content; } [Serializable] public class GeminiResponse { public Candidate[] candidates; } public class GeminiService : MonoBehaviour { public static GeminiService Instance { get; private set; } private int activeRequests = 0; private const int MAX_CONCURRENT_REQUESTS = 5; [SerializeField] private string[] apiKeys = { "YOUR_KEY_1", "YOUR_KEY_2" }; private int currentKeyIndex = 0; [SerializeField] private string geminiURL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent"; private float nextRequestTime = 0f; private string[] fallbackDialogues = { "{ \"text\": \"Nice weather, isn't it?\", \"speedMod\": 0.0, \"suspicionMod\": -5.0, \"aggressionMod\": 0.0, \"braveryMod\": 0.0, \"healthMod\": 0.0 }", "{ \"text\": \"Did you hear something? Probably just a rat.\", \"speedMod\": 0.0, \"suspicionMod\": 2.0, \"aggressionMod\": 0.1, \"braveryMod\": -5.0, \"healthMod\": 0.0 }", "{ \"text\": \"I'm so tired of this shift.\", \"speedMod\": -0.2, \"suspicionMod\": 0.0, \"aggressionMod\": -0.1, \"braveryMod\": 5.0, \"healthMod\": 0.0 }", "{ \"text\": \"You looks strong, I should be careful.\", \"speedMod\": 0.1, \"suspicionMod\": 5.0, \"aggressionMod\": -0.2, \"braveryMod\": 10.0, \"healthMod\": 0.0 }" }; private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } private string GetNextKey() { if (apiKeys == null || apiKeys.Length == 0) return ""; string key = apiKeys[currentKeyIndex]; currentKeyIndex = (currentKeyIndex + 1) % apiKeys.Length; return key; } public void GetResponse(string persona, string prompt, Action onComplete) { if (Time.time < nextRequestTime) { Debug.LogWarning("[Gemini] API is cooling down. Using fallback."); onComplete?.Invoke(fallbackDialogues[UnityEngine.Random.Range(0, fallbackDialogues.Length)]); return; } if (activeRequests >= MAX_CONCURRENT_REQUESTS) { onComplete?.Invoke(fallbackDialogues[UnityEngine.Random.Range(0, fallbackDialogues.Length)]); return; } StartCoroutine(PostRequest(persona, prompt, onComplete)); } private IEnumerator PostRequest(string persona, string prompt, Action onComplete) { activeRequests++; string jsonInstruction = " Respond ONLY with a JSON object: { " + "'text': 'dialogue content', " + "'speedMod': 0.0 (change movement speed), " + "'suspicionMod': 0.0 (change suspicion level), " + "'aggressionMod': 0.0 (0.1 to 0.5 makes NPC shoot faster, negative makes them slower), " + "'braveryMod': 0.0 (positive makes them less likely to panic, negative makes them scared), " + "'healthMod': 0.0 (positive heals NPC, negative damages them) " + "}. Keep values realistic."; string escapedPersona = persona.Replace("\"", "\\\""); string escapedPrompt = prompt.Replace("\"", "\\\""); var jsonBody = $@"{{ ""systemInstruction"": {{""parts"": [{{ ""text"": ""{escapedPersona} {jsonInstruction}"" }}]}}, ""contents"": [{{""parts"": [{{ ""text"": ""{escapedPrompt}"" }}]}}], ""generationConfig"": {{ ""maxOutputTokens"": 150, ""temperature"": 0.8, ""responseMimeType"": ""application/json"" }} }}"; var requestURL = $"{geminiURL}?key={GetNextKey()}"; using (var request = new UnityWebRequest(requestURL, "POST")) { byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { var response = JsonUtility.FromJson(request.downloadHandler.text); if (response?.candidates?.Length > 0 && response.candidates[0].content?.parts?.Length > 0) { onComplete?.Invoke(response.candidates[0].content.parts[0].text); } } else { Debug.LogError($"[Gemini] API Error: {request.error}"); if (request.responseCode == 429) { nextRequestTime = Time.time + 60f; } onComplete?.Invoke(fallbackDialogues[UnityEngine.Random.Range(0, fallbackDialogues.Length)]); } } activeRequests--; } } }