Files
DeviantMobile-Rohan/Assets/Scripts/AI/Actions/AIActions.cs

472 lines
14 KiB
C#

using UnityEngine;
/// <summary>
/// DepositBagAction — Navigate to own vault and deposit carried bags.
/// Score increases with more bags carried and when vault is nearby.
/// </summary>
public class DepositBagAction : AIAction
{
public override string Name => "DepositBag";
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead || !bb.isCarrying) return 0f;
if (bb.myVault == null) return 0f;
float score = 0.4f; // Base desire to deposit
// More bags = stronger urge to deposit (risk increases)
score += bb.carriedBagCount * 0.2f;
// High value = deposit urgently
if (bb.totalCarriedValue >= 200f)
score += 0.15f;
// Enemies nearby while carrying = deposit NOW
if (bb.nearbyEnemyCount > 0 && bb.nearestEnemyDistance < 12f)
score += 0.25f;
// Low health while carrying = deposit ASAP
if (bb.healthPercent < 0.4f)
score += 0.2f;
// Close to vault = just do it
float vaultDist = Vector3.Distance(
bb.myVault.transform.position,
bb.myVault.transform.position // Will be overridden by brain position
);
// Bonus if losing (need to score to catch up)
if (bb.scoreDifference < -200f)
score += 0.1f;
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.myVault == null) return;
brain.NavigateTo(bb.myVault.transform.position);
// Check if close enough to deposit
float dist = Vector3.Distance(brain.transform.position, bb.myVault.transform.position);
if (dist < 3f)
{
CashCarrier carrier = brain.GetComponent<CashCarrier>();
if (carrier != null && carrier.IsCarrying)
{
carrier.DepositToVault(bb.myVault);
}
}
}
}
/// <summary>
/// AttackEnemyAction — Engage nearest enemy. Prioritizes bag carriers and low-HP targets.
/// Respects team engagement limits (max 2 AI per enemy).
/// </summary>
public class AttackEnemyAction : AIAction
{
public override string Name => "AttackEnemy";
public override bool CanInterrupt => true;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
if (bb.nearbyEnemyCount == 0) return 0f;
float score = 0.35f; // Base aggression
// Enemy carrier spotted — HIGH priority
if (bb.nearestEnemyCarrier != null)
{
float carrierDist = Vector3.Distance(bb.nearestEnemyCarrier.transform.position,
bb.myVault != null ? bb.myVault.transform.position : Vector3.zero);
score += 0.35f;
// More bags on carrier = more urgent
score += bb.nearestEnemyCarrier.BagCount * 0.1f;
}
// Close enemy = react aggressively
float proximityFactor = InverseCurve(bb.nearestEnemyDistance, 15f);
score *= (0.5f + proximityFactor * 0.5f);
// Outnumber advantage
if (bb.nearbyAllyCount > bb.nearbyEnemyCount)
score += 0.1f;
// Don't be aggressive when low health (unless enemy carrier)
if (bb.healthPercent < 0.3f && bb.nearestEnemyCarrier == null)
score *= 0.3f;
// Carrying bags = less aggressive (protect the payload)
if (bb.carriedBagCount > 0)
score *= 0.5f;
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
// Choose target: carrier > low HP > nearest
Transform target = null;
float bestPriority = float.MinValue;
for (int i = 0; i < bb.perceivedEnemies.Count; i++)
{
PerceivedEntity enemy = bb.perceivedEnemies[i];
if (enemy.gameObject == null) continue;
// Skip if too many teammates already targeting
int engagedCount = tb.GetEnemyTargetCount(enemy.transform);
if (engagedCount >= 2) continue;
float priority = 0f;
// Carrier priority
if (enemy.isCarryingBag)
priority += 50f + enemy.carriedBagCount * 20f;
// Low HP priority
priority += (1f - enemy.healthPercent) * 30f;
// Distance priority (closer = higher)
priority += InverseCurve(enemy.distance, 15f) * 20f;
// Visible bonus
if (enemy.isVisible) priority += 10f;
if (priority > bestPriority)
{
bestPriority = priority;
target = enemy.transform;
}
}
if (target == null) return;
// Register targeting
bb.currentTarget = target;
tb.AddEnemyTarget(target);
// Delegate combat to CharacterAIController
brain.EngageTarget(target);
}
public override void OnExit(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.currentTarget != null)
tb.RemoveEnemyTarget(bb.currentTarget);
bb.currentTarget = null;
}
}
/// <summary>
/// DefendVaultAction — Rush to own vault when under attack.
/// Highest priority interrupt action.
/// </summary>
public class DefendVaultAction : AIAction
{
public override string Name => "DefendVault";
public override bool CanInterrupt => true;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
if (!bb.myVaultUnderAttack) return 0f;
float score = 0.9f; // Very high base — vault defense is critical
// Closer to vault = we should definitely defend
if (bb.myVault != null)
{
float dist = Vector3.Distance(bb.myVault.transform.position, bb.myVault.transform.position);
score += InverseCurve(dist, 30f) * 0.1f;
}
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.myVault == null) return;
// If carrying, deposit first
CashCarrier carrier = brain.GetComponent<CashCarrier>();
float vaultDist = Vector3.Distance(brain.transform.position, bb.myVault.transform.position);
if (carrier != null && carrier.IsCarrying && vaultDist < 3f)
{
carrier.DepositToVault(bb.myVault);
}
// Check for enemies near vault
Transform nearestEnemyAtVault = null;
float nearestDist = float.MaxValue;
for (int i = 0; i < bb.perceivedEnemies.Count; i++)
{
PerceivedEntity enemy = bb.perceivedEnemies[i];
if (enemy.gameObject == null) continue;
float enemyVaultDist = Vector3.Distance(enemy.transform.position, bb.myVault.transform.position);
if (enemyVaultDist < 10f && enemyVaultDist < nearestDist)
{
nearestDist = enemyVaultDist;
nearestEnemyAtVault = enemy.transform;
}
}
if (nearestEnemyAtVault != null)
{
brain.EngageTarget(nearestEnemyAtVault);
}
else
{
brain.NavigateTo(bb.myVault.transform.position);
}
}
}
/// <summary>
/// PatrolAction — Wander randomly via NavMesh when no other objective.
/// Lowest-priority fallback action.
/// </summary>
public class PatrolAction : AIAction
{
public override string Name => "Patrol";
private float _pauseEndTime;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
// Patrol is the default — returns low score so anything else takes priority
return 0.1f;
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (Time.time < _pauseEndTime) return;
if (!bb.hasPatrolDestination || brain.HasReachedDestination())
{
if (bb.hasPatrolDestination)
{
// Pause at patrol point
_pauseEndTime = Time.time + Random.Range(1f, 3f);
bb.hasPatrolDestination = false;
return;
}
// Generate new patrol destination
Vector3 randomPoint = brain.transform.position + Random.insideUnitSphere * 12f;
randomPoint.y = brain.transform.position.y;
UnityEngine.AI.NavMeshHit hit;
if (UnityEngine.AI.NavMesh.SamplePosition(randomPoint, out hit, 12f, UnityEngine.AI.NavMesh.AllAreas))
{
bb.patrolDestination = hit.position;
bb.hasPatrolDestination = true;
brain.NavigateTo(bb.patrolDestination);
}
}
else
{
brain.NavigateTo(bb.patrolDestination);
}
}
public override void OnExit(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
bb.hasPatrolDestination = false;
}
}
/// <summary>
/// RetreatAction — Fall back to vault when low health.
/// Deposits any carried bags along the way.
/// </summary>
public class RetreatAction : AIAction
{
public override string Name => "Retreat";
public override bool CanInterrupt => true;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
// Only retreat when health is low
if (bb.healthPercent > 0.3f) return 0f;
float score = 0.7f; // High priority when triggered
// Lower health = more urgent
score += InverseCurve(bb.healthPercent, 0.3f) * 0.3f;
// Even more urgent if carrying bags
if (bb.isCarrying)
score += 0.1f;
// Less urgent if no enemies nearby
if (bb.nearbyEnemyCount == 0)
score *= 0.6f;
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.myVault == null) return;
brain.NavigateTo(bb.myVault.transform.position);
// Deposit if close to vault
float vaultDist = Vector3.Distance(brain.transform.position, bb.myVault.transform.position);
if (vaultDist < 3f)
{
CashCarrier carrier = brain.GetComponent<CashCarrier>();
if (carrier != null && carrier.IsCarrying)
carrier.DepositToVault(bb.myVault);
}
// If health recovered, exit retreat
if (bb.healthPercent > 0.5f)
{
// Score will drop, brain will switch naturally
}
}
}
/// <summary>
/// EscortCarrierAction — Follow and protect an ally who is carrying bags.
/// Maintains distance, engages threats approaching the carrier.
/// </summary>
public class EscortCarrierAction : AIAction
{
public override string Name => "EscortCarrier";
private const float ESCORT_DISTANCE = 4f;
private const float THREAT_RANGE = 10f;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
if (bb.isCarrying) return 0f; // Don't escort if carrying ourselves
// Need an ally carrier to escort
if (bb.nearestAllyCarrier == null) return 0f;
float score = 0.45f;
// More bags on ally = more valuable to escort
score += bb.nearestAllyCarrier.BagCount * 0.1f;
// Enemies near the carrier = escort more important
float carrierPos = 0;
for (int i = 0; i < bb.perceivedEnemies.Count; i++)
{
float enemyDist = Vector3.Distance(
bb.perceivedEnemies[i].transform.position,
bb.nearestAllyCarrier.transform.position);
if (enemyDist < THREAT_RANGE)
score += 0.15f;
}
// Already close to carrier = maintain escort
float distToCarrier = Vector3.Distance(bb.nearestAllyCarrier.transform.position, bb.myVault.transform.position);
if (distToCarrier < ESCORT_DISTANCE * 2f)
score += 0.1f;
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.nearestAllyCarrier == null) return;
Vector3 carrierPos = bb.nearestAllyCarrier.transform.position;
// Check for threats near carrier
Transform nearestThreat = null;
float nearestThreatDist = float.MaxValue;
for (int i = 0; i < bb.perceivedEnemies.Count; i++)
{
PerceivedEntity enemy = bb.perceivedEnemies[i];
float distToCarrier = Vector3.Distance(enemy.transform.position, carrierPos);
if (distToCarrier < THREAT_RANGE && distToCarrier < nearestThreatDist)
{
nearestThreatDist = distToCarrier;
nearestThreat = enemy.transform;
}
}
if (nearestThreat != null)
{
// Engage threat
brain.EngageTarget(nearestThreat);
}
else
{
// Follow carrier at distance
float distToCarrier = Vector3.Distance(brain.transform.position, carrierPos);
if (distToCarrier > ESCORT_DISTANCE)
{
brain.NavigateTo(carrierPos);
}
else
{
brain.StopNavigation();
}
}
}
}
/// <summary>
/// InterceptCarrierAction — Chase and attack an enemy who is carrying bags.
/// Highest combat priority.
/// </summary>
public class InterceptCarrierAction : AIAction
{
public override string Name => "InterceptCarrier";
public override bool CanInterrupt => true;
public override float Score(AIBlackboard bb, TeamBlackboard tb)
{
if (bb.isDead) return 0f;
if (bb.nearestEnemyCarrier == null) return 0f;
float score = 0.75f; // Very high base priority
// More bags = more vital to intercept
score += bb.nearestEnemyCarrier.BagCount * 0.08f;
// Close to our vault = CRITICAL
if (bb.myVault != null)
{
float carrierToVault = Vector3.Distance(
bb.nearestEnemyCarrier.transform.position,
bb.myVault.transform.position);
if (carrierToVault < 15f)
score += 0.15f;
}
// Low health = less interception
if (bb.healthPercent < 0.25f)
score *= 0.4f;
return Mathf.Clamp01(score);
}
public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb)
{
if (bb.nearestEnemyCarrier == null) return;
brain.EngageTarget(bb.nearestEnemyCarrier.transform);
}
}