BattleActionSystem.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  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. Debug.Log($"🎯 Executing {actionData.actionType} for {character.CharacterName}: {actionData.GetActionDescription()}");
  108. OnActionStarted?.Invoke(character, actionData.actionType);
  109. StartCoroutine(ExecuteActionCoroutine(character, actionData));
  110. }
  111. private IEnumerator ExecuteActionCoroutine(Character character, EnhancedCharacterActionData actionData)
  112. {
  113. switch (actionData.actionType)
  114. {
  115. case BattleActionType.Move:
  116. yield return ExecuteMoveAction(character, actionData);
  117. break;
  118. case BattleActionType.Attack:
  119. yield return ExecuteAttackAction(character, actionData);
  120. break;
  121. case BattleActionType.UseItem:
  122. yield return ExecuteItemAction(character, actionData);
  123. break;
  124. case BattleActionType.CastSpell:
  125. yield return ExecuteSpellAction(character, actionData);
  126. break;
  127. case BattleActionType.Defend:
  128. yield return ExecuteDefendAction(character, actionData);
  129. break;
  130. case BattleActionType.Wait:
  131. yield return ExecuteWaitAction(character, actionData);
  132. break;
  133. case BattleActionType.RunAway:
  134. yield return ExecuteRunAwayAction(character, actionData);
  135. break;
  136. }
  137. actionData.state = ActionDecisionState.ActionExecuted;
  138. OnActionCompleted?.Invoke(character);
  139. }
  140. #region Action Execution Methods
  141. private IEnumerator ExecuteMoveAction(Character character, EnhancedCharacterActionData actionData)
  142. {
  143. Debug.Log($"🚶 {character.CharacterName} moving to {actionData.targetPosition}");
  144. // Use existing movement system or implement new one
  145. character.transform.position = actionData.targetPosition;
  146. yield return new WaitForSeconds(0.5f); // Animation time
  147. }
  148. private IEnumerator ExecuteAttackAction(Character character, EnhancedCharacterActionData actionData)
  149. {
  150. if (actionData.targetEnemy == null)
  151. {
  152. Debug.LogWarning($"Attack action for {character.CharacterName} has no target!");
  153. yield break;
  154. }
  155. Debug.Log($"⚔️ {character.CharacterName} attacking {actionData.targetEnemy.name}");
  156. // Use existing attack system
  157. Character targetCharacter = actionData.targetEnemy.GetComponent<Character>();
  158. if (targetCharacter != null)
  159. {
  160. character.AttackTarget(targetCharacter);
  161. }
  162. yield return new WaitForSeconds(1f); // Attack animation time
  163. }
  164. private IEnumerator ExecuteItemAction(Character character, EnhancedCharacterActionData actionData)
  165. {
  166. Debug.Log($"🧪 {character.CharacterName} using item: {actionData.selectedItemName}");
  167. // Placeholder item usage logic
  168. yield return UseItem(character, actionData.selectedItemName, actionData.selectedItemIndex, actionData.targetCharacter);
  169. yield return new WaitForSeconds(0.8f);
  170. }
  171. private IEnumerator ExecuteSpellAction(Character character, EnhancedCharacterActionData actionData)
  172. {
  173. Debug.Log($"✨ {character.CharacterName} casting spell: {actionData.selectedSpellName}");
  174. // Placeholder spell casting logic
  175. yield return CastSpell(character, actionData.selectedSpellName, actionData.targetCharacter, actionData.targetPosition);
  176. yield return new WaitForSeconds(1.2f);
  177. }
  178. private IEnumerator ExecuteDefendAction(Character character, EnhancedCharacterActionData actionData)
  179. {
  180. Debug.Log($"🛡️ {character.CharacterName} defending");
  181. // Apply defend buff (reduce incoming damage, increase AC, etc.)
  182. ApplyDefendBuff(character);
  183. yield return new WaitForSeconds(0.3f);
  184. }
  185. private IEnumerator ExecuteWaitAction(Character character, EnhancedCharacterActionData actionData)
  186. {
  187. Debug.Log($"⏳ {character.CharacterName} waiting");
  188. // Character does nothing but maintains position
  189. yield return new WaitForSeconds(0.1f);
  190. }
  191. private IEnumerator ExecuteRunAwayAction(Character character, EnhancedCharacterActionData actionData)
  192. {
  193. Debug.Log($"🏃 {character.CharacterName} running away");
  194. // Move character away from enemies or off battlefield
  195. yield return HandleRunAway(character);
  196. yield return new WaitForSeconds(1f);
  197. }
  198. #endregion
  199. #region Event Handlers
  200. private void OnActionTypeSelected(BattleActionType actionType)
  201. {
  202. if (currentCharacter == null) return;
  203. Debug.Log($"🎯 Action type selected: {actionType} for {currentCharacter.CharacterName}");
  204. // Initialize enhanced action data if needed
  205. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  206. if (actionData == null)
  207. {
  208. actionData = new EnhancedCharacterActionData();
  209. currentCharacter.SetEnhancedActionData(actionData);
  210. }
  211. switch (actionType)
  212. {
  213. case BattleActionType.Move:
  214. StartTargetSelectionForMove();
  215. break;
  216. case BattleActionType.Attack:
  217. StartTargetSelectionForAttack();
  218. break;
  219. case BattleActionType.UseItem:
  220. ShowItemSelection();
  221. break;
  222. case BattleActionType.CastSpell:
  223. ShowSpellSelection();
  224. break;
  225. case BattleActionType.Defend:
  226. actionData.SetDefendAction();
  227. CompleteActionSelection();
  228. break;
  229. case BattleActionType.Wait:
  230. actionData.SetWaitAction();
  231. CompleteActionSelection();
  232. break;
  233. case BattleActionType.RunAway:
  234. actionData.SetRunAwayAction();
  235. CompleteActionSelection();
  236. break;
  237. }
  238. }
  239. private void OnActionWheelClosed()
  240. {
  241. if (!isWaitingForTarget)
  242. {
  243. CancelActionSelection();
  244. }
  245. }
  246. private void OnItemSelected(string itemName, int itemIndex)
  247. {
  248. var actionData = currentCharacter?.GetEnhancedActionData<EnhancedCharacterActionData>();
  249. if (actionData == null) return;
  250. // Determine if item requires targeting
  251. bool requiresTarget = DoesItemRequireTarget(itemName);
  252. actionData.SetItemAction(itemName, itemIndex, requiresTarget);
  253. if (requiresTarget)
  254. {
  255. StartTargetSelectionForItem();
  256. }
  257. else
  258. {
  259. CompleteActionSelection();
  260. }
  261. }
  262. private void OnItemSelectionCancelled()
  263. {
  264. CancelActionSelection();
  265. }
  266. private void OnSpellSelected(string spellName)
  267. {
  268. var actionData = currentCharacter?.GetEnhancedActionData<EnhancedCharacterActionData>();
  269. if (actionData == null) return;
  270. // Determine spell properties
  271. var spellInfo = GetSpellInfo(spellName);
  272. actionData.SetSpellAction(spellName, spellInfo.requiresTarget, spellInfo.isAoE, spellInfo.aoeRadius);
  273. if (spellInfo.requiresTarget)
  274. {
  275. StartTargetSelectionForSpell();
  276. }
  277. else
  278. {
  279. CompleteActionSelection();
  280. }
  281. }
  282. private void OnSpellSelectionCancelled()
  283. {
  284. CancelActionSelection();
  285. }
  286. #endregion
  287. #region Target Selection
  288. private void StartTargetSelectionForMove()
  289. {
  290. Debug.Log("🎯 Select move target...");
  291. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  292. isWaitingForTarget = true;
  293. // Show move cursor or indicators
  294. }
  295. private void StartTargetSelectionForAttack()
  296. {
  297. Debug.Log("🎯 Select attack target...");
  298. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  299. isWaitingForTarget = true;
  300. // Show attack cursor or highlight enemies
  301. }
  302. private void StartTargetSelectionForItem()
  303. {
  304. Debug.Log("🎯 Select item target...");
  305. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  306. isWaitingForTarget = true;
  307. }
  308. private void StartTargetSelectionForSpell()
  309. {
  310. Debug.Log("🎯 Select spell target...");
  311. pendingAction = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  312. isWaitingForTarget = true;
  313. }
  314. private void HandleTargetSelection()
  315. {
  316. if (Input.GetMouseButtonDown(0))
  317. {
  318. Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
  319. RaycastHit hit;
  320. // Check for character targets
  321. if (Physics.Raycast(ray, out hit, Mathf.Infinity, enemyLayerMask | playerLayerMask))
  322. {
  323. Character targetCharacter = hit.collider.GetComponent<Character>();
  324. if (targetCharacter != null)
  325. {
  326. SetCharacterTarget(targetCharacter);
  327. return;
  328. }
  329. }
  330. // Check for ground/position targets
  331. if (Physics.Raycast(ray, out hit, Mathf.Infinity, groundLayerMask))
  332. {
  333. SetPositionTarget(hit.point);
  334. return;
  335. }
  336. }
  337. // Cancel targeting with right click or escape
  338. if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.Escape))
  339. {
  340. CancelTargetSelection();
  341. }
  342. }
  343. private void SetCharacterTarget(Character target)
  344. {
  345. if (pendingAction == null) return;
  346. if (pendingAction.actionType == BattleActionType.Attack && target.CompareTag("Enemy"))
  347. {
  348. pendingAction.SetTarget(target.gameObject);
  349. CompleteActionSelection();
  350. }
  351. else if (pendingAction.actionType == BattleActionType.UseItem || pendingAction.actionType == BattleActionType.CastSpell)
  352. {
  353. pendingAction.SetTarget(target);
  354. CompleteActionSelection();
  355. }
  356. }
  357. private void SetPositionTarget(Vector3 position)
  358. {
  359. if (pendingAction == null) return;
  360. if (pendingAction.actionType == BattleActionType.Move)
  361. {
  362. pendingAction.SetMoveAction(position);
  363. CompleteActionSelection();
  364. }
  365. else if (pendingAction.actionType == BattleActionType.CastSpell && pendingAction.isAreaOfEffect)
  366. {
  367. pendingAction.SetTarget(position);
  368. CompleteActionSelection();
  369. }
  370. }
  371. private void CancelTargetSelection()
  372. {
  373. isWaitingForTarget = false;
  374. pendingAction = null;
  375. CancelActionSelection();
  376. }
  377. #endregion
  378. #region Helper Methods
  379. private void CompleteActionSelection()
  380. {
  381. isWaitingForTarget = false;
  382. pendingAction = null;
  383. if (currentCharacter != null)
  384. {
  385. currentCharacter.SetVisualState(ActionDecisionState.ActionSelected);
  386. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  387. if (actionData != null)
  388. {
  389. Debug.Log($"✅ Action selection complete for {currentCharacter.CharacterName}: {actionData.GetActionDescription()}");
  390. }
  391. }
  392. }
  393. private void CancelActionSelection()
  394. {
  395. isWaitingForTarget = false;
  396. pendingAction = null;
  397. if (currentCharacter != null)
  398. {
  399. var actionData = currentCharacter.GetEnhancedActionData<EnhancedCharacterActionData>();
  400. if (actionData != null)
  401. actionData.Reset();
  402. currentCharacter.SetVisualState(ActionDecisionState.NoAction);
  403. Debug.Log($"❌ Action selection cancelled for {currentCharacter.CharacterName}");
  404. }
  405. currentCharacter = null;
  406. }
  407. private void ShowItemSelection()
  408. {
  409. if (itemSelectionUI != null)
  410. {
  411. itemSelectionUI.ShowItemSelection(currentCharacter);
  412. }
  413. else
  414. {
  415. Debug.LogWarning("No ItemSelectionUI found - creating placeholder item action");
  416. // Placeholder: auto-select a health potion
  417. OnItemSelected("Health Potion", 0);
  418. }
  419. }
  420. private void ShowSpellSelection()
  421. {
  422. if (spellSelectionUI != null)
  423. {
  424. spellSelectionUI.ShowSpellSelection(currentCharacter);
  425. }
  426. else
  427. {
  428. Debug.LogWarning("No SpellSelectionUI found - creating placeholder spell action");
  429. // Placeholder: auto-select a basic spell
  430. OnSpellSelected("Magic Missile");
  431. }
  432. }
  433. private bool DoesItemRequireTarget(string itemName)
  434. {
  435. // Placeholder logic - determine if item needs a target
  436. return itemName.ToLower().Contains("heal") || itemName.ToLower().Contains("buff") || itemName.ToLower().Contains("restore");
  437. }
  438. private (bool requiresTarget, bool isAoE, float aoeRadius) GetSpellInfo(string spellName)
  439. {
  440. // Placeholder spell database
  441. switch (spellName.ToLower())
  442. {
  443. case "magic missile":
  444. return (true, false, 0f);
  445. case "heal":
  446. return (true, false, 0f);
  447. case "fireball":
  448. return (true, true, 3f);
  449. case "shield":
  450. return (false, false, 0f);
  451. default:
  452. return (true, false, 0f);
  453. }
  454. }
  455. private IEnumerator UseItem(Character user, string itemName, int itemIndex, Character target)
  456. {
  457. Debug.Log($"📦 Using item {itemName} on {(target ? target.CharacterName : "self")}");
  458. // Try to find and use the actual item from inventory
  459. bool itemConsumed = false;
  460. var inventory = user.GetComponent<Inventory>();
  461. if (inventory != null)
  462. {
  463. // Try to find the item in the inventory
  464. var miscItems = inventory.Miscellaneous;
  465. foreach (var slot in miscItems)
  466. {
  467. if (slot.item != null && slot.item.itemName == itemName && slot.quantity > 0)
  468. {
  469. if (slot.item is MiscellaneousItem miscItem && miscItem.isConsumable)
  470. {
  471. // Use the item effect
  472. Character effectTarget = target ?? user;
  473. miscItem.UseItem(effectTarget);
  474. // Remove one from inventory
  475. inventory.RemoveItem(slot.item, 1);
  476. itemConsumed = true;
  477. Debug.Log($"💚 {user.CharacterName} used {itemName} on {effectTarget.CharacterName}");
  478. break;
  479. }
  480. }
  481. }
  482. }
  483. // If no ScriptableObject item found, try CombatDataTransfer session data
  484. if (!itemConsumed && CombatDataTransfer.HasValidSession())
  485. {
  486. var session = CombatDataTransfer.GetCurrentSession();
  487. var playerData = session.playerTeam.Find(p =>
  488. p.characterName == user.CharacterName ||
  489. user.CharacterName.StartsWith(p.characterName));
  490. if (playerData != null && playerData.miscItems != null)
  491. {
  492. // Remove item from session data
  493. for (int i = 0; i < playerData.miscItems.Count; i++)
  494. {
  495. if (playerData.miscItems[i] == itemName)
  496. {
  497. playerData.miscItems.RemoveAt(i);
  498. itemConsumed = true;
  499. // Apply basic effects based on item name
  500. Character effectTarget = target ?? user;
  501. ApplyItemEffectByName(itemName, effectTarget);
  502. Debug.Log($"💚 {user.CharacterName} used {itemName} on {effectTarget.CharacterName} (from session data)");
  503. break;
  504. }
  505. }
  506. }
  507. }
  508. // Fallback - basic effects without consumption
  509. if (!itemConsumed)
  510. {
  511. Debug.LogWarning($"📦 Could not find consumable item {itemName} in {user.CharacterName}'s inventory - applying basic effect");
  512. Character effectTarget = target ?? user;
  513. ApplyItemEffectByName(itemName, effectTarget);
  514. }
  515. yield return null;
  516. }
  517. /// <summary>
  518. /// Apply item effects based on item name (fallback method)
  519. /// </summary>
  520. private void ApplyItemEffectByName(string itemName, Character target)
  521. {
  522. string lowerName = itemName.ToLower();
  523. if (lowerName.Contains("heal") || lowerName.Contains("potion"))
  524. {
  525. target.Heal(20); // Standard healing amount
  526. Debug.Log($"💚 {target.CharacterName} healed for 20 HP from {itemName}");
  527. }
  528. else if (lowerName.Contains("antidote"))
  529. {
  530. // Could add poison removal here if implemented
  531. target.Heal(5); // Minor healing from antidote
  532. Debug.Log($"💚 {target.CharacterName} recovered from {itemName}");
  533. }
  534. else if (lowerName.Contains("bread") || lowerName.Contains("food"))
  535. {
  536. target.Heal(10); // Minor healing from food
  537. Debug.Log($"💚 {target.CharacterName} recovered 10 HP from {itemName}");
  538. }
  539. else
  540. {
  541. // Generic beneficial effect
  542. target.Heal(15);
  543. Debug.Log($"💚 {target.CharacterName} gained 15 HP from {itemName}");
  544. }
  545. }
  546. private IEnumerator CastSpell(Character caster, string spellName, Character target, Vector3 targetPosition)
  547. {
  548. Debug.Log($"🔮 Casting {spellName} from {caster.CharacterName}");
  549. // Placeholder spell effects
  550. switch (spellName.ToLower())
  551. {
  552. case "magic missile":
  553. if (target != null)
  554. {
  555. target.TakeDamage(15);
  556. Debug.Log($"💥 {target.CharacterName} hit by Magic Missile for 15 damage");
  557. }
  558. break;
  559. case "heal":
  560. if (target != null)
  561. {
  562. target.Heal(25);
  563. Debug.Log($"💚 {target.CharacterName} healed for 25 HP");
  564. }
  565. break;
  566. case "fireball":
  567. // AoE damage around target position
  568. Debug.Log($"🔥 Fireball explodes at {targetPosition}");
  569. break;
  570. }
  571. yield return null;
  572. }
  573. private void ApplyDefendBuff(Character character)
  574. {
  575. // Placeholder: apply defensive buff
  576. Debug.Log($"🛡️ {character.CharacterName} gains defensive stance");
  577. // TODO: Implement actual defensive bonuses
  578. }
  579. private IEnumerator HandleRunAway(Character character)
  580. {
  581. // Placeholder: move character away from combat
  582. Vector3 runDirection = -character.transform.forward;
  583. Vector3 runTarget = character.transform.position + runDirection * 5f;
  584. character.transform.position = runTarget;
  585. Debug.Log($"🏃 {character.CharacterName} retreats to {runTarget}");
  586. yield return null;
  587. }
  588. #endregion
  589. }