GameManager.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Unity.VisualScripting;
  6. using UnityEngine;
  7. using UnityEngine.AI;
  8. using UnityEngine.Rendering.UI;
  9. public class GameManager : MonoBehaviour
  10. {
  11. public static GameManager Instance { get; private set; }
  12. public List<GameObject> playerCharacters = new List<GameObject>();
  13. public List<GameObject> enemyCharacters = new List<GameObject>();
  14. [Header("Decision Controller")]
  15. public PlayerDecisionController playerDecisionController;
  16. private Dictionary<GameObject, GameObject> enemySelectedTargets = new Dictionary<GameObject, GameObject>();
  17. public bool isContinuousRunActive = false; // Indicates if the game is in continuous run mode
  18. private const string ENEMY_LAYER_NAME = "Enemies";
  19. private int enemyLayerMask;
  20. private bool isActionExecutionPhaseActive;
  21. private bool waitingForAdvanceInput = false;
  22. private void Awake()
  23. {
  24. if (Instance == null)
  25. {
  26. Instance = this;
  27. DontDestroyOnLoad(gameObject);
  28. // Initialize PlayerDecisionController if not set
  29. if (playerDecisionController == null)
  30. {
  31. playerDecisionController = FindFirstObjectByType<PlayerDecisionController>();
  32. if (playerDecisionController == null)
  33. {
  34. GameObject pdcGO = new GameObject("PlayerDecisionController");
  35. playerDecisionController = pdcGO.AddComponent<PlayerDecisionController>();
  36. }
  37. }
  38. int enemyLayer = LayerMask.NameToLayer(ENEMY_LAYER_NAME);
  39. if (enemyLayer == -1)
  40. {
  41. Debug.LogError($"Layer '{ENEMY_LAYER_NAME}' not found. Please create it in Project Settings > Tags and Layers, and assign your enemy prefabs to it.");
  42. // Disable functionality or handle error appropriately
  43. }
  44. else
  45. {
  46. enemyLayerMask = 1 << enemyLayer;
  47. }
  48. }
  49. else
  50. {
  51. Destroy(gameObject);
  52. return; // Avoid further initialization if this is not the singleton instance
  53. }
  54. }
  55. internal void StartBattle(List<GameObject> placedPlayerCharacters, List<GameObject> placedEnemyCharacters)
  56. {
  57. playerCharacters = placedPlayerCharacters;
  58. enemyCharacters = placedEnemyCharacters;
  59. // Refresh PlayerDecisionController with current player characters
  60. if (playerDecisionController != null)
  61. {
  62. playerDecisionController.RefreshPlayerCharacters();
  63. playerDecisionController.SetEnabled(false); // Disable initially until decision phase
  64. }
  65. // Start the battle logic here
  66. EnemiesDecideAction();
  67. StartCoroutine(MainBattleLoopCoroutine());
  68. }
  69. private IEnumerator MainBattleLoopCoroutine()
  70. {
  71. while (true) // Main battle loop
  72. {
  73. // --- Player Decision Phase ---
  74. Time.timeScale = 1f; // Ensure normal time scale for player decision phase
  75. // Check for completed movements before turn starts
  76. CheckAndResetCompletedMovements();
  77. // Keep existing actions, only allow new decisions for characters with no action
  78. playerDecisionController.UpdateVisualStates(); // Update colors: pink for no action, green for incomplete
  79. playerDecisionController.SetEnabled(true);
  80. // Show lines for any existing incomplete actions
  81. playerDecisionController.ShowActiveActionLines();
  82. // Wait for all players to make their initial decisions (only needed at turn start)
  83. while (!playerDecisionController.AllCharactersHaveActions())
  84. {
  85. yield return null; // Wait for player input through PlayerDecisionController
  86. }
  87. // Disable input during execution phase and hide action lines
  88. playerDecisionController.SetEnabled(false);
  89. playerDecisionController.HideActiveActionLines();
  90. // --- Pause point after decision (if not in continuous run mode) ---
  91. if (!isContinuousRunActive)
  92. {
  93. PauseGame();
  94. yield return new WaitUntil(() => Time.timeScale > 0 || isContinuousRunActive);
  95. }
  96. if (Time.timeScale == 0 && isContinuousRunActive)
  97. {
  98. ResumeGame();
  99. }
  100. // --- Action execution phase ---
  101. // yield return StartCoroutine(ExecutePlayerActionsCoroutine());
  102. yield return StartCoroutine(ExecuteAllActionsSimultaneously());
  103. // TODO: add ement action execution here for a full turn
  104. if (BattleUIManager.Instance != null)
  105. {
  106. BattleUIManager.Instance.SetButtonsInteractable(true);
  107. }
  108. EnemiesDecideAction(); // Enemies decide their actions after players have executed their actions
  109. // --- Post-Actions: Loop or Pause ---
  110. if (isContinuousRunActive)
  111. {
  112. if (Time.timeScale > 0)
  113. {
  114. yield return new WaitForSeconds(0.5f); // Brief delay if running
  115. }
  116. }
  117. else
  118. {
  119. PauseGame();
  120. }
  121. // Check for battle end conditions using proper alive/dead detection
  122. List<GameObject> alivePlayers = GetAlivePlayers();
  123. List<GameObject> aliveEnemies = GetAliveEnemies();
  124. // Debug: Log current battle state
  125. Debug.Log($"🔍 Battle state check: {alivePlayers.Count} alive players, {aliveEnemies.Count} alive enemies");
  126. if (alivePlayers.Count == 0 || aliveEnemies.Count == 0)
  127. {
  128. Debug.Log("🏁 BATTLE END DETECTED!");
  129. PauseGame();
  130. if (BattleUIManager.Instance != null)
  131. {
  132. BattleUIManager.Instance.DisableBattleControls(); // Disable UI controls
  133. }
  134. // Handle battle end logic
  135. if (alivePlayers.Count == 0)
  136. {
  137. Debug.Log("💀 PLAYER DEFEAT");
  138. HandlePlayerDefeat(playerCharacters);
  139. }
  140. else if (aliveEnemies.Count == 0)
  141. {
  142. Debug.Log("🏆 PLAYER VICTORY");
  143. HandlePlayerVictory(enemyCharacters);
  144. }
  145. break; // Exit the loop if battle is over
  146. }
  147. }
  148. }
  149. public List<GameObject> GetAllCharacters()
  150. {
  151. return playerCharacters.Concat(enemyCharacters).ToList();
  152. }
  153. /// <summary>
  154. /// Get all alive player characters
  155. /// </summary>
  156. public List<GameObject> GetAlivePlayers()
  157. {
  158. var alive = playerCharacters
  159. .Where(p => p != null)
  160. .Where(p =>
  161. {
  162. var character = p.GetComponent<Character>();
  163. bool isAlive = character != null && !character.IsDead;
  164. Debug.Log($"🔍 Player {p.name}: Character={character != null}, IsDead={character?.IsDead}, Alive={isAlive}");
  165. return isAlive;
  166. })
  167. .ToList();
  168. Debug.Log($"🔍 Total alive players: {alive.Count}/{playerCharacters.Count}");
  169. return alive;
  170. }
  171. /// <summary>
  172. /// Get all alive enemy characters
  173. /// </summary>
  174. public List<GameObject> GetAliveEnemies()
  175. {
  176. var alive = enemyCharacters
  177. .Where(e => e != null)
  178. .Where(e =>
  179. {
  180. var character = e.GetComponent<Character>();
  181. bool isAlive = character != null && !character.IsDead;
  182. Debug.Log($"🔍 Enemy {e.name}: Character={character != null}, IsDead={character?.IsDead}, Alive={isAlive}");
  183. return isAlive;
  184. })
  185. .ToList();
  186. Debug.Log($"🔍 Total alive enemies: {alive.Count}/{enemyCharacters.Count}");
  187. return alive;
  188. }
  189. /// <summary>
  190. /// Handle player victory (all enemies defeated)
  191. /// </summary>
  192. private void HandlePlayerVictory(List<GameObject> defeatedEnemies)
  193. {
  194. Debug.Log("🏆 Players have won the battle!");
  195. // Find or create the post-battle loot system
  196. var lootSystem = FindFirstObjectByType<PostBattleLootSystem>();
  197. if (lootSystem == null)
  198. {
  199. // Create loot system if it doesn't exist
  200. GameObject lootSystemGO = new GameObject("PostBattleLootSystem");
  201. lootSystem = lootSystemGO.AddComponent<PostBattleLootSystem>();
  202. }
  203. // Initialize and start looting
  204. lootSystem.InitializeLootSystem(defeatedEnemies);
  205. lootSystem.OnLootingComplete += OnLootingComplete;
  206. lootSystem.StartLooting();
  207. }
  208. /// <summary>
  209. /// Handle player defeat (all players dead)
  210. /// </summary>
  211. private void HandlePlayerDefeat(List<GameObject> defeatedPlayers)
  212. {
  213. Debug.Log("💀 All players have been defeated!");
  214. // Find or create the game over screen
  215. var gameOverScreen = FindFirstObjectByType<GameOverScreen>();
  216. if (gameOverScreen == null)
  217. {
  218. // Create game over screen if it doesn't exist
  219. GameObject gameOverGO = new GameObject("GameOverScreen");
  220. gameOverScreen = gameOverGO.AddComponent<GameOverScreen>();
  221. }
  222. // Show game over screen with statistics
  223. gameOverScreen.ShowGameOver(defeatedPlayers);
  224. }
  225. /// <summary>
  226. /// Called when looting is complete, proceed to end battle
  227. /// </summary>
  228. private void OnLootingComplete()
  229. {
  230. Debug.Log("🏆 Battle and looting complete, returning to exploration");
  231. // Save any updated team data back to the game state
  232. SaveBattleResults();
  233. // Return to MapScene2 (exploration scene)
  234. StartCoroutine(ReturnToExplorationScene());
  235. }
  236. /// <summary>
  237. /// Save battle results and return to exploration
  238. /// </summary>
  239. private System.Collections.IEnumerator ReturnToExplorationScene()
  240. {
  241. // Give a brief moment for any final UI updates
  242. yield return new WaitForSeconds(0.5f);
  243. // Load the exploration scene
  244. try
  245. {
  246. UnityEngine.SceneManagement.SceneManager.LoadScene("MapScene2");
  247. }
  248. catch (System.Exception e)
  249. {
  250. Debug.LogError($"Failed to load MapScene2: {e.Message}");
  251. // Fallback - try to find and use battle setup
  252. var battleSetup = FindFirstObjectByType<EnhancedBattleSetup>();
  253. if (battleSetup != null)
  254. {
  255. battleSetup.EndBattleSession(true); // Player victory
  256. }
  257. else
  258. {
  259. Debug.LogWarning("No scene transition method available");
  260. }
  261. }
  262. }
  263. /// <summary>
  264. /// Save battle results back to persistent game state
  265. /// </summary>
  266. private void SaveBattleResults()
  267. {
  268. // Update team data with any changes from battle (health, items, etc.)
  269. var alivePlayerCharacters = GetAlivePlayers();
  270. // You could save character states here if needed
  271. Debug.Log($"💾 Saved battle results for {alivePlayerCharacters.Count} surviving players");
  272. }
  273. #region Debug and Testing Methods
  274. /// <summary>
  275. /// [DEBUG] Force battle end for testing post-battle systems
  276. /// </summary>
  277. [ContextMenu("Test Battle Victory")]
  278. public void TestBattleVictory()
  279. {
  280. Debug.Log("🧪 [TEST] Forcing battle victory to test post-battle loot system");
  281. // Kill all enemies for testing
  282. foreach (var enemy in enemyCharacters)
  283. {
  284. if (enemy != null)
  285. {
  286. var character = enemy.GetComponent<Character>();
  287. if (character != null && !character.IsDead)
  288. {
  289. // Force enemy death for testing
  290. character.TakeDamage(1000);
  291. }
  292. }
  293. }
  294. // Manually trigger victory
  295. HandlePlayerVictory(enemyCharacters);
  296. }
  297. /// <summary>
  298. /// [DEBUG] Force battle defeat for testing game over
  299. /// </summary>
  300. [ContextMenu("Test Battle Defeat")]
  301. public void TestBattleDefeat()
  302. {
  303. Debug.Log("🧪 [TEST] Forcing battle defeat to test game over screen");
  304. // Kill all players for testing
  305. foreach (var player in playerCharacters)
  306. {
  307. if (player != null)
  308. {
  309. var character = player.GetComponent<Character>();
  310. if (character != null && !character.IsDead)
  311. {
  312. // Force player death for testing
  313. character.TakeDamage(1000);
  314. }
  315. }
  316. }
  317. // Manually trigger defeat
  318. HandlePlayerDefeat(playerCharacters);
  319. }
  320. #endregion
  321. private IEnumerator ExecuteAllActionsSimultaneously()
  322. {
  323. isActionExecutionPhaseActive = true;
  324. var allActions = new Dictionary<GameObject, GameObject>();
  325. var movementActions = new Dictionary<GameObject, Vector3>();
  326. // Collect player actions from Character components
  327. foreach (GameObject playerGO in playerCharacters)
  328. {
  329. Character character = playerGO.GetComponent<Character>();
  330. if (character != null && character.actionData.hasTarget)
  331. {
  332. if (character.actionData.targetEnemy != null)
  333. {
  334. allActions[playerGO] = character.actionData.targetEnemy;
  335. }
  336. else if (character.actionData.targetPosition != Vector3.zero)
  337. {
  338. movementActions[playerGO] = character.actionData.targetPosition;
  339. }
  340. }
  341. }
  342. foreach (var action in enemySelectedTargets)
  343. {
  344. allActions[action.Key] = action.Value;
  345. }
  346. int playerActionsCount = allActions.Count - enemySelectedTargets.Count + movementActions.Count;
  347. enemySelectedTargets.Clear(); // Clear enemy targets as well
  348. if (allActions.Count == 0 && movementActions.Count == 0)
  349. {
  350. isActionExecutionPhaseActive = false;
  351. yield break;
  352. }
  353. // Configure All NavMeshAgents before starting movement
  354. foreach (var action in allActions)
  355. {
  356. GameObject actor = action.Key;
  357. if (actor == null)
  358. {
  359. continue;
  360. }
  361. Character character = actor.GetComponent<Character>();
  362. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  363. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  364. if (agent != null)
  365. {
  366. // For melee weapons (range <= 2), get much closer. For ranged, stay back more.
  367. float stoppingDistance = attackRange <= 2 ? 0.2f : Mathf.Max(0.5f, attackRange - 1.0f);
  368. agent.stoppingDistance = stoppingDistance;
  369. //
  370. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  371. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  372. // Start movement
  373. GameObject target = action.Value;
  374. if (target != null)
  375. {
  376. agent.SetDestination(target.transform.position);
  377. agent.isStopped = false;
  378. }
  379. }
  380. else
  381. {
  382. Debug.LogWarning($"NavMeshAgent not found or disabled on {actor.name}. Cannot move towards target.");
  383. }
  384. }
  385. // Handle pure movement actions (no enemy target)
  386. foreach (var movement in movementActions)
  387. {
  388. GameObject playerGO = movement.Key;
  389. Vector3 targetPosition = movement.Value;
  390. NavMeshAgent agent = playerGO.GetComponent<NavMeshAgent>();
  391. if (agent != null)
  392. {
  393. agent.SetDestination(targetPosition);
  394. agent.isStopped = false;
  395. }
  396. }
  397. // handle movement duration based on mode
  398. //
  399. if (allActions.Count > 0 || movementActions.Count > 0)
  400. {
  401. yield return StartCoroutine(MovementPhaseWithTargetTracking(allActions, movementActions, 1.0f));
  402. }
  403. // else
  404. // {
  405. // // If there are only attacks (no movement), still wait for 1 second
  406. // yield return new WaitForSeconds(1.0f);
  407. // }
  408. if (!isContinuousRunActive)
  409. {
  410. //
  411. //
  412. // Check for completed movement actions before showing lines
  413. CheckAndResetCompletedMovements();
  414. // Enable input and show lines for actions during pause - allow re-picking actions
  415. playerDecisionController.UpdateVisualStates(); // Update colors
  416. playerDecisionController.SetEnabled(true); // Allow action changes
  417. playerDecisionController.ShowActiveActionLines();
  418. PauseGame();
  419. waitingForAdvanceInput = true;
  420. yield return new WaitUntil(() => !waitingForAdvanceInput);
  421. // Disable input when advancing
  422. playerDecisionController.SetEnabled(false);
  423. ResumeGame();
  424. yield return new WaitForSeconds(1.0f);
  425. }
  426. foreach (var action in allActions)
  427. {
  428. GameObject actor = action.Key;
  429. GameObject target = action.Value;
  430. if (actor == null || target == null) continue;
  431. Character character = actor.GetComponent<Character>();
  432. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  433. float distance = Vector3.Distance(actor.transform.position, target.transform.position);
  434. if (distance <= attackRange)
  435. {
  436. // Execute the character's attack action
  437. character.ExecuteAction();
  438. }
  439. else
  440. {
  441. }
  442. }
  443. // Check movement completion and reset action if reached (movement actions are one-time)
  444. foreach (var movement in movementActions)
  445. {
  446. GameObject actor = movement.Key;
  447. Vector3 targetPosition = movement.Value;
  448. if (actor == null) continue;
  449. Character character = actor.GetComponent<Character>();
  450. if (character != null)
  451. {
  452. // Use horizontal distance only, ignore Y differences
  453. Vector3 actorPos = actor.transform.position;
  454. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  455. Vector2 targetPos2D = new Vector2(targetPosition.x, targetPosition.z);
  456. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  457. if (horizontalDistance <= 1.2f) // Movement completion threshold
  458. {
  459. character.actionData.Reset();
  460. character.SetVisualState(ActionDecisionState.NoAction);
  461. }
  462. }
  463. }
  464. isActionExecutionPhaseActive = false;
  465. BattleUIManager.Instance.SetButtonsInteractable(true);
  466. }
  467. private IEnumerator MovementPhaseWithTargetTracking(Dictionary<GameObject, GameObject> allActions, Dictionary<GameObject, Vector3> movementActions, float movementDuration)
  468. {
  469. float elapsedTime = 0f;
  470. // Store witch agents are still moving (not in attack range)
  471. Dictionary<NavMeshAgent, GameObject> activeMovements = new Dictionary<NavMeshAgent, GameObject>();
  472. Dictionary<NavMeshAgent, float> agentAttackRanges = new Dictionary<NavMeshAgent, float>();
  473. // Initialize active movements for attack actions
  474. foreach (var action in allActions)
  475. {
  476. GameObject actor = action.Key;
  477. GameObject target = action.Value;
  478. if (actor == null || target == null) continue;
  479. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  480. if (agent != null && agent.enabled)
  481. {
  482. // Get the character's attack range
  483. Character character = actor.GetComponent<Character>();
  484. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  485. // Configure agent for proper pathfinding around obstacles
  486. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  487. agent.avoidancePriority = UnityEngine.Random.Range(0, 100); // Random priority to avoid clustering
  488. activeMovements[agent] = target;
  489. agentAttackRanges[agent] = attackRange;
  490. }
  491. }
  492. // Initialize active movements for pure movement actions
  493. foreach (var movement in movementActions)
  494. {
  495. GameObject actor = movement.Key;
  496. Vector3 targetPosition = movement.Value;
  497. if (actor == null) continue;
  498. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  499. if (agent != null && agent.enabled)
  500. {
  501. // For movement actions, use a small stopping distance to get closer
  502. float arrivalRange = 1.2f;
  503. // Configure agent for proper pathfinding around obstacles
  504. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  505. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  506. agent.stoppingDistance = 0.1f; // Get very close to the target
  507. // Create a dummy target GameObject at the position for consistency with the tracking system
  508. GameObject dummyTarget = new GameObject("MovementTarget");
  509. dummyTarget.transform.position = targetPosition;
  510. activeMovements[agent] = dummyTarget;
  511. agentAttackRanges[agent] = arrivalRange;
  512. }
  513. }
  514. while (elapsedTime < movementDuration && Time.timeScale > 0 && activeMovements.Count > 0)
  515. {
  516. var agentsToRemove = new List<NavMeshAgent>();
  517. foreach (var movement in activeMovements)
  518. {
  519. NavMeshAgent agent = movement.Key;
  520. GameObject target = movement.Value;
  521. float attackRange = agentAttackRanges[agent];
  522. if (agent == null || target == null || agent.gameObject == null)
  523. {
  524. agentsToRemove.Add(agent);
  525. continue;
  526. }
  527. float distanceToTarget = Vector3.Distance(agent.transform.position, target.transform.position);
  528. if (distanceToTarget <= attackRange)
  529. {
  530. agent.isStopped = true;
  531. agent.velocity = Vector3.zero;
  532. agentsToRemove.Add(agent);
  533. // If this is a movement target (dummy), check if we should reset the action
  534. if (target.name == "MovementTarget")
  535. {
  536. Character character = agent.GetComponent<Character>();
  537. if (character != null && character.actionData.targetPosition != Vector3.zero)
  538. {
  539. character.actionData.Reset();
  540. character.SetVisualState(ActionDecisionState.NoAction);
  541. }
  542. }
  543. }
  544. else if (!agent.isStopped)
  545. {
  546. // For movement targets, go directly to the position
  547. if (target.name == "MovementTarget")
  548. {
  549. agent.SetDestination(target.transform.position);
  550. // Check if agent has reached destination or is very close but stopped moving
  551. if (agent.pathStatus == NavMeshPathStatus.PathComplete &&
  552. (!agent.hasPath || agent.remainingDistance < 0.1f))
  553. {
  554. Character character = agent.GetComponent<Character>();
  555. if (character != null)
  556. {
  557. Vector3 actorPos = agent.transform.position;
  558. Vector3 targetPos = character.actionData.targetPosition;
  559. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  560. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  561. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  562. if (horizontalDistance <= 1.2f)
  563. {
  564. agent.isStopped = true;
  565. agentsToRemove.Add(agent);
  566. character.actionData.Reset();
  567. character.SetVisualState(ActionDecisionState.NoAction);
  568. }
  569. }
  570. }
  571. }
  572. else
  573. {
  574. // Update destination to track moving target, but stop just outside attack range
  575. Vector3 directionToTarget = (target.transform.position - agent.transform.position).normalized;
  576. Vector3 stoppingPosition = target.transform.position - directionToTarget * (attackRange * 0.9f);
  577. agent.SetDestination(stoppingPosition);
  578. }
  579. // Check if agent is stuck or can't reach destination
  580. if (agent.pathStatus == NavMeshPathStatus.PathInvalid)
  581. {
  582. Debug.LogWarning($"{agent.gameObject.name} cannot find path to {target.name}");
  583. agent.SetDestination(target.transform.position);
  584. }
  585. }
  586. }
  587. foreach (var agent in agentsToRemove)
  588. {
  589. activeMovements.Remove(agent);
  590. agentAttackRanges.Remove(agent);
  591. }
  592. yield return null; // wait one frame
  593. elapsedTime += Time.deltaTime;
  594. }
  595. // Stop any remaining moving agents and clean up dummy targets
  596. foreach (var movement in activeMovements)
  597. {
  598. if (movement.Key != null && movement.Key.enabled)
  599. {
  600. movement.Key.isStopped = true;
  601. movement.Key.velocity = Vector3.zero;
  602. }
  603. // Clean up dummy movement targets
  604. if (movement.Value != null && movement.Value.name == "MovementTarget")
  605. {
  606. Destroy(movement.Value);
  607. }
  608. }
  609. }
  610. public void AdvanceAction()
  611. {
  612. // Hide action lines when advancing
  613. if (playerDecisionController != null)
  614. {
  615. playerDecisionController.HideActiveActionLines();
  616. }
  617. if (waitingForAdvanceInput)
  618. {
  619. waitingForAdvanceInput = false;
  620. }
  621. else if (Time.timeScale == 0 && !isActionExecutionPhaseActive)
  622. {
  623. ResumeGame();
  624. }
  625. else
  626. {
  627. }
  628. }
  629. public void PauseGame()
  630. {
  631. Time.timeScale = 0f; // Pause the game
  632. BattleUIManager.Instance.SetButtonsInteractable(true);
  633. }
  634. public void ResumeGame()
  635. {
  636. Time.timeScale = 1f; // Resume the game
  637. }
  638. private void EnemiesDecideAction()
  639. {
  640. enemySelectedTargets.Clear();
  641. foreach (var enemy in enemyCharacters)
  642. {
  643. if (enemy == null)
  644. {
  645. Debug.LogWarning("Found null enemy in enemyCharacters list");
  646. continue;
  647. }
  648. if (playerCharacters.Count == 0)
  649. {
  650. Debug.LogWarning("No player characters available for enemies to target");
  651. continue;
  652. }
  653. // Logic for enemy action decision
  654. GameObject closestPlayer = null;
  655. float minDistance = float.MaxValue;
  656. List<GameObject> alivePlayers = playerCharacters.Where(player => !player.GetComponent<Character>().IsDead).ToList();
  657. foreach (var player in alivePlayers)
  658. {
  659. if (player == null) continue; // Safety check
  660. float distance = Vector3.Distance(enemy.transform.position, player.transform.position);
  661. if (distance < minDistance)
  662. {
  663. minDistance = distance;
  664. closestPlayer = player;
  665. }
  666. }
  667. if (closestPlayer != null)
  668. {
  669. // Set the enemy's action data to attack the target
  670. Character enemyCharacter = enemy.GetComponent<Character>();
  671. if (enemyCharacter != null)
  672. {
  673. enemyCharacter.actionData.SetAttackTarget(closestPlayer);
  674. }
  675. enemySelectedTargets[enemy] = closestPlayer; // Assign the closest player as the target
  676. }
  677. else
  678. {
  679. Debug.LogWarning($"{enemy.name} could not find a valid target");
  680. }
  681. enemy.GetComponent<Character>().SetVisualState(ActionDecisionState.EnemyAction);
  682. }
  683. }
  684. private void CheckAndResetCompletedMovements()
  685. {
  686. foreach (GameObject playerGO in playerCharacters)
  687. {
  688. Character character = playerGO.GetComponent<Character>();
  689. if (character != null && character.actionData.targetPosition != Vector3.zero && character.actionData.targetEnemy == null)
  690. {
  691. // This is a movement action, check if completed using horizontal distance only
  692. Vector3 playerPos = playerGO.transform.position;
  693. Vector3 targetPos = character.actionData.targetPosition;
  694. // Use only X and Z coordinates (ignore Y differences)
  695. Vector2 playerPos2D = new Vector2(playerPos.x, playerPos.z);
  696. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  697. float horizontalDistance = Vector2.Distance(playerPos2D, targetPos2D);
  698. if (horizontalDistance <= 1.2f) // Threshold for "close enough"
  699. {
  700. character.actionData.Reset();
  701. character.SetVisualState(ActionDecisionState.NoAction);
  702. }
  703. }
  704. }
  705. }
  706. }