738 lines
18 KiB
C#
738 lines
18 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
public class SimpleTeamAI : MonoBehaviour
|
|
{
|
|
public enum TeamType
|
|
{
|
|
PlayerTeam,
|
|
EnemyTeam
|
|
}
|
|
|
|
public enum AIState
|
|
{
|
|
Idle,
|
|
Combat,
|
|
Collect,
|
|
Deposit,
|
|
Vault,
|
|
Hit,
|
|
Patrol
|
|
}
|
|
|
|
[Header("Team Settings")]
|
|
[SerializeField] private TeamType teamType;
|
|
|
|
[SerializeField] private float roamRadius = 8f;
|
|
[SerializeField] private float roamChangeTime = 3f;
|
|
|
|
private float nextRoamTime;
|
|
|
|
[Header("Combat Settings")]
|
|
[SerializeField] private float attackRange = 2f;
|
|
[SerializeField] private float attackCooldown = 1.2f;
|
|
[SerializeField] private float targetPriorityRange = 100f;
|
|
private float nextCombatSearch;
|
|
[SerializeField] private float combatSearchInterval = 0.3f;
|
|
|
|
[Header("Collection Settings")]
|
|
[SerializeField] private float scanRadius = 15f;
|
|
[SerializeField] private int depositCashAmount = 500;
|
|
|
|
[Header("Debug")]
|
|
public AIState currentState;
|
|
public Transform currentTarget;
|
|
[SerializeField] private Transform combatTarget;
|
|
|
|
private NavMeshAgent agent;
|
|
private Animator anim;
|
|
private CashManager cashManager;
|
|
private Health health;
|
|
private TeamMember teamMember;
|
|
|
|
private bool isAttacking;
|
|
private bool isHit;
|
|
private bool hasCash;
|
|
private bool isBusy;
|
|
|
|
private Coroutine attackRoutine;
|
|
|
|
private string enemyTag;
|
|
private string depositStationTag;
|
|
|
|
private Vault openingVault;
|
|
|
|
private Coroutine depositRoutine;
|
|
private bool isDepositing;
|
|
|
|
[SerializeField] private ParticleSystem depositAura;
|
|
|
|
void Awake()
|
|
{
|
|
agent = GetComponent<NavMeshAgent>();
|
|
anim = GetComponent<Animator>();
|
|
cashManager = GetComponent<CashManager>();
|
|
health = GetComponent<Health>();
|
|
teamMember = GetComponent<TeamMember>();
|
|
|
|
StopDepositEffect();
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
SetupTeam();
|
|
|
|
if (agent != null)
|
|
{
|
|
agent.enabled = true;
|
|
agent.isStopped = false;
|
|
agent.updatePosition = true;
|
|
//agent.updateRotation = false;
|
|
|
|
if (!agent.isOnNavMesh)
|
|
{
|
|
NavMeshHit hit;
|
|
|
|
if (NavMesh.SamplePosition(transform.position, out hit, 20f, NavMesh.AllAreas))
|
|
{
|
|
agent.Warp(hit.position);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"{gameObject.name} is not on NavMesh.");
|
|
}
|
|
}
|
|
}
|
|
|
|
currentState = AIState.Idle;
|
|
}
|
|
|
|
void SetupTeam()
|
|
{
|
|
// Auto-detect team from TeamMember first
|
|
if (teamMember != null)
|
|
{
|
|
if (teamMember.CurrentTeam == TeamMember.Team.PlayerTeam)
|
|
{
|
|
teamType = TeamType.PlayerTeam;
|
|
}
|
|
else
|
|
{
|
|
teamType = TeamType.EnemyTeam;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fallback: auto-detect from tag if TeamMember is missing
|
|
if (CompareTag("Player"))
|
|
{
|
|
teamType = TeamType.PlayerTeam;
|
|
}
|
|
else if (CompareTag("Enemy"))
|
|
{
|
|
teamType = TeamType.EnemyTeam;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"{gameObject.name}: No TeamMember and no Player/Enemy tag found. Using Inspector teamType.");
|
|
}
|
|
}
|
|
|
|
if (teamType == TeamType.PlayerTeam)
|
|
{
|
|
enemyTag = "Enemy";
|
|
depositStationTag = "PlayerDepositSt";
|
|
}
|
|
else
|
|
{
|
|
enemyTag = "Player";
|
|
depositStationTag = "EnemyDepositSt";
|
|
}
|
|
|
|
Debug.Log($"{gameObject.name}: SimpleTeamAI auto-detected team as {teamType}. EnemyTag={enemyTag}, DepositTag={depositStationTag}");
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (agent == null || anim == null)
|
|
return;
|
|
|
|
if (!agent.enabled || !agent.isOnNavMesh)
|
|
return;
|
|
|
|
if (health != null && health.isDead)
|
|
{
|
|
DisableMovement();
|
|
return;
|
|
}
|
|
|
|
if (agent.speed == 0)
|
|
agent.speed = 6f;
|
|
|
|
if (isHit)
|
|
{
|
|
SetState(AIState.Hit);
|
|
}
|
|
if (isBusy)
|
|
{
|
|
anim.SetFloat("Speed", 0f);
|
|
return;
|
|
}
|
|
//else if (isBusy)
|
|
//{
|
|
// // Keep current state while opening vault / hit / special action
|
|
//}
|
|
else
|
|
{
|
|
//cashManager = GetComponent<CashManager>();
|
|
|
|
if (cashManager != null && cashManager.currentCash >= depositCashAmount)
|
|
{
|
|
SetState(AIState.Deposit);
|
|
}
|
|
else
|
|
{
|
|
if (Time.time >= nextCombatSearch)
|
|
{
|
|
nextCombatSearch = Time.time + combatSearchInterval;
|
|
combatTarget = FindNearestCombatTarget();
|
|
}
|
|
|
|
if (combatTarget != null)
|
|
{
|
|
float targetDist = Vector3.Distance(transform.position, combatTarget.position);
|
|
|
|
if (targetDist <= targetPriorityRange)
|
|
{
|
|
SetState(AIState.Combat);
|
|
}
|
|
else
|
|
{
|
|
SetState(AIState.Collect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetState(AIState.Collect);
|
|
}
|
|
}
|
|
}
|
|
|
|
HandleState();
|
|
|
|
anim.SetFloat("Speed", agent.velocity.magnitude);
|
|
}
|
|
|
|
void SetState(AIState newState)
|
|
{
|
|
currentState = newState;
|
|
}
|
|
|
|
void HandleState()
|
|
{
|
|
switch (currentState)
|
|
{
|
|
case AIState.Combat:
|
|
HandleCombat();
|
|
break;
|
|
|
|
case AIState.Collect:
|
|
HandleCollection();
|
|
break;
|
|
|
|
case AIState.Deposit:
|
|
HandleDeposit();
|
|
break;
|
|
|
|
case AIState.Hit:
|
|
DisableMovement();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void HandleCombat()
|
|
{
|
|
if (combatTarget == null)
|
|
{
|
|
combatTarget = FindNearestCombatTarget();
|
|
|
|
if (combatTarget == null)
|
|
{
|
|
SetState(AIState.Collect);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Health targetHealth = combatTarget.GetComponent<Health>();
|
|
|
|
if (targetHealth != null && targetHealth.isDead)
|
|
{
|
|
combatTarget = null;
|
|
SetState(AIState.Collect);
|
|
return;
|
|
}
|
|
|
|
float dist = Vector3.Distance(transform.position, combatTarget.position);
|
|
|
|
Vector3 dir = combatTarget.position - transform.position;
|
|
dir.y = 0;
|
|
|
|
if (dir.sqrMagnitude > 0.01f)
|
|
{
|
|
transform.rotation = Quaternion.Slerp(
|
|
transform.rotation,
|
|
Quaternion.LookRotation(dir),
|
|
Time.deltaTime * 10f
|
|
);
|
|
}
|
|
|
|
if (!isAttacking)
|
|
{
|
|
if (dist > attackRange)
|
|
{
|
|
agent.isStopped = false;
|
|
agent.SetDestination(combatTarget.position);
|
|
}
|
|
else
|
|
{
|
|
agent.isStopped = true;
|
|
agent.velocity = Vector3.zero;
|
|
|
|
if (attackRoutine == null)
|
|
attackRoutine = StartCoroutine(AttackRoutine());
|
|
}
|
|
}
|
|
}
|
|
|
|
void HandleCollection()
|
|
{
|
|
if (currentTarget == null)
|
|
{
|
|
currentTarget = FindVaultTarget();
|
|
|
|
if (currentTarget != null)
|
|
{
|
|
Vault vault = currentTarget.GetComponent<Vault>();
|
|
|
|
if (vault == null || !vault.TryReserve(this))
|
|
{
|
|
currentTarget = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentTarget == null)
|
|
{
|
|
RoamAround();
|
|
return;
|
|
}
|
|
|
|
Vault currentVault = currentTarget.GetComponent<Vault>();
|
|
|
|
if (currentVault == null || !currentVault.IsReservedBy(this))
|
|
{
|
|
currentTarget = null;
|
|
return;
|
|
}
|
|
|
|
agent.isStopped = false;
|
|
agent.SetDestination(currentTarget.position);
|
|
|
|
float dist = Vector3.Distance(transform.position, currentTarget.position);
|
|
|
|
if (!isBusy && currentTarget.CompareTag("Vault") && dist <= 1.5f)
|
|
{
|
|
SetState(AIState.Vault);
|
|
StartCoroutine(VaultRoutine());
|
|
}
|
|
}
|
|
|
|
void RoamAround()
|
|
{
|
|
if (Time.time < nextRoamTime && agent.hasPath)
|
|
return;
|
|
|
|
Vector3 randomPos = transform.position + Random.insideUnitSphere * roamRadius;
|
|
randomPos.y = transform.position.y;
|
|
|
|
NavMeshHit hit;
|
|
|
|
if (NavMesh.SamplePosition(randomPos, out hit, roamRadius, NavMesh.AllAreas))
|
|
{
|
|
agent.isStopped = false;
|
|
agent.SetDestination(hit.position);
|
|
nextRoamTime = Time.time + roamChangeTime;
|
|
}
|
|
}
|
|
|
|
Transform FindVaultTarget()
|
|
{
|
|
Collider[] hits = Physics.OverlapSphere(transform.position, scanRadius);
|
|
|
|
Transform best = null;
|
|
float minDist = Mathf.Infinity;
|
|
|
|
foreach (Collider hit in hits)
|
|
{
|
|
if (hit.CompareTag("Vault"))
|
|
{
|
|
Vault vault = hit.GetComponent<Vault>();
|
|
|
|
if (vault == null)
|
|
continue;
|
|
|
|
if (vault.IsReserved())
|
|
continue;
|
|
|
|
float d = Vector3.Distance(transform.position, hit.transform.position);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
best = hit.transform;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
Transform FindNearestCombatTarget()
|
|
{
|
|
if (teamMember == null)
|
|
return null;
|
|
|
|
Transform best = null;
|
|
float minDist = Mathf.Infinity;
|
|
|
|
foreach (TeamMember other in TeamMember.AllMembers)
|
|
{
|
|
if (other == null)
|
|
continue;
|
|
|
|
if (other == teamMember)
|
|
continue;
|
|
|
|
if (!teamMember.IsEnemy(other))
|
|
continue;
|
|
|
|
Health targetHealth = other.GetComponent<Health>();
|
|
|
|
if (targetHealth != null && targetHealth.isDead)
|
|
continue;
|
|
|
|
float d = Vector3.Distance(
|
|
transform.position,
|
|
other.transform.position);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
best = other.transform;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
IEnumerator VaultRoutine()
|
|
{
|
|
if (isBusy) yield break;
|
|
|
|
if (currentTarget == null)
|
|
yield break;
|
|
|
|
Vault vault = currentTarget.GetComponent<Vault>();
|
|
openingVault = vault;
|
|
|
|
if (vault.IsPlayerInside())
|
|
{
|
|
vault.Release(this);
|
|
|
|
currentTarget = null;
|
|
openingVault = null;
|
|
|
|
yield break;
|
|
}
|
|
|
|
if (vault == null || !vault.IsReservedBy(this))
|
|
{
|
|
currentTarget = null;
|
|
yield break;
|
|
}
|
|
|
|
isBusy = true;
|
|
vault.MarkBeingOpened(this);
|
|
|
|
agent.isStopped = true;
|
|
agent.velocity = Vector3.zero;
|
|
|
|
if (vault != null && vault.InteractionPoint != null)
|
|
{
|
|
transform.position = vault.InteractionPoint.position;
|
|
agent.Warp(transform.position);
|
|
|
|
Vector3 lookDir = vault.transform.position - transform.position;
|
|
lookDir.y = 0;
|
|
|
|
if (lookDir.sqrMagnitude > 0.01f)
|
|
transform.rotation = Quaternion.LookRotation(lookDir);
|
|
}
|
|
|
|
anim.SetTrigger("OpenVault");
|
|
|
|
yield return new WaitForSeconds(6f);
|
|
|
|
//cashManager = GetComponent<CashManager>();
|
|
|
|
//if (cashManager != null && vault != null)
|
|
//{
|
|
// cashManager.AddCash(vault.GetVaultCash());
|
|
//}
|
|
|
|
//if (currentTarget != null)
|
|
//{
|
|
// Destroy(currentTarget.gameObject);
|
|
//}
|
|
|
|
hasCash = true;
|
|
currentTarget = null;
|
|
|
|
agent.isStopped = false;
|
|
isBusy = false;
|
|
}
|
|
|
|
public void AddVaultCashAndDestroyVault()
|
|
{
|
|
if (openingVault == null)
|
|
return;
|
|
|
|
if (isHit || currentState == AIState.Hit)
|
|
return;
|
|
|
|
if (health != null && health.isDead)
|
|
return;
|
|
|
|
if (cashManager == null)
|
|
cashManager = GetComponent<CashManager>();
|
|
|
|
if (cashManager != null)
|
|
cashManager.AddCash(openingVault.GetVaultCash());
|
|
|
|
VaultSpawner.Instance?.NotifyVaultDestroyed();
|
|
|
|
Destroy(openingVault.gameObject);
|
|
|
|
openingVault = null;
|
|
currentTarget = null;
|
|
hasCash = true;
|
|
isBusy = false;
|
|
|
|
if (agent != null && agent.enabled && agent.isOnNavMesh)
|
|
agent.isStopped = false;
|
|
|
|
SetState(AIState.Deposit);
|
|
}
|
|
|
|
void HandleDeposit()
|
|
{
|
|
GameObject deposit = GameObject.FindGameObjectWithTag(depositStationTag);
|
|
|
|
if (deposit == null)
|
|
{
|
|
Debug.LogWarning($"{gameObject.name}: Deposit station not found with tag {depositStationTag}");
|
|
return;
|
|
}
|
|
|
|
Transform point = deposit.transform.Find("DepositPoint");
|
|
|
|
if (point == null)
|
|
{
|
|
Debug.LogWarning($"{gameObject.name}: DepositPoint child missing under {depositStationTag}");
|
|
return;
|
|
}
|
|
|
|
currentTarget = point;
|
|
|
|
agent.isStopped = false;
|
|
agent.SetDestination(point.position);
|
|
}
|
|
|
|
IEnumerator AttackRoutine()
|
|
{
|
|
if (isAttacking || isHit) yield break;
|
|
|
|
if (combatTarget == null)
|
|
{
|
|
attackRoutine = null;
|
|
yield break;
|
|
}
|
|
|
|
float dist = Vector3.Distance(transform.position, combatTarget.position);
|
|
|
|
if (dist > attackRange)
|
|
{
|
|
attackRoutine = null;
|
|
yield break;
|
|
}
|
|
|
|
isAttacking = true;
|
|
agent.isStopped = true;
|
|
|
|
int attackIndex = Random.Range(0, 6);
|
|
|
|
switch (attackIndex)
|
|
{
|
|
case 0: anim.SetTrigger("Haymaker"); break;
|
|
case 1: anim.SetTrigger("LeftKick"); break;
|
|
case 2: anim.SetTrigger("LowKick"); break;
|
|
case 3: anim.SetTrigger("BicycleKick"); break;
|
|
case 4: anim.SetTrigger("SpinningBackFist"); break;
|
|
case 5: anim.SetTrigger("ElbowSmash"); break;
|
|
}
|
|
|
|
yield return new WaitForSeconds(1f);
|
|
|
|
isAttacking = false;
|
|
//agent.isStopped = false;
|
|
|
|
yield return new WaitForSeconds(attackCooldown);
|
|
|
|
attackRoutine = null;
|
|
}
|
|
|
|
public void CancelAttackAndHit(string bodyPart = "Body")
|
|
{
|
|
isHit = true;
|
|
SetState(AIState.Hit);
|
|
|
|
if (attackRoutine != null)
|
|
{
|
|
StopCoroutine(attackRoutine);
|
|
attackRoutine = null;
|
|
}
|
|
|
|
isAttacking = false;
|
|
|
|
DisableMovement();
|
|
|
|
anim.ResetTrigger("Haymaker");
|
|
anim.ResetTrigger("LeftKick");
|
|
anim.ResetTrigger("LowKick");
|
|
anim.ResetTrigger("BicycleKick");
|
|
anim.ResetTrigger("SpinningBackFist");
|
|
anim.ResetTrigger("ElbowSmash");
|
|
|
|
CharacterCombat combat = GetComponent<CharacterCombat>();
|
|
combat?.OnDamageTaken(bodyPart);
|
|
|
|
StartCoroutine(RecoverFromHit());
|
|
}
|
|
|
|
IEnumerator RecoverFromHit()
|
|
{
|
|
yield return new WaitForSeconds(0.5f);
|
|
|
|
isHit = false;
|
|
SetState(AIState.Idle);
|
|
|
|
EnableMovement();
|
|
}
|
|
|
|
public void DisableMovement()
|
|
{
|
|
if (agent != null && agent.enabled && agent.isOnNavMesh)
|
|
{
|
|
agent.isStopped = true;
|
|
agent.ResetPath();
|
|
agent.velocity = Vector3.zero;
|
|
}
|
|
}
|
|
|
|
public void EnableMovement()
|
|
{
|
|
if (agent != null && agent.enabled && agent.isOnNavMesh)
|
|
{
|
|
agent.speed = 6f;
|
|
agent.isStopped = false;
|
|
}
|
|
}
|
|
|
|
public void OnHitStart()
|
|
{
|
|
isHit = true;
|
|
SetState(AIState.Hit);
|
|
DisableMovement();
|
|
}
|
|
|
|
public void OnHitEnd()
|
|
{
|
|
isHit = false;
|
|
SetState(AIState.Idle);
|
|
EnableMovement();
|
|
}
|
|
|
|
public void ResetAfterDeposit()
|
|
{
|
|
hasCash = false;
|
|
currentTarget = null;
|
|
SetState(AIState.Idle);
|
|
}
|
|
|
|
public void StartDeposit(float depositTime)
|
|
{
|
|
if (isDepositing)
|
|
return;
|
|
|
|
depositRoutine =
|
|
StartCoroutine(DepositRoutine(depositTime));
|
|
}
|
|
|
|
IEnumerator DepositRoutine(float depositTime)
|
|
{
|
|
isDepositing = true;
|
|
isBusy = true;
|
|
|
|
agent.isStopped = true;
|
|
agent.velocity = Vector3.zero;
|
|
|
|
//anim.SetTrigger("Deposit");
|
|
|
|
yield return new WaitForSeconds(depositTime);
|
|
|
|
CashManager cash =
|
|
GetComponent<CashManager>();
|
|
|
|
if (cash != null)
|
|
{
|
|
int depositedAmount =
|
|
cash.DepositAllCash();
|
|
|
|
DepositStationManager.Instance.AddCash(
|
|
depositedAmount,
|
|
teamMember.CurrentTeam
|
|
);
|
|
}
|
|
|
|
StopDepositEffect();
|
|
|
|
isDepositing = false;
|
|
isBusy = false;
|
|
|
|
ResetAfterDeposit();
|
|
}
|
|
|
|
public void StartDepositEffect()
|
|
{
|
|
if (depositAura != null)
|
|
depositAura.Play();
|
|
}
|
|
|
|
public void StopDepositEffect()
|
|
{
|
|
if (depositAura != null)
|
|
depositAura.Stop();
|
|
}
|
|
} |