GameManager.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  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. using UnityEngine.UIElements;
  10. public class GameManager : MonoBehaviour
  11. {
  12. public static GameManager Instance { get; private set; }
  13. public List<GameObject> playerCharacters = new List<GameObject>();
  14. public List<GameObject> enemyCharacters = new List<GameObject>();
  15. [Header("Decision Controller")]
  16. public PlayerDecisionController playerDecisionController;
  17. private Dictionary<GameObject, GameObject> enemySelectedTargets = new Dictionary<GameObject, GameObject>();
  18. public bool isContinuousRunActive = false; // Indicates if the game is in continuous run mode
  19. private const string ENEMY_LAYER_NAME = "Enemies";
  20. private int enemyLayerMask;
  21. private bool isActionExecutionPhaseActive;
  22. private bool waitingForAdvanceInput = false;
  23. private void Awake()
  24. {
  25. if (Instance == null)
  26. {
  27. Instance = this;
  28. DontDestroyOnLoad(gameObject);
  29. // Initialize PlayerDecisionController if not set
  30. if (playerDecisionController == null)
  31. {
  32. playerDecisionController = FindFirstObjectByType<PlayerDecisionController>();
  33. if (playerDecisionController == null)
  34. {
  35. GameObject pdcGO = new GameObject("PlayerDecisionController");
  36. playerDecisionController = pdcGO.AddComponent<PlayerDecisionController>();
  37. }
  38. }
  39. int enemyLayer = LayerMask.NameToLayer(ENEMY_LAYER_NAME);
  40. if (enemyLayer == -1)
  41. {
  42. Debug.LogError($"Layer '{ENEMY_LAYER_NAME}' not found. Please create it in Project Settings > Tags and Layers, and assign your enemy prefabs to it.");
  43. // Disable functionality or handle error appropriately
  44. }
  45. else
  46. {
  47. enemyLayerMask = 1 << enemyLayer;
  48. }
  49. }
  50. else
  51. {
  52. Destroy(gameObject);
  53. return; // Avoid further initialization if this is not the singleton instance
  54. }
  55. }
  56. internal void StartBattle(List<GameObject> placedPlayerCharacters, List<GameObject> placedEnemyCharacters)
  57. {
  58. playerCharacters = placedPlayerCharacters;
  59. enemyCharacters = placedEnemyCharacters;
  60. // Refresh PlayerDecisionController with current player characters
  61. if (playerDecisionController != null)
  62. {
  63. playerDecisionController.RefreshPlayerCharacters();
  64. playerDecisionController.SetEnabled(false); // Disable initially until decision phase
  65. }
  66. // Start the battle logic here
  67. EnemiesDecideAction();
  68. StartCoroutine(MainBattleLoopCoroutine());
  69. }
  70. private IEnumerator MainBattleLoopCoroutine()
  71. {
  72. while (true) // Main battle loop
  73. {
  74. // --- Player Decision Phase ---
  75. Time.timeScale = 1f; // Ensure normal time scale for player decision phase
  76. // Check for completed movements before turn starts
  77. CheckAndResetCompletedMovements();
  78. // Keep existing actions, only allow new decisions for characters with no action
  79. playerDecisionController.UpdateVisualStates(); // Update colors: pink for no action, green for incomplete
  80. playerDecisionController.SetEnabled(true);
  81. // Show lines for any existing incomplete actions
  82. playerDecisionController.ShowActiveActionLines();
  83. // Wait for all players to make their initial decisions (only needed at turn start)
  84. while (!playerDecisionController.AllCharactersHaveActions())
  85. {
  86. yield return null; // Wait for player input through PlayerDecisionController
  87. }
  88. // Disable input during execution phase and hide action lines
  89. playerDecisionController.SetEnabled(false);
  90. playerDecisionController.HideActiveActionLines();
  91. // --- Pause point after decision (if not in continuous run mode) ---
  92. if (!isContinuousRunActive)
  93. {
  94. PauseGame();
  95. yield return new WaitUntil(() => Time.timeScale > 0 || isContinuousRunActive);
  96. }
  97. if (Time.timeScale == 0 && isContinuousRunActive)
  98. {
  99. ResumeGame();
  100. }
  101. // --- Action execution phase ---
  102. // yield return StartCoroutine(ExecutePlayerActionsCoroutine());
  103. yield return StartCoroutine(ExecuteAllActionsSimultaneously());
  104. // TODO: add ement action execution here for a full turn
  105. if (BattleUIManager.Instance != null)
  106. {
  107. BattleUIManager.Instance.SetButtonsInteractable(true);
  108. }
  109. EnemiesDecideAction(); // Enemies decide their actions after players have executed their actions
  110. // --- Post-Actions: Loop or Pause ---
  111. if (isContinuousRunActive)
  112. {
  113. if (Time.timeScale > 0)
  114. {
  115. yield return new WaitForSeconds(0.5f); // Brief delay if running
  116. }
  117. }
  118. else
  119. {
  120. PauseGame();
  121. }
  122. // Check for battle end conditions using proper alive/dead detection
  123. List<GameObject> alivePlayers = GetAlivePlayers();
  124. List<GameObject> aliveEnemies = GetAliveEnemies();
  125. if (alivePlayers.Count == 0 || aliveEnemies.Count == 0)
  126. {
  127. PauseGame();
  128. if (BattleUIManager.Instance != null)
  129. {
  130. BattleUIManager.Instance.DisableBattleControls(); // Disable UI controls
  131. }
  132. // Handle battle end logic
  133. if (alivePlayers.Count == 0)
  134. {
  135. HandlePlayerDefeat(playerCharacters);
  136. }
  137. else if (aliveEnemies.Count == 0)
  138. {
  139. HandlePlayerVictory(enemyCharacters);
  140. }
  141. break; // Exit the loop if battle is over
  142. }
  143. }
  144. }
  145. public List<GameObject> GetAllCharacters()
  146. {
  147. return playerCharacters.Concat(enemyCharacters).ToList();
  148. }
  149. /// <summary>
  150. /// Get all alive player characters
  151. /// </summary>
  152. public List<GameObject> GetAlivePlayers()
  153. {
  154. var alive = playerCharacters
  155. .Where(p => p != null)
  156. .Where(p =>
  157. {
  158. var character = p.GetComponent<Character>();
  159. bool isAlive = character != null && !character.IsDead;
  160. return isAlive;
  161. })
  162. .ToList();
  163. return alive;
  164. }
  165. /// <summary>
  166. /// Get all alive enemy characters
  167. /// </summary>
  168. public List<GameObject> GetAliveEnemies()
  169. {
  170. var alive = enemyCharacters
  171. .Where(e => e != null)
  172. .Where(e =>
  173. {
  174. var character = e.GetComponent<Character>();
  175. bool isAlive = character != null && !character.IsDead;
  176. return isAlive;
  177. })
  178. .ToList();
  179. return alive;
  180. }
  181. /// <summary>
  182. /// Handle player victory (all enemies defeated)
  183. /// </summary>
  184. private void HandlePlayerVictory(List<GameObject> defeatedEnemies)
  185. {
  186. // Check if there are actually any enemies to loot
  187. bool hasLootableEnemies = false;
  188. if (defeatedEnemies != null && defeatedEnemies.Count > 0)
  189. {
  190. foreach (var enemy in defeatedEnemies)
  191. {
  192. if (enemy != null)
  193. {
  194. var character = enemy.GetComponent<Character>();
  195. if (character != null && character.IsDead)
  196. {
  197. hasLootableEnemies = true;
  198. break;
  199. }
  200. }
  201. }
  202. }
  203. if (hasLootableEnemies)
  204. {
  205. // Find or create the post-battle loot system
  206. var lootSystem = FindFirstObjectByType<PostBattleLootSystem>();
  207. if (lootSystem == null)
  208. {
  209. // Create loot system if it doesn't exist
  210. GameObject lootSystemGO = new GameObject("PostBattleLootSystem");
  211. lootSystem = lootSystemGO.AddComponent<PostBattleLootSystem>();
  212. }
  213. // Initialize and start looting
  214. lootSystem.InitializeLootSystem(defeatedEnemies);
  215. lootSystem.OnLootingComplete += OnLootingComplete;
  216. lootSystem.StartLooting();
  217. }
  218. else
  219. {
  220. // No loot to collect, show victory screen with return button
  221. StartCoroutine(ShowVictoryScreenAndReturn());
  222. }
  223. }
  224. /// <summary>
  225. /// Handle player defeat (all players dead)
  226. /// </summary>
  227. private void HandlePlayerDefeat(List<GameObject> defeatedPlayers)
  228. {
  229. // Find or create the game over screen
  230. var gameOverScreen = FindFirstObjectByType<GameOverScreen>();
  231. if (gameOverScreen == null)
  232. {
  233. // Create game over screen if it doesn't exist
  234. GameObject gameOverGO = new GameObject("GameOverScreen");
  235. gameOverScreen = gameOverGO.AddComponent<GameOverScreen>();
  236. }
  237. // Show game over screen with statistics
  238. gameOverScreen.ShowGameOver(defeatedPlayers);
  239. }
  240. /// <summary>
  241. /// Show a simple victory screen when there's no loot and allow return to map
  242. /// </summary>
  243. private System.Collections.IEnumerator ShowVictoryScreenAndReturn()
  244. {
  245. // Create a simple victory overlay using UI Toolkit or IMGUI
  246. GameObject victoryUIGO = new GameObject("VictoryUI");
  247. var uiDocument = victoryUIGO.AddComponent<UIDocument>();
  248. // Create basic victory UI
  249. var rootElement = uiDocument.rootVisualElement;
  250. // Create overlay
  251. var overlay = new VisualElement();
  252. overlay.style.position = Position.Absolute;
  253. overlay.style.top = 0;
  254. overlay.style.left = 0;
  255. overlay.style.right = 0;
  256. overlay.style.bottom = 0;
  257. overlay.style.backgroundColor = new Color(0, 0, 0, 0.8f);
  258. overlay.style.justifyContent = Justify.Center;
  259. overlay.style.alignItems = Align.Center;
  260. // Create victory panel
  261. var panel = new VisualElement();
  262. panel.style.backgroundColor = new Color(0.1f, 0.3f, 0.1f, 0.95f);
  263. panel.style.borderTopWidth = 3;
  264. panel.style.borderBottomWidth = 3;
  265. panel.style.borderLeftWidth = 3;
  266. panel.style.borderRightWidth = 3;
  267. panel.style.borderTopColor = Color.yellow;
  268. panel.style.borderBottomColor = Color.yellow;
  269. panel.style.borderLeftColor = Color.yellow;
  270. panel.style.borderRightColor = Color.yellow;
  271. panel.style.borderTopLeftRadius = 15;
  272. panel.style.borderTopRightRadius = 15;
  273. panel.style.borderBottomLeftRadius = 15;
  274. panel.style.borderBottomRightRadius = 15;
  275. panel.style.paddingTop = 40;
  276. panel.style.paddingBottom = 40;
  277. panel.style.paddingLeft = 60;
  278. panel.style.paddingRight = 60;
  279. panel.style.width = 500;
  280. // Victory title
  281. var title = new Label("🏆 VICTORY! 🏆");
  282. title.style.fontSize = 36;
  283. title.style.color = Color.yellow;
  284. title.style.unityTextAlign = TextAnchor.MiddleCenter;
  285. title.style.marginBottom = 20;
  286. title.style.unityFontStyleAndWeight = FontStyle.Bold;
  287. // Victory message
  288. var message = new Label("The enemies have been defeated!\nThe battle is won!");
  289. message.style.fontSize = 18;
  290. message.style.color = Color.white;
  291. message.style.unityTextAlign = TextAnchor.MiddleCenter;
  292. message.style.marginBottom = 30;
  293. message.style.whiteSpace = WhiteSpace.Normal;
  294. // Return button
  295. var returnButton = new Button(() =>
  296. {
  297. // Destroy the victory UI
  298. if (victoryUIGO != null) Destroy(victoryUIGO);
  299. // Proceed to return to exploration
  300. OnLootingComplete();
  301. });
  302. returnButton.text = "Return to Map";
  303. returnButton.style.fontSize = 18;
  304. returnButton.style.paddingTop = 15;
  305. returnButton.style.paddingBottom = 15;
  306. returnButton.style.paddingLeft = 30;
  307. returnButton.style.paddingRight = 30;
  308. returnButton.style.backgroundColor = new Color(0.2f, 0.7f, 0.2f, 0.9f);
  309. returnButton.style.color = Color.white;
  310. returnButton.style.borderTopLeftRadius = 8;
  311. returnButton.style.borderTopRightRadius = 8;
  312. returnButton.style.borderBottomLeftRadius = 8;
  313. returnButton.style.borderBottomRightRadius = 8;
  314. returnButton.style.marginBottom = 10;
  315. // Backup return button (fallback)
  316. var backupButton = new Button(() =>
  317. {
  318. // Destroy the victory UI
  319. if (victoryUIGO != null) Destroy(victoryUIGO);
  320. // Direct scene load as fallback
  321. try
  322. {
  323. UnityEngine.SceneManagement.SceneManager.LoadScene("MapScene2");
  324. }
  325. catch (System.Exception e)
  326. {
  327. Debug.LogError($"Backup scene transition failed: {e.Message}");
  328. }
  329. });
  330. backupButton.text = "Force Return to Map";
  331. backupButton.style.fontSize = 14;
  332. backupButton.style.paddingTop = 10;
  333. backupButton.style.paddingBottom = 10;
  334. backupButton.style.paddingLeft = 20;
  335. backupButton.style.paddingRight = 20;
  336. backupButton.style.backgroundColor = new Color(0.7f, 0.4f, 0.2f, 0.9f);
  337. backupButton.style.color = Color.white;
  338. backupButton.style.borderTopLeftRadius = 5;
  339. backupButton.style.borderTopRightRadius = 5;
  340. backupButton.style.borderBottomLeftRadius = 5;
  341. backupButton.style.borderBottomRightRadius = 5;
  342. // Space key hint
  343. var hint = new Label("Press SPACE or click the button to continue");
  344. hint.style.fontSize = 14;
  345. hint.style.color = Color.gray;
  346. hint.style.unityTextAlign = TextAnchor.MiddleCenter;
  347. hint.style.marginTop = 15;
  348. // Assemble UI
  349. panel.Add(title);
  350. panel.Add(message);
  351. panel.Add(returnButton);
  352. panel.Add(backupButton);
  353. panel.Add(hint);
  354. overlay.Add(panel);
  355. rootElement.Add(overlay);
  356. // Make the UI focusable for keyboard input
  357. rootElement.focusable = true;
  358. rootElement.Focus();
  359. // Handle keyboard input
  360. bool waitingForInput = true;
  361. rootElement.RegisterCallback<KeyDownEvent>((evt) =>
  362. {
  363. if (evt.keyCode == KeyCode.Space && waitingForInput)
  364. {
  365. waitingForInput = false;
  366. if (victoryUIGO != null) Destroy(victoryUIGO);
  367. OnLootingComplete();
  368. }
  369. });
  370. // Wait for user input (this coroutine will be stopped when the button is clicked or space is pressed)
  371. while (waitingForInput && victoryUIGO != null)
  372. {
  373. yield return null;
  374. }
  375. }
  376. /// <summary>
  377. /// Called when looting is complete, proceed to end battle
  378. /// </summary>
  379. private void OnLootingComplete()
  380. {
  381. // Save any updated team data back to the game state
  382. SaveBattleResults();
  383. // Return to MapScene2 (exploration scene)
  384. StartCoroutine(ReturnToExplorationScene());
  385. }
  386. /// <summary>
  387. /// Save battle results and return to exploration
  388. /// </summary>
  389. private System.Collections.IEnumerator ReturnToExplorationScene()
  390. {
  391. // Give a brief moment for any final UI updates
  392. yield return new WaitForSeconds(0.5f);
  393. // Load the exploration scene
  394. try
  395. {
  396. UnityEngine.SceneManagement.SceneManager.LoadScene("MapScene2");
  397. }
  398. catch (System.Exception e)
  399. {
  400. Debug.LogError($"❌ Failed to load MapScene2: {e.Message}");
  401. Debug.LogError($"Stack trace: {e.StackTrace}");
  402. // Fallback - try to find and use battle setup
  403. var battleSetup = FindFirstObjectByType<EnhancedBattleSetup>();
  404. if (battleSetup != null)
  405. {
  406. Debug.Log("🔄 Trying fallback via EnhancedBattleSetup...");
  407. battleSetup.EndBattleSession(true); // Player victory
  408. }
  409. else
  410. {
  411. Debug.LogWarning("⚠️ No scene transition method available");
  412. // Create a simple UI to inform the player
  413. GameObject errorUIGO = new GameObject("SceneLoadErrorUI");
  414. var uiDocument = errorUIGO.AddComponent<UIDocument>();
  415. var rootElement = uiDocument.rootVisualElement;
  416. var overlay = new VisualElement();
  417. overlay.style.position = Position.Absolute;
  418. overlay.style.top = 0;
  419. overlay.style.left = 0;
  420. overlay.style.right = 0;
  421. overlay.style.bottom = 0;
  422. overlay.style.backgroundColor = new Color(0.5f, 0, 0, 0.8f);
  423. overlay.style.justifyContent = Justify.Center;
  424. overlay.style.alignItems = Align.Center;
  425. var errorLabel = new Label("Scene transition failed. Please restart the game.");
  426. errorLabel.style.fontSize = 18;
  427. errorLabel.style.color = Color.white;
  428. errorLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
  429. errorLabel.style.backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.9f);
  430. errorLabel.style.paddingTop = 20;
  431. errorLabel.style.paddingBottom = 20;
  432. errorLabel.style.paddingLeft = 20;
  433. errorLabel.style.paddingRight = 20;
  434. errorLabel.style.borderTopLeftRadius = 10;
  435. errorLabel.style.borderTopRightRadius = 10;
  436. errorLabel.style.borderBottomLeftRadius = 10;
  437. errorLabel.style.borderBottomRightRadius = 10;
  438. overlay.Add(errorLabel);
  439. rootElement.Add(overlay);
  440. }
  441. }
  442. }
  443. /// <summary>
  444. /// Save battle results back to persistent game state
  445. /// </summary>
  446. private void SaveBattleResults()
  447. {
  448. // Update team data with any changes from battle (health, items, etc.)
  449. var alivePlayerCharacters = GetAlivePlayers();
  450. // You could save character states here if needed
  451. }
  452. #region Debug and Testing Methods
  453. /// <summary>
  454. /// [PUBLIC] Manually trigger return to map - can be called by UI buttons or debug scripts
  455. /// </summary>
  456. public void ManualReturnToMap()
  457. {
  458. // Save any updated team data back to the game state
  459. SaveBattleResults();
  460. // Return to MapScene2 (exploration scene)
  461. StartCoroutine(ReturnToExplorationScene());
  462. }
  463. #endregion
  464. private IEnumerator ExecuteAllActionsSimultaneously()
  465. {
  466. isActionExecutionPhaseActive = true;
  467. var allActions = new Dictionary<GameObject, GameObject>();
  468. var movementActions = new Dictionary<GameObject, Vector3>();
  469. // Collect player actions from Character components
  470. foreach (GameObject playerGO in playerCharacters)
  471. {
  472. Character character = playerGO.GetComponent<Character>();
  473. if (character != null && character.actionData.hasTarget)
  474. {
  475. if (character.actionData.targetEnemy != null)
  476. {
  477. allActions[playerGO] = character.actionData.targetEnemy;
  478. }
  479. else if (character.actionData.targetPosition != Vector3.zero)
  480. {
  481. movementActions[playerGO] = character.actionData.targetPosition;
  482. }
  483. }
  484. }
  485. foreach (var action in enemySelectedTargets)
  486. {
  487. allActions[action.Key] = action.Value;
  488. }
  489. int playerActionsCount = allActions.Count - enemySelectedTargets.Count + movementActions.Count;
  490. enemySelectedTargets.Clear(); // Clear enemy targets as well
  491. if (allActions.Count == 0 && movementActions.Count == 0)
  492. {
  493. isActionExecutionPhaseActive = false;
  494. yield break;
  495. }
  496. // Configure All NavMeshAgents before starting movement
  497. foreach (var action in allActions)
  498. {
  499. GameObject actor = action.Key;
  500. if (actor == null)
  501. {
  502. continue;
  503. }
  504. Character character = actor.GetComponent<Character>();
  505. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  506. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  507. if (agent != null)
  508. {
  509. // For melee weapons (range <= 2), get much closer. For ranged, stay back more.
  510. float stoppingDistance = attackRange <= 2 ? 0.2f : Mathf.Max(0.5f, attackRange - 1.0f);
  511. agent.stoppingDistance = stoppingDistance;
  512. // Configure movement speed based on character's MovementSpeed stat
  513. if (character != null)
  514. {
  515. // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
  516. // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
  517. agent.speed = (character.MovementSpeed / 10f) * 1.75f;
  518. // Set high acceleration to reach max speed quickly
  519. agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
  520. }
  521. else
  522. {
  523. agent.speed = 1.75f; // Default speed if no character component (halved)
  524. agent.acceleration = 5.25f; // Default acceleration (halved)
  525. }
  526. //
  527. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  528. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  529. // Start movement
  530. GameObject target = action.Value;
  531. if (target != null)
  532. {
  533. agent.SetDestination(target.transform.position);
  534. agent.isStopped = false;
  535. }
  536. }
  537. else
  538. {
  539. Debug.LogWarning($"NavMeshAgent not found or disabled on {actor.name}. Cannot move towards target.");
  540. }
  541. }
  542. // Handle pure movement actions (no enemy target)
  543. foreach (var movement in movementActions)
  544. {
  545. GameObject playerGO = movement.Key;
  546. Vector3 targetPosition = movement.Value;
  547. NavMeshAgent agent = playerGO.GetComponent<NavMeshAgent>();
  548. if (agent != null)
  549. {
  550. // Configure movement speed for pure movement actions based on character's MovementSpeed stat
  551. Character character = playerGO.GetComponent<Character>();
  552. if (character != null)
  553. {
  554. // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
  555. // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
  556. agent.speed = (character.MovementSpeed / 10f) * 1.75f;
  557. // Set high acceleration to reach max speed quickly
  558. agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
  559. }
  560. else
  561. {
  562. agent.speed = 1.75f; // Default speed if no character component (halved)
  563. agent.acceleration = 5.25f; // Default acceleration (halved)
  564. }
  565. agent.SetDestination(targetPosition);
  566. agent.isStopped = false;
  567. }
  568. }
  569. // handle movement duration based on mode
  570. //
  571. if (allActions.Count > 0 || movementActions.Count > 0)
  572. {
  573. yield return StartCoroutine(MovementPhaseWithTargetTracking(allActions, movementActions, 1.0f));
  574. }
  575. // else
  576. // {
  577. // // If there are only attacks (no movement), still wait for 1 second
  578. // yield return new WaitForSeconds(1.0f);
  579. // }
  580. if (!isContinuousRunActive)
  581. {
  582. //
  583. //
  584. // Check for completed movement actions before showing lines
  585. CheckAndResetCompletedMovements();
  586. // Enable input and show lines for actions during pause - allow re-picking actions
  587. playerDecisionController.UpdateVisualStates(); // Update colors
  588. playerDecisionController.SetEnabled(true); // Allow action changes
  589. playerDecisionController.ShowActiveActionLines();
  590. PauseGame();
  591. waitingForAdvanceInput = true;
  592. yield return new WaitUntil(() => !waitingForAdvanceInput);
  593. // Disable input when advancing
  594. playerDecisionController.SetEnabled(false);
  595. ResumeGame();
  596. yield return new WaitForSeconds(1.0f);
  597. }
  598. foreach (var action in allActions)
  599. {
  600. GameObject actor = action.Key;
  601. GameObject target = action.Value;
  602. if (actor == null || target == null) continue;
  603. Character character = actor.GetComponent<Character>();
  604. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  605. float distance = Vector3.Distance(actor.transform.position, target.transform.position);
  606. if (distance <= attackRange)
  607. {
  608. // Execute the character's attack action
  609. character.ExecuteAction();
  610. }
  611. else
  612. {
  613. }
  614. }
  615. // Check movement completion and reset action if reached (movement actions are one-time)
  616. foreach (var movement in movementActions)
  617. {
  618. GameObject actor = movement.Key;
  619. Vector3 targetPosition = movement.Value;
  620. if (actor == null) continue;
  621. Character character = actor.GetComponent<Character>();
  622. if (character != null)
  623. {
  624. // Use horizontal distance only, ignore Y differences
  625. Vector3 actorPos = actor.transform.position;
  626. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  627. Vector2 targetPos2D = new Vector2(targetPosition.x, targetPosition.z);
  628. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  629. if (horizontalDistance <= 1.2f) // Movement completion threshold
  630. {
  631. character.actionData.Reset();
  632. character.SetVisualState(ActionDecisionState.NoAction);
  633. }
  634. }
  635. }
  636. isActionExecutionPhaseActive = false;
  637. BattleUIManager.Instance.SetButtonsInteractable(true);
  638. }
  639. private IEnumerator MovementPhaseWithTargetTracking(Dictionary<GameObject, GameObject> allActions, Dictionary<GameObject, Vector3> movementActions, float movementDuration)
  640. {
  641. float elapsedTime = 0f;
  642. // Store witch agents are still moving (not in attack range)
  643. Dictionary<NavMeshAgent, GameObject> activeMovements = new Dictionary<NavMeshAgent, GameObject>();
  644. Dictionary<NavMeshAgent, float> agentAttackRanges = new Dictionary<NavMeshAgent, float>();
  645. // Initialize active movements for attack actions
  646. foreach (var action in allActions)
  647. {
  648. GameObject actor = action.Key;
  649. GameObject target = action.Value;
  650. if (actor == null || target == null) continue;
  651. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  652. if (agent != null && agent.enabled)
  653. {
  654. // Get the character's attack range
  655. Character character = actor.GetComponent<Character>();
  656. float attackRange = character != null ? character.GetAttackRange() : 2.0f;
  657. // Configure movement speed based on character's MovementSpeed stat
  658. if (character != null)
  659. {
  660. // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
  661. // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
  662. agent.speed = (character.MovementSpeed / 10f) * 1.75f;
  663. // Set high acceleration to reach max speed quickly
  664. agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
  665. }
  666. else
  667. {
  668. agent.speed = 1.75f; // Default speed if no character component (halved)
  669. agent.acceleration = 5.25f; // Default acceleration (halved)
  670. }
  671. // Configure agent for proper pathfinding around obstacles
  672. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  673. agent.avoidancePriority = UnityEngine.Random.Range(0, 100); // Random priority to avoid clustering
  674. activeMovements[agent] = target;
  675. agentAttackRanges[agent] = attackRange;
  676. }
  677. }
  678. // Initialize active movements for pure movement actions
  679. foreach (var movement in movementActions)
  680. {
  681. GameObject actor = movement.Key;
  682. Vector3 targetPosition = movement.Value;
  683. if (actor == null) continue;
  684. NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
  685. if (agent != null && agent.enabled)
  686. {
  687. // For movement actions, use a small stopping distance to get closer
  688. float arrivalRange = 1.2f;
  689. // Configure movement speed based on character's MovementSpeed stat
  690. Character character = actor.GetComponent<Character>();
  691. if (character != null)
  692. {
  693. // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
  694. // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
  695. agent.speed = (character.MovementSpeed / 10f) * 1.75f;
  696. // Set high acceleration to reach max speed quickly
  697. agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
  698. }
  699. else
  700. {
  701. agent.speed = 1.75f; // Default speed if no character component (halved)
  702. agent.acceleration = 5.25f; // Default acceleration (halved)
  703. }
  704. // Configure agent for proper pathfinding around obstacles
  705. agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
  706. agent.avoidancePriority = UnityEngine.Random.Range(0, 100);
  707. agent.stoppingDistance = 0.1f; // Get very close to the target
  708. // Create a dummy target GameObject at the position for consistency with the tracking system
  709. GameObject dummyTarget = new GameObject("MovementTarget");
  710. dummyTarget.transform.position = targetPosition;
  711. activeMovements[agent] = dummyTarget;
  712. agentAttackRanges[agent] = arrivalRange;
  713. }
  714. }
  715. while (elapsedTime < movementDuration && Time.timeScale > 0 && activeMovements.Count > 0)
  716. {
  717. var agentsToRemove = new List<NavMeshAgent>();
  718. foreach (var movement in activeMovements)
  719. {
  720. NavMeshAgent agent = movement.Key;
  721. GameObject target = movement.Value;
  722. float attackRange = agentAttackRanges[agent];
  723. if (agent == null || target == null || agent.gameObject == null)
  724. {
  725. agentsToRemove.Add(agent);
  726. continue;
  727. }
  728. float distanceToTarget = Vector3.Distance(agent.transform.position, target.transform.position);
  729. // Debug velocity and speed every 10 frames to avoid spam
  730. if (Time.frameCount % 10 == 0)
  731. {
  732. Character character = agent.GetComponent<Character>();
  733. string charName = character != null ? character.CharacterName : agent.gameObject.name;
  734. }
  735. if (distanceToTarget <= attackRange)
  736. {
  737. agent.isStopped = true;
  738. agent.velocity = Vector3.zero;
  739. agentsToRemove.Add(agent);
  740. // If this is a movement target (dummy), check if we should reset the action
  741. if (target.name == "MovementTarget")
  742. {
  743. Character character = agent.GetComponent<Character>();
  744. if (character != null && character.actionData.targetPosition != Vector3.zero)
  745. {
  746. character.actionData.Reset();
  747. character.SetVisualState(ActionDecisionState.NoAction);
  748. }
  749. }
  750. }
  751. else if (!agent.isStopped)
  752. {
  753. // For movement targets, go directly to the position
  754. if (target.name == "MovementTarget")
  755. {
  756. agent.SetDestination(target.transform.position);
  757. // Check if agent has reached destination or is very close but stopped moving
  758. if (agent.pathStatus == NavMeshPathStatus.PathComplete &&
  759. (!agent.hasPath || agent.remainingDistance < 0.1f))
  760. {
  761. Character character = agent.GetComponent<Character>();
  762. if (character != null)
  763. {
  764. Vector3 actorPos = agent.transform.position;
  765. Vector3 targetPos = character.actionData.targetPosition;
  766. Vector2 actorPos2D = new Vector2(actorPos.x, actorPos.z);
  767. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  768. float horizontalDistance = Vector2.Distance(actorPos2D, targetPos2D);
  769. if (horizontalDistance <= 1.2f)
  770. {
  771. agent.isStopped = true;
  772. agentsToRemove.Add(agent);
  773. character.actionData.Reset();
  774. character.SetVisualState(ActionDecisionState.NoAction);
  775. }
  776. }
  777. }
  778. }
  779. else
  780. {
  781. // Update destination to track moving target, but stop just outside attack range
  782. Vector3 directionToTarget = (target.transform.position - agent.transform.position).normalized;
  783. Vector3 stoppingPosition = target.transform.position - directionToTarget * (attackRange * 0.9f);
  784. agent.SetDestination(stoppingPosition);
  785. }
  786. // Check if agent is stuck or can't reach destination
  787. if (agent.pathStatus == NavMeshPathStatus.PathInvalid)
  788. {
  789. Debug.LogWarning($"{agent.gameObject.name} cannot find path to {target.name}");
  790. agent.SetDestination(target.transform.position);
  791. }
  792. }
  793. }
  794. foreach (var agent in agentsToRemove)
  795. {
  796. activeMovements.Remove(agent);
  797. agentAttackRanges.Remove(agent);
  798. }
  799. yield return null; // wait one frame
  800. elapsedTime += Time.deltaTime;
  801. }
  802. // Stop any remaining moving agents and clean up dummy targets
  803. foreach (var movement in activeMovements)
  804. {
  805. if (movement.Key != null && movement.Key.enabled)
  806. {
  807. movement.Key.isStopped = true;
  808. movement.Key.velocity = Vector3.zero;
  809. }
  810. // Clean up dummy movement targets
  811. if (movement.Value != null && movement.Value.name == "MovementTarget")
  812. {
  813. Destroy(movement.Value);
  814. }
  815. }
  816. }
  817. public void AdvanceAction()
  818. {
  819. // Hide action lines when advancing
  820. if (playerDecisionController != null)
  821. {
  822. playerDecisionController.HideActiveActionLines();
  823. }
  824. if (waitingForAdvanceInput)
  825. {
  826. waitingForAdvanceInput = false;
  827. }
  828. else if (Time.timeScale == 0 && !isActionExecutionPhaseActive)
  829. {
  830. ResumeGame();
  831. }
  832. else
  833. {
  834. }
  835. }
  836. public void PauseGame()
  837. {
  838. Time.timeScale = 0f; // Pause the game
  839. BattleUIManager.Instance.SetButtonsInteractable(true);
  840. }
  841. public void ResumeGame()
  842. {
  843. Time.timeScale = 1f; // Resume the game
  844. }
  845. private void EnemiesDecideAction()
  846. {
  847. enemySelectedTargets.Clear();
  848. foreach (var enemy in enemyCharacters)
  849. {
  850. if (enemy == null)
  851. {
  852. Debug.LogWarning("Found null enemy in enemyCharacters list");
  853. continue;
  854. }
  855. if (playerCharacters.Count == 0)
  856. {
  857. Debug.LogWarning("No player characters available for enemies to target");
  858. continue;
  859. }
  860. // Logic for enemy action decision
  861. GameObject closestPlayer = null;
  862. float minDistance = float.MaxValue;
  863. List<GameObject> alivePlayers = playerCharacters.Where(player => !player.GetComponent<Character>().IsDead).ToList();
  864. foreach (var player in alivePlayers)
  865. {
  866. if (player == null) continue; // Safety check
  867. float distance = Vector3.Distance(enemy.transform.position, player.transform.position);
  868. if (distance < minDistance)
  869. {
  870. minDistance = distance;
  871. closestPlayer = player;
  872. }
  873. }
  874. if (closestPlayer != null)
  875. {
  876. // Set the enemy's action data to attack the target
  877. Character enemyCharacter = enemy.GetComponent<Character>();
  878. if (enemyCharacter != null)
  879. {
  880. enemyCharacter.actionData.SetAttackTarget(closestPlayer);
  881. }
  882. enemySelectedTargets[enemy] = closestPlayer; // Assign the closest player as the target
  883. }
  884. else
  885. {
  886. Debug.LogWarning($"{enemy.name} could not find a valid target");
  887. }
  888. enemy.GetComponent<Character>().SetVisualState(ActionDecisionState.EnemyAction);
  889. }
  890. }
  891. private void CheckAndResetCompletedMovements()
  892. {
  893. foreach (GameObject playerGO in playerCharacters)
  894. {
  895. Character character = playerGO.GetComponent<Character>();
  896. if (character != null && character.actionData.targetPosition != Vector3.zero && character.actionData.targetEnemy == null)
  897. {
  898. // This is a movement action, check if completed using horizontal distance only
  899. Vector3 playerPos = playerGO.transform.position;
  900. Vector3 targetPos = character.actionData.targetPosition;
  901. // Use only X and Z coordinates (ignore Y differences)
  902. Vector2 playerPos2D = new Vector2(playerPos.x, playerPos.z);
  903. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  904. float horizontalDistance = Vector2.Distance(playerPos2D, targetPos2D);
  905. if (horizontalDistance <= 1.2f) // Threshold for "close enough"
  906. {
  907. character.actionData.Reset();
  908. character.SetVisualState(ActionDecisionState.NoAction);
  909. }
  910. }
  911. }
  912. }
  913. }