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; public float rotationSpeed = 40f; public DamageNumber damagePopupPrefab; public GameObject goldBagPrefab; public Transform goldSpawnPoint; public float goldDropYOffset = 0.15f; // Y offset for gold drop position 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); // Make damage number face the camera without mirroring if (newPopup != null) { Transform cameraTransform = Camera.main?.transform; if (cameraTransform != null) { // Copy camera's rotation (billboard effect) but keep it upright Vector3 cameraEuler = cameraTransform.eulerAngles; newPopup.transform.rotation = Quaternion.Euler(0f, cameraEuler.y, 0f); } } } 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"); // Disable collider immediately to prevent gold bag clipping Collider collider = GetComponent(); if (collider != null) { collider.enabled = false; } // Grant experience to all party members GrantExperienceToParty(); // Drop gold bag (50% chance) StartCoroutine(CoroutineDropGoldBag()); formationGroup?.NotifyMonsterDeath(this); Debug.Log("Check Notify MonsterGroup 2"); Destroy(gameObject, 2f); } private IEnumerator CoroutineDropGoldBag() { // 50% chance to drop gold if (Random.value < 0.5f) { if (goldBagPrefab != null) { Vector3 dropPosition; if (goldSpawnPoint != null) { dropPosition = goldSpawnPoint.position + Vector3.up * goldDropYOffset; } else { dropPosition = transform.position + Vector3.up * goldDropYOffset; } //yield return new WaitForSeconds(1f); GameObject goldBag = Instantiate(goldBagPrefab, dropPosition, Quaternion.Euler(-90f, 0f, 0f)); Debug.Log($"[Loot] {gameObject.name} dropped gold at {dropPosition}"); } } else { Debug.Log($"[Loot] {gameObject.name} didn't drop gold (50% chance)"); } yield return null; } private void GrantExperienceToParty() { TeamCohesionManager cohesionManager = FindFirstObjectByType(); if (cohesionManager == null || data == null) return; int expPerMember = Mathf.RoundToInt(data.exp); foreach (var character in cohesionManager.groupMembers) { character.experience += expPerMember; // 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) { } }