Character.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. using TMPro;
  2. using UnityEngine;
  3. public enum ActionDecisionState
  4. {
  5. NoAction,
  6. ActionSelected,
  7. ActionExecuted,
  8. EnemyAction
  9. }
  10. [System.Serializable]
  11. public class CharacterActionData
  12. {
  13. public ActionDecisionState state = ActionDecisionState.NoAction;
  14. public Vector3 targetPosition;
  15. public GameObject targetEnemy;
  16. public bool hasTarget => targetEnemy != null || targetPosition != Vector3.zero;
  17. public void Reset()
  18. {
  19. state = ActionDecisionState.NoAction;
  20. targetPosition = Vector3.zero;
  21. targetEnemy = null;
  22. }
  23. public void SetMoveTarget(Vector3 position)
  24. {
  25. targetPosition = position;
  26. targetEnemy = null;
  27. state = ActionDecisionState.ActionSelected;
  28. }
  29. public void SetAttackTarget(GameObject enemy)
  30. {
  31. targetEnemy = enemy;
  32. targetPosition = Vector3.zero;
  33. state = ActionDecisionState.ActionSelected;
  34. }
  35. }
  36. public abstract class Character : MonoBehaviour
  37. {
  38. private string characterName;
  39. private int maxHealth;
  40. private int currentHealth;
  41. private int attack;
  42. private int strength;
  43. private int constitution;
  44. private int dexterity;
  45. private int wisdom;
  46. private int perception;
  47. private Sprite characterSprite;
  48. private int level;
  49. private int experiencePoints;
  50. private int experienceToLevelUp;
  51. private int initModifier;
  52. private int damageModifier;
  53. private int spellModifier;
  54. private int movementSpeed;
  55. private int armorClass;
  56. private bool isDead = false;
  57. private bool isTargetable = true;
  58. private Bank bank;
  59. private Inventory inventory;
  60. private TextMeshProUGUI nameText;
  61. // Health bar components
  62. private UnityEngine.UI.Slider healthSlider;
  63. private Canvas healthBarCanvas;
  64. // Add action system components
  65. [Header("Action System")]
  66. public CharacterActionData actionData = new CharacterActionData();
  67. [Header("Visual Feedback Settings")]
  68. public Color noActionColor = Color.yellow;
  69. public Color actionSelectedColor = Color.green;
  70. public Color normalColor = Color.white;
  71. public float outlineWidth = 0.1f;
  72. // Visual feedback components
  73. private Renderer characterRenderer;
  74. private Material characterMaterial;
  75. private Color originalColor;
  76. private GameObject outlineObject;
  77. public string CharacterName
  78. {
  79. get => characterName; set
  80. {
  81. characterName = value;
  82. UpdateNameDisplay();
  83. }
  84. }
  85. public int MaxHealth { get => maxHealth; set => maxHealth = value; }
  86. public int CurrentHealth { get => currentHealth; set => currentHealth = value; }
  87. public int Attack { get => attack; set => attack = value; }
  88. public int Strength { get => strength; set => strength = value; }
  89. public int Constitution { get => constitution; set => constitution = value; }
  90. public int Dexterity { get => dexterity; set => dexterity = value; }
  91. public int Wisdom { get => wisdom; set => wisdom = value; }
  92. public int Perception { get => perception; set => perception = value; }
  93. public Sprite CharacterSprite { get => characterSprite; set => characterSprite = value; }
  94. public int Level { get => level; set => level = value; }
  95. public int ExperiencePoints { get => experiencePoints; set => experiencePoints = value; }
  96. public int ExperienceToLevelUp { get => experienceToLevelUp; set => experienceToLevelUp = value; }
  97. public int InitModifier { get => initModifier; set => initModifier = value; }
  98. public int DamageModifier { get => damageModifier; set => damageModifier = value; }
  99. public int SpellModifier { get => spellModifier; set => spellModifier = value; }
  100. public int MovementSpeed { get => movementSpeed; set => movementSpeed = value; }
  101. public int ArmorClass { get => armorClass; set => armorClass = value; }
  102. public bool IsDead { get => isDead; }
  103. public bool IsTargetable { get => isTargetable; }
  104. // Enhanced action data - dynamically typed to avoid compilation dependencies
  105. private object _enhancedActionData;
  106. public object enhancedActionData
  107. {
  108. get => _enhancedActionData;
  109. set => _enhancedActionData = value;
  110. }
  111. // Helper method to get typed action data
  112. public T GetEnhancedActionData<T>() where T : class
  113. {
  114. return _enhancedActionData as T;
  115. }
  116. // Helper method to set enhanced action data safely
  117. public void SetEnhancedActionData(object data)
  118. {
  119. _enhancedActionData = data;
  120. }
  121. protected Weapon spawnedWeapon;
  122. public Weapon Weapon { get => spawnedWeapon; set => spawnedWeapon = value; }
  123. public abstract Character Spawn(int count);
  124. public abstract Weapon GetWeapon();
  125. public Character()
  126. {
  127. }
  128. void Start()
  129. {
  130. InitializeStats();
  131. // SpawnAndEquipWeapon();
  132. InitializeActionSystem();
  133. Debug.Log($"Character Start() completed for {CharacterName}");
  134. healthSlider = GetComponentInChildren<UnityEngine.UI.Slider>();
  135. }
  136. protected virtual void InitializeStats()
  137. {
  138. // Override in subclasses to set character stats
  139. }
  140. /// <summary>
  141. /// Apply stats from combat data to this character
  142. /// </summary>
  143. public virtual void ApplyStatsFromCombatData(object combatData)
  144. {
  145. // Handle TeamCharacterCombatData
  146. if (combatData is CombatDataTransfer.TeamCharacterCombatData teamData)
  147. {
  148. CharacterName = teamData.characterName;
  149. Strength = teamData.strength;
  150. Dexterity = teamData.dexterity;
  151. Constitution = teamData.constitution;
  152. Wisdom = teamData.wisdom;
  153. Perception = teamData.perception;
  154. // Calculate derived stats based on new base stats
  155. MaxHealth = Mathf.Max(10, 10 + (Constitution - 10) * 2 + (Strength - 10) + (Dexterity - 10) / 2);
  156. CurrentHealth = MaxHealth;
  157. InitModifier = Dexterity - 10;
  158. DamageModifier = (Strength - 10) / 2;
  159. SpellModifier = Wisdom - 10;
  160. MovementSpeed = 30 + (int)(Mathf.Ceil((Dexterity - 10) / 5.0f) * 5);
  161. ArmorClass = 10 + (Dexterity - 10) / 2 + (Constitution - 10) / 3;
  162. Debug.Log($"Applied team stats to {CharacterName}: STR:{Strength} DEX:{Dexterity} CON:{Constitution} WIS:{Wisdom} PER:{Perception}");
  163. }
  164. // Handle EnemyCombatData
  165. else if (combatData is CombatDataTransfer.EnemyCombatData enemyData)
  166. {
  167. CharacterName = enemyData.enemyName;
  168. // Apply enemy stats (you may want to add these fields to EnemyCombatData)
  169. // For now, keep the initialized stats but update the name
  170. Debug.Log($"Applied enemy data to {CharacterName}");
  171. }
  172. else
  173. {
  174. Debug.LogWarning($"Unknown combat data type for {CharacterName}");
  175. }
  176. }
  177. public Weapon GetEquippedWeapon()
  178. {
  179. return spawnedWeapon;
  180. }
  181. public virtual void SpawnAndEquipWeapon()
  182. {
  183. Debug.Log($"SpawnAndEquipWeapon called for {CharacterName}");
  184. Weapon weaponPrefab = GetWeapon();
  185. Debug.Log($"GetWeapon returned: {(weaponPrefab != null ? weaponPrefab.name : "null")}");
  186. if (weaponPrefab != null)
  187. {
  188. GameObject weaponInstance = Instantiate(weaponPrefab.gameObject);
  189. weaponInstance.transform.SetParent(this.transform, false);
  190. Debug.Log($"Instantiated weapon: {weaponInstance.name}");
  191. spawnedWeapon = weaponInstance.GetComponent<Weapon>();
  192. Debug.Log($"spawnedWeapon component: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}");
  193. if (spawnedWeapon != null)
  194. {
  195. // Set the wielder reference directly to avoid timing issues
  196. spawnedWeapon.SetWielder(this);
  197. Debug.Log($"{CharacterName} equipped {spawnedWeapon.weaponName}");
  198. // Position the weapon appropriately (you may want to customize this)
  199. PositionWeapon(weaponInstance);
  200. }
  201. else
  202. {
  203. Debug.LogError($"Weapon prefab for {CharacterName} doesn't have a Weapon component!");
  204. Destroy(weaponInstance);
  205. }
  206. }
  207. else
  208. {
  209. Debug.Log($"{CharacterName} no weapon prefab, trying direct creation...");
  210. CreateDirectWeapon();
  211. }
  212. }
  213. protected virtual Weapon CreateDirectWeapon()
  214. {
  215. Debug.LogWarning($"{CharacterName} - CreateDirectWeapon not implemented in subclass!");
  216. return null;
  217. }
  218. // Virtual method to position the weapon - can be overridden by specific character types
  219. protected virtual void PositionWeapon(GameObject weaponInstance)
  220. {
  221. // Default positioning - you can customize this based on your character setup
  222. weaponInstance.transform.localPosition = Vector3.zero;
  223. weaponInstance.transform.localRotation = Quaternion.identity;
  224. weaponInstance.transform.localScale = Vector3.one; // Ensure proper scale
  225. // Optional: Look for specific attachment points
  226. Transform weaponAttachPoint = transform.Find("WeaponAttachPoint");
  227. if (weaponAttachPoint != null)
  228. {
  229. weaponInstance.transform.SetParent(weaponAttachPoint, false);
  230. weaponInstance.transform.localPosition = Vector3.zero;
  231. weaponInstance.transform.localRotation = Quaternion.identity;
  232. Debug.Log($"Weapon attached to {weaponAttachPoint.name}");
  233. }
  234. else
  235. {
  236. // If no specific attach point, just position relative to character
  237. weaponInstance.transform.localPosition = new Vector3(0.5f, 0, 0); // Slightly to the right
  238. Debug.Log($"Weapon positioned at default location relative to {CharacterName}");
  239. }
  240. }
  241. void Awake()
  242. {
  243. nameText = GetComponentInChildren<TextMeshProUGUI>();
  244. if (nameText == null)
  245. {
  246. Debug.LogWarning($"No TextMeshProUGUI component found in children of {gameObject.name}");
  247. }
  248. // Initialize visual feedback system
  249. InitializeVisualFeedback();
  250. UpdateNameDisplay();
  251. }
  252. // Initialize visual feedback components
  253. private void InitializeVisualFeedback()
  254. {
  255. characterRenderer = GetComponent<Renderer>();
  256. if (characterRenderer != null)
  257. {
  258. // Create a unique material instance to avoid modifying shared materials
  259. characterMaterial = new Material(characterRenderer.material);
  260. characterRenderer.material = characterMaterial;
  261. originalColor = characterMaterial.color;
  262. }
  263. else
  264. {
  265. Debug.LogWarning($"No Renderer found on {gameObject.name}. Visual feedback will not work.");
  266. }
  267. // Ensure we have a collider for mouse detection
  268. Collider col = GetComponent<Collider>();
  269. if (col == null)
  270. {
  271. col = gameObject.AddComponent<CapsuleCollider>();
  272. Debug.Log($"Added CapsuleCollider to {gameObject.name} for mouse detection");
  273. }
  274. }
  275. private void UpdateNameDisplay()
  276. {
  277. if (nameText != null && !string.IsNullOrEmpty(characterName))
  278. {
  279. nameText.text = characterName;
  280. }
  281. }
  282. public void RefreshNameDisplay()
  283. {
  284. UpdateNameDisplay();
  285. }
  286. public virtual float GetAttackRange()
  287. {
  288. return spawnedWeapon != null ? spawnedWeapon.Range : 1.0f; // Default range if no weapon is equipped
  289. }
  290. private void InitializeActionSystem()
  291. {
  292. actionData.Reset();
  293. SetVisualState(ActionDecisionState.NoAction);
  294. }
  295. public void SetVisualState(ActionDecisionState state)
  296. {
  297. if (characterRenderer == null) return;
  298. switch (state)
  299. {
  300. case ActionDecisionState.NoAction:
  301. ShowNoActionState();
  302. break;
  303. case ActionDecisionState.ActionSelected:
  304. ShowActionSelectedState();
  305. break;
  306. default:
  307. ShowNormalState();
  308. break;
  309. }
  310. }
  311. private void ShowNoActionState()
  312. {
  313. if (characterMaterial != null)
  314. characterMaterial.color = noActionColor;
  315. CreateOutline();
  316. }
  317. private void ShowActionSelectedState()
  318. {
  319. if (characterMaterial != null)
  320. characterMaterial.color = actionSelectedColor;
  321. RemoveOutline();
  322. }
  323. private void ShowNormalState()
  324. {
  325. if (characterMaterial != null)
  326. characterMaterial.color = originalColor;
  327. RemoveOutline();
  328. }
  329. private void CreateOutline()
  330. {
  331. if (outlineObject != null) return;
  332. outlineObject = new GameObject("Outline");
  333. outlineObject.transform.SetParent(transform);
  334. outlineObject.transform.localPosition = Vector3.zero;
  335. outlineObject.transform.localScale = Vector3.one * (1f + outlineWidth);
  336. // For 3D characters, we'll create a simple colored outline using a MeshRenderer
  337. MeshFilter meshFilter = outlineObject.AddComponent<MeshFilter>();
  338. MeshRenderer outlineRenderer = outlineObject.AddComponent<MeshRenderer>();
  339. // Copy the mesh from the character
  340. MeshFilter characterMesh = GetComponent<MeshFilter>();
  341. if (characterMesh != null)
  342. {
  343. meshFilter.mesh = characterMesh.mesh;
  344. }
  345. // Create a simple black material for the outline
  346. Material outlineMaterial = new Material(Shader.Find("Standard"));
  347. outlineMaterial.color = Color.black;
  348. outlineRenderer.material = outlineMaterial;
  349. // Set rendering order to appear behind the character
  350. outlineRenderer.sortingOrder = -1;
  351. }
  352. private void RemoveOutline()
  353. {
  354. if (outlineObject != null)
  355. {
  356. DestroyImmediate(outlineObject);
  357. outlineObject = null;
  358. }
  359. }
  360. public void ExecuteAction()
  361. {
  362. Debug.Log($"=== ExecuteAction called for {CharacterName} ===");
  363. Debug.Log($"Action state: {actionData.state}");
  364. Debug.Log($"Has target enemy: {actionData.targetEnemy != null}");
  365. Debug.Log($"Target enemy name: {(actionData.targetEnemy != null ? actionData.targetEnemy.name : "none")}");
  366. if (actionData.state != ActionDecisionState.ActionSelected)
  367. {
  368. Debug.Log($"Action not selected, returning. State: {actionData.state}");
  369. return;
  370. }
  371. if (actionData.targetEnemy != null)
  372. {
  373. Debug.Log($"{CharacterName} executing attack on {actionData.targetEnemy.name}");
  374. // Get equipped weapon and perform attack
  375. Debug.Log($"Getting equipped weapon for {CharacterName}...");
  376. Debug.Log($"spawnedWeapon is: {(spawnedWeapon != null ? spawnedWeapon.GetType().Name : "null")}");
  377. Weapon equippedWeapon = GetEquippedWeapon();
  378. Debug.Log($"GetEquippedWeapon returned: {(equippedWeapon != null ? equippedWeapon.GetType().Name : "null")}");
  379. if (equippedWeapon != null)
  380. {
  381. Debug.Log($"Using weapon: {equippedWeapon.weaponName}");
  382. equippedWeapon.Attack(actionData.targetEnemy);
  383. Character targetCharacter = actionData.targetEnemy.GetComponent<Character>();
  384. if (targetCharacter != null && targetCharacter.IsDead)
  385. {
  386. actionData.state = ActionDecisionState.ActionExecuted;
  387. SetVisualState(actionData.state);
  388. }
  389. }
  390. else
  391. {
  392. Debug.LogError($"{CharacterName} has no weapon equipped for attack!");
  393. Debug.LogError($"spawnedWeapon field is: {(spawnedWeapon != null ? "not null" : "null")}");
  394. Debug.LogError($"Weapon property is: {(Weapon != null ? "not null" : "null")}");
  395. }
  396. }
  397. else if (actionData.targetPosition != Vector3.zero)
  398. {
  399. Debug.Log($"{CharacterName} moves to {actionData.targetPosition}");
  400. // Add your movement logic here
  401. actionData.state = ActionDecisionState.ActionExecuted;
  402. SetVisualState(actionData.state);
  403. }
  404. }
  405. public bool HasActionSelected()
  406. {
  407. return actionData.state == ActionDecisionState.ActionSelected;
  408. }
  409. public bool IsActionComplete()
  410. {
  411. // If action is already executed or no action, it's complete
  412. if (actionData.state != ActionDecisionState.ActionSelected) return true;
  413. if (actionData.targetEnemy != null)
  414. {
  415. // Check if we're within attack range of the target
  416. float distance = Vector3.Distance(transform.position, actionData.targetEnemy.transform.position);
  417. float attackRange = GetAttackRange();
  418. return distance <= attackRange;
  419. }
  420. else if (actionData.targetPosition != Vector3.zero)
  421. {
  422. // Check if we've reached the movement target using horizontal distance
  423. Vector3 currentPos = transform.position;
  424. Vector3 targetPos = actionData.targetPosition;
  425. Vector2 currentPos2D = new Vector2(currentPos.x, currentPos.z);
  426. Vector2 targetPos2D = new Vector2(targetPos.x, targetPos.z);
  427. float horizontalDistance = Vector2.Distance(currentPos2D, targetPos2D);
  428. return horizontalDistance <= 1.2f; // Arrival threshold
  429. }
  430. return true; // No target means completed
  431. }
  432. public void ResetAction()
  433. {
  434. actionData.Reset();
  435. SetVisualState(actionData.state);
  436. }
  437. public void TakeDamage(int damage)
  438. {
  439. if (isDead) return;
  440. currentHealth -= damage;
  441. Debug.Log($"{CharacterName} takes {damage} damage. Health: {currentHealth}/{maxHealth}");
  442. // Update health bar
  443. UpdateHealthBar();
  444. if (currentHealth <= 0)
  445. {
  446. Die();
  447. }
  448. }
  449. public void Heal(int healAmount)
  450. {
  451. if (isDead) return;
  452. currentHealth = Mathf.Min(currentHealth + healAmount, maxHealth);
  453. Debug.Log($"{CharacterName} healed for {healAmount} HP. Health: {currentHealth}/{maxHealth}");
  454. // Update health bar
  455. UpdateHealthBar();
  456. }
  457. public void AttackTarget(Character target)
  458. {
  459. if (target == null || target.IsDead)
  460. {
  461. Debug.LogWarning($"{CharacterName} tried to attack invalid target");
  462. return;
  463. }
  464. // Calculate damage based on weapon and stats
  465. int damage = CalculateDamage();
  466. Debug.Log($"⚔️ {CharacterName} attacks {target.CharacterName} for {damage} damage");
  467. target.TakeDamage(damage);
  468. }
  469. private int CalculateDamage()
  470. {
  471. // Base damage calculation
  472. int baseDamage = attack + damageModifier;
  473. // Add weapon damage if equipped
  474. if (spawnedWeapon != null)
  475. {
  476. baseDamage += spawnedWeapon.damage;
  477. }
  478. // Add some randomness (±20%)
  479. float randomMultiplier = UnityEngine.Random.Range(0.8f, 1.2f);
  480. return Mathf.Max(1, Mathf.RoundToInt(baseDamage * randomMultiplier));
  481. }
  482. // Debug method to test attacks manually
  483. public void DebugAttack(GameObject target)
  484. {
  485. Weapon equippedWeapon = GetEquippedWeapon();
  486. if (equippedWeapon != null)
  487. {
  488. Debug.Log($"DEBUG: {CharacterName} manually attacking {target.name}");
  489. equippedWeapon.Attack(target);
  490. }
  491. else
  492. {
  493. Debug.LogError($"DEBUG: {CharacterName} has no weapon equipped!");
  494. }
  495. }
  496. private void Die()
  497. {
  498. isDead = true;
  499. isTargetable = false;
  500. currentHealth = 0;
  501. gameObject.SetActive(false); // Disable the character GameObject
  502. characterRenderer.enabled = false;
  503. // Make character gray
  504. if (characterMaterial != null)
  505. {
  506. characterMaterial.color = Color.gray;
  507. }
  508. // Update health bar to show death
  509. UpdateHealthBar();
  510. Debug.Log($"{CharacterName} has died!");
  511. }
  512. private void UpdateHealthBar()
  513. {
  514. if (healthSlider != null)
  515. {
  516. healthSlider.value = currentHealth;
  517. healthSlider.maxValue = maxHealth;
  518. // Change color based on health percentage
  519. UnityEngine.UI.Image fillImage = healthSlider.fillRect.GetComponent<UnityEngine.UI.Image>();
  520. if (fillImage != null)
  521. {
  522. float healthPercent = (float)currentHealth / maxHealth;
  523. if (healthPercent > 0.6f)
  524. fillImage.color = Color.green;
  525. else if (healthPercent > 0.3f)
  526. fillImage.color = Color.yellow;
  527. else
  528. fillImage.color = Color.red;
  529. }
  530. Debug.Log($"Updated health bar for {CharacterName}: {currentHealth}/{maxHealth}");
  531. }
  532. }
  533. }