Character.cs 19 KB

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