using UnityEngine; using System.Collections; using DamageNumbersPro; using UnityEditor.Embree; public class MonsterController : MonoBehaviour { public MonsterData data; public MonsterFormationGroup formationGroup; private MonsterAnimatorController animator; private Vector3 currentTarget; public int currentHP; private float attackTimer = 0f; // Start at 0 so first attack is ready after cooldown public float rotationSpeed = 8f; // Higher = smoother rotation public DamageNumber damagePopupPrefab; public GameObject goldBagPrefab; public float goldSpawnDelay = 0.5f; private void Start() { animator = GetComponent(); currentHP = data.maxHP; // Start with attack ready (set timer to cooldown value) if (data != null) { attackTimer = data.attackCooldown; } FindFirstObjectByType()?.RegisterMonster(this); } public void ShowDamageNumber(float amount) { if (damagePopupPrefab == null) return; // Position du popup : au-dessus du monstre Vector3 popupPosition = transform.position + Vector3.up * 2f; // Spawn world-space popup (si prefab DamageNumberMesh) DamageNumber newPopup = damagePopupPrefab.Spawn(popupPosition, amount); } public void MoveTo(Vector3 newPosition) { currentTarget = newPosition; StopAllCoroutines(); StartCoroutine(MoveStep()); } IEnumerator MoveStep() { animator?.PlayMove(); while (Vector3.Distance(transform.position, currentTarget) > 0.05f) { transform.position = Vector3.MoveTowards(transform.position, currentTarget, data.moveSpeed * Time.deltaTime); yield return null; } animator?.StopMove(); } public void FaceTarget(Vector3 target, bool instant = false) { Vector3 direction = (target - transform.position).normalized; direction.y = 0; // rester sur le plan horizontal if (direction != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(direction); if (instant) { // Instant rotation for combat transform.rotation = targetRotation; } else { // Smooth rotation for movement transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed); } } } public bool IsFacingTarget(Vector3 target, float angleThreshold = 30f) { Vector3 directionToTarget = (target - transform.position).normalized; directionToTarget.y = 0; if (directionToTarget == Vector3.zero) return true; float angle = Vector3.Angle(transform.forward, directionToTarget); return angle <= angleThreshold; } public void StopMove() { animator?.StopMove(); } public void Attack() { animator?.PlayAttack(); } public void UpdateAttackTimer(float deltaTime) { attackTimer += deltaTime; } public bool CanAttack() { return attackTimer >= data.attackCooldown; } public void ResetAttackTimer() { attackTimer = 0f; } public int GetDamage() { // Échec d'attaque ? if (Random.value < data.missChance) { Debug.Log($"{gameObject.name} a raté son attaque !"); return 0; } float min = data.attackDamage / 2f; float max = data.attackDamage * 1.5f; int damage = Mathf.RoundToInt(Random.Range(min, max)); return damage; } public void TakeDamage(int amount) { currentHP -= amount; ShowDamageNumber(amount); if (currentHP <= 0) { Debug.Log("Monstre tué"); Die(); // uniquement animation de mort } else { animator?.PlayHurt(); // seulement s'il reste en vie } } public void Die() { Debug.Log("Méthode Die Appellée"); animator?.PlayDeath(); Debug.Log("Check Notify MonsterGroup 1"); // Grant experience to all party members GrantExperienceToParty(); // 50% chance to drop gold bag DropGoldBag(); //GetComponent().enabled = false; formationGroup?.NotifyMonsterDeath(this); Debug.Log("Check Notify MonsterGroup 2"); Destroy(gameObject, 2f); } private void DropGoldBag() { // 50% chance to drop gold if (Random.value < 0.5f) { if (goldBagPrefab != null) { StartCoroutine(SpawnGoldWithDelay()); } else { Debug.LogWarning($"[Loot] {gameObject.name} should drop gold but goldBagPrefab is not assigned!"); } } else { Debug.Log($"[Loot] {gameObject.name} didn't drop gold (50% chance)"); } } private IEnumerator SpawnGoldWithDelay() { Vector3 dropPosition = transform.position + Vector3.up * 0.5f; yield return new WaitForSeconds(goldSpawnDelay); Quaternion goldRotation = Quaternion.Euler(-90f, 0f, 0f); GameObject goldBag = Instantiate(goldBagPrefab, dropPosition, goldRotation); Debug.Log($"[Loot] {gameObject.name} dropped a gold bag!"); } private void GrantExperienceToParty() { TeamCohesionManager cohesionManager = FindFirstObjectByType(); if (cohesionManager == null || data == null) return; int expPerMember = Mathf.RoundToInt(data.exp); Debug.Log($"[Experience] Distributing {expPerMember} XP to each party member"); foreach (var character in cohesionManager.groupMembers) { character.experience += expPerMember; Debug.Log($"[Experience] {character.characterName} gained {expPerMember} XP (Total: {character.experience})"); // Update the experience bar UIUpdater.Instance?.UpdateCharacterExperience(character); // Check for level up CheckLevelUp(character); } } private void CheckLevelUp(CharacterInGroup character) { while (character.experience >= 100) { character.experience -= 100; character.level++; // Increase max HP and stamina on level up int hpIncrease = 5 + character.constitution; // Base 5 + constitution bonus int staminaIncrease = 5 + (character.constitution / 2); // Base 5 + half constitution character.maxHP += hpIncrease; character.currentHP += hpIncrease; // Also heal by the increase amount character.maxFatigue += staminaIncrease; character.currentFatigue += staminaIncrease; // Also restore by the increase amount Debug.Log($"[Level Up] {character.characterName} reached level {character.level}! HP +{hpIncrease}, Stamina +{staminaIncrease}"); // Update all UI bars UIUpdater.Instance?.UpdateCharacterHP(character); UIUpdater.Instance?.UpdateCharacterFatigue(character); UIUpdater.Instance?.UpdateCharacterExperience(character); } } public void Setup(MonsterGroupManager mgr) {} }