111 lines
3.1 KiB
C#
111 lines
3.1 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using Quests;
|
|
|
|
[RequireComponent(typeof(CharacterStats))]
|
|
public class CombatController : MonoBehaviour
|
|
{
|
|
[Header("Combat Feedback Prefabs (Optional)")]
|
|
[SerializeField] private GameObject attackEffectPrefab;
|
|
[SerializeField] private GameObject hitEffectPrefab;
|
|
|
|
public CharacterStats Stats { get; private set; }
|
|
public CharacterStats CurrentTarget { get; private set; }
|
|
|
|
public event Action<CharacterStats> OnAttackExecuted;
|
|
|
|
private float lastAttackTime = 0f;
|
|
|
|
private void Awake()
|
|
{
|
|
Stats = GetComponent<CharacterStats>();
|
|
Stats.onDeath.AddListener(OnSelfDeath);
|
|
}
|
|
|
|
public void SetTarget(CharacterStats target)
|
|
{
|
|
if (target == this.Stats || (target != null && target.IsDead))
|
|
{
|
|
ClearTarget();
|
|
return;
|
|
}
|
|
|
|
CurrentTarget = target;
|
|
}
|
|
|
|
public void ClearTarget()
|
|
{
|
|
CurrentTarget = null;
|
|
}
|
|
|
|
public bool IsInAttackRange(CharacterStats target)
|
|
{
|
|
if (target == null) return false;
|
|
|
|
Vector3 flatSelf = new Vector3(transform.position.x, 0, transform.position.z);
|
|
Vector3 flatTarget = new Vector3(target.transform.position.x, 0, target.transform.position.z);
|
|
|
|
float distance = Vector3.Distance(flatSelf, flatTarget);
|
|
return distance <= Stats.AttackRange;
|
|
}
|
|
|
|
public bool CanAttack()
|
|
{
|
|
if (Stats.IsDead || CurrentTarget == null || CurrentTarget.IsDead) return false;
|
|
if (!IsInAttackRange(CurrentTarget)) return false;
|
|
|
|
float attackCooldown = 1.0f / Mathf.Max(Stats.AttackSpeed, 0.1f);
|
|
return Time.time >= lastAttackTime + attackCooldown;
|
|
}
|
|
|
|
public bool TryExecuteAttack()
|
|
{
|
|
if (!CanAttack()) return false;
|
|
|
|
lastAttackTime = Time.time;
|
|
|
|
// Face target
|
|
Vector3 dir = (CurrentTarget.transform.position - transform.position);
|
|
dir.y = 0;
|
|
if (dir != Vector3.zero)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(dir);
|
|
}
|
|
|
|
// Deal Damage
|
|
float damage = Stats.BaseDamage;
|
|
CurrentTarget.TakeDamage(damage, gameObject);
|
|
|
|
// Visual effects
|
|
if (attackEffectPrefab != null)
|
|
{
|
|
Instantiate(attackEffectPrefab, transform.position + transform.forward * 0.5f + Vector3.up, transform.rotation);
|
|
}
|
|
if (hitEffectPrefab != null && CurrentTarget != null)
|
|
{
|
|
Instantiate(hitEffectPrefab, CurrentTarget.transform.position + Vector3.up, Quaternion.identity);
|
|
}
|
|
|
|
OnAttackExecuted?.Invoke(CurrentTarget);
|
|
|
|
// If target died from this attack
|
|
if (CurrentTarget.IsDead)
|
|
{
|
|
// Report kill to Quest System if attacker is player
|
|
if (gameObject.CompareTag("Player") && QuestManager.Instance != null)
|
|
{
|
|
QuestManager.Instance.ReportKill(CurrentTarget.CharacterName);
|
|
}
|
|
|
|
ClearTarget();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnSelfDeath(GameObject killer)
|
|
{
|
|
ClearTarget();
|
|
}
|
|
}
|