| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184 |
- 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<GameObject> playerCharacters = new List<GameObject>();
- public List<GameObject> enemyCharacters = new List<GameObject>();
- [Header("Decision Controller")]
- public PlayerDecisionController playerDecisionController;
- private Dictionary<GameObject, GameObject> enemySelectedTargets = new Dictionary<GameObject, GameObject>();
- 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<PlayerDecisionController>();
- if (playerDecisionController == null)
- {
- GameObject pdcGO = new GameObject("PlayerDecisionController");
- playerDecisionController = pdcGO.AddComponent<PlayerDecisionController>();
- }
- }
- 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<GameObject> placedPlayerCharacters, List<GameObject> 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<GameObject> alivePlayers = GetAlivePlayers();
- List<GameObject> 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<GameObject> GetAllCharacters()
- {
- return playerCharacters.Concat(enemyCharacters).ToList();
- }
- /// <summary>
- /// Get all alive player characters
- /// </summary>
- public List<GameObject> GetAlivePlayers()
- {
- var alive = playerCharacters
- .Where(p => p != null)
- .Where(p =>
- {
- var character = p.GetComponent<Character>();
- 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;
- }
- /// <summary>
- /// Get all alive enemy characters
- /// </summary>
- public List<GameObject> GetAliveEnemies()
- {
- var alive = enemyCharacters
- .Where(e => e != null)
- .Where(e =>
- {
- var character = e.GetComponent<Character>();
- 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;
- }
- /// <summary>
- /// Handle player victory (all enemies defeated)
- /// </summary>
- private void HandlePlayerVictory(List<GameObject> 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<Character>();
- if (character != null && character.IsDead)
- {
- hasLootableEnemies = true;
- break;
- }
- }
- }
- }
- if (hasLootableEnemies)
- {
- // Find or create the post-battle loot system
- var lootSystem = FindFirstObjectByType<PostBattleLootSystem>();
- if (lootSystem == null)
- {
- // Create loot system if it doesn't exist
- GameObject lootSystemGO = new GameObject("PostBattleLootSystem");
- lootSystem = lootSystemGO.AddComponent<PostBattleLootSystem>();
- }
- // 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());
- }
- }
- /// <summary>
- /// Handle player defeat (all players dead)
- /// </summary>
- private void HandlePlayerDefeat(List<GameObject> defeatedPlayers)
- {
- Debug.Log("💀 All players have been defeated!");
- // Find or create the game over screen
- var gameOverScreen = FindFirstObjectByType<GameOverScreen>();
- if (gameOverScreen == null)
- {
- // Create game over screen if it doesn't exist
- GameObject gameOverGO = new GameObject("GameOverScreen");
- gameOverScreen = gameOverGO.AddComponent<GameOverScreen>();
- }
- // Show game over screen with statistics
- gameOverScreen.ShowGameOver(defeatedPlayers);
- }
- /// <summary>
- /// Show a simple victory screen when there's no loot and allow return to map
- /// </summary>
- private System.Collections.IEnumerator ShowVictoryScreenAndReturn()
- {
- // Create a simple victory overlay using UI Toolkit or IMGUI
- GameObject victoryUIGO = new GameObject("VictoryUI");
- var uiDocument = victoryUIGO.AddComponent<UIDocument>();
- // 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<KeyDownEvent>((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;
- }
- }
- /// <summary>
- /// Called when looting is complete, proceed to end battle
- /// </summary>
- 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());
- }
- /// <summary>
- /// Save battle results and return to exploration
- /// </summary>
- 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<EnhancedBattleSetup>();
- 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<UIDocument>();
- 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);
- }
- }
- }
- /// <summary>
- /// Save battle results back to persistent game state
- /// </summary>
- 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
- /// <summary>
- /// [DEBUG] Force battle end for testing post-battle systems
- /// </summary>
- [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<Character>();
- if (character != null && !character.IsDead)
- {
- // Force enemy death for testing
- character.TakeDamage(1000);
- }
- }
- }
- // Manually trigger victory
- HandlePlayerVictory(enemyCharacters);
- }
- /// <summary>
- /// [DEBUG] Force battle defeat for testing game over
- /// </summary>
- [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<Character>();
- if (character != null && !character.IsDead)
- {
- // Force player death for testing
- character.TakeDamage(1000);
- }
- }
- }
- // Manually trigger defeat
- HandlePlayerDefeat(playerCharacters);
- }
- /// <summary>
- /// [PUBLIC] Manually trigger return to map - can be called by UI buttons or debug scripts
- /// </summary>
- 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<GameObject, GameObject>();
- var movementActions = new Dictionary<GameObject, Vector3>();
- // Collect player actions from Character components
- foreach (GameObject playerGO in playerCharacters)
- {
- Character character = playerGO.GetComponent<Character>();
- 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<Character>();
- float attackRange = character != null ? character.GetAttackRange() : 2.0f;
- NavMeshAgent agent = actor.GetComponent<NavMeshAgent>();
- 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;
- // Configure movement speed based on character's MovementSpeed stat
- if (character != null)
- {
- // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
- // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
- agent.speed = (character.MovementSpeed / 10f) * 1.75f;
- // Set high acceleration to reach max speed quickly
- agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
- }
- else
- {
- agent.speed = 1.75f; // Default speed if no character component (halved)
- agent.acceleration = 5.25f; // Default acceleration (halved)
- }
- //
- 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<NavMeshAgent>();
- if (agent != null)
- {
- // Configure movement speed for pure movement actions based on character's MovementSpeed stat
- Character character = playerGO.GetComponent<Character>();
- if (character != null)
- {
- // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
- // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
- agent.speed = (character.MovementSpeed / 10f) * 1.75f;
- // Set high acceleration to reach max speed quickly
- agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
- Debug.Log($"🚀 MOVEMENT DEBUG - {character.CharacterName}: MovementSpeed={character.MovementSpeed}, AgentSpeed={agent.speed:F1}, Acceleration={agent.acceleration:F1}, TimeScale={Time.timeScale}");
- }
- else
- {
- agent.speed = 1.75f; // Default speed if no character component (halved)
- agent.acceleration = 5.25f; // Default acceleration (halved)
- Debug.Log($"🚀 MOVEMENT DEBUG - {playerGO.name}: DEFAULT AgentSpeed={agent.speed:F1}, Acceleration={agent.acceleration:F1}, TimeScale={Time.timeScale}");
- }
- 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<Character>();
- 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<Character>();
- 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<GameObject, GameObject> allActions, Dictionary<GameObject, Vector3> movementActions, float movementDuration)
- {
- float elapsedTime = 0f;
- // Store witch agents are still moving (not in attack range)
- Dictionary<NavMeshAgent, GameObject> activeMovements = new Dictionary<NavMeshAgent, GameObject>();
- Dictionary<NavMeshAgent, float> agentAttackRanges = new Dictionary<NavMeshAgent, float>();
- // 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<NavMeshAgent>();
- if (agent != null && agent.enabled)
- {
- // Get the character's attack range
- Character character = actor.GetComponent<Character>();
- float attackRange = character != null ? character.GetAttackRange() : 2.0f;
- // Configure movement speed based on character's MovementSpeed stat
- if (character != null)
- {
- // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
- // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
- agent.speed = (character.MovementSpeed / 10f) * 1.75f;
- // Set high acceleration to reach max speed quickly
- agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
- }
- else
- {
- agent.speed = 1.75f; // Default speed if no character component (halved)
- agent.acceleration = 5.25f; // Default acceleration (halved)
- }
- // 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<NavMeshAgent>();
- if (agent != null && agent.enabled)
- {
- // For movement actions, use a small stopping distance to get closer
- float arrivalRange = 1.2f;
- // Configure movement speed based on character's MovementSpeed stat
- Character character = actor.GetComponent<Character>();
- if (character != null)
- {
- // Convert character movement speed to NavMeshAgent speed (HALVED for better gameplay)
- // Base conversion: MovementSpeed 10 = 1.75 units/sec, scale from there
- agent.speed = (character.MovementSpeed / 10f) * 1.75f;
- // Set high acceleration to reach max speed quickly
- agent.acceleration = agent.speed * 3.0f; // Reach max speed in ~0.33 seconds
- }
- else
- {
- agent.speed = 1.75f; // Default speed if no character component (halved)
- agent.acceleration = 5.25f; // Default acceleration (halved)
- }
- // 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;
- }
- }
- Debug.Log($"🏃 MOVEMENT PHASE START - TimeScale={Time.timeScale}, ActiveMovements={activeMovements.Count}, Duration={movementDuration}");
- while (elapsedTime < movementDuration && Time.timeScale > 0 && activeMovements.Count > 0)
- {
- var agentsToRemove = new List<NavMeshAgent>();
- 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);
- // Debug velocity and speed every 10 frames to avoid spam
- if (Time.frameCount % 10 == 0)
- {
- Character character = agent.GetComponent<Character>();
- string charName = character != null ? character.CharacterName : agent.gameObject.name;
- Debug.Log($"🏃♂️ VELOCITY - {charName}: Speed={agent.speed:F1}, Velocity={agent.velocity.magnitude:F2}, Distance={distanceToTarget:F1}");
- }
- 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<Character>();
- 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<Character>();
- 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<GameObject> alivePlayers = playerCharacters.Where(player => !player.GetComponent<Character>().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<Character>();
- 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<Character>().SetVisualState(ActionDecisionState.EnemyAction);
- }
- }
- private void CheckAndResetCompletedMovements()
- {
- foreach (GameObject playerGO in playerCharacters)
- {
- Character character = playerGO.GetComponent<Character>();
- 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);
- }
- }
- }
- }
- }
|