using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; using UnityEngine.UIElements; using UnityEngine.AI; public class PlayerDecisionController : MonoBehaviour { [Header("References")] public LayerMask enemyLayerMask = 1 << 10; // Enemy layer public LayerMask playerLayerMask = 1 << 9; // Player layer [Header("State")] private Character selectedCharacter; private bool isDragging = false; private Vector3 dragStartPosition; [Header("Settings")] public float enemySnapDistance = 1f; private List playerCharacters = new List(); private TargetingLine targetingLine; private Camera mainCamera; private bool isEnabled = true; private List activeActionLines = new List(); [Header("Character Stats UI")] public VisualTreeAsset characterStatsUXML; public StyleSheet characterStatsUSS; private UIDocument uiDocument; private VisualElement statsPanel; private bool isStatsPanelVisible = false; void Awake() { mainCamera = Camera.main; targetingLine = GetComponent(); if (targetingLine == null) { targetingLine = gameObject.AddComponent(); } } void Start() { InitializePlayerCharacters(); SetupCharacterStatsUI(); // Wait a moment for battle setup to complete, then refresh stats display StartCoroutine(RefreshStatsAfterSetup()); } private IEnumerator RefreshStatsAfterSetup() { // Wait for battle setup to complete yield return new WaitForSeconds(0.5f); // Refresh stats display if panel is visible and character is selected if (isStatsPanelVisible && selectedCharacter != null) { UpdateCharacterStatsDisplay(selectedCharacter); } } void Update() { HandleInput(); } private void InitializePlayerCharacters() { // Get player characters from GameManager if available if (GameManager.Instance != null) { playerCharacters.Clear(); foreach (GameObject playerGO in GameManager.Instance.playerCharacters) { Character character = playerGO.GetComponent(); if (character != null) { playerCharacters.Add(character); } } } else { // Fallback: find all characters (for testing without GameManager) Character[] characters = FindObjectsByType(FindObjectsSortMode.None); playerCharacters.AddRange(characters); } foreach (Character character in playerCharacters) { character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } private void HandleInput() { if (!isEnabled) return; // Toggle character stats panel with 'C' key if (Input.GetKeyDown(KeyCode.C)) { ToggleStatsPanel(); } if (Input.GetMouseButtonDown(0)) // Left click { HandleLeftClickDown(); } else if (Input.GetMouseButton(0) && isDragging) // Left drag { HandleLeftDrag(); } else if (Input.GetMouseButtonUp(0)) // Left release { HandleLeftClickUp(); } else if (Input.GetMouseButtonDown(1)) // Right click { HandleRightClick(); } } /// /// Check if we're currently in move targeting mode (move action selected but not yet targeted) /// private bool IsInMoveTargetingMode() { if (selectedCharacter == null) return false; var actionData = selectedCharacter.GetEnhancedActionData(); return actionData != null && actionData.actionType == BattleActionType.Move && actionData.state == ActionDecisionState.NoAction; } private void HandleLeftClickDown() { Vector3 mouseWorldPosition = GetMouseWorldPosition(); Character clickedCharacter = GetPlayerCharacterAtPosition(mouseWorldPosition); if (clickedCharacter != null) { // Start drag mode from character - this allows both move and attack by dragging selectedCharacter = clickedCharacter; isDragging = true; dragStartPosition = clickedCharacter.transform.position; // Update character stats display if panel is visible UpdateCharacterStatsDisplay(clickedCharacter); // Clear any existing action lines before starting new targeting ClearActionLineForCharacter(clickedCharacter); // Start targeting line for drag operations targetingLine.StartTargeting(dragStartPosition); // CinemachineCameraController.Instance.FocusOnCharacter(clickedCharacter.transform); } else { // Clicked empty space - start drag if we have a selected character and are in move mode if (selectedCharacter != null && IsInMoveTargetingMode()) { isDragging = true; targetingLine.StartTargeting(selectedCharacter.transform.position); } } } private void HandleLeftDrag() { if (selectedCharacter == null) return; Vector3 mouseWorldPos = GetMouseWorldPosition(); GameObject enemyAtMouse = GetEnemyAtPosition(mouseWorldPos); if (enemyAtMouse != null) { // Snap to enemy Vector3 enemyPos = enemyAtMouse.transform.position; targetingLine.UpdateTargeting(dragStartPosition, enemyPos, true); } else { // Follow mouse targetingLine.UpdateTargeting(dragStartPosition, mouseWorldPos, false); } } private void HandleLeftClickUp() { Vector3 mouseWorldPos = GetMouseWorldPosition(); GameObject enemyAtMouse = GetEnemyAtPosition(mouseWorldPos); // Handle different cases based on current state if (isDragging && selectedCharacter != null) { // We're in active targeting mode if (enemyAtMouse != null) { // Attack target selected selectedCharacter.actionData.SetAttackTarget(enemyAtMouse); UpdateEnhancedActionData(selectedCharacter, BattleActionType.Attack, enemyAtMouse, mouseWorldPos); selectedCharacter.SetVisualState(selectedCharacter.actionData.state); } else { // Check if user actually dragged (minimum distance threshold) OR if we're in action wheel move mode float dragDistance = Vector3.Distance(dragStartPosition, mouseWorldPos); const float minDragDistance = 0.5f; // Minimum distance to consider it a drag vs click bool isActionWheelMove = IsInMoveTargetingMode(); bool isDragMove = dragDistance > minDragDistance; if (isActionWheelMove || isDragMove) { // Move target selected selectedCharacter.actionData.SetMoveTarget(mouseWorldPos); UpdateEnhancedActionData(selectedCharacter, BattleActionType.Move, null, mouseWorldPos); selectedCharacter.SetVisualState(selectedCharacter.actionData.state); } } // Notify BattleActionIntegration that targeting is complete var integration = FindFirstObjectByType(); if (integration != null) { integration.OnTargetingComplete(selectedCharacter); } // Cleanup targeting targetingLine.StopTargeting(); isDragging = false; selectedCharacter = null; // Clear selection after action is set } else if (selectedCharacter != null) { // Just a character selection click - notify integration but don't set actions var integration = FindFirstObjectByType(); if (integration != null) { integration.OnTargetingComplete(selectedCharacter); } // Don't clear selectedCharacter here - let integration handle it } // Cleanup targetingLine.StopTargeting(); selectedCharacter = null; isDragging = false; } private void HandleRightClick() { if (isDragging && selectedCharacter != null) { // Cancel current action selectedCharacter.actionData.Reset(); selectedCharacter.SetVisualState(ActionDecisionState.NoAction); targetingLine.StopTargeting(); selectedCharacter = null; isDragging = false; } else { // Right click on character to reset their action Vector3 mouseWorldPos = GetMouseWorldPosition(); Character clickedCharacter = GetPlayerCharacterAtPosition(mouseWorldPos); if (clickedCharacter != null) { clickedCharacter.actionData.Reset(); clickedCharacter.SetVisualState(ActionDecisionState.NoAction); } } } private Vector3 GetMouseWorldPosition() { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; // Raycast against a ground plane or existing colliders if (Physics.Raycast(ray, out hit, 200f)) { return hit.point; } // Fallback: project onto a horizontal plane at y=0 Plane groundPlane = new Plane(Vector3.up, Vector3.zero); if (groundPlane.Raycast(ray, out float distance)) { Vector3 position = ray.GetPoint(distance); return position; } return Vector3.zero; } private Character GetPlayerCharacterAtPosition(Vector3 worldPosition) { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, 200f, playerLayerMask)) { return hit.collider.GetComponent(); } // Try raycast without layer mask to see what we're hitting if (Physics.Raycast(ray, out hit, 200f)) { } else { } return null; } private GameObject GetEnemyAtPosition(Vector3 worldPosition) { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, 200f, enemyLayerMask)) { return hit.collider.gameObject; } return null; } public bool AllCharactersHaveActions() { foreach (var character in playerCharacters) { // Characters need actions if they have no action set if (character.actionData.state == ActionDecisionState.NoAction) return false; } return true; } public void ResetAllCharacterActions() { foreach (var character in playerCharacters) { character.actionData.Reset(); character.SetVisualState(ActionDecisionState.NoAction); } } public void UpdateVisualStates() { foreach (var character in playerCharacters) { // Update visual states based on current action status if (character.actionData.state == ActionDecisionState.NoAction) { character.SetVisualState(ActionDecisionState.NoAction); // Pink } else if (character.HasActionSelected()) { character.SetVisualState(ActionDecisionState.ActionSelected); // Green } } } public void RefreshPlayerCharacters() { InitializePlayerCharacters(); } public void SetEnabled(bool enabled) { isEnabled = enabled; if (!enabled) { if (isDragging) { // Cancel any current dragging operation targetingLine.StopTargeting(); selectedCharacter = null; isDragging = false; } // Don't automatically hide action lines when disabling - let caller decide } } /// /// Check if PlayerDecisionController is currently in targeting/dragging mode /// public bool IsInTargetingMode => isDragging && selectedCharacter != null; /// /// Get the currently selected character (for action wheel integration) /// public Character GetSelectedCharacter() => selectedCharacter; /// /// Reset the controller state (for debugging/recovery) /// public void ResetState() { if (isDragging && targetingLine != null) { targetingLine.StopTargeting(); } selectedCharacter = null; isDragging = false; isEnabled = true; } public void ShowActiveActionLines() { HideActiveActionLines(); // Clear any existing lines first foreach (var character in playerCharacters) { if (character.HasActionSelected() && !character.IsActionComplete()) { GameObject lineObject = new GameObject($"ActionLine_{character.name}"); TargetingLine line = lineObject.AddComponent(); activeActionLines.Add(line); Vector3 startPos = character.transform.position + Vector3.up * 0.5f; if (character.actionData.targetEnemy != null) { Vector3 endPos = character.actionData.targetEnemy.transform.position + Vector3.up * 0.5f; line.StartTargeting(startPos); line.UpdateTargeting(startPos, endPos, true); // true for enemy target (red line) } else if (character.actionData.targetPosition != Vector3.zero) { line.StartTargeting(startPos); line.UpdateTargeting(startPos, character.actionData.targetPosition, false); // false for movement (blue line) } } } } public void HideActiveActionLines() { foreach (var line in activeActionLines) { if (line != null) { line.StopTargeting(); Destroy(line.gameObject); } } activeActionLines.Clear(); } public void ClearActionLineForCharacter(Character character) { for (int i = activeActionLines.Count - 1; i >= 0; i--) { var line = activeActionLines[i]; if (line != null && line.gameObject.name == $"ActionLine_{character.name}") { line.StopTargeting(); Destroy(line.gameObject); activeActionLines.RemoveAt(i); break; } } } private void UpdateEnhancedActionData(Character character, BattleActionType actionType, GameObject targetEnemy, Vector3 targetPosition) { // Check if character has enhanced action data var enhancedData = character.GetEnhancedActionData(); if (enhancedData != null) { // Update the enhanced action data to match the targeting result enhancedData.actionType = actionType; enhancedData.state = ActionDecisionState.ActionSelected; if (actionType == BattleActionType.Attack && targetEnemy != null) { enhancedData.targetEnemy = targetEnemy; enhancedData.targetPosition = Vector3.zero; } else if (actionType == BattleActionType.Move) { enhancedData.targetPosition = targetPosition; enhancedData.targetEnemy = null; } } } /// /// Manually start targeting mode for a specific character and action type /// Called by the action wheel system /// public void StartTargetingForCharacter(Character character, BattleActionType actionType) { selectedCharacter = character; dragStartPosition = character.transform.position; // Update character stats display if panel is visible if (isStatsPanelVisible && selectedCharacter != null) { UpdateCharacterStatsDisplay(selectedCharacter); } // Store the action type for later reference var enhancedData = character.GetEnhancedActionData(); if (enhancedData == null) { enhancedData = new EnhancedCharacterActionData(); character.SetEnhancedActionData(enhancedData); } enhancedData.actionType = actionType; enhancedData.state = ActionDecisionState.NoAction; // Waiting for target // Start targeting mode - user needs to click to set target isDragging = true; // Clear any existing action lines before starting new targeting ClearActionLineForCharacter(character); // Start the targeting line targetingLine.StartTargeting(dragStartPosition); } /// /// Cancel current targeting operation /// public void CancelTargeting() { if (isDragging && selectedCharacter != null) { selectedCharacter.actionData.Reset(); selectedCharacter.SetVisualState(ActionDecisionState.NoAction); targetingLine.StopTargeting(); selectedCharacter = null; isDragging = false; } } #region Character Stats UI private void SetupCharacterStatsUI() { // Get or create UIDocument component uiDocument = GetComponent(); if (uiDocument == null) { uiDocument = gameObject.AddComponent(); } // Set PanelSettings to mainSettings if (uiDocument.panelSettings == null) { SetupPanelSettings(uiDocument); } // Try to load UXML asset if (characterStatsUXML != null) { uiDocument.visualTreeAsset = characterStatsUXML; } // Check if we have content from UXML or need to create programmatically bool hasValidContent = uiDocument.rootVisualElement != null && uiDocument.rootVisualElement.childCount > 0 && uiDocument.rootVisualElement.Q("character-stats-container") != null; // Create UI structure programmatically only if UXML not found or invalid if (characterStatsUXML == null || !hasValidContent) { CreateCharacterStatsPanelProgrammatically(); } // Get the stats panel statsPanel = uiDocument.rootVisualElement.Q("character-stats-container"); if (statsPanel == null) { CreateCharacterStatsPanelProgrammatically(); statsPanel = uiDocument.rootVisualElement.Q("character-stats-container"); } // Apply stylesheet if available and not already applied if (characterStatsUSS != null && !uiDocument.rootVisualElement.styleSheets.Contains(characterStatsUSS)) { uiDocument.rootVisualElement.styleSheets.Add(characterStatsUSS); } // Hide panel initially if (statsPanel != null) { statsPanel.style.display = DisplayStyle.None; } } private void CreateCharacterStatsPanelProgrammatically() { var root = uiDocument.rootVisualElement; root.Clear(); // Create main container var container = new VisualElement(); container.name = "character-stats-container"; container.style.position = Position.Absolute; container.style.top = 10; container.style.right = 10; container.style.width = 280; container.style.backgroundColor = new Color(0, 0, 0, 0.8f); container.style.borderTopColor = container.style.borderBottomColor = container.style.borderLeftColor = container.style.borderRightColor = new Color(1, 1, 1, 0.3f); container.style.borderTopWidth = container.style.borderBottomWidth = container.style.borderLeftWidth = container.style.borderRightWidth = 1; container.style.borderTopLeftRadius = container.style.borderTopRightRadius = container.style.borderBottomLeftRadius = container.style.borderBottomRightRadius = 5; container.style.paddingTop = container.style.paddingBottom = container.style.paddingLeft = container.style.paddingRight = 10; container.style.color = Color.white; // Title var title = new Label("Character Stats"); title.name = "title-label"; title.style.fontSize = 16; title.style.color = new Color(1f, 0.84f, 0f); // Gold title.style.marginBottom = 10; title.style.unityFontStyleAndWeight = FontStyle.Bold; title.style.unityTextAlign = TextAnchor.MiddleCenter; container.Add(title); // Character info section var charInfo = new VisualElement(); charInfo.name = "character-info"; var charName = new Label("Character Name"); charName.name = "character-name"; charName.style.fontSize = 14; charName.style.color = new Color(0.56f, 0.93f, 0.56f); // Light green charName.style.unityFontStyleAndWeight = FontStyle.Bold; var charLevel = new Label("Level 1"); charLevel.name = "character-level"; charLevel.style.fontSize = 11; charLevel.style.color = new Color(0.87f, 0.63f, 0.87f); // Plum charLevel.style.marginBottom = 5; charInfo.Add(charName); charInfo.Add(charLevel); container.Add(charInfo); // Attributes section var attrSection = CreateStatsSection("Attributes", new string[] { "str-stat:STR: 10", "dex-stat:DEX: 10", "con-stat:CON: 10", "wis-stat:WIS: 10", "per-stat:PER: 10" }); container.Add(attrSection); // Combat section var combatSection = CreateStatsSection("Combat Stats", new string[] { "health-stat:Health: 100/100", "attack-stat:Attack: 15", "ac-stat:AC: 12", "movement-stat:Movement: 10" }); container.Add(combatSection); // Debug section var debugSection = CreateStatsSection("Debug Info", new string[] { "agent-speed-stat:Agent Speed: 3.5" }); container.Add(debugSection); // Controls var controls = new VisualElement(); var helpText = new Label("Press 'C' to toggle stats panel"); helpText.style.fontSize = 10; helpText.style.color = new Color(0.8f, 0.8f, 0.8f); helpText.style.unityTextAlign = TextAnchor.MiddleCenter; helpText.style.marginTop = 5; controls.Add(helpText); container.Add(controls); root.Add(container); } private VisualElement CreateStatsSection(string title, string[] stats) { var section = new VisualElement(); section.style.marginBottom = 8; var header = new Label(title); header.style.fontSize = 12; header.style.color = new Color(0.53f, 0.81f, 0.92f); // Sky blue header.style.marginBottom = 4; header.style.unityFontStyleAndWeight = FontStyle.Bold; section.Add(header); foreach (string stat in stats) { string[] parts = stat.Split(':'); var label = new Label(parts.Length > 1 ? parts[1] : stat); if (parts.Length > 1) label.name = parts[0]; label.style.fontSize = 11; label.style.color = Color.white; label.style.marginLeft = 5; label.style.marginBottom = 2; section.Add(label); } return section; } // New method to force refresh stats display public void RefreshCharacterStatsDisplay() { if (selectedCharacter != null && isStatsPanelVisible) { UpdateCharacterStatsDisplay(selectedCharacter); } } private void ToggleStatsPanel() { if (statsPanel == null) return; isStatsPanelVisible = !isStatsPanelVisible; statsPanel.style.display = isStatsPanelVisible ? DisplayStyle.Flex : DisplayStyle.None; // Update display when showing panel if (isStatsPanelVisible && selectedCharacter != null) { UpdateCharacterStatsDisplay(selectedCharacter); } } private void UpdateCharacterStatsDisplay(Character character) { if (statsPanel == null || !isStatsPanelVisible) return; // Update character info var nameLabel = statsPanel.Q