enemyScript.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. using System.Collections;
  2. using UnityEngine;
  3. using Spine.Unity;
  4. using Spine.Unity.Examples;
  5. using Mirror;
  6. public class enemyScript : NetworkBehaviour
  7. {
  8. public const int HEALTH_INC = 2;
  9. public const float DAMAGE_INC = 1.2f;
  10. public const float XP_GAIN = 1.5f;
  11. public const int XP_GAIN_Base = 5;
  12. [SyncVar(hook = nameof(OnHealthChange))]
  13. public int health;
  14. [SyncVar(hook = nameof(OnMagicalHealthChange))]
  15. public int magicalHealth;
  16. // NEW: Shield break boolean
  17. [SyncVar]
  18. public bool shieldBreak = false;
  19. public SpriteHealthBar healthBar;
  20. public SpriteHealthBar MagicalhealthBar;
  21. public float speed;
  22. public float chaseRadius;
  23. public float attackRadius;
  24. public bool rotate;
  25. //public LayerMask layerMask;
  26. public playerNetwork target;
  27. private Rigidbody2D rb2;
  28. public SkeletonAnimation animator;
  29. private Vector2 movement;
  30. public Vector3 dir;
  31. public TextMesh enemyName;
  32. public TextMesh enemyLevel;
  33. public bool isInChaseRange;
  34. public bool isInAttackRange;
  35. public Transform uiEnemy;
  36. public int enemyAttackDamage = 10;
  37. MeshRenderer meshRenderer;
  38. public GameObject hitVfx;
  39. void Awake()
  40. {
  41. meshRenderer = GetComponent<MeshRenderer>();
  42. scanCooldown = Random.Range(0.5f, 1.5f);
  43. }
  44. private void Start()
  45. {
  46. rb2 = GetComponent<Rigidbody2D>();
  47. //target = GameObject.FindWithTag("Player").transform;
  48. UpdateAnimation(directionString, animationString);
  49. defaultPos = transform.position;
  50. }
  51. [SyncVar(hook = nameof(OnLevelChanged))]
  52. public int level;
  53. void OnLevelChanged(int oldVal, int newVal)
  54. {
  55. if (isServer) { return; }
  56. SetLevel(newVal);
  57. }
  58. public void SetLevel(int _level)
  59. {
  60. if (enemyLevel != null)
  61. {
  62. enemyLevel.text = _level.ToString();
  63. }
  64. level = _level;
  65. int healthIncrement = level * HEALTH_INC;
  66. maxHealth = 100 + healthIncrement;
  67. health = (int)maxHealth;
  68. magicalHealth = (int)maxHealth;
  69. enemyAttackDamage += (int)(level * DAMAGE_INC);
  70. // MODIFIED: Reset shield break when level is set
  71. shieldBreak = false;
  72. // Debug.Log($"{health}/{maxHealth}");
  73. }
  74. public Vector3 defScale;
  75. Vector3 defaultPos;
  76. float playerDistCheckTimer = 0f;
  77. void LateUpdate()
  78. {
  79. LOD();
  80. }
  81. public const float disappearDistFromPlayer = 15f;
  82. void LOD()
  83. {
  84. if (playerDistCheckTimer > 0) { playerDistCheckTimer -= Time.deltaTime; return; }
  85. playerDistCheckTimer = Random.Range(1.5f, 2.5f);
  86. if (playerNetwork.localPlayerTransform == null) { return; }
  87. float distToPlayer = Vector3.Distance(playerNetwork.localPlayerTransform.position, transform.position);
  88. meshRenderer.enabled = distToPlayer < disappearDistFromPlayer;
  89. }
  90. #if UNITY_SERVER || UNITY_EDITOR
  91. [Server]
  92. private void Update()
  93. {
  94. // animator.skeleton.SetSkin
  95. // set animation state to running if in chase Range
  96. //isInChaseRange = true
  97. // isInChaseRange = Physics2D.OverlapCircle(transform.position, chaseRadius , layerMask);
  98. // isInAttackRange = Physics2D.OverlapCircle(transform.position, attackRadius, layerMask);
  99. // MODIFIED: Check both health and magicalHealth for death condition
  100. if (health <= 0 || (shieldBreak && magicalHealth <= 0))
  101. {
  102. return;
  103. }
  104. if (target != null)
  105. {
  106. isInChaseRange = Vector3.Distance(transform.position, target.transform.position) < chaseRadius;
  107. isInAttackRange = Vector3.Distance(transform.position, target.transform.position) < attackRadius;
  108. }
  109. else
  110. {
  111. isInChaseRange = false;
  112. isInAttackRange = false;
  113. }
  114. ScanPlayers();
  115. if (target != null)
  116. {
  117. enemyFollow();
  118. }
  119. }
  120. #endif
  121. float scanTimer = 0;
  122. float scanCooldown;
  123. public void ScanPlayers()
  124. {
  125. if (scanTimer > 0) { scanTimer -= Time.deltaTime; return; }
  126. scanTimer = scanCooldown;
  127. playerNetwork[] playersinNetwork = FindObjectsOfType<playerNetwork>();
  128. float closestDist = float.MaxValue;
  129. playerNetwork closestPlayer = null;
  130. foreach (playerNetwork player in playersinNetwork)
  131. {
  132. if (player.health <= 0) { continue; }
  133. float dist = Vector3.Distance(transform.position, player.transform.position);
  134. if (dist < closestDist)
  135. {
  136. closestPlayer = player;
  137. closestDist = dist;
  138. }
  139. }
  140. if (closestDist < chaseRadius)
  141. {
  142. target = closestPlayer;
  143. }
  144. else
  145. {
  146. target = null;
  147. }
  148. //if(target == null) {return;}
  149. }
  150. // [ClientRpc]
  151. // void RpcUpdateAnim(string animDir , string animName, bool isLoop){
  152. // UpdateAnimation(animDir , animName, isLoop);
  153. // }
  154. [SyncVar(hook = nameof(OnFlipped))]
  155. bool isFlipped = false;
  156. void OnFlipped(bool oldVal, bool newVal)
  157. {
  158. if (isServer) { return; }
  159. transform.localScale = new Vector3(defScale.x * (newVal ? -1 : 1), defScale.y, defScale.z);
  160. HandleFlip();
  161. }
  162. void HandleFlip()
  163. {
  164. if (uiEnemy == null)
  165. {
  166. return;
  167. }
  168. if (transform.localScale.x < 0)
  169. {
  170. uiEnemy.localScale = new Vector3(-1, 1, 1);
  171. }
  172. else
  173. {
  174. uiEnemy.localScale = new Vector3(1, 1, 1);
  175. }
  176. }
  177. private void enemyFollow()
  178. {
  179. if (Mathf.Abs(dir.y) > Mathf.Abs(dir.x))
  180. {
  181. if (dir.y < 0)
  182. {
  183. directionString = "Back";
  184. }
  185. else
  186. {
  187. directionString = "Front";
  188. }
  189. }
  190. else
  191. {
  192. directionString = "Side";
  193. if (dir.x < 0)
  194. {
  195. transform.localScale = new Vector3(defScale.x, defScale.y, 0);
  196. isFlipped = false;
  197. }
  198. else
  199. {
  200. transform.localScale = new Vector3(-defScale.x, defScale.y, 0);
  201. isFlipped = true;
  202. }
  203. HandleFlip();
  204. }
  205. if (animationHistory != directionString + animationString)
  206. {
  207. UpdateAnimation(directionString, animationString);
  208. // RpcUpdateAnim(directionString, animationString,true);
  209. }
  210. animationHistory = directionString + animationString;
  211. if (target != null)
  212. {
  213. dir = transform.position - target.transform.position;
  214. }
  215. float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
  216. dir.Normalize();
  217. movement = dir;
  218. if (rotate)
  219. {
  220. //set anim direction x, y dir
  221. }
  222. }
  223. string animationHistory = "";
  224. [SyncVar(hook = nameof(OnAnimationDirectionChanged))]
  225. public string directionString = "Side";
  226. [SyncVar(hook = nameof(OnAnimationNameChanged))]
  227. public string animationString = "Idle";
  228. void OnAnimationDirectionChanged(string oldVal, string newVal)
  229. {
  230. UpdateAnimation(newVal, animationString);
  231. }
  232. void OnAnimationNameChanged(string oldVal, string newVal)
  233. {
  234. UpdateAnimation(directionString, newVal);
  235. }
  236. float attackTimer = 0f;
  237. float attackDuration = 1.4f;
  238. [SyncVar]
  239. public float maxHealth;
  240. #if UNITY_SERVER || UNITY_EDITOR
  241. [Server]
  242. private void FixedUpdate()
  243. {
  244. // MODIFIED: Updated death condition
  245. if (health <= 0 || (shieldBreak && magicalHealth <= 0))
  246. {
  247. return;
  248. }
  249. healthBar.SetHealth(health, maxHealth);
  250. MagicalhealthBar.SetHealth(magicalHealth, maxHealth);
  251. if (isInChaseRange && !isInAttackRange)
  252. {
  253. MoveEnemy(movement);
  254. //Set animation to moving
  255. animationString = "Walk";
  256. }
  257. if (isInAttackRange)
  258. {
  259. rb2.velocity = Vector2.zero;
  260. //Set animation to attack
  261. animationString = "Attack";
  262. if (attackTimer < attackDuration)
  263. {
  264. attackTimer += Time.deltaTime;
  265. }
  266. else
  267. {
  268. attackTimer = 0;
  269. Attack();
  270. }
  271. //TODO: ATTACK HERE
  272. }
  273. if (!isInAttackRange && !isInChaseRange)
  274. {
  275. //SetAnimation to idle
  276. animationString = "Idle";
  277. }
  278. }
  279. #endif
  280. public void Attack()
  281. {
  282. target.TakeDamage(enemyAttackDamage);
  283. }
  284. private void MoveEnemy(Vector2 dir)
  285. {
  286. rb2.MovePosition((Vector2)transform.position + (dir * speed * Time.deltaTime));
  287. }
  288. void UpdateAnimation(string direction, string animationName)
  289. {
  290. // try{
  291. StartCoroutine(CoroutineUpdateAnim(direction, animationName));
  292. }
  293. IEnumerator CoroutineUpdateAnim(string direction, string animationName)
  294. {
  295. while (animator == null)
  296. {
  297. yield return new WaitForSeconds(0.1f);
  298. Debug.LogError("animator is null!");
  299. }
  300. while (animator.skeleton == null)
  301. {
  302. yield return new WaitForSeconds(0.1f);
  303. Debug.LogError("animator skelton is null!");
  304. }
  305. while (animator.AnimationState == null)
  306. {
  307. yield return new WaitForSeconds(0.1f);
  308. Debug.LogError("animator state is null!");
  309. }
  310. animator.skeleton.SetSkin(direction);
  311. animator.skeleton.SetSlotsToSetupPose();
  312. animator.AnimationState.SetAnimation(0, $"{direction}_{animationName}", !animationName.ToLower().Contains("death"));
  313. // }catch(Exception e){
  314. // Debug.LogError(e.ToString());
  315. // }
  316. Debug.Log($"Updating enemy animation {direction}_{animationName}");
  317. }
  318. [Command(requiresAuthority = false)]
  319. void CmdTakeDamage(int damage, uint id)
  320. {
  321. takedmg(damage, id);
  322. Debug.Log("Enemy Attack Recieved ");
  323. }
  324. public void TakeDamage(int damage, uint id)
  325. {
  326. if (isServer)
  327. {
  328. takedmg(damage, id);
  329. }
  330. else
  331. {
  332. CmdTakeDamage(damage, id);
  333. }
  334. }
  335. // MODIFIED: Completely rewritten damage system
  336. void takedmg(int damage, uint id)
  337. {
  338. if (health <= 0) { return; }
  339. int finalDamage = damage;
  340. // If shield is not broken, reduce damage by half and damage magical health
  341. if (!shieldBreak && magicalHealth > 0)
  342. {
  343. finalDamage = damage / 2;
  344. // Calculate magical health damage based on player attack damage + enemy level
  345. int magicalDamage = damage + level;
  346. magicalHealth -= magicalDamage;
  347. // Check if shield breaks
  348. if (magicalHealth <= 0)
  349. {
  350. shieldBreak = true;
  351. magicalHealth = 0;
  352. Debug.Log("Shield Broken!");
  353. }
  354. }
  355. // Apply damage to health
  356. health -= finalDamage;
  357. // Check for death
  358. if (health <= 0)
  359. {
  360. StartCoroutine(couroutineDeath());
  361. foreach (playerNetwork player in FindObjectsOfType<playerNetwork>())
  362. {
  363. if (player.netId == id)
  364. {
  365. player.OnEnemyKilled(level);
  366. }
  367. }
  368. }
  369. Debug.Log($"Enemy Takes Damage: {finalDamage} | Shield Broken: {shieldBreak} | Health: {health} | Magical Health: {magicalHealth}");
  370. }
  371. [Command(requiresAuthority = false)]
  372. void CmdTakeMagicalDamage(int damage, uint id)
  373. {
  374. takeMagicalDmg(damage, id);
  375. Debug.Log("Enemy Magical Attack Recieved ");
  376. }
  377. public void TakeMagicalDamage(int damage, uint id)
  378. {
  379. if (isServer)
  380. {
  381. takeMagicalDmg(damage, id);
  382. }
  383. else
  384. {
  385. CmdTakeMagicalDamage(damage, id);
  386. }
  387. }
  388. // MODIFIED: Updated magical damage to use same system
  389. void takeMagicalDmg(int damage, uint id)
  390. {
  391. if (magicalHealth <= 0 && shieldBreak) { return; }
  392. int finalDamage = damage;
  393. // If shield is not broken, reduce damage by half and damage magical health
  394. if (!shieldBreak && magicalHealth > 0)
  395. {
  396. finalDamage = damage / 2;
  397. // Calculate magical health damage based on player attack damage + enemy level
  398. int magicalDamage = damage + level;
  399. magicalHealth -= magicalDamage;
  400. // Check if shield breaks
  401. if (magicalHealth <= 0)
  402. {
  403. shieldBreak = true;
  404. magicalHealth = 0;
  405. Debug.Log("Shield Broken!");
  406. }
  407. }
  408. // Apply damage to health
  409. health -= finalDamage;
  410. // Check for death
  411. if (health <= 0)
  412. {
  413. StartCoroutine(couroutineDeath());
  414. foreach (playerNetwork player in FindObjectsOfType<playerNetwork>())
  415. {
  416. if (player.netId == id)
  417. {
  418. player.OnEnemyKilled(level);
  419. }
  420. }
  421. }
  422. Debug.Log($"Enemy Takes Magical Damage: {finalDamage} | Shield Broken: {shieldBreak} | Health: {health} | Magical Health: {magicalHealth}");
  423. }
  424. IEnumerator couroutineDeath()
  425. {
  426. animationString = "Death";
  427. StartCoroutine(PopDisappearUI());
  428. UpdateAnimation(directionString, animationString);
  429. // RpcUpdateAnim(directionString, animationString,false);
  430. Vector3 lootSpawnPos = transform.position;
  431. lootSpawnPos.z = GameManager.instance.LootSpawnPointsParent.GetChild(0).position.z;
  432. //instantiate loot item
  433. GameObject newLoot = Instantiate(GameManager.instance.GetRandomLoot(), lootSpawnPos, Quaternion.identity);
  434. NetworkServer.Spawn(newLoot);
  435. yield return new WaitForSecondsRealtime(7);// dead corpse delay
  436. if (!isServer)
  437. {
  438. CmdDie();
  439. }
  440. else
  441. {
  442. GameManager.OnEnemyDeath(this, defaultPos);
  443. }
  444. }
  445. [Command]
  446. void CmdDie()
  447. {
  448. GameManager.OnEnemyDeath(this, defaultPos);
  449. }
  450. public void OnHealthChange(int oldVlaue, int newValue)
  451. {
  452. healthBar.SetHealth(newValue, maxHealth);
  453. }
  454. public void OnMagicalHealthChange(int oldVlaue, int newValue)
  455. {
  456. MagicalhealthBar.SetHealth(newValue, maxHealth);
  457. }
  458. //etc for ui Disspear coroutine
  459. IEnumerator PopDisappearUI()
  460. {
  461. Vector3 originalScale = uiEnemy.localScale;
  462. // First, scale up slightly
  463. float popDuration = 0.15f;
  464. float elapsedTime = 0f;
  465. Vector3 popScale = originalScale * 1.2f;
  466. while (elapsedTime < popDuration)
  467. {
  468. float t = elapsedTime / popDuration;
  469. uiEnemy.localScale = Vector3.Lerp(originalScale, popScale, t);
  470. elapsedTime += Time.deltaTime;
  471. yield return null;
  472. }
  473. // Then scale down to zero quickly
  474. float shrinkDuration = 0.3f;
  475. elapsedTime = 0f;
  476. while (elapsedTime < shrinkDuration)
  477. {
  478. float t = elapsedTime / shrinkDuration;
  479. // Use ease-in curve for faster shrinking
  480. float easedT = t * t;
  481. uiEnemy.localScale = Vector3.Lerp(popScale, Vector3.zero, easedT);
  482. elapsedTime += Time.deltaTime;
  483. yield return null;
  484. }
  485. uiEnemy.localScale = Vector3.zero;
  486. uiEnemy.gameObject.SetActive(false);
  487. }
  488. }