using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; 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(); void Awake() { mainCamera = Camera.main; targetingLine = GetComponent(); if (targetingLine == null) { targetingLine = gameObject.AddComponent(); } } void Start() { InitializePlayerCharacters(); } 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; 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; Debug.Log($"đŸ–ąī¸ Starting drag from character {clickedCharacter.CharacterName}"); // 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); Debug.Log($"đŸŽ¯ Started move targeting for {selectedCharacter.CharacterName}"); } } } 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); Debug.Log($"âš”ī¸ Attack target set for {selectedCharacter.CharacterName}"); } 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); if (isActionWheelMove) { Debug.Log($"👟 Move target set for {selectedCharacter.CharacterName} (action wheel mode)"); } else { Debug.Log($"👟 Move target set for {selectedCharacter.CharacterName} (dragged {dragDistance:F1} units)"); } } else { // User just clicked on character without dragging - don't set any action Debug.Log($"đŸ–ąī¸ Character {selectedCharacter.CharacterName} selected (clicked without dragging)"); } } // 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); } Debug.Log($"👆 Character {selectedCharacter.CharacterName} selected (no action set)"); // 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)) { Debug.Log($"🌍 Mouse world position: {hit.point} (hit: {hit.collider.gameObject.name})"); 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); Debug.Log($"🌍 Mouse world position (fallback plane): {position}"); return position; } Debug.Log($"❌ Could not determine mouse world 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)) { Debug.Log($"đŸŽ¯ Enemy detected: {hit.collider.gameObject.name} on layer {hit.collider.gameObject.layer}"); return hit.collider.gameObject; } // Debug: Check what we're hitting without layer mask if (Physics.Raycast(ray, out hit, 200f)) { Debug.Log($"🔍 Hit object: {hit.collider.gameObject.name} on layer {hit.collider.gameObject.layer} (not enemy layer)"); } else { Debug.Log($"❌ No raycast hit detected at mouse position"); } 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() { Debug.Log("🔄 PlayerDecisionController: Resetting state"); if (isDragging && targetingLine != null) { targetingLine.StopTargeting(); } selectedCharacter = null; isDragging = false; isEnabled = true; Debug.Log("✅ PlayerDecisionController: State reset complete"); } 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) { Debug.Log($"🔄 Updating enhanced action data for {character.CharacterName}: {actionType}"); // 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; Debug.Log($"đŸ—Ąī¸ Enhanced data updated for attack on {targetEnemy.name}"); } else if (actionType == BattleActionType.Move) { enhancedData.targetPosition = targetPosition; enhancedData.targetEnemy = null; Debug.Log($"👟 Enhanced data updated for move to {targetPosition}"); } } else { Debug.Log($"â„šī¸ No enhanced action data found for {character.CharacterName}"); } } /// /// Manually start targeting mode for a specific character and action type /// Called by the action wheel system /// public void StartTargetingForCharacter(Character character, BattleActionType actionType) { Debug.Log($"đŸŽ¯ StartTargetingForCharacter called: {character.CharacterName} -> {actionType}"); selectedCharacter = character; dragStartPosition = character.transform.position; // 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); Debug.Log($"✅ Targeting mode active for {character.CharacterName} - {actionType}"); } /// /// Cancel current targeting operation /// public void CancelTargeting() { if (isDragging && selectedCharacter != null) { selectedCharacter.actionData.Reset(); selectedCharacter.SetVisualState(ActionDecisionState.NoAction); targetingLine.StopTargeting(); selectedCharacter = null; isDragging = false; Debug.Log("❌ Targeting cancelled"); } } }