using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using TheIsland.Models; using TheIsland.Network; using TheIsland.Agents; using TheIsland.UI; using TheIsland.Visual; namespace TheIsland.Core { /// /// Main game controller. /// Manages agent spawning, UI updates, and event handling. /// public class GameManager : MonoBehaviour { #region Singleton private static GameManager _instance; public static GameManager Instance => _instance; #endregion #region Configuration [Header("Agent Spawning")] [SerializeField] private GameObject agentPrefab; [SerializeField] private Transform agentContainer; [SerializeField] private Vector3[] spawnPositions = new Vector3[] { new Vector3(-3f, 0f, 0f), new Vector3(0f, 0f, 0f), new Vector3(3f, 0f, 0f) }; [Header("UI References")] [SerializeField] private TextMeshProUGUI connectionStatus; [SerializeField] private TextMeshProUGUI tickInfo; [SerializeField] private TextMeshProUGUI goldDisplay; [SerializeField] private Button resetButton; [SerializeField] private TMP_InputField commandInput; [SerializeField] private Button sendButton; [Header("Notification Panel")] [SerializeField] private GameObject notificationPanel; [SerializeField] private TextMeshProUGUI notificationText; [SerializeField] private float notificationDuration = 3f; #endregion #region Private Fields private Dictionary _agents = new Dictionary(); private Dictionary _agentUIs = new Dictionary(); private Dictionary _agentVisuals = new Dictionary(); private int _playerGold = 100; private int _currentTick; private int _currentDay; private int _nextSpawnIndex; // World state private string _currentTimeOfDay = "day"; private string _currentWeather = "Sunny"; #endregion #region Properties public int PlayerGold => _playerGold; public string CurrentTimeOfDay => _currentTimeOfDay; public string CurrentWeather => _currentWeather; public int CurrentDay => _currentDay; public int AliveAgentCount { get { int count = 0; // Check AgentVisual first (newest system) foreach (var visual in _agentVisuals.Values) { if (visual.IsAlive) count++; } if (count > 0) return count; // Fallback to AgentUI foreach (var agentUI in _agentUIs.Values) { if (agentUI.IsAlive) count++; } if (count > 0) return count; // Fallback to AgentController (legacy) foreach (var agent in _agents.Values) { if (agent.IsAlive) count++; } return count; return count; } } public Dictionary AllAgentVisuals => _agentVisuals; #endregion #region Unity Lifecycle private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; } private void Start() { // Subscribe to network events SubscribeToNetworkEvents(); // Setup UI SetupUI(); // Initial connection status UpdateConnectionStatus(false); } private void OnDestroy() { // Unsubscribe from network events UnsubscribeFromNetworkEvents(); // Cleanup UI listeners CleanupUI(); } #endregion #region Network Event Subscription private void SubscribeToNetworkEvents() { var network = NetworkManager.Instance; if (network == null) return; network.OnConnected += HandleConnected; network.OnDisconnected += HandleDisconnected; network.OnAgentsUpdate += HandleAgentsUpdate; network.OnAgentSpeak += HandleAgentSpeak; network.OnAgentDied += HandleAgentDied; network.OnFeed += HandleFeed; network.OnTick += HandleTick; network.OnSystemMessage += HandleSystemMessage; network.OnUserUpdate += HandleUserUpdate; // New phase events network.OnWeatherChange += HandleWeatherChange; network.OnPhaseChange += HandlePhaseChange; network.OnDayChange += HandleDayChange; network.OnHeal += HandleHeal; network.OnEncourage += HandleEncourage; network.OnTalk += HandleTalk; network.OnRevive += HandleRevive; network.OnSocialInteraction += HandleSocialInteraction; network.OnGiftEffect += HandleGiftEffect; // Phase 8 network.OnAgentAction += HandleAgentAction; // Phase 13 network.OnRandomEvent += HandleRandomEvent; // Phase 17-C network.OnGiveItem += HandleGiveItem; // Phase 23 network.OnGroupActivity += HandleGroupActivity; // Phase 24 network.OnVFXEvent += HandleVFXEvent; // Phase 8 } private void UnsubscribeFromNetworkEvents() { var network = NetworkManager.Instance; if (network == null) return; network.OnConnected -= HandleConnected; network.OnDisconnected -= HandleDisconnected; network.OnAgentsUpdate -= HandleAgentsUpdate; network.OnAgentSpeak -= HandleAgentSpeak; network.OnAgentDied -= HandleAgentDied; network.OnFeed -= HandleFeed; network.OnTick -= HandleTick; network.OnSystemMessage -= HandleSystemMessage; network.OnUserUpdate -= HandleUserUpdate; // New phase events network.OnWeatherChange -= HandleWeatherChange; network.OnPhaseChange -= HandlePhaseChange; network.OnDayChange -= HandleDayChange; network.OnHeal -= HandleHeal; network.OnEncourage -= HandleEncourage; network.OnTalk -= HandleTalk; network.OnRevive -= HandleRevive; network.OnSocialInteraction -= HandleSocialInteraction; network.OnGiftEffect -= HandleGiftEffect; // Phase 8 network.OnRandomEvent -= HandleRandomEvent; // Phase 17-C network.OnGiveItem -= HandleGiveItem; // Phase 23 network.OnGroupActivity -= HandleGroupActivity; // Phase 24 network.OnVFXEvent -= HandleVFXEvent; // Phase 8 } #endregion #region UI Setup private void SetupUI() { // Reset button if (resetButton != null) { resetButton.onClick.RemoveAllListeners(); resetButton.onClick.AddListener(OnResetClicked); } // Send button if (sendButton != null) { sendButton.onClick.RemoveAllListeners(); sendButton.onClick.AddListener(OnSendClicked); } // Command input enter key if (commandInput != null) { commandInput.onSubmit.RemoveAllListeners(); commandInput.onSubmit.AddListener(OnCommandSubmit); } // Hide notification initially if (notificationPanel != null) { notificationPanel.SetActive(false); } UpdateGoldDisplay(); } private void CleanupUI() { if (resetButton != null) { resetButton.onClick.RemoveListener(OnResetClicked); } if (sendButton != null) { sendButton.onClick.RemoveListener(OnSendClicked); } if (commandInput != null) { commandInput.onSubmit.RemoveListener(OnCommandSubmit); } } private void UpdateConnectionStatus(bool connected) { if (connectionStatus == null) return; connectionStatus.text = connected ? "Connected" : "Disconnected"; connectionStatus.color = connected ? Color.green : Color.red; } private void UpdateTickInfo() { if (tickInfo == null) return; // Format time of day nicely string timeDisplay = _currentTimeOfDay switch { "dawn" => "Dawn", "day" => "Day", "dusk" => "Dusk", "night" => "Night", _ => "Day" }; tickInfo.text = $"Day {_currentDay} | {timeDisplay} | {_currentWeather} | Tick {_currentTick} | Alive: {AliveAgentCount}"; } private void UpdateGoldDisplay() { if (goldDisplay == null) return; goldDisplay.text = $"Gold: {_playerGold}"; } #endregion #region Network Event Handlers private void HandleConnected() { Debug.Log("[GameManager] Connected to server"); UpdateConnectionStatus(true); ShowNotification("Connected to The Island!"); } private void HandleDisconnected() { Debug.Log("[GameManager] Disconnected from server"); UpdateConnectionStatus(false); ShowNotification("Disconnected from server", isError: true); } private void HandleAgentsUpdate(List agentsData) { // Null check to prevent exceptions if (agentsData == null || agentsData.Count == 0) { Debug.LogWarning("[GameManager] Received empty agents update"); return; } foreach (var data in agentsData) { // Check for AgentVisual first (newest system - 2.5D sprites) if (_agentVisuals.TryGetValue(data.id, out AgentVisual agentVisual)) { agentVisual.UpdateStats(data); } // Check for AgentUI (programmatic UI system) else if (_agentUIs.TryGetValue(data.id, out AgentUI agentUI)) { agentUI.UpdateStats(data); } // Fallback to AgentController (legacy) else if (_agents.TryGetValue(data.id, out AgentController controller)) { controller.UpdateStats(data); } else { // Spawn new agent SpawnAgent(data); } } UpdateTickInfo(); } private void HandleAgentSpeak(AgentSpeakData data) { // Check AgentVisual first (newest system - 2.5D sprites) if (_agentVisuals.TryGetValue(data.agent_id, out AgentVisual agentVisual)) { agentVisual.ShowSpeech(data.text); agentVisual.DoJump(); // Add jump effect } // Check AgentUI (programmatic UI system) else if (_agentUIs.TryGetValue(data.agent_id, out AgentUI agentUI)) { agentUI.ShowSpeech(data.text); } // Fallback to AgentController (legacy) else if (_agents.TryGetValue(data.agent_id, out AgentController controller)) { controller.ShowSpeech(data.text); } else { Debug.LogWarning($"[GameManager] Agent {data.agent_id} not found for speech"); } } private void HandleAgentDied(AgentDiedData data) { Debug.Log($"[GameManager] Agent died: {data.agent_name}"); ShowNotification(data.message, isError: true); } private void HandleFeed(FeedEventData data) { Debug.Log($"[GameManager] Feed event: {data.message}"); // Update gold if this was our action if (data.user == NetworkManager.Instance.Username) { _playerGold = data.user_gold; UpdateGoldDisplay(); } ShowNotification(data.message); } private void HandleTick(TickData data) { _currentTick = data.tick; _currentDay = data.day; // Update weather and time of day from tick data if (!string.IsNullOrEmpty(data.time_of_day)) { _currentTimeOfDay = data.time_of_day; } if (!string.IsNullOrEmpty(data.weather)) { _currentWeather = data.weather; } UpdateTickInfo(); } private void HandleSystemMessage(SystemEventData data) { Debug.Log($"[GameManager] System: {data.message}"); ShowNotification(data.message); } private void HandleUserUpdate(UserUpdateData data) { if (data.user == NetworkManager.Instance.Username) { _playerGold = data.gold; UpdateGoldDisplay(); } } private void HandleWeatherChange(WeatherChangeData data) { Debug.Log($"[GameManager] Weather changed: {data.old_weather} -> {data.new_weather}"); _currentWeather = data.new_weather; ShowNotification($"Weather: {data.new_weather}"); UpdateTickInfo(); } private void HandlePhaseChange(PhaseChangeData data) { Debug.Log($"[GameManager] Phase changed: {data.old_phase} -> {data.new_phase}"); _currentTimeOfDay = data.new_phase; ShowNotification($"The {data.new_phase} begins..."); UpdateTickInfo(); } private void HandleDayChange(DayChangeData data) { Debug.Log($"[GameManager] New day: {data.day}"); _currentDay = data.day; ShowNotification($"Day {data.day} begins!"); UpdateTickInfo(); } private void HandleHeal(HealEventData data) { Debug.Log($"[GameManager] Heal event: {data.message}"); // Update gold if this was our action if (data.user == NetworkManager.Instance.Username) { _playerGold = data.user_gold; UpdateGoldDisplay(); } ShowNotification(data.message); } private void HandleEncourage(EncourageEventData data) { Debug.Log($"[GameManager] Encourage event: {data.message}"); // Update gold if this was our action if (data.user == NetworkManager.Instance.Username) { _playerGold = data.user_gold; UpdateGoldDisplay(); } ShowNotification(data.message); } private void HandleGiveItem(GiveItemEventData data) { Debug.Log($"[GameManager] Give Item: {data.message}"); ShowNotification(data.message); if (_agentVisuals.TryGetValue(data.from_id, out AgentVisual fromVisual) && _agentVisuals.TryGetValue(data.to_id, out AgentVisual toVisual)) { // Trigger Visual Effect (Phase 23) // We assume AgentVisual has DoGiveItem method fromVisual.DoGiveItem(toVisual.transform, data.item_type); } } private void HandleGroupActivity(GroupActivityEventData data) { Debug.Log($"[GameManager] Group Activity: {data.activity_type} with {data.storyteller_name}"); if (data.activity_type == "storytelling") { ShowNotification($"🔥 {data.storyteller_name} tells a story about {data.topic}..."); // Storyteller Visuals if (_agentVisuals.TryGetValue(data.storyteller_id, out AgentVisual storyteller)) { storyteller.ShowSpeech(data.content, 8f); // Long duration for story storyteller.DoStorytelling(); // Animation trigger } // Listener Visuals if (data.listener_ids != null) { foreach (int listenerId in data.listener_ids) { if (_agentVisuals.TryGetValue(listenerId, out AgentVisual listener)) { // Listeners face the storyteller and show interest if (storyteller != null) { listener.DoListen(storyteller.transform); } } } } } } private void HandleVFXEvent(VFXEventData data) { Debug.Log($"[GameManager] VFX: {data.effect}"); if (!string.IsNullOrEmpty(data.message)) { ShowNotification(data.message); } Vector3 targetPos = new Vector3(0, 2, 0); // Default air center if (data.target_id > 0) { if (_agentVisuals.TryGetValue(data.target_id, out AgentVisual visual)) { targetPos = visual.transform.position; } else if (_agentUIs.TryGetValue(data.target_id, out AgentUI ui)) { targetPos = ui.transform.position; } } if (VFXManager.Instance != null) { VFXManager.Instance.PlayEffect(data.effect, targetPos); } } private void HandleTalk(TalkEventData data) { Debug.Log($"[GameManager] Talk event: {data.agent_name} responds about '{data.topic}'"); // Show the agent's speech response if (_agentVisuals.TryGetValue(GetAgentIdByName(data.agent_name), out AgentVisual agentVisual)) { agentVisual.ShowSpeech(data.response); } else if (_agentUIs.TryGetValue(GetAgentIdByName(data.agent_name), out AgentUI agentUI)) { agentUI.ShowSpeech(data.response); } } private void HandleRevive(ReviveEventData data) { Debug.Log($"[GameManager] Revive event: {data.message}"); // Update gold if this was our action if (data.user == NetworkManager.Instance.Username) { _playerGold = data.user_gold; UpdateGoldDisplay(); } ShowNotification(data.message); } private void HandleSocialInteraction(SocialInteractionData data) { Debug.Log($"[GameManager] Social: {data.initiator_name} -> {data.target_name} ({data.interaction_type})"); // Show dialogue from initiator if (_agentVisuals.TryGetValue(data.initiator_id, out AgentVisual initiatorVisual)) { initiatorVisual.ShowSpeech(data.dialogue); } else if (_agentUIs.TryGetValue(data.initiator_id, out AgentUI initiatorUI)) { initiatorUI.ShowSpeech(data.dialogue); } } /// /// Handle gift effect event (Twitch bits, subscriptions). /// Plays VFX and shows gratitude speech. /// private void HandleGiftEffect(GiftEffectData data) { Debug.Log($"[GameManager] Gift: {data.user} sent {data.value} {data.gift_type}"); // Find target agent position for VFX Vector3 effectPosition = Vector3.zero; int agentId = GetAgentIdByName(data.agent_name); if (agentId >= 0 && _agentVisuals.TryGetValue(agentId, out AgentVisual agentVisual)) { effectPosition = agentVisual.transform.position; // Show gratitude speech with extended duration if (!string.IsNullOrEmpty(data.gratitude)) { float duration = data.duration > 0 ? data.duration : 8f; agentVisual.ShowSpeech(data.gratitude, duration); } } else if (agentId >= 0 && _agentUIs.TryGetValue(agentId, out AgentUI agentUI)) { effectPosition = agentUI.transform.position; if (!string.IsNullOrEmpty(data.gratitude)) { agentUI.ShowSpeech(data.gratitude); } } else { // Default to center if no agent found effectPosition = new Vector3(0, 1, 0); } // Play VFX if (VFXManager.Instance != null) { VFXManager.Instance.PlayEffect(data.gift_type, effectPosition); } // Show notification ShowNotification(data.message); } #region Agent Action (Phase 13) private void HandleAgentAction(AgentActionData data) { Debug.Log($"[GameManager] Action: {data.agent_name} -> {data.action_type} at {data.location}"); // Resolve target position Vector3 targetPos = GetLocationPosition(data.location, data.target_name); // Find agent and command movement if (_agentVisuals.TryGetValue(data.agent_id, out AgentVisual agentVisual)) { // Phase 21-C: Handle Follow/Target Logic if (data.action_type == "Follow" && !string.IsNullOrEmpty(data.target_name)) { int targetId = GetAgentIdByName(data.target_name); if (targetId >= 0 && _agentVisuals.TryGetValue(targetId, out AgentVisual targetVisual)) { agentVisual.SetFollowTarget(targetVisual.transform); Debug.Log($"[GameManager] {data.agent_name} is now following {data.target_name}"); } } else { // Clear follow target for non-follow actions agentVisual.SetFollowTarget(null); agentVisual.MoveTo(targetPos); } // Optional: Show thought bubble or speech if (!string.IsNullOrEmpty(data.dialogue)) { agentVisual.ShowSpeech(data.dialogue, 3f); } } else if (_agentUIs.TryGetValue(data.agent_id, out AgentUI agentUI)) { // Fallback for UI-only agents (just show speech) if (!string.IsNullOrEmpty(data.dialogue)) { agentUI.ShowSpeech(data.dialogue); } } } private Vector3 GetLocationPosition(string location, string targetName) { // Map logical locations to world coordinates switch (location.ToLower()) { case "tree_left": return new Vector3(-12f, 0f, 5.0f); // Phase 20-C.2: Recalibrated case "tree_right": return new Vector3(13f, 0f, 5.5f); // Phase 20-C.2: Recalibrated case "campfire": case "center": return new Vector3(0f, 0f, 0f); case "water": case "beach": // Shore starts at Z=7.0. Safe beach area is Z=[3, 6] return new Vector3(Random.Range(-5, 5), 0f, Random.Range(4, 6)); case "nearby": // Wandering constrained to dry land Z=[-6, 6] return new Vector3(Random.Range(-15, 15), 0f, Random.Range(-6, 6)); case "herb_patch": // Phase 16: Herb gathering location return new Vector3(-8f, 0f, -5f); case "agent": // Move to another agent int targetId = GetAgentIdByName(targetName); if (targetId >= 0 && _agentVisuals.TryGetValue(targetId, out AgentVisual target)) { // Stand slightly offset from target return target.transform.position + new Vector3(1.5f, 0, 0); } return Vector3.zero; default: return new Vector3(0f, 0f, 0f); // Fallback to center } } #endregion #region Random Events (Phase 17-C) private void HandleRandomEvent(RandomEventData data) { Debug.Log($"[GameManager] Random Event: {data.event_type} - {data.message}"); // Display global notification banner string eventIcon = data.event_type switch { "storm_damage" => "⛈️ ", "treasure_found" => "💎 ", "beast_attack" => "🐺 ", "rumor_spread" => "💬 ", _ => "" }; ShowNotification(eventIcon + data.message); // Optional: Trigger visual effects based on event type // (Could add screen shake for storm, highlight agent for treasure, etc.) } #endregion #endregion #region Agent Management private void SpawnAgent(AgentData data) { if (agentPrefab == null) { Debug.LogError("[GameManager] Agent prefab not assigned!"); return; } // Determine spawn position Vector3 spawnPos = GetNextSpawnPosition(); // Instantiate prefab GameObject agentObj = Instantiate( agentPrefab, spawnPos, Quaternion.identity, agentContainer ); // Try to get AgentVisual first (newest system - 2.5D sprites) AgentVisual agentVisual = agentObj.GetComponent(); if (agentVisual != null) { agentVisual.Initialize(data); _agentVisuals[data.id] = agentVisual; Debug.Log($"[GameManager] Spawned agent (AgentVisual): {data.name} at {spawnPos}"); return; } // Try to get AgentUI (programmatic UI system) AgentUI agentUI = agentObj.GetComponent(); if (agentUI == null) { // Add AgentUI component - it will create all UI elements automatically agentUI = agentObj.AddComponent(); } agentUI.Initialize(data); _agentUIs[data.id] = agentUI; // Also check for legacy AgentController AgentController controller = agentObj.GetComponent(); if (controller != null) { controller.Initialize(data); _agents[data.id] = controller; } Debug.Log($"[GameManager] Spawned agent: {data.name} at {spawnPos}"); } private Vector3 GetNextSpawnPosition() { if (spawnPositions == null || spawnPositions.Length == 0) { return Vector3.zero; } Vector3 pos = spawnPositions[_nextSpawnIndex % spawnPositions.Length]; _nextSpawnIndex++; return pos; } /// /// Get an agent controller by ID. /// public AgentController GetAgent(int agentId) { _agents.TryGetValue(agentId, out AgentController controller); return controller; } /// /// Get an agent controller by name. /// public AgentController GetAgentByName(string name) { foreach (var agent in _agents.Values) { if (agent.CurrentData?.name == name) { return agent; } } return null; } /// /// Get agent ID by name (searches all agent systems). /// private int GetAgentIdByName(string name) { // Check AgentVisual first (newest system) foreach (var kvp in _agentVisuals) { if (kvp.Value.CurrentData?.name == name) { return kvp.Key; } } // Check AgentUI foreach (var kvp in _agentUIs) { if (kvp.Value.CurrentData?.name == name) { return kvp.Key; } } // Check AgentController (legacy) foreach (var kvp in _agents) { if (kvp.Value.CurrentData?.name == name) { return kvp.Key; } } return -1; } #endregion #region UI Actions private void OnResetClicked() { NetworkManager.Instance.ResetGame(); ShowNotification("Reset requested..."); } private void OnSendClicked() { SendCommand(); } private void OnCommandSubmit(string text) { SendCommand(); } private void SendCommand() { if (commandInput == null || string.IsNullOrWhiteSpace(commandInput.text)) return; NetworkManager.Instance.SendCommand(commandInput.text); commandInput.text = ""; commandInput.ActivateInputField(); } /// /// Feed a specific agent by name. /// Called from UI buttons. /// public void FeedAgent(string agentName) { NetworkManager.Instance.FeedAgent(agentName); } #endregion #region Notifications private void ShowNotification(string message, bool isError = false) { if (notificationPanel == null || notificationText == null) return; notificationText.text = message; notificationText.color = isError ? Color.red : Color.white; notificationPanel.SetActive(true); // Auto-hide CancelInvoke(nameof(HideNotification)); Invoke(nameof(HideNotification), notificationDuration); } private void HideNotification() { if (notificationPanel != null) { notificationPanel.SetActive(false); } } #endregion } }