123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- using System.Collections;
- using UnityEngine;
- using Spine.Unity;
- using Spine.Unity.Examples;
- using Mirror;
- using DG.Tweening;
- public class enemyScript : NetworkBehaviour
- {
- // Health and Damage Constants
- public const int HEALTH_INC = 2;
- public const float DAMAGE_INC = 1.2f;
-
- // XP System Constants
- public const float XP_GAIN = 1.5f; // Legacy
- public const int XP_GAIN_Base = 5; // Legacy
- public const float XP_EXPONENTIAL_BASE = 1.3f;
- public const float XP_LEVEL_MULTIPLIER = 8f;
- public const float XP_PLAYER_LEVEL_BONUS = 0.05f;
-
- // Resistance and Shield Constants
- public const int RESISTANCE_INC = 1;
- public const int MAX_RESISTANCE = 15;
- public const float SHIELD_DAMAGE_DIVIDER = 2f;
-
- [Header("Health & Shield System")]
- [SyncVar(hook = nameof(OnHealthChange))]
- public int health;
- [SyncVar(hook = nameof(OnMagicalHealthChange))]
- public int magicalHealth;
- [SyncVar]
- public int physicalResistance;
- [SyncVar]
- public int magicalResistance;
- [SyncVar]
- public bool shieldActive = true;
-
- [Header("UI Components")]
- public SpriteHealthBar healthBar;
- public SpriteHealthBar MagicalhealthBar;
- public Transform uiEnemy;
- public TextMesh enemyName;
- public TextMesh enemyLevel;
-
- [Header("Movement & Combat")]
- public float speed;
- public float chaseRadius;
- public float attackRadius;
- public bool rotate;
- public int enemyAttackDamage = 10;
- public float damageTimingPercent = 0.6f;
-
- [Header("Targeting & State")]
- public playerNetwork target;
- public bool isInChaseRange;
- public bool isInAttackRange;
-
- [Header("Components")]
- private Rigidbody2D rb2;
- public SkeletonAnimation animator;
- private Vector2 movement;
- public Vector3 dir;
- MeshRenderer meshRenderer;
- [SyncVar]
- public bool hasDealtDamage = false;
- void Awake(){
- meshRenderer = GetComponent<MeshRenderer>();
- scanCooldown = Random.Range(0.5f, 1.5f);
- }
- private void Start(){
- rb2 = GetComponent<Rigidbody2D>();
- 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);
-
- int resistanceIncrement = level * RESISTANCE_INC;
- physicalResistance = Mathf.Min(resistanceIncrement, MAX_RESISTANCE);
- magicalResistance = Mathf.Min(resistanceIncrement, MAX_RESISTANCE);
-
- shieldActive = true;
- }
- 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);
- if (health <= 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
- [Header("Scanning & LOD")]
- float scanTimer =0;
- float scanCooldown;
- public void ScanPlayers(){
-
- if(scanTimer >0){scanTimer-=Time.deltaTime; return;}
- scanTimer = scanCooldown;
- playerNetwork[] playersinNetwork = FindObjectsOfType<playerNetwork>();
- 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
- }
- }
- [Header("Animation System")]
- 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);
- }
- [Header("Attack Timing")]
- float attackTimer = 0f;
- float attackDuration = 1.4f;
- [SyncVar]
- public float maxHealth;
-
- #if UNITY_SERVER || UNITY_EDITOR
- [Server]
- private void FixedUpdate() {
- if (health <= 0)
- {
- return;
- }
- healthBar.SetHealth(health, maxHealth);
- MagicalhealthBar.SetHealth(magicalHealth, maxHealth);
- if (isInChaseRange && !isInAttackRange)
- {
- MoveEnemy(movement);
- //Set animation to moving
- animationString = "Walk";
- // Reset attack state when not in attack range
- hasDealtDamage = false;
- attackTimer = 0;
- }
- else if (isInAttackRange)
- {
- rb2.velocity = Vector2.zero;
-
- // MODIFIED: Only attack if enemy has stopped moving (velocity near zero)
- if (rb2.velocity.magnitude < 0.1f)
- {
- //Set animation to attack
- animationString = "Attack";
- if (attackTimer < attackDuration)
- {
- attackTimer += Time.deltaTime;
-
- // MODIFIED: Deal damage at specific timing in animation
- float attackProgress = attackTimer / attackDuration;
- if (!hasDealtDamage && attackProgress >= damageTimingPercent)
- {
- hasDealtDamage = true;
- Attack();
- }
- }
- else
- {
- // MODIFIED: Reset for next attack cycle
- attackTimer = 0;
- hasDealtDamage = false;
- }
- }
- }
- if (!isInAttackRange && !isInChaseRange)
- {
- //SetAnimation to idle
- animationString = "Idle";
- // Reset attack state when idle
- hasDealtDamage = false;
- attackTimer = 0;
- }
- }
- #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);
- }
- }
- void takedmg(int damage,uint id){
- if(health<=0){return;}
-
- // Apply physical resistance to base damage
- int damageAfterResistance = Mathf.Max(1, damage - physicalResistance);
-
- // If shield is active, damage goes to both magical health (shield) AND regular health
- if(shieldActive && magicalHealth > 0){
- // Shield takes full damage after resistance
- magicalHealth -= damageAfterResistance;
-
- // Regular health takes reduced damage (divided by shield divider)
- float shieldMultiplier = 1f / SHIELD_DAMAGE_DIVIDER;
- int healthDamage = Mathf.Max(1, Mathf.RoundToInt(damageAfterResistance * shieldMultiplier));
- health -= healthDamage;
-
- if(magicalHealth <= 0){
- shieldActive = false;
- PlayShieldBreakAnimation();
- }
- } else {
- // If shield is broken, damage goes directly to health with full resistance
- health -= damageAfterResistance;
- }
-
- //hit vfx
- // GameObject newObject = Instantiate(hitVfx , transform.position , Quaternion.identity );
- // newObject.transform.localPosition = Vector3.zero;
- // newObject.transform.parent = transform;
- if(health<= 0 ){
- StartCoroutine(couroutineDeath());
- foreach(playerNetwork player in FindObjectsOfType<playerNetwork>()){
- if(player.netId == id){
- //This one attacked me
- player.OnEnemyKilled(level);
- }
- }
- }
-
- // Debug logging removed for cleaner code
-
- }
- [Command(requiresAuthority =false)]
- void CmdTakeMagicalDamage(int damage,uint id){
- takeMagicalDmg(damage,id);
- Debug.Log("Enemy Attack Recieved ");
- }
- public void TakeMagicalDamage(int damage, uint id){
- if(isServer){
- takeMagicalDmg(damage,id);
- }
- else{
- CmdTakeMagicalDamage(damage,id);
- }
- }
- void takeMagicalDmg(int damage,uint id){
- if(health<=0){return;}
-
- // Apply magical resistance to base damage
- int damageAfterResistance = Mathf.Max(1, damage - magicalResistance);
-
- // If shield is active, damage goes to both magical health (shield) AND regular health
- if(shieldActive && magicalHealth > 0){
- // Shield takes full damage after resistance
- magicalHealth -= damageAfterResistance;
-
- // Regular health takes reduced damage (divided by shield divider)
- float shieldMultiplier = 1f / SHIELD_DAMAGE_DIVIDER;
- int healthDamage = Mathf.Max(1, Mathf.RoundToInt(damageAfterResistance * shieldMultiplier));
- health -= healthDamage;
-
- if(magicalHealth <= 0){
- shieldActive = false;
- PlayShieldBreakAnimation();
- }
- } else {
- // If shield is broken, damage goes directly to health with full resistance
- health -= damageAfterResistance;
- }
-
- if(health<= 0 ){
- StartCoroutine(couroutineDeath());
- foreach(playerNetwork player in FindObjectsOfType<playerNetwork>()){
- if(player.netId == id){
- //This one attacked me
- player.OnEnemyKilled(level);
- }
- }
- }
-
- // Debug logging removed for cleaner code
-
- }
- IEnumerator couroutineDeath(){
-
- animationString = "Death";
- 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(5);
- if (!isServer)
- {
- CmdDie();
- }
- else
- {
- GameManager.OnEnemyDeath(this, defaultPos);
- }
-
- /* transform.position = defaultPos;
- health = (int)maxHealth;
- magicalHealth = (int)maxHealth;*/
- //animationString = "Idle";
- }
- [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);
- }
-
- [Header("Shield Visual Effects")]
- public Transform shieldUI;
- public SpriteRenderer shieldIconUI;
- public ParticleSystem shieldBreakVfx;
- public void PlayShieldBreakAnimation()
- {
- if (shieldBreakVfx != null) shieldBreakVfx.Play();
- if (shieldUI != null)
- {
- shieldUI.DOScale(1.2f, 0.15f)
- .SetEase(Ease.OutBack)
- .OnComplete(() =>
- {
- shieldUI.DOScale(0f, 0.3f).SetEase(Ease.InQuad);
- if (shieldIconUI != null)
- {
- shieldIconUI.DOFade(0f, 0.3f).SetEase(Ease.InQuad)
- .OnComplete(() =>
- {
- shieldUI.gameObject.SetActive(false);
- });
- }
- });
- }
- }
-
- // Helper method to get resistance info for UI or debugging
- public string GetResistanceInfo()
- {
- string shieldStatus = shieldActive ? "Active" : "Broken";
- return $"Physical: {physicalResistance}, Magical: {magicalResistance}, Shield: {shieldStatus}";
- }
-
- public int CalculateEffectiveDamage(int baseDamage, bool isMagical = false)
- {
- int resistance = isMagical ? magicalResistance : physicalResistance;
-
- if(shieldActive && magicalHealth > 0){
- float shieldMultiplier = 1f / SHIELD_DAMAGE_DIVIDER;
- int damageAfterShield = Mathf.RoundToInt(baseDamage * shieldMultiplier);
- return Mathf.Max(1, damageAfterShield - resistance);
- } else {
- return Mathf.Max(1, baseDamage - resistance);
- }
- }
-
- public bool IsShieldActive()
- {
- return shieldActive && magicalHealth > 0;
- }
-
- public static int CalculateExponentialXP(int enemyLevel, int playerLevel = 0)
- {
- float baseXP = XP_LEVEL_MULTIPLIER * Mathf.Pow(XP_EXPONENTIAL_BASE, enemyLevel - 1);
-
- float levelDifference = Mathf.Max(0, enemyLevel - playerLevel);
- float bonusXP = baseXP * XP_PLAYER_LEVEL_BONUS * levelDifference;
-
- int totalXP = Mathf.RoundToInt(baseXP + bonusXP);
- totalXP = Mathf.Min(totalXP, 5000);
-
- return Mathf.Max(10, totalXP);
- }
-
- public static int CalculateLegacyXP(int enemyLevel)
- {
- return XP_GAIN_Base + Mathf.FloorToInt(XP_GAIN * (enemyLevel - 1));
- }
- }
|