PlayerDecisionController .cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using Unity.VisualScripting;
  4. using UnityEngine;
  5. using UnityEngine.UIElements;
  6. using UnityEngine.AI;
  7. public class PlayerDecisionController : MonoBehaviour
  8. {
  9. [Header("References")]
  10. public LayerMask enemyLayerMask = 1 << 10; // Enemy layer
  11. public LayerMask playerLayerMask = 1 << 9; // Player layer
  12. [Header("State")]
  13. private Character selectedCharacter;
  14. private bool isDragging = false;
  15. private Vector3 dragStartPosition;
  16. [Header("Settings")]
  17. public float enemySnapDistance = 1f;
  18. private List<Character> playerCharacters = new List<Character>();
  19. private TargetingLine targetingLine;
  20. private Camera mainCamera;
  21. private bool isEnabled = true;
  22. private List<TargetingLine> activeActionLines = new List<TargetingLine>();
  23. [Header("Character Stats UI")]
  24. public VisualTreeAsset characterStatsUXML;
  25. public StyleSheet characterStatsUSS;
  26. private UIDocument uiDocument;
  27. private VisualElement statsPanel;
  28. private bool isStatsPanelVisible = false;
  29. void Awake()
  30. {
  31. mainCamera = Camera.main;
  32. targetingLine = GetComponent<TargetingLine>();
  33. if (targetingLine == null)
  34. {
  35. targetingLine = gameObject.AddComponent<TargetingLine>();
  36. }
  37. }
  38. void Start()
  39. {
  40. InitializePlayerCharacters();
  41. SetupCharacterStatsUI();
  42. // Wait a moment for battle setup to complete, then refresh stats display
  43. StartCoroutine(RefreshStatsAfterSetup());
  44. }
  45. private IEnumerator RefreshStatsAfterSetup()
  46. {
  47. // Wait for battle setup to complete
  48. yield return new WaitForSeconds(0.5f);
  49. // Refresh stats display if panel is visible and character is selected
  50. if (isStatsPanelVisible && selectedCharacter != null)
  51. {
  52. UpdateCharacterStatsDisplay(selectedCharacter);
  53. }
  54. }
  55. void Update()
  56. {
  57. HandleInput();
  58. }
  59. private void InitializePlayerCharacters()
  60. {
  61. // Get player characters from GameManager if available
  62. if (GameManager.Instance != null)
  63. {
  64. playerCharacters.Clear();
  65. foreach (GameObject playerGO in GameManager.Instance.playerCharacters)
  66. {
  67. Character character = playerGO.GetComponent<Character>();
  68. if (character != null)
  69. {
  70. playerCharacters.Add(character);
  71. }
  72. }
  73. }
  74. else
  75. {
  76. // Fallback: find all characters (for testing without GameManager)
  77. Character[] characters = FindObjectsByType<Character>(FindObjectsSortMode.None);
  78. playerCharacters.AddRange(characters);
  79. }
  80. foreach (Character character in playerCharacters)
  81. {
  82. character.actionData.Reset();
  83. character.SetVisualState(ActionDecisionState.NoAction);
  84. }
  85. }
  86. private void HandleInput()
  87. {
  88. if (!isEnabled) return;
  89. // Toggle character stats panel with 'C' key
  90. if (Input.GetKeyDown(KeyCode.C))
  91. {
  92. ToggleStatsPanel();
  93. }
  94. if (Input.GetMouseButtonDown(0)) // Left click
  95. {
  96. HandleLeftClickDown();
  97. }
  98. else if (Input.GetMouseButton(0) && isDragging) // Left drag
  99. {
  100. HandleLeftDrag();
  101. }
  102. else if (Input.GetMouseButtonUp(0)) // Left release
  103. {
  104. HandleLeftClickUp();
  105. }
  106. else if (Input.GetMouseButtonDown(1)) // Right click
  107. {
  108. HandleRightClick();
  109. }
  110. }
  111. /// <summary>
  112. /// Check if we're currently in move targeting mode (move action selected but not yet targeted)
  113. /// </summary>
  114. private bool IsInMoveTargetingMode()
  115. {
  116. if (selectedCharacter == null) return false;
  117. var actionData = selectedCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  118. return actionData != null &&
  119. actionData.actionType == BattleActionType.Move &&
  120. actionData.state == ActionDecisionState.NoAction;
  121. }
  122. private void HandleLeftClickDown()
  123. {
  124. Vector3 mouseWorldPosition = GetMouseWorldPosition();
  125. Character clickedCharacter = GetPlayerCharacterAtPosition(mouseWorldPosition);
  126. if (clickedCharacter != null)
  127. {
  128. // Start drag mode from character - this allows both move and attack by dragging
  129. selectedCharacter = clickedCharacter;
  130. isDragging = true;
  131. dragStartPosition = clickedCharacter.transform.position;
  132. // Update character stats display if panel is visible
  133. UpdateCharacterStatsDisplay(clickedCharacter);
  134. // Clear any existing action lines before starting new targeting
  135. ClearActionLineForCharacter(clickedCharacter);
  136. // Start targeting line for drag operations
  137. targetingLine.StartTargeting(dragStartPosition);
  138. // CinemachineCameraController.Instance.FocusOnCharacter(clickedCharacter.transform);
  139. }
  140. else
  141. {
  142. // Clicked empty space - start drag if we have a selected character and are in move mode
  143. if (selectedCharacter != null && IsInMoveTargetingMode())
  144. {
  145. isDragging = true;
  146. targetingLine.StartTargeting(selectedCharacter.transform.position);
  147. }
  148. }
  149. }
  150. private void HandleLeftDrag()
  151. {
  152. if (selectedCharacter == null) return;
  153. Vector3 mouseWorldPos = GetMouseWorldPosition();
  154. GameObject enemyAtMouse = GetEnemyAtPosition(mouseWorldPos);
  155. if (enemyAtMouse != null)
  156. {
  157. // Snap to enemy
  158. Vector3 enemyPos = enemyAtMouse.transform.position;
  159. targetingLine.UpdateTargeting(dragStartPosition, enemyPos, true);
  160. }
  161. else
  162. {
  163. // Follow mouse
  164. targetingLine.UpdateTargeting(dragStartPosition, mouseWorldPos, false);
  165. }
  166. }
  167. private void HandleLeftClickUp()
  168. {
  169. Vector3 mouseWorldPos = GetMouseWorldPosition();
  170. GameObject enemyAtMouse = GetEnemyAtPosition(mouseWorldPos);
  171. // Handle different cases based on current state
  172. if (isDragging && selectedCharacter != null)
  173. {
  174. // We're in active targeting mode
  175. if (enemyAtMouse != null)
  176. {
  177. // Attack target selected
  178. selectedCharacter.actionData.SetAttackTarget(enemyAtMouse);
  179. UpdateEnhancedActionData(selectedCharacter, BattleActionType.Attack, enemyAtMouse, mouseWorldPos);
  180. selectedCharacter.SetVisualState(selectedCharacter.actionData.state);
  181. }
  182. else
  183. {
  184. // Check if user actually dragged (minimum distance threshold) OR if we're in action wheel move mode
  185. float dragDistance = Vector3.Distance(dragStartPosition, mouseWorldPos);
  186. const float minDragDistance = 0.5f; // Minimum distance to consider it a drag vs click
  187. bool isActionWheelMove = IsInMoveTargetingMode();
  188. bool isDragMove = dragDistance > minDragDistance;
  189. if (isActionWheelMove || isDragMove)
  190. {
  191. // Move target selected
  192. selectedCharacter.actionData.SetMoveTarget(mouseWorldPos);
  193. UpdateEnhancedActionData(selectedCharacter, BattleActionType.Move, null, mouseWorldPos);
  194. selectedCharacter.SetVisualState(selectedCharacter.actionData.state);
  195. }
  196. }
  197. // Notify BattleActionIntegration that targeting is complete
  198. var integration = FindFirstObjectByType<BattleActionIntegration>();
  199. if (integration != null)
  200. {
  201. integration.OnTargetingComplete(selectedCharacter);
  202. }
  203. // Cleanup targeting
  204. targetingLine.StopTargeting();
  205. isDragging = false;
  206. selectedCharacter = null; // Clear selection after action is set
  207. }
  208. else if (selectedCharacter != null)
  209. {
  210. // Just a character selection click - notify integration but don't set actions
  211. var integration = FindFirstObjectByType<BattleActionIntegration>();
  212. if (integration != null)
  213. {
  214. integration.OnTargetingComplete(selectedCharacter);
  215. }
  216. // Don't clear selectedCharacter here - let integration handle it
  217. }
  218. // Cleanup
  219. targetingLine.StopTargeting();
  220. selectedCharacter = null;
  221. isDragging = false;
  222. }
  223. private void HandleRightClick()
  224. {
  225. if (isDragging && selectedCharacter != null)
  226. {
  227. // Cancel current action
  228. selectedCharacter.actionData.Reset();
  229. selectedCharacter.SetVisualState(ActionDecisionState.NoAction);
  230. targetingLine.StopTargeting();
  231. selectedCharacter = null;
  232. isDragging = false;
  233. }
  234. else
  235. {
  236. // Right click on character to reset their action
  237. Vector3 mouseWorldPos = GetMouseWorldPosition();
  238. Character clickedCharacter = GetPlayerCharacterAtPosition(mouseWorldPos);
  239. if (clickedCharacter != null)
  240. {
  241. clickedCharacter.actionData.Reset();
  242. clickedCharacter.SetVisualState(ActionDecisionState.NoAction);
  243. }
  244. }
  245. }
  246. private Vector3 GetMouseWorldPosition()
  247. {
  248. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  249. RaycastHit hit;
  250. // Raycast against a ground plane or existing colliders
  251. if (Physics.Raycast(ray, out hit, 200f))
  252. {
  253. return hit.point;
  254. }
  255. // Fallback: project onto a horizontal plane at y=0
  256. Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
  257. if (groundPlane.Raycast(ray, out float distance))
  258. {
  259. Vector3 position = ray.GetPoint(distance);
  260. return position;
  261. }
  262. return Vector3.zero;
  263. }
  264. private Character GetPlayerCharacterAtPosition(Vector3 worldPosition)
  265. {
  266. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  267. RaycastHit hit;
  268. if (Physics.Raycast(ray, out hit, 200f, playerLayerMask))
  269. {
  270. return hit.collider.GetComponent<Character>();
  271. }
  272. // Try raycast without layer mask to see what we're hitting
  273. if (Physics.Raycast(ray, out hit, 200f))
  274. {
  275. }
  276. else
  277. {
  278. }
  279. return null;
  280. }
  281. private GameObject GetEnemyAtPosition(Vector3 worldPosition)
  282. {
  283. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  284. RaycastHit hit;
  285. if (Physics.Raycast(ray, out hit, 200f, enemyLayerMask))
  286. {
  287. return hit.collider.gameObject;
  288. }
  289. return null;
  290. }
  291. public bool AllCharactersHaveActions()
  292. {
  293. foreach (var character in playerCharacters)
  294. {
  295. // Characters need actions if they have no action set
  296. if (character.actionData.state == ActionDecisionState.NoAction)
  297. return false;
  298. }
  299. return true;
  300. }
  301. public void ResetAllCharacterActions()
  302. {
  303. foreach (var character in playerCharacters)
  304. {
  305. character.actionData.Reset();
  306. character.SetVisualState(ActionDecisionState.NoAction);
  307. }
  308. }
  309. public void UpdateVisualStates()
  310. {
  311. foreach (var character in playerCharacters)
  312. {
  313. // Update visual states based on current action status
  314. if (character.actionData.state == ActionDecisionState.NoAction)
  315. {
  316. character.SetVisualState(ActionDecisionState.NoAction); // Pink
  317. }
  318. else if (character.HasActionSelected())
  319. {
  320. character.SetVisualState(ActionDecisionState.ActionSelected); // Green
  321. }
  322. }
  323. }
  324. public void RefreshPlayerCharacters()
  325. {
  326. InitializePlayerCharacters();
  327. }
  328. public void SetEnabled(bool enabled)
  329. {
  330. isEnabled = enabled;
  331. if (!enabled)
  332. {
  333. if (isDragging)
  334. {
  335. // Cancel any current dragging operation
  336. targetingLine.StopTargeting();
  337. selectedCharacter = null;
  338. isDragging = false;
  339. }
  340. // Don't automatically hide action lines when disabling - let caller decide
  341. }
  342. }
  343. /// <summary>
  344. /// Check if PlayerDecisionController is currently in targeting/dragging mode
  345. /// </summary>
  346. public bool IsInTargetingMode => isDragging && selectedCharacter != null;
  347. /// <summary>
  348. /// Get the currently selected character (for action wheel integration)
  349. /// </summary>
  350. public Character GetSelectedCharacter() => selectedCharacter;
  351. /// <summary>
  352. /// Reset the controller state (for debugging/recovery)
  353. /// </summary>
  354. public void ResetState()
  355. {
  356. if (isDragging && targetingLine != null)
  357. {
  358. targetingLine.StopTargeting();
  359. }
  360. selectedCharacter = null;
  361. isDragging = false;
  362. isEnabled = true;
  363. }
  364. public void ShowActiveActionLines()
  365. {
  366. HideActiveActionLines(); // Clear any existing lines first
  367. foreach (var character in playerCharacters)
  368. {
  369. if (character.HasActionSelected() && !character.IsActionComplete())
  370. {
  371. GameObject lineObject = new GameObject($"ActionLine_{character.name}");
  372. TargetingLine line = lineObject.AddComponent<TargetingLine>();
  373. activeActionLines.Add(line);
  374. Vector3 startPos = character.transform.position + Vector3.up * 0.5f;
  375. if (character.actionData.targetEnemy != null)
  376. {
  377. Vector3 endPos = character.actionData.targetEnemy.transform.position + Vector3.up * 0.5f;
  378. line.StartTargeting(startPos);
  379. line.UpdateTargeting(startPos, endPos, true); // true for enemy target (red line)
  380. }
  381. else if (character.actionData.targetPosition != Vector3.zero)
  382. {
  383. line.StartTargeting(startPos);
  384. line.UpdateTargeting(startPos, character.actionData.targetPosition, false); // false for movement (blue line)
  385. }
  386. }
  387. }
  388. }
  389. public void HideActiveActionLines()
  390. {
  391. foreach (var line in activeActionLines)
  392. {
  393. if (line != null)
  394. {
  395. line.StopTargeting();
  396. Destroy(line.gameObject);
  397. }
  398. }
  399. activeActionLines.Clear();
  400. }
  401. public void ClearActionLineForCharacter(Character character)
  402. {
  403. for (int i = activeActionLines.Count - 1; i >= 0; i--)
  404. {
  405. var line = activeActionLines[i];
  406. if (line != null && line.gameObject.name == $"ActionLine_{character.name}")
  407. {
  408. line.StopTargeting();
  409. Destroy(line.gameObject);
  410. activeActionLines.RemoveAt(i);
  411. break;
  412. }
  413. }
  414. }
  415. private void UpdateEnhancedActionData(Character character, BattleActionType actionType, GameObject targetEnemy, Vector3 targetPosition)
  416. {
  417. // Check if character has enhanced action data
  418. var enhancedData = character.GetEnhancedActionData<EnhancedCharacterActionData>();
  419. if (enhancedData != null)
  420. {
  421. // Update the enhanced action data to match the targeting result
  422. enhancedData.actionType = actionType;
  423. enhancedData.state = ActionDecisionState.ActionSelected;
  424. if (actionType == BattleActionType.Attack && targetEnemy != null)
  425. {
  426. enhancedData.targetEnemy = targetEnemy;
  427. enhancedData.targetPosition = Vector3.zero;
  428. }
  429. else if (actionType == BattleActionType.Move)
  430. {
  431. enhancedData.targetPosition = targetPosition;
  432. enhancedData.targetEnemy = null;
  433. }
  434. }
  435. }
  436. /// <summary>
  437. /// Manually start targeting mode for a specific character and action type
  438. /// Called by the action wheel system
  439. /// </summary>
  440. public void StartTargetingForCharacter(Character character, BattleActionType actionType)
  441. {
  442. selectedCharacter = character;
  443. dragStartPosition = character.transform.position;
  444. // Update character stats display if panel is visible
  445. if (isStatsPanelVisible && selectedCharacter != null)
  446. {
  447. UpdateCharacterStatsDisplay(selectedCharacter);
  448. }
  449. // Store the action type for later reference
  450. var enhancedData = character.GetEnhancedActionData<EnhancedCharacterActionData>();
  451. if (enhancedData == null)
  452. {
  453. enhancedData = new EnhancedCharacterActionData();
  454. character.SetEnhancedActionData(enhancedData);
  455. }
  456. enhancedData.actionType = actionType;
  457. enhancedData.state = ActionDecisionState.NoAction; // Waiting for target
  458. // Start targeting mode - user needs to click to set target
  459. isDragging = true;
  460. // Clear any existing action lines before starting new targeting
  461. ClearActionLineForCharacter(character);
  462. // Start the targeting line
  463. targetingLine.StartTargeting(dragStartPosition);
  464. }
  465. /// <summary>
  466. /// Cancel current targeting operation
  467. /// </summary>
  468. public void CancelTargeting()
  469. {
  470. if (isDragging && selectedCharacter != null)
  471. {
  472. selectedCharacter.actionData.Reset();
  473. selectedCharacter.SetVisualState(ActionDecisionState.NoAction);
  474. targetingLine.StopTargeting();
  475. selectedCharacter = null;
  476. isDragging = false;
  477. }
  478. }
  479. #region Character Stats UI
  480. private void SetupCharacterStatsUI()
  481. {
  482. // Get or create UIDocument component
  483. uiDocument = GetComponent<UIDocument>();
  484. if (uiDocument == null)
  485. {
  486. uiDocument = gameObject.AddComponent<UIDocument>();
  487. }
  488. // Set PanelSettings to mainSettings
  489. if (uiDocument.panelSettings == null)
  490. {
  491. SetupPanelSettings(uiDocument);
  492. }
  493. // Try to load UXML asset
  494. if (characterStatsUXML != null)
  495. {
  496. uiDocument.visualTreeAsset = characterStatsUXML;
  497. }
  498. // Check if we have content from UXML or need to create programmatically
  499. bool hasValidContent = uiDocument.rootVisualElement != null &&
  500. uiDocument.rootVisualElement.childCount > 0 &&
  501. uiDocument.rootVisualElement.Q("character-stats-container") != null;
  502. // Create UI structure programmatically only if UXML not found or invalid
  503. if (characterStatsUXML == null || !hasValidContent)
  504. {
  505. CreateCharacterStatsPanelProgrammatically();
  506. }
  507. // Get the stats panel
  508. statsPanel = uiDocument.rootVisualElement.Q("character-stats-container");
  509. if (statsPanel == null)
  510. {
  511. CreateCharacterStatsPanelProgrammatically();
  512. statsPanel = uiDocument.rootVisualElement.Q("character-stats-container");
  513. }
  514. // Apply stylesheet if available and not already applied
  515. if (characterStatsUSS != null && !uiDocument.rootVisualElement.styleSheets.Contains(characterStatsUSS))
  516. {
  517. uiDocument.rootVisualElement.styleSheets.Add(characterStatsUSS);
  518. }
  519. // Hide panel initially
  520. if (statsPanel != null)
  521. {
  522. statsPanel.style.display = DisplayStyle.None;
  523. }
  524. }
  525. private void CreateCharacterStatsPanelProgrammatically()
  526. {
  527. var root = uiDocument.rootVisualElement;
  528. root.Clear();
  529. // Create main container
  530. var container = new VisualElement();
  531. container.name = "character-stats-container";
  532. container.style.position = Position.Absolute;
  533. container.style.top = 10;
  534. container.style.right = 10;
  535. container.style.width = 280;
  536. container.style.backgroundColor = new Color(0, 0, 0, 0.8f);
  537. container.style.borderTopColor = container.style.borderBottomColor =
  538. container.style.borderLeftColor = container.style.borderRightColor = new Color(1, 1, 1, 0.3f);
  539. container.style.borderTopWidth = container.style.borderBottomWidth =
  540. container.style.borderLeftWidth = container.style.borderRightWidth = 1;
  541. container.style.borderTopLeftRadius = container.style.borderTopRightRadius =
  542. container.style.borderBottomLeftRadius = container.style.borderBottomRightRadius = 5;
  543. container.style.paddingTop = container.style.paddingBottom =
  544. container.style.paddingLeft = container.style.paddingRight = 10;
  545. container.style.color = Color.white;
  546. // Title
  547. var title = new Label("Character Stats");
  548. title.name = "title-label";
  549. title.style.fontSize = 16;
  550. title.style.color = new Color(1f, 0.84f, 0f); // Gold
  551. title.style.marginBottom = 10;
  552. title.style.unityFontStyleAndWeight = FontStyle.Bold;
  553. title.style.unityTextAlign = TextAnchor.MiddleCenter;
  554. container.Add(title);
  555. // Character info section
  556. var charInfo = new VisualElement();
  557. charInfo.name = "character-info";
  558. var charName = new Label("Character Name");
  559. charName.name = "character-name";
  560. charName.style.fontSize = 14;
  561. charName.style.color = new Color(0.56f, 0.93f, 0.56f); // Light green
  562. charName.style.unityFontStyleAndWeight = FontStyle.Bold;
  563. var charLevel = new Label("Level 1");
  564. charLevel.name = "character-level";
  565. charLevel.style.fontSize = 11;
  566. charLevel.style.color = new Color(0.87f, 0.63f, 0.87f); // Plum
  567. charLevel.style.marginBottom = 5;
  568. charInfo.Add(charName);
  569. charInfo.Add(charLevel);
  570. container.Add(charInfo);
  571. // Attributes section
  572. var attrSection = CreateStatsSection("Attributes", new string[]
  573. {
  574. "str-stat:STR: 10",
  575. "dex-stat:DEX: 10",
  576. "con-stat:CON: 10",
  577. "wis-stat:WIS: 10",
  578. "per-stat:PER: 10"
  579. });
  580. container.Add(attrSection);
  581. // Combat section
  582. var combatSection = CreateStatsSection("Combat Stats", new string[]
  583. {
  584. "health-stat:Health: 100/100",
  585. "attack-stat:Attack: 15",
  586. "ac-stat:AC: 12",
  587. "movement-stat:Movement: 10"
  588. });
  589. container.Add(combatSection);
  590. // Debug section
  591. var debugSection = CreateStatsSection("Debug Info", new string[]
  592. {
  593. "agent-speed-stat:Agent Speed: 3.5"
  594. });
  595. container.Add(debugSection);
  596. // Controls
  597. var controls = new VisualElement();
  598. var helpText = new Label("Press 'C' to toggle stats panel");
  599. helpText.style.fontSize = 10;
  600. helpText.style.color = new Color(0.8f, 0.8f, 0.8f);
  601. helpText.style.unityTextAlign = TextAnchor.MiddleCenter;
  602. helpText.style.marginTop = 5;
  603. controls.Add(helpText);
  604. container.Add(controls);
  605. root.Add(container);
  606. }
  607. private VisualElement CreateStatsSection(string title, string[] stats)
  608. {
  609. var section = new VisualElement();
  610. section.style.marginBottom = 8;
  611. var header = new Label(title);
  612. header.style.fontSize = 12;
  613. header.style.color = new Color(0.53f, 0.81f, 0.92f); // Sky blue
  614. header.style.marginBottom = 4;
  615. header.style.unityFontStyleAndWeight = FontStyle.Bold;
  616. section.Add(header);
  617. foreach (string stat in stats)
  618. {
  619. string[] parts = stat.Split(':');
  620. var label = new Label(parts.Length > 1 ? parts[1] : stat);
  621. if (parts.Length > 1) label.name = parts[0];
  622. label.style.fontSize = 11;
  623. label.style.color = Color.white;
  624. label.style.marginLeft = 5;
  625. label.style.marginBottom = 2;
  626. section.Add(label);
  627. }
  628. return section;
  629. }
  630. // New method to force refresh stats display
  631. public void RefreshCharacterStatsDisplay()
  632. {
  633. if (selectedCharacter != null && isStatsPanelVisible)
  634. {
  635. UpdateCharacterStatsDisplay(selectedCharacter);
  636. }
  637. }
  638. private void ToggleStatsPanel()
  639. {
  640. if (statsPanel == null) return;
  641. isStatsPanelVisible = !isStatsPanelVisible;
  642. statsPanel.style.display = isStatsPanelVisible ? DisplayStyle.Flex : DisplayStyle.None;
  643. // Update display when showing panel
  644. if (isStatsPanelVisible && selectedCharacter != null)
  645. {
  646. UpdateCharacterStatsDisplay(selectedCharacter);
  647. }
  648. }
  649. private void UpdateCharacterStatsDisplay(Character character)
  650. {
  651. if (statsPanel == null || !isStatsPanelVisible) return;
  652. // Update character info
  653. var nameLabel = statsPanel.Q<Label>("character-name");
  654. var levelLabel = statsPanel.Q<Label>("character-level");
  655. if (nameLabel != null) nameLabel.text = character.CharacterName;
  656. if (levelLabel != null) levelLabel.text = $"Level {character.Level}";
  657. // Update attributes
  658. UpdateStatLabel("str-stat", $"STR: {character.Strength}");
  659. UpdateStatLabel("dex-stat", $"DEX: {character.Dexterity}");
  660. UpdateStatLabel("con-stat", $"CON: {character.Constitution}");
  661. UpdateStatLabel("wis-stat", $"WIS: {character.Wisdom}");
  662. UpdateStatLabel("per-stat", $"PER: {character.Perception}");
  663. // Update combat stats
  664. UpdateStatLabel("health-stat", $"Health: {character.CurrentHealth}/{character.MaxHealth}");
  665. UpdateStatLabel("attack-stat", $"Attack: {character.Attack}");
  666. UpdateStatLabel("ac-stat", $"AC: {character.ArmorClass}");
  667. UpdateStatLabel("movement-stat", $"Movement: {character.MovementSpeed}");
  668. // Update debug info - get NavMeshAgent speed
  669. var agent = character.GetComponent<NavMeshAgent>();
  670. float agentSpeed = agent != null ? agent.speed : 0f;
  671. UpdateStatLabel("agent-speed-stat", $"Agent Speed: {agentSpeed:F1}");
  672. }
  673. private void UpdateStatLabel(string elementName, string text)
  674. {
  675. var label = statsPanel.Q<Label>(elementName);
  676. if (label != null) label.text = text;
  677. }
  678. /// <summary>
  679. /// Setup PanelSettings with multiple fallback methods
  680. /// </summary>
  681. private void SetupPanelSettings(UIDocument uiDoc)
  682. {
  683. // Try Resources first (original approach)
  684. var mainSettings = Resources.Load<PanelSettings>("MainSettings");
  685. if (mainSettings != null)
  686. {
  687. uiDoc.panelSettings = mainSettings;
  688. return;
  689. }
  690. // Try to find and reuse existing panel settings from other UI
  691. var existingUI = GameObject.Find("TravelUI")?.GetComponent<UIDocument>();
  692. if (existingUI?.panelSettings != null)
  693. {
  694. uiDoc.panelSettings = existingUI.panelSettings;
  695. return;
  696. }
  697. // Try to find from MainTeamSelectScript
  698. var mainTeamSelect = FindFirstObjectByType<MainTeamSelectScript>();
  699. if (mainTeamSelect != null)
  700. {
  701. var mainUIDoc = mainTeamSelect.GetComponent<UIDocument>();
  702. if (mainUIDoc?.panelSettings != null)
  703. {
  704. uiDoc.panelSettings = mainUIDoc.panelSettings;
  705. return;
  706. }
  707. }
  708. // Try to find any UIDocument in the scene with PanelSettings
  709. var allUIDocuments = FindObjectsByType<UIDocument>(FindObjectsSortMode.None);
  710. foreach (var doc in allUIDocuments)
  711. {
  712. if (doc.panelSettings != null && doc != uiDoc)
  713. {
  714. uiDoc.panelSettings = doc.panelSettings;
  715. return;
  716. }
  717. }
  718. #if UNITY_EDITOR
  719. // If still no panel settings, try to find any PanelSettings asset using Editor API
  720. var panelSettingsGuids = UnityEditor.AssetDatabase.FindAssets("t:PanelSettings");
  721. if (panelSettingsGuids.Length > 0)
  722. {
  723. var path = UnityEditor.AssetDatabase.GUIDToAssetPath(panelSettingsGuids[0]);
  724. var settings = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.UIElements.PanelSettings>(path);
  725. if (settings != null)
  726. {
  727. uiDoc.panelSettings = settings;
  728. return;
  729. }
  730. }
  731. #endif
  732. Debug.LogWarning("⚠️ Could not assign Panel Settings to Character Stats UI. You may need to assign it manually.");
  733. }
  734. #endregion
  735. }