using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.VisualScripting; using UnityEngine; using UnityEngine.AI; using UnityEngine.Rendering.UI; using UnityEngine.UIElements; public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } public List playerCharacters = new List(); public List enemyCharacters = new List(); [Header("Decision Controller")] public PlayerDecisionController playerDecisionController; private Dictionary enemySelectedTargets = new Dictionary(); public bool isContinuousRunActive = false; // Indicates if the game is in continuous run mode private const string ENEMY_LAYER_NAME = "Enemies"; private int enemyLayerMask; private bool isActionExecutionPhaseActive; private bool waitingForAdvanceInput = false; private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); // Initialize PlayerDecisionController if not set if (playerDecisionController == null) { playerDecisionController = FindFirstObjectByType(); if (playerDecisionController == null) { GameObject pdcGO = new GameObject("PlayerDecisionController"); playerDecisionController = pdcGO.AddComponent(); } } int enemyLayer = LayerMask.NameToLayer(ENEMY_LAYER_NAME); if (enemyLayer == -1) { Debug.LogError($"Layer '{ENEMY_LAYER_NAME}' not found. Please create it in Project Settings > Tags and Layers, and assign your enemy prefabs to it."); // Disable functionality or handle error appropriately } else { enemyLayerMask = 1 << enemyLayer; } } else { Destroy(gameObject); return; // Avoid further initialization if this is not the singleton instance } } internal void StartBattle(List placedPlayerCharacters, List placedEnemyCharacters) { playerCharacters = placedPlayerCharacters; enemyCharacters = placedEnemyCharacters; // Refresh PlayerDecisionController with current player characters if (playerDecisionController != null) { playerDecisionController.RefreshPlayerCharacters(); playerDecisionController.SetEnabled(false); // Disable initially until decision phase } // Start the battle logic here EnemiesDecideAction(); StartCoroutine(MainBattleLoopCoroutine()); } private IEnumerator MainBattleLoopCoroutine() { while (true) // Main battle loop { // --- Player Decision Phase --- Time.timeScale = 1f; // Ensure normal time scale for player decision phase // Check for completed movements before turn starts CheckAndResetCompletedMovements(); // Keep existing actions, only allow new decisions for characters with no action playerDecisionController.UpdateVisualStates(); // Update colors: pink for no action, green for incomplete playerDecisionController.SetEnabled(true); // Show lines for any existing incomplete actions playerDecisionController.ShowActiveActionLines(); // Wait for all players to make their initial decisions (only needed at turn start) while (!playerDecisionController.AllCharactersHaveActions()) { yield return null; // Wait for player input through PlayerDecisionController } // Disable input during execution phase and hide action lines playerDecisionController.SetEnabled(false); playerDecisionController.HideActiveActionLines(); // --- Pause point after decision (if not in continuous run mode) --- if (!isContinuousRunActive) { PauseGame(); yield return new WaitUntil(() => Time.timeScale > 0 || isContinuousRunActive); } if (Time.timeScale == 0 && isContinuousRunActive) { ResumeGame(); } // --- Action execution phase --- // yield return StartCoroutine(ExecutePlayerActionsCoroutine()); yield return StartCoroutine(ExecuteAllActionsSimultaneously()); // TODO: add ement action execution here for a full turn if (BattleUIManager.Instance != null) { BattleUIManager.Instance.SetButtonsInteractable(true); } EnemiesDecideAction(); // Enemies decide their actions after players have executed their actions // --- Post-Actions: Loop or Pause --- if (isContinuousRunActive) { if (Time.timeScale > 0) { yield return new WaitForSeconds(0.5f); // Brief delay if running } } else { PauseGame(); } // Check for battle end conditions using proper alive/dead detection List alivePlayers = GetAlivePlayers(); List aliveEnemies = GetAliveEnemies(); // Debug: Log current battle state Debug.Log($"πŸ” Battle state check: {alivePlayers.Count} alive players, {aliveEnemies.Count} alive enemies"); if (alivePlayers.Count == 0 || aliveEnemies.Count == 0) { Debug.Log("🏁 BATTLE END DETECTED!"); PauseGame(); if (BattleUIManager.Instance != null) { BattleUIManager.Instance.DisableBattleControls(); // Disable UI controls } // Handle battle end logic if (alivePlayers.Count == 0) { Debug.Log("πŸ’€ PLAYER DEFEAT"); HandlePlayerDefeat(playerCharacters); } else if (aliveEnemies.Count == 0) { Debug.Log("πŸ† PLAYER VICTORY"); HandlePlayerVictory(enemyCharacters); } break; // Exit the loop if battle is over } } } public List GetAllCharacters() { return playerCharacters.Concat(enemyCharacters).ToList(); } /// /// Get all alive player characters /// public List GetAlivePlayers() { var alive = playerCharacters .Where(p => p != null) .Where(p => { var character = p.GetComponent(); bool isAlive = character != null && !character.IsDead; Debug.Log($"πŸ” Player {p.name}: Character={character != null}, IsDead={character?.IsDead}, Alive={isAlive}"); return isAlive; }) .ToList(); Debug.Log($"πŸ” Total alive players: {alive.Count}/{playerCharacters.Count}"); return alive; } /// /// Get all alive enemy characters /// public List GetAliveEnemies() { var alive = enemyCharacters .Where(e => e != null) .Where(e => { var character = e.GetComponent(); bool isAlive = character != null && !character.IsDead; Debug.Log($"πŸ” Enemy {e.name}: Character={character != null}, IsDead={character?.IsDead}, Alive={isAlive}"); return isAlive; }) .ToList(); Debug.Log($"πŸ” Total alive enemies: {alive.Count}/{enemyCharacters.Count}"); return alive; } /// /// Handle player victory (all enemies defeated) /// private void HandlePlayerVictory(List defeatedEnemies) { Debug.Log("πŸ† Players have won the battle!"); // Check if there are actually any enemies to loot bool hasLootableEnemies = false; if (defeatedEnemies != null && defeatedEnemies.Count > 0) { foreach (var enemy in defeatedEnemies) { if (enemy != null) { var character = enemy.GetComponent(); if (character != null && character.IsDead) { hasLootableEnemies = true; break; } } } } if (hasLootableEnemies) { // Find or create the post-battle loot system var lootSystem = FindFirstObjectByType(); if (lootSystem == null) { // Create loot system if it doesn't exist GameObject lootSystemGO = new GameObject("PostBattleLootSystem"); lootSystem = lootSystemGO.AddComponent(); } // Initialize and start looting lootSystem.InitializeLootSystem(defeatedEnemies); lootSystem.OnLootingComplete += OnLootingComplete; lootSystem.StartLooting(); } else { // No loot to collect, show victory screen with return button Debug.Log("πŸ† No loot to collect, showing victory screen"); StartCoroutine(ShowVictoryScreenAndReturn()); } } /// /// Handle player defeat (all players dead) /// private void HandlePlayerDefeat(List defeatedPlayers) { Debug.Log("πŸ’€ All players have been defeated!"); // Find or create the game over screen var gameOverScreen = FindFirstObjectByType(); if (gameOverScreen == null) { // Create game over screen if it doesn't exist GameObject gameOverGO = new GameObject("GameOverScreen"); gameOverScreen = gameOverGO.AddComponent(); } // Show game over screen with statistics gameOverScreen.ShowGameOver(defeatedPlayers); } /// /// Show a simple victory screen when there's no loot and allow return to map /// private System.Collections.IEnumerator ShowVictoryScreenAndReturn() { // Create a simple victory overlay using UI Toolkit or IMGUI GameObject victoryUIGO = new GameObject("VictoryUI"); var uiDocument = victoryUIGO.AddComponent(); // Create basic victory UI var rootElement = uiDocument.rootVisualElement; // Create overlay var overlay = new VisualElement(); overlay.style.position = Position.Absolute; overlay.style.top = 0; overlay.style.left = 0; overlay.style.right = 0; overlay.style.bottom = 0; overlay.style.backgroundColor = new Color(0, 0, 0, 0.8f); overlay.style.justifyContent = Justify.Center; overlay.style.alignItems = Align.Center; // Create victory panel var panel = new VisualElement(); panel.style.backgroundColor = new Color(0.1f, 0.3f, 0.1f, 0.95f); panel.style.borderTopWidth = 3; panel.style.borderBottomWidth = 3; panel.style.borderLeftWidth = 3; panel.style.borderRightWidth = 3; panel.style.borderTopColor = Color.yellow; panel.style.borderBottomColor = Color.yellow; panel.style.borderLeftColor = Color.yellow; panel.style.borderRightColor = Color.yellow; panel.style.borderTopLeftRadius = 15; panel.style.borderTopRightRadius = 15; panel.style.borderBottomLeftRadius = 15; panel.style.borderBottomRightRadius = 15; panel.style.paddingTop = 40; panel.style.paddingBottom = 40; panel.style.paddingLeft = 60; panel.style.paddingRight = 60; panel.style.width = 500; // Victory title var title = new Label("πŸ† VICTORY! πŸ†"); title.style.fontSize = 36; title.style.color = Color.yellow; title.style.unityTextAlign = TextAnchor.MiddleCenter; title.style.marginBottom = 20; title.style.unityFontStyleAndWeight = FontStyle.Bold; // Victory message var message = new Label("The enemies have been defeated!\nThe battle is won!"); message.style.fontSize = 18; message.style.color = Color.white; message.style.unityTextAlign = TextAnchor.MiddleCenter; message.style.marginBottom = 30; message.style.whiteSpace = WhiteSpace.Normal; // Return button var returnButton = new Button(() => { // Destroy the victory UI if (victoryUIGO != null) Destroy(victoryUIGO); // Proceed to return to exploration OnLootingComplete(); }); returnButton.text = "Return to Map"; returnButton.style.fontSize = 18; returnButton.style.paddingTop = 15; returnButton.style.paddingBottom = 15; returnButton.style.paddingLeft = 30; returnButton.style.paddingRight = 30; returnButton.style.backgroundColor = new Color(0.2f, 0.7f, 0.2f, 0.9f); returnButton.style.color = Color.white; returnButton.style.borderTopLeftRadius = 8; returnButton.style.borderTopRightRadius = 8; returnButton.style.borderBottomLeftRadius = 8; returnButton.style.borderBottomRightRadius = 8; returnButton.style.marginBottom = 10; // Backup return button (fallback) var backupButton = new Button(() => { // Destroy the victory UI if (victoryUIGO != null) Destroy(victoryUIGO); // Direct scene load as fallback try { Debug.Log("πŸ—ΊοΈ Using backup scene transition to MapScene2"); UnityEngine.SceneManagement.SceneManager.LoadScene("MapScene2"); } catch (System.Exception e) { Debug.LogError($"Backup scene transition failed: {e.Message}"); } }); backupButton.text = "Force Return to Map"; backupButton.style.fontSize = 14; backupButton.style.paddingTop = 10; backupButton.style.paddingBottom = 10; backupButton.style.paddingLeft = 20; backupButton.style.paddingRight = 20; backupButton.style.backgroundColor = new Color(0.7f, 0.4f, 0.2f, 0.9f); backupButton.style.color = Color.white; backupButton.style.borderTopLeftRadius = 5; backupButton.style.borderTopRightRadius = 5; backupButton.style.borderBottomLeftRadius = 5; backupButton.style.borderBottomRightRadius = 5; // Space key hint var hint = new Label("Press SPACE or click the button to continue"); hint.style.fontSize = 14; hint.style.color = Color.gray; hint.style.unityTextAlign = TextAnchor.MiddleCenter; hint.style.marginTop = 15; // Assemble UI panel.Add(title); panel.Add(message); panel.Add(returnButton); panel.Add(backupButton); panel.Add(hint); overlay.Add(panel); rootElement.Add(overlay); // Make the UI focusable for keyboard input rootElement.focusable = true; rootElement.Focus(); // Handle keyboard input bool waitingForInput = true; rootElement.RegisterCallback((evt) => { if (evt.keyCode == KeyCode.Space && waitingForInput) { waitingForInput = false; if (victoryUIGO != null) Destroy(victoryUIGO); OnLootingComplete(); } }); // Wait for user input (this coroutine will be stopped when the button is clicked or space is pressed) while (waitingForInput && victoryUIGO != null) { yield return null; } } /// /// Called when looting is complete, proceed to end battle /// private void OnLootingComplete() { Debug.Log("πŸ† Battle and looting complete, returning to exploration"); Debug.Log($"πŸ† Called from: {System.Environment.StackTrace}"); // Save any updated team data back to the game state SaveBattleResults(); // Return to MapScene2 (exploration scene) StartCoroutine(ReturnToExplorationScene()); } /// /// Save battle results and return to exploration /// private System.Collections.IEnumerator ReturnToExplorationScene() { Debug.Log("πŸ—ΊοΈ Starting return to exploration scene..."); // Give a brief moment for any final UI updates yield return new WaitForSeconds(0.5f); // Load the exploration scene try { Debug.Log("πŸ—ΊοΈ Loading MapScene2..."); UnityEngine.SceneManagement.SceneManager.LoadScene("MapScene2"); Debug.Log("πŸ—ΊοΈ Scene load initiated successfully"); } catch (System.Exception e) { Debug.LogError($"❌ Failed to load MapScene2: {e.Message}"); Debug.LogError($"Stack trace: {e.StackTrace}"); // Fallback - try to find and use battle setup var battleSetup = FindFirstObjectByType(); if (battleSetup != null) { Debug.Log("πŸ”„ Trying fallback via EnhancedBattleSetup..."); battleSetup.EndBattleSession(true); // Player victory } else { Debug.LogWarning("⚠️ No scene transition method available"); // Create a simple UI to inform the player GameObject errorUIGO = new GameObject("SceneLoadErrorUI"); var uiDocument = errorUIGO.AddComponent(); var rootElement = uiDocument.rootVisualElement; var overlay = new VisualElement(); overlay.style.position = Position.Absolute; overlay.style.top = 0; overlay.style.left = 0; overlay.style.right = 0; overlay.style.bottom = 0; overlay.style.backgroundColor = new Color(0.5f, 0, 0, 0.8f); overlay.style.justifyContent = Justify.Center; overlay.style.alignItems = Align.Center; var errorLabel = new Label("Scene transition failed. Please restart the game."); errorLabel.style.fontSize = 18; errorLabel.style.color = Color.white; errorLabel.style.unityTextAlign = TextAnchor.MiddleCenter; errorLabel.style.backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.9f); errorLabel.style.paddingTop = 20; errorLabel.style.paddingBottom = 20; errorLabel.style.paddingLeft = 20; errorLabel.style.paddingRight = 20; errorLabel.style.borderTopLeftRadius = 10; errorLabel.style.borderTopRightRadius = 10; errorLabel.style.borderBottomLeftRadius = 10; errorLabel.style.borderBottomRightRadius = 10; overlay.Add(errorLabel); rootElement.Add(overlay); } } } /// /// Save battle results back to persistent game state /// private void SaveBattleResults() { // Update team data with any changes from battle (health, items, etc.) var alivePlayerCharacters = GetAlivePlayers(); // You could save character states here if needed Debug.Log($"πŸ’Ύ Saved battle results for {alivePlayerCharacters.Count} surviving players"); } #region Debug and Testing Methods /// /// [DEBUG] Force battle end for testing post-battle systems /// [ContextMenu("Test Battle Victory")] public void TestBattleVictory() { Debug.Log("πŸ§ͺ [TEST] Forcing battle victory to test post-battle loot system"); // Kill all enemies for testing foreach (var enemy in enemyCharacters) { if (enemy != null) { var character = enemy.GetComponent(); if (character != null && !character.IsDead) { // Force enemy death for testing character.TakeDamage(1000); } } } // Manually trigger victory HandlePlayerVictory(enemyCharacters); } /// /// [DEBUG] Force battle defeat for testing game over /// [ContextMenu("Test Battle Defeat")] public void TestBattleDefeat() { Debug.Log("πŸ§ͺ [TEST] Forcing battle defeat to test game over screen"); // Kill all players for testing foreach (var player in playerCharacters) { if (player != null) { var character = player.GetComponent(); if (character != null && !character.IsDead) { // Force player death for testing character.TakeDamage(1000); } } } // Manually trigger defeat HandlePlayerDefeat(playerCharacters); } /// /// [PUBLIC] Manually trigger return to map - can be called by UI buttons or debug scripts /// public void ManualReturnToMap() { Debug.Log("πŸ—ΊοΈ [MANUAL] Manual return to map requested"); // Save any updated team data back to the game state SaveBattleResults(); // Return to MapScene2 (exploration scene) StartCoroutine(ReturnToExplorationScene()); } #endregion private IEnumerator ExecuteAllActionsSimultaneously() { isActionExecutionPhaseActive = true; var allActions = new Dictionary(); var movementActions = new Dictionary(); // Collect player actions from Character components foreach (GameObject playerGO in playerCharacters) { Character character = playerGO.GetComponent(); if (character != null && character.actionData.hasTarget) { if (character.actionData.targetEnemy != null) { allActions[playerGO] = character.actionData.targetEnemy; } else if (character.actionData.targetPosition != Vector3.zero) { movementActions[playerGO] = character.actionData.targetPosition; } } } foreach (var action in enemySelectedTargets) { allActions[action.Key] = action.Value; } int playerActionsCount = allActions.Count - enemySelectedTargets.Count + movementActions.Count; enemySelectedTargets.Clear(); // Clear enemy targets as well if (allActions.Count == 0 && movementActions.Count == 0) { isActionExecutionPhaseActive = false; yield break; } // Configure All NavMeshAgents before starting movement foreach (var action in allActions) { GameObject actor = action.Key; if (actor == null) { continue; } Character character = actor.GetComponent(); float attackRange = character != null ? character.GetAttackRange() : 2.0f; NavMeshAgent agent = actor.GetComponent(); if (agent != null) { // For melee weapons (range <= 2), get much closer. For ranged, stay back more. float stoppingDistance = attackRange <= 2 ? 0.2f : Mathf.Max(0.5f, attackRange - 1.0f); agent.stoppingDistance = stoppingDistance; // agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance; agent.avoidancePriority = UnityEngine.Random.Range(0, 100); // Start movement GameObject target = action.Value; if (target != null) { agent.SetDestination(target.transform.position); agent.isStopped = false; } } else { Debug.LogWarning($"NavMeshAgent not found or disabled on {actor.name}. Cannot move towards target."); } } // Handle pure movement actions (no enemy target) foreach (var movement in movementActions) { GameObject playerGO = movement.Key; Vector3 targetPosition = movement.Value; NavMeshAgent agent = playerGO.GetComponent(); if (agent != null) { agent.SetDestination(targetPosition); agent.isStopped = false; } } // handle movement duration based on mode // if (allActions.Count > 0 || movementActions.Count > 0) { yield return StartCoroutine(MovementPhaseWithTargetTracking(allActions, movementActions, 1.0f)); } // else // { // // If there are only attacks (no movement), still wait for 1 second // yield return new WaitForSeconds(1.0f); // } if (!isContinuousRunActive) { // // // Check for completed movement actions before showing lines CheckAndResetCompletedMovements(); // Enable input and show lines for actions during pause - allow re-picking actions playerDecisionController.UpdateVisualStates(); // Update colors playerDecisionController.SetEnabled(true); // Allow action changes playerDecisionController.ShowActiveActionLines(); PauseGame(); waitingForAdvanceInput = true; yield return new WaitUntil(() => !waitingForAdvanceInput); // Disable input when advancing playerDecisionController.SetEnabled(false); ResumeGame(); yield return new WaitForSeconds(1.0f); } foreach (var action in allActions) { GameObject actor = action.Key; GameObject target = action.Value; if (actor == null || target == null) continue; Character character = actor.GetComponent(); float attackRange = character != null ? character.GetAttackRange() : 2.0f; float distance = Vector3.Distance(actor.transform.position, target.transform.position); if (distance <= attackRange) { // Execute the character's attack action character.ExecuteAction(); } else { } } // Check movement completion and reset action if reached (movement actions are one-time) foreach (var movement in movementActions) { GameObject actor = movement.Key; Vector3 targetPosition = movement.Value; if (actor == null) continue; Character character = actor.GetComponent(); if (character != null) { // Use horizontal distance only, ignore Y differences Vector3 actorPos = actor.transform.position; Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z); Vector2 targetPos2D = new Vector2(targetPosition.x, targetPosition.z); float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D); if (horizontalDistance <= 1.2f) // Movement completion threshold { character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } } isActionExecutionPhaseActive = false; BattleUIManager.Instance.SetButtonsInteractable(true); } private IEnumerator MovementPhaseWithTargetTracking(Dictionary allActions, Dictionary movementActions, float movementDuration) { float elapsedTime = 0f; // Store witch agents are still moving (not in attack range) Dictionary activeMovements = new Dictionary(); Dictionary agentAttackRanges = new Dictionary(); // Initialize active movements for attack actions foreach (var action in allActions) { GameObject actor = action.Key; GameObject target = action.Value; if (actor == null || target == null) continue; NavMeshAgent agent = actor.GetComponent(); if (agent != null && agent.enabled) { // Get the character's attack range Character character = actor.GetComponent(); float attackRange = character != null ? character.GetAttackRange() : 2.0f; // Configure agent for proper pathfinding around obstacles agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance; agent.avoidancePriority = UnityEngine.Random.Range(0, 100); // Random priority to avoid clustering activeMovements[agent] = target; agentAttackRanges[agent] = attackRange; } } // Initialize active movements for pure movement actions foreach (var movement in movementActions) { GameObject actor = movement.Key; Vector3 targetPosition = movement.Value; if (actor == null) continue; NavMeshAgent agent = actor.GetComponent(); if (agent != null && agent.enabled) { // For movement actions, use a small stopping distance to get closer float arrivalRange = 1.2f; // Configure agent for proper pathfinding around obstacles agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance; agent.avoidancePriority = UnityEngine.Random.Range(0, 100); agent.stoppingDistance = 0.1f; // Get very close to the target // Create a dummy target GameObject at the position for consistency with the tracking system GameObject dummyTarget = new GameObject("MovementTarget"); dummyTarget.transform.position = targetPosition; activeMovements[agent] = dummyTarget; agentAttackRanges[agent] = arrivalRange; } } while (elapsedTime < movementDuration && Time.timeScale > 0 && activeMovements.Count > 0) { var agentsToRemove = new List(); foreach (var movement in activeMovements) { NavMeshAgent agent = movement.Key; GameObject target = movement.Value; float attackRange = agentAttackRanges[agent]; if (agent == null || target == null || agent.gameObject == null) { agentsToRemove.Add(agent); continue; } float distanceToTarget = Vector3.Distance(agent.transform.position, target.transform.position); if (distanceToTarget <= attackRange) { agent.isStopped = true; agent.velocity = Vector3.zero; agentsToRemove.Add(agent); // If this is a movement target (dummy), check if we should reset the action if (target.name == "MovementTarget") { Character character = agent.GetComponent(); if (character != null && character.actionData.targetPosition != Vector3.zero) { character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } } else if (!agent.isStopped) { // For movement targets, go directly to the position if (target.name == "MovementTarget") { agent.SetDestination(target.transform.position); // Check if agent has reached destination or is very close but stopped moving if (agent.pathStatus == NavMeshPathStatus.PathComplete && (!agent.hasPath || agent.remainingDistance < 0.1f)) { Character character = agent.GetComponent(); if (character != null) { Vector3 actorPos = agent.transform.position; Vector3 targetPos = character.actionData.targetPosition; Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z); Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z); float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D); if (horizontalDistance <= 1.2f) { agent.isStopped = true; agentsToRemove.Add(agent); character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } } } else { // Update destination to track moving target, but stop just outside attack range Vector3 directionToTarget = (target.transform.position - agent.transform.position).normalized; Vector3 stoppingPosition = target.transform.position - directionToTarget * (attackRange * 0.9f); agent.SetDestination(stoppingPosition); } // Check if agent is stuck or can't reach destination if (agent.pathStatus == NavMeshPathStatus.PathInvalid) { Debug.LogWarning($"{agent.gameObject.name} cannot find path to {target.name}"); agent.SetDestination(target.transform.position); } } } foreach (var agent in agentsToRemove) { activeMovements.Remove(agent); agentAttackRanges.Remove(agent); } yield return null; // wait one frame elapsedTime += Time.deltaTime; } // Stop any remaining moving agents and clean up dummy targets foreach (var movement in activeMovements) { if (movement.Key != null && movement.Key.enabled) { movement.Key.isStopped = true; movement.Key.velocity = Vector3.zero; } // Clean up dummy movement targets if (movement.Value != null && movement.Value.name == "MovementTarget") { Destroy(movement.Value); } } } public void AdvanceAction() { // Hide action lines when advancing if (playerDecisionController != null) { playerDecisionController.HideActiveActionLines(); } if (waitingForAdvanceInput) { waitingForAdvanceInput = false; } else if (Time.timeScale == 0 && !isActionExecutionPhaseActive) { ResumeGame(); } else { } } public void PauseGame() { Time.timeScale = 0f; // Pause the game BattleUIManager.Instance.SetButtonsInteractable(true); } public void ResumeGame() { Time.timeScale = 1f; // Resume the game } private void EnemiesDecideAction() { enemySelectedTargets.Clear(); foreach (var enemy in enemyCharacters) { if (enemy == null) { Debug.LogWarning("Found null enemy in enemyCharacters list"); continue; } if (playerCharacters.Count == 0) { Debug.LogWarning("No player characters available for enemies to target"); continue; } // Logic for enemy action decision GameObject closestPlayer = null; float minDistance = float.MaxValue; List alivePlayers = playerCharacters.Where(player => !player.GetComponent().IsDead).ToList(); foreach (var player in alivePlayers) { if (player == null) continue; // Safety check float distance = Vector3.Distance(enemy.transform.position, player.transform.position); if (distance < minDistance) { minDistance = distance; closestPlayer = player; } } if (closestPlayer != null) { // Set the enemy's action data to attack the target Character enemyCharacter = enemy.GetComponent(); if (enemyCharacter != null) { enemyCharacter.actionData.SetAttackTarget(closestPlayer); } enemySelectedTargets[enemy] = closestPlayer; // Assign the closest player as the target } else { Debug.LogWarning($"{enemy.name} could not find a valid target"); } enemy.GetComponent().SetVisualState(ActionDecisionState.EnemyAction); } } private void CheckAndResetCompletedMovements() { foreach (GameObject playerGO in playerCharacters) { Character character = playerGO.GetComponent(); if (character != null && character.actionData.targetPosition != Vector3.zero && character.actionData.targetEnemy == null) { // This is a movement action, check if completed using horizontal distance only Vector3 playerPos = playerGO.transform.position; Vector3 targetPos = character.actionData.targetPosition; // Use only X and Z coordinates (ignore Y differences) Vector2 playerPos2D = new Vector2(playerPos.x, playerPos.z); Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z); float horizontalDistance = Vector2.Distance(playerPos2D, targetPos2D); if (horizontalDistance <= 1.2f) // Threshold for "close enough" { character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } } } }