using System.Collections; using UnityEngine; using Spine.Unity; using Spine.Unity.Examples; using Mirror; public class enemyScript : NetworkBehaviour { public const int HEALTH_INC = 2; public const float DAMAGE_INC = 1.2f; public const float XP_GAIN = 1.5f; public const int XP_GAIN_Base = 5; [SyncVar(hook = nameof(OnHealthChange))] public int health; [SyncVar(hook = nameof(OnMagicalHealthChange))] public int magicalHealth; // NEW: Shield break boolean [SyncVar] public bool shieldBreak = false; public SpriteHealthBar healthBar; public SpriteHealthBar MagicalhealthBar; public float speed; public float chaseRadius; public float attackRadius; public bool rotate; //public LayerMask layerMask; public playerNetwork target; private Rigidbody2D rb2; public SkeletonAnimation animator; private Vector2 movement; public Vector3 dir; public TextMesh enemyName; public TextMesh enemyLevel; public bool isInChaseRange; public bool isInAttackRange; public Transform uiEnemy; public int enemyAttackDamage = 10; MeshRenderer meshRenderer; public GameObject hitVfx; void Awake() { meshRenderer = GetComponent(); scanCooldown = Random.Range(0.5f, 1.5f); } private void Start() { rb2 = GetComponent(); //target = GameObject.FindWithTag("Player").transform; UpdateAnimation(directionString, animationString); defaultPos = transform.position; } [SyncVar(hook = nameof(OnLevelChanged))] public int level; void OnLevelChanged(int oldVal, int newVal) { if (isServer) { return; } SetLevel(newVal); } public void SetLevel(int _level) { if (enemyLevel != null) { enemyLevel.text = _level.ToString(); } level = _level; int healthIncrement = level * HEALTH_INC; maxHealth = 100 + healthIncrement; health = (int)maxHealth; magicalHealth = (int)maxHealth; enemyAttackDamage += (int)(level * DAMAGE_INC); // MODIFIED: Reset shield break when level is set shieldBreak = false; // Debug.Log($"{health}/{maxHealth}"); } public Vector3 defScale; Vector3 defaultPos; float playerDistCheckTimer = 0f; void LateUpdate() { LOD(); } public const float disappearDistFromPlayer = 15f; void LOD() { if (playerDistCheckTimer > 0) { playerDistCheckTimer -= Time.deltaTime; return; } playerDistCheckTimer = Random.Range(1.5f, 2.5f); if (playerNetwork.localPlayerTransform == null) { return; } float distToPlayer = Vector3.Distance(playerNetwork.localPlayerTransform.position, transform.position); meshRenderer.enabled = distToPlayer < disappearDistFromPlayer; } #if UNITY_SERVER || UNITY_EDITOR [Server] private void Update() { // animator.skeleton.SetSkin // set animation state to running if in chase Range //isInChaseRange = true // isInChaseRange = Physics2D.OverlapCircle(transform.position, chaseRadius , layerMask); // isInAttackRange = Physics2D.OverlapCircle(transform.position, attackRadius, layerMask); // MODIFIED: Check both health and magicalHealth for death condition if (health <= 0 || (shieldBreak && magicalHealth <= 0)) { return; } if (target != null) { isInChaseRange = Vector3.Distance(transform.position, target.transform.position) < chaseRadius; isInAttackRange = Vector3.Distance(transform.position, target.transform.position) < attackRadius; } else { isInChaseRange = false; isInAttackRange = false; } ScanPlayers(); if (target != null) { enemyFollow(); } } #endif float scanTimer = 0; float scanCooldown; public void ScanPlayers() { if (scanTimer > 0) { scanTimer -= Time.deltaTime; return; } scanTimer = scanCooldown; playerNetwork[] playersinNetwork = FindObjectsOfType(); float closestDist = float.MaxValue; playerNetwork closestPlayer = null; foreach (playerNetwork player in playersinNetwork) { if (player.health <= 0) { continue; } float dist = Vector3.Distance(transform.position, player.transform.position); if (dist < closestDist) { closestPlayer = player; closestDist = dist; } } if (closestDist < chaseRadius) { target = closestPlayer; } else { target = null; } //if(target == null) {return;} } // [ClientRpc] // void RpcUpdateAnim(string animDir , string animName, bool isLoop){ // UpdateAnimation(animDir , animName, isLoop); // } [SyncVar(hook = nameof(OnFlipped))] bool isFlipped = false; void OnFlipped(bool oldVal, bool newVal) { if (isServer) { return; } transform.localScale = new Vector3(defScale.x * (newVal ? -1 : 1), defScale.y, defScale.z); HandleFlip(); } void HandleFlip() { if (uiEnemy == null) { return; } if (transform.localScale.x < 0) { uiEnemy.localScale = new Vector3(-1, 1, 1); } else { uiEnemy.localScale = new Vector3(1, 1, 1); } } private void enemyFollow() { if (Mathf.Abs(dir.y) > Mathf.Abs(dir.x)) { if (dir.y < 0) { directionString = "Back"; } else { directionString = "Front"; } } else { directionString = "Side"; if (dir.x < 0) { transform.localScale = new Vector3(defScale.x, defScale.y, 0); isFlipped = false; } else { transform.localScale = new Vector3(-defScale.x, defScale.y, 0); isFlipped = true; } HandleFlip(); } if (animationHistory != directionString + animationString) { UpdateAnimation(directionString, animationString); // RpcUpdateAnim(directionString, animationString,true); } animationHistory = directionString + animationString; if (target != null) { dir = transform.position - target.transform.position; } float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; dir.Normalize(); movement = dir; if (rotate) { //set anim direction x, y dir } } string animationHistory = ""; [SyncVar(hook = nameof(OnAnimationDirectionChanged))] public string directionString = "Side"; [SyncVar(hook = nameof(OnAnimationNameChanged))] public string animationString = "Idle"; void OnAnimationDirectionChanged(string oldVal, string newVal) { UpdateAnimation(newVal, animationString); } void OnAnimationNameChanged(string oldVal, string newVal) { UpdateAnimation(directionString, newVal); } float attackTimer = 0f; float attackDuration = 1.4f; [SyncVar] public float maxHealth; #if UNITY_SERVER || UNITY_EDITOR [Server] private void FixedUpdate() { // MODIFIED: Updated death condition if (health <= 0 || (shieldBreak && magicalHealth <= 0)) { return; } healthBar.SetHealth(health, maxHealth); MagicalhealthBar.SetHealth(magicalHealth, maxHealth); if (isInChaseRange && !isInAttackRange) { MoveEnemy(movement); //Set animation to moving animationString = "Walk"; } if (isInAttackRange) { rb2.velocity = Vector2.zero; //Set animation to attack animationString = "Attack"; if (attackTimer < attackDuration) { attackTimer += Time.deltaTime; } else { attackTimer = 0; Attack(); } //TODO: ATTACK HERE } if (!isInAttackRange && !isInChaseRange) { //SetAnimation to idle animationString = "Idle"; } } #endif public void Attack() { target.TakeDamage(enemyAttackDamage); } private void MoveEnemy(Vector2 dir) { rb2.MovePosition((Vector2)transform.position + (dir * speed * Time.deltaTime)); } void UpdateAnimation(string direction, string animationName) { // try{ StartCoroutine(CoroutineUpdateAnim(direction, animationName)); } IEnumerator CoroutineUpdateAnim(string direction, string animationName) { while (animator == null) { yield return new WaitForSeconds(0.1f); Debug.LogError("animator is null!"); } while (animator.skeleton == null) { yield return new WaitForSeconds(0.1f); Debug.LogError("animator skelton is null!"); } while (animator.AnimationState == null) { yield return new WaitForSeconds(0.1f); Debug.LogError("animator state is null!"); } animator.skeleton.SetSkin(direction); animator.skeleton.SetSlotsToSetupPose(); animator.AnimationState.SetAnimation(0, $"{direction}_{animationName}", !animationName.ToLower().Contains("death")); // }catch(Exception e){ // Debug.LogError(e.ToString()); // } Debug.Log($"Updating enemy animation {direction}_{animationName}"); } [Command(requiresAuthority = false)] void CmdTakeDamage(int damage, uint id) { takedmg(damage, id); Debug.Log("Enemy Attack Recieved "); } public void TakeDamage(int damage, uint id) { if (isServer) { takedmg(damage, id); } else { CmdTakeDamage(damage, id); } } // MODIFIED: Completely rewritten damage system void takedmg(int damage, uint id) { if (health <= 0) { return; } int finalDamage = damage; // If shield is not broken, reduce damage by half and damage magical health if (!shieldBreak && magicalHealth > 0) { finalDamage = damage / 2; // Calculate magical health damage based on player attack damage + enemy level int magicalDamage = damage + level; magicalHealth -= magicalDamage; // Check if shield breaks if (magicalHealth <= 0) { shieldBreak = true; magicalHealth = 0; Debug.Log("Shield Broken!"); } } // Apply damage to health health -= finalDamage; // Check for death if (health <= 0) { StartCoroutine(couroutineDeath()); foreach (playerNetwork player in FindObjectsOfType()) { if (player.netId == id) { player.OnEnemyKilled(level); } } } Debug.Log($"Enemy Takes Damage: {finalDamage} | Shield Broken: {shieldBreak} | Health: {health} | Magical Health: {magicalHealth}"); } [Command(requiresAuthority = false)] void CmdTakeMagicalDamage(int damage, uint id) { takeMagicalDmg(damage, id); Debug.Log("Enemy Magical Attack Recieved "); } public void TakeMagicalDamage(int damage, uint id) { if (isServer) { takeMagicalDmg(damage, id); } else { CmdTakeMagicalDamage(damage, id); } } // MODIFIED: Updated magical damage to use same system void takeMagicalDmg(int damage, uint id) { if (magicalHealth <= 0 && shieldBreak) { return; } int finalDamage = damage; // If shield is not broken, reduce damage by half and damage magical health if (!shieldBreak && magicalHealth > 0) { finalDamage = damage / 2; // Calculate magical health damage based on player attack damage + enemy level int magicalDamage = damage + level; magicalHealth -= magicalDamage; // Check if shield breaks if (magicalHealth <= 0) { shieldBreak = true; magicalHealth = 0; Debug.Log("Shield Broken!"); } } // Apply damage to health health -= finalDamage; // Check for death if (health <= 0) { StartCoroutine(couroutineDeath()); foreach (playerNetwork player in FindObjectsOfType()) { if (player.netId == id) { player.OnEnemyKilled(level); } } } Debug.Log($"Enemy Takes Magical Damage: {finalDamage} | Shield Broken: {shieldBreak} | Health: {health} | Magical Health: {magicalHealth}"); } IEnumerator couroutineDeath() { animationString = "Death"; StartCoroutine(PopDisappearUI()); UpdateAnimation(directionString, animationString); // RpcUpdateAnim(directionString, animationString,false); Vector3 lootSpawnPos = transform.position; lootSpawnPos.z = GameManager.instance.LootSpawnPointsParent.GetChild(0).position.z; //instantiate loot item GameObject newLoot = Instantiate(GameManager.instance.GetRandomLoot(), lootSpawnPos, Quaternion.identity); NetworkServer.Spawn(newLoot); yield return new WaitForSecondsRealtime(7);// dead corpse delay if (!isServer) { CmdDie(); } else { GameManager.OnEnemyDeath(this, defaultPos); } } [Command] void CmdDie() { GameManager.OnEnemyDeath(this, defaultPos); } public void OnHealthChange(int oldVlaue, int newValue) { healthBar.SetHealth(newValue, maxHealth); } public void OnMagicalHealthChange(int oldVlaue, int newValue) { MagicalhealthBar.SetHealth(newValue, maxHealth); } //etc for ui Disspear coroutine IEnumerator PopDisappearUI() { Vector3 originalScale = uiEnemy.localScale; // First, scale up slightly float popDuration = 0.15f; float elapsedTime = 0f; Vector3 popScale = originalScale * 1.2f; while (elapsedTime < popDuration) { float t = elapsedTime / popDuration; uiEnemy.localScale = Vector3.Lerp(originalScale, popScale, t); elapsedTime += Time.deltaTime; yield return null; } // Then scale down to zero quickly float shrinkDuration = 0.3f; elapsedTime = 0f; while (elapsedTime < shrinkDuration) { float t = elapsedTime / shrinkDuration; // Use ease-in curve for faster shrinking float easedT = t * t; uiEnemy.localScale = Vector3.Lerp(popScale, Vector3.zero, easedT); elapsedTime += Time.deltaTime; yield return null; } uiEnemy.localScale = Vector3.zero; uiEnemy.gameObject.SetActive(false); } }