BattleActionSystem.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Collections;
  4. /// <summary>
  5. /// Manages the execution of battle actions and coordinates with existing systems
  6. /// </summary>
  7. public class BattleActionSystem : MonoBehaviour
  8. {
  9. [Header("References")]
  10. public BattleActionWheel actionWheel;
  11. [Header("Item System")]
  12. public BattleItemSelector itemSelectionUI;
  13. [Header("Spell System")]
  14. public SpellSelectionUI spellSelectionUI;
  15. [Header("Settings")]
  16. public LayerMask enemyLayerMask = 1 << 10;
  17. public LayerMask playerLayerMask = 1 << 9;
  18. public LayerMask groundLayerMask = 1 << 0;
  19. private Character currentCharacter;
  20. private EnhancedCharacterActionData pendingAction;
  21. private bool isWaitingForTarget = false;
  22. private Camera mainCamera;
  23. // Events
  24. public event System.Action<Character> OnActionCompleted;
  25. public event System.Action<Character, BattleActionType> OnActionStarted;
  26. void Awake()
  27. {
  28. mainCamera = Camera.main;
  29. // Find or create action wheel
  30. if (actionWheel == null)
  31. actionWheel = FindFirstObjectByType<BattleActionWheel>();
  32. }
  33. void Start()
  34. {
  35. // Subscribe to action wheel events
  36. if (actionWheel != null)
  37. {
  38. actionWheel.OnActionSelected += OnActionTypeSelected;
  39. actionWheel.OnWheelClosed += OnActionWheelClosed;
  40. }
  41. // Subscribe to item/spell selection events
  42. if (itemSelectionUI != null)
  43. {
  44. itemSelectionUI.OnItemSelected += OnItemSelected;
  45. itemSelectionUI.OnSelectionCancelled += OnItemSelectionCancelled;
  46. }
  47. if (spellSelectionUI != null)
  48. {
  49. spellSelectionUI.OnSpellSelected += OnSpellSelected;
  50. spellSelectionUI.OnSelectionCancelled += OnSpellSelectionCancelled;
  51. }
  52. }
  53. void Update()
  54. {
  55. if (isWaitingForTarget)
  56. {
  57. HandleTargetSelection();
  58. }
  59. }
  60. /// <summary>
  61. /// Shows the action wheel for a character
  62. /// </summary>
  63. public void ShowActionWheel(Character character)
  64. {
  65. currentCharacter = character;
  66. if (actionWheel != null)
  67. {
  68. Vector3 wheelPosition = character.transform.position + Vector3.up * 2f;
  69. actionWheel.ShowWheel(character, wheelPosition);
  70. }
  71. else
  72. {
  73. Debug.LogError("BattleActionSystem: No action wheel assigned!");
  74. }
  75. }
  76. /// <summary>
  77. /// Hides the action wheel
  78. /// </summary>
  79. public void HideActionWheel()
  80. {
  81. if (actionWheel != null)
  82. {
  83. actionWheel.HideWheel();
  84. }
  85. }
  86. /// <summary>
  87. /// Executes the character's selected action
  88. /// </summary>
  89. public void ExecuteCharacterAction(Character character)
  90. {
  91. if (character == null)
  92. {
  93. Debug.LogWarning("BattleActionSystem: Invalid character");
  94. return;
  95. }
  96. var actionData = character.GetEnhancedActionData<EnhancedCharacterActionData>();
  97. if (actionData == null)
  98. {
  99. Debug.LogWarning($"BattleActionSystem: Character {character.CharacterName} has no enhanced action data");
  100. return;
  101. }
  102. if (!actionData.hasValidAction)
  103. {
  104. Debug.LogWarning($"BattleActionSystem: Character {character.CharacterName} has no valid action");
  105. return;
  106. }
  107. OnActionStarted?.Invoke(character, actionData.actionType);
  108. StartCoroutine(ExecuteActionCoroutine(character, actionData));
  109. }
  110. private IEnumerator ExecuteActionCoroutine(Character character, EnhancedCharacterActionData actionData)
  111. {
  112. switch (actionData.actionType)
  113. {
  114. case BattleActionType.Move:
  115. yield return ExecuteMoveAction(character, actionData);
  116. break;
  117. case BattleActionType.Attack:
  118. yield return ExecuteAttackAction(character, actionData);
  119. break;
  120. case BattleActionType.UseItem:
  121. yield return ExecuteItemAction(character, actionData);
  122. break;
  123. case BattleActionType.CastSpell:
  124. yield return ExecuteSpellAction(character, actionData);
  125. break;
  126. case BattleActionType.Defend:
  127. yield return ExecuteDefendAction(character, actionData);
  128. break;
  129. case BattleActionType.Wait:
  130. yield return ExecuteWaitAction(character, actionData);
  131. break;
  132. case BattleActionType.RunAway:
  133. yield return ExecuteRunAwayAction(character, actionData);
  134. break;
  135. }
  136. actionData.state = ActionDecisionState.ActionExecuted;
  137. OnActionCompleted?.Invoke(character);
  138. }
  139. #region Action Execution Methods
  140. private IEnumerator ExecuteMoveAction(Character character, EnhancedCharacterActionData actionData)
  141. {
  142. // Use existing movement system or implement new one
  143. character.transform.position = actionData.targetPosition;
  144. yield return new WaitForSeconds(0.5f); // Animation time
  145. }
  146. private IEnumerator ExecuteAttackAction(Character character, EnhancedCharacterActionData actionData)
  147. {
  148. if (actionData.targetEnemy == null)
  149. {
  150. Debug.LogWarning($"Attack action for {character.CharacterName} has no target!");
  151. yield break;
  152. }
  153. // Use existing attack system
  154. Character targetCharacter = actionData.targetEnemy.GetComponent<Character>();
  155. if (targetCharacter != null)
  156. {
  157. character.AttackTarget(targetCharacter);
  158. }
  159. yield return new WaitForSeconds(1f); // Attack animation time
  160. }
  161. private IEnumerator ExecuteItemAction(Character character, EnhancedCharacterActionData actionData)
  162. {
  163. // Placeholder item usage logic
  164. yield return UseItem(character, actionData.selectedItemName, actionData.selectedItemIndex, actionData.targetCharacter);
  165. yield return new WaitForSeconds(0.8f);
  166. }
  167. private IEnumerator ExecuteSpellAction(Character character, EnhancedCharacterActionData actionData)
  168. {
  169. // Placeholder spell casting logic
  170. yield return CastSpell(character, actionData.selectedSpellName, actionData.targetCharacter, actionData.targetPosition);
  171. yield return new WaitForSeconds(1.2f);
  172. }
  173. private IEnumerator ExecuteDefendAction(Character character, EnhancedCharacterActionData actionData)
  174. {
  175. // Apply defend buff (reduce incoming damage, increase AC, etc.)
  176. ApplyDefendBuff(character);
  177. yield return new WaitForSeconds(0.3f);
  178. }
  179. private IEnumerator ExecuteWaitAction(Character character, EnhancedCharacterActionData actionData)
  180. {
  181. // Character does nothing but maintains position
  182. yield return new WaitForSeconds(0.1f);
  183. }
  184. private IEnumerator ExecuteRunAwayAction(Character character, EnhancedCharacterActionData actionData)
  185. {
  186. // Move character away from enemies or off battlefield
  187. yield return HandleRunAway(character);
  188. yield return new WaitForSeconds(1f);
  189. }
  190. #endregion
  191. #region Event Handlers
  192. private void OnActionTypeSelected(BattleActionType actionType)
  193. {
  194. if (currentCharacter == null) return;
  195. // Initialize enhanced action data if needed
  196. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  197. if (actionData == null)
  198. {
  199. actionData = new EnhancedCharacterActionData();
  200. currentCharacter.SetEnhancedActionData(actionData);
  201. }
  202. switch (actionType)
  203. {
  204. case BattleActionType.Move:
  205. StartTargetSelectionForMove();
  206. break;
  207. case BattleActionType.Attack:
  208. StartTargetSelectionForAttack();
  209. break;
  210. case BattleActionType.UseItem:
  211. ShowItemSelection();
  212. break;
  213. case BattleActionType.CastSpell:
  214. ShowSpellSelection();
  215. break;
  216. case BattleActionType.Defend:
  217. actionData.SetDefendAction();
  218. CompleteActionSelection();
  219. break;
  220. case BattleActionType.Wait:
  221. actionData.SetWaitAction();
  222. CompleteActionSelection();
  223. break;
  224. case BattleActionType.RunAway:
  225. actionData.SetRunAwayAction();
  226. CompleteActionSelection();
  227. break;
  228. }
  229. }
  230. private void OnActionWheelClosed()
  231. {
  232. if (!isWaitingForTarget)
  233. {
  234. CancelActionSelection();
  235. }
  236. }
  237. private void OnItemSelected(string itemName, int itemIndex)
  238. {
  239. var actionData = currentCharacter?.GetEnhancedActionData<EnhancedCharacterActionData>();
  240. if (actionData == null) return;
  241. // Determine if item requires targeting
  242. bool requiresTarget = DoesItemRequireTarget(itemName);
  243. actionData.SetItemAction(itemName, itemIndex, requiresTarget);
  244. if (requiresTarget)
  245. {
  246. StartTargetSelectionForItem();
  247. }
  248. else
  249. {
  250. CompleteActionSelection();
  251. }
  252. }
  253. private void OnItemSelectionCancelled()
  254. {
  255. CancelActionSelection();
  256. }
  257. private void OnSpellSelected(string spellName)
  258. {
  259. var actionData = currentCharacter?.GetEnhancedActionData<EnhancedCharacterActionData>();
  260. if (actionData == null) return;
  261. // Determine spell properties
  262. var spellInfo = GetSpellInfo(spellName);
  263. actionData.SetSpellAction(spellName, spellInfo.requiresTarget, spellInfo.isAoE, spellInfo.aoeRadius);
  264. if (spellInfo.requiresTarget)
  265. {
  266. StartTargetSelectionForSpell();
  267. }
  268. else
  269. {
  270. CompleteActionSelection();
  271. }
  272. }
  273. private void OnSpellSelectionCancelled()
  274. {
  275. CancelActionSelection();
  276. }
  277. #endregion
  278. #region Target Selection
  279. private void StartTargetSelectionForMove()
  280. {
  281. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  282. isWaitingForTarget = true;
  283. // Show move cursor or indicators
  284. }
  285. private void StartTargetSelectionForAttack()
  286. {
  287. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  288. isWaitingForTarget = true;
  289. // Show attack cursor or highlight enemies
  290. }
  291. private void StartTargetSelectionForItem()
  292. {
  293. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  294. isWaitingForTarget = true;
  295. }
  296. private void StartTargetSelectionForSpell()
  297. {
  298. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  299. isWaitingForTarget = true;
  300. }
  301. private void HandleTargetSelection()
  302. {
  303. if (Input.GetMouseButtonDown(0))
  304. {
  305. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  306. RaycastHit hit;
  307. // Check for character targets
  308. if (Physics.Raycast(ray, out hit, Mathf.Infinity, enemyLayerMask | playerLayerMask))
  309. {
  310. Character targetCharacter = hit.collider.GetComponent<Character>();
  311. if (targetCharacter != null)
  312. {
  313. SetCharacterTarget(targetCharacter);
  314. return;
  315. }
  316. }
  317. // Check for ground/position targets
  318. if (Physics.Raycast(ray, out hit, Mathf.Infinity, groundLayerMask))
  319. {
  320. SetPositionTarget(hit.point);
  321. return;
  322. }
  323. }
  324. // Cancel targeting with right click or escape
  325. if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.Escape))
  326. {
  327. CancelTargetSelection();
  328. }
  329. }
  330. private void SetCharacterTarget(Character target)
  331. {
  332. if (pendingAction == null) return;
  333. if (pendingAction.actionType == BattleActionType.Attack && target.CompareTag("Enemy"))
  334. {
  335. pendingAction.SetTarget(target.gameObject);
  336. CompleteActionSelection();
  337. }
  338. else if (pendingAction.actionType == BattleActionType.UseItem || pendingAction.actionType == BattleActionType.CastSpell)
  339. {
  340. pendingAction.SetTarget(target);
  341. CompleteActionSelection();
  342. }
  343. }
  344. private void SetPositionTarget(Vector3 position)
  345. {
  346. if (pendingAction == null) return;
  347. if (pendingAction.actionType == BattleActionType.Move)
  348. {
  349. pendingAction.SetMoveAction(position);
  350. CompleteActionSelection();
  351. }
  352. else if (pendingAction.actionType == BattleActionType.CastSpell && pendingAction.isAreaOfEffect)
  353. {
  354. pendingAction.SetTarget(position);
  355. CompleteActionSelection();
  356. }
  357. }
  358. private void CancelTargetSelection()
  359. {
  360. isWaitingForTarget = false;
  361. pendingAction = null;
  362. CancelActionSelection();
  363. }
  364. #endregion
  365. #region Helper Methods
  366. private void CompleteActionSelection()
  367. {
  368. isWaitingForTarget = false;
  369. pendingAction = null;
  370. if (currentCharacter != null)
  371. {
  372. currentCharacter.SetVisualState(ActionDecisionState.ActionSelected);
  373. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  374. }
  375. }
  376. private void CancelActionSelection()
  377. {
  378. isWaitingForTarget = false;
  379. pendingAction = null;
  380. if (currentCharacter != null)
  381. {
  382. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  383. if (actionData != null)
  384. actionData.Reset();
  385. currentCharacter.SetVisualState(ActionDecisionState.NoAction);
  386. }
  387. currentCharacter = null;
  388. }
  389. private void ShowItemSelection()
  390. {
  391. if (itemSelectionUI != null)
  392. {
  393. itemSelectionUI.ShowItemSelection(currentCharacter);
  394. }
  395. else
  396. {
  397. Debug.LogWarning("No ItemSelectionUI found - creating placeholder item action");
  398. // Placeholder: auto-select a health potion
  399. OnItemSelected("Health Potion", 0);
  400. }
  401. }
  402. private void ShowSpellSelection()
  403. {
  404. if (spellSelectionUI != null)
  405. {
  406. spellSelectionUI.ShowSpellSelection(currentCharacter);
  407. }
  408. else
  409. {
  410. Debug.LogWarning("No SpellSelectionUI found - creating placeholder spell action");
  411. // Placeholder: auto-select a basic spell
  412. OnSpellSelected("Magic Missile");
  413. }
  414. }
  415. private bool DoesItemRequireTarget(string itemName)
  416. {
  417. // Placeholder logic - determine if item needs a target
  418. return itemName.ToLower().Contains("heal") || itemName.ToLower().Contains("buff") || itemName.ToLower().Contains("restore");
  419. }
  420. private (bool requiresTarget, bool isAoE, float aoeRadius) GetSpellInfo(string spellName)
  421. {
  422. // Placeholder spell database
  423. switch (spellName.ToLower())
  424. {
  425. case "magic missile":
  426. return (true, false, 0f);
  427. case "heal":
  428. return (true, false, 0f);
  429. case "fireball":
  430. return (true, true, 3f);
  431. case "shield":
  432. return (false, false, 0f);
  433. default:
  434. return (true, false, 0f);
  435. }
  436. }
  437. private IEnumerator UseItem(Character user, string itemName, int itemIndex, Character target)
  438. {
  439. // Try to find and use the actual item from inventory
  440. bool itemConsumed = false;
  441. var inventory = user.GetComponent<Inventory>();
  442. if (inventory != null)
  443. {
  444. // Try to find the item in the inventory
  445. var miscItems = inventory.Miscellaneous;
  446. foreach (var slot in miscItems)
  447. {
  448. if (slot.item != null && slot.item.itemName == itemName && slot.quantity > 0)
  449. {
  450. if (slot.item is MiscellaneousItem miscItem && miscItem.isConsumable)
  451. {
  452. // Use the item effect
  453. Character effectTarget = target ?? user;
  454. miscItem.UseItem(effectTarget);
  455. // Remove one from inventory
  456. inventory.RemoveItem(slot.item, 1);
  457. itemConsumed = true;
  458. break;
  459. }
  460. }
  461. }
  462. }
  463. // If no ScriptableObject item found, try CombatDataTransfer session data
  464. if (!itemConsumed && CombatDataTransfer.HasValidSession())
  465. {
  466. var session = CombatDataTransfer.GetCurrentSession();
  467. var playerData = session.playerTeam.Find(p =>
  468. p.characterName == user.CharacterName ||
  469. user.CharacterName.StartsWith(p.characterName));
  470. if (playerData != null && playerData.miscItems != null)
  471. {
  472. // Remove item from session data
  473. for (int i = 0; i < playerData.miscItems.Count; i++)
  474. {
  475. if (playerData.miscItems[i] == itemName)
  476. {
  477. playerData.miscItems.RemoveAt(i);
  478. itemConsumed = true;
  479. // Apply basic effects based on item name
  480. Character effectTarget = target ?? user;
  481. ApplyItemEffectByName(itemName, effectTarget);
  482. break;
  483. }
  484. }
  485. }
  486. }
  487. // Fallback - basic effects without consumption
  488. if (!itemConsumed)
  489. {
  490. Debug.LogWarning($"📦 Could not find consumable item {itemName} in {user.CharacterName}'s inventory - applying basic effect");
  491. Character effectTarget = target ?? user;
  492. ApplyItemEffectByName(itemName, effectTarget);
  493. }
  494. yield return null;
  495. }
  496. /// <summary>
  497. /// Apply item effects based on item name (fallback method)
  498. /// </summary>
  499. private void ApplyItemEffectByName(string itemName, Character target)
  500. {
  501. string lowerName = itemName.ToLower();
  502. if (lowerName.Contains("heal") || lowerName.Contains("potion"))
  503. {
  504. target.Heal(20); // Standard healing amount
  505. }
  506. else if (lowerName.Contains("antidote"))
  507. {
  508. // Could add poison removal here if implemented
  509. target.Heal(5); // Minor healing from antidote
  510. }
  511. else if (lowerName.Contains("bread") || lowerName.Contains("food"))
  512. {
  513. target.Heal(10); // Minor healing from food
  514. }
  515. else
  516. {
  517. // Generic beneficial effect
  518. target.Heal(15);
  519. }
  520. }
  521. private IEnumerator CastSpell(Character caster, string spellName, Character target, Vector3 targetPosition)
  522. {
  523. // Placeholder spell effects
  524. switch (spellName.ToLower())
  525. {
  526. case "magic missile":
  527. if (target != null)
  528. {
  529. target.TakeDamage(15);
  530. }
  531. break;
  532. case "heal":
  533. if (target != null)
  534. {
  535. target.Heal(25);
  536. }
  537. break;
  538. case "fireball":
  539. // AoE damage around target position
  540. break;
  541. }
  542. yield return null;
  543. }
  544. private void ApplyDefendBuff(Character character)
  545. {
  546. // Placeholder: apply defensive buff
  547. // TODO: Implement actual defensive bonuses
  548. }
  549. private IEnumerator HandleRunAway(Character character)
  550. {
  551. // Placeholder: move character away from combat
  552. Vector3 runDirection = -character.transform.forward;
  553. Vector3 runTarget = character.transform.position + runDirection * 5f;
  554. character.transform.position = runTarget;
  555. yield return null;
  556. }
  557. #endregion
  558. }