mmorpg2d/Assets/Script/enemyScript.cs
2025-07-09 10:31:02 +05:30

590 lines
16 KiB
C#

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<MeshRenderer>();
scanCooldown = Random.Range(0.5f, 1.5f);
}
private void Start()
{
rb2 = GetComponent<Rigidbody2D>();
//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<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
}
}
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<playerNetwork>())
{
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<playerNetwork>())
{
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);
}
}