using UnityEngine; /// /// DepositBagAction — Navigate to own vault and deposit carried bags. /// Score increases with more bags carried and when vault is nearby. /// 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(); if (carrier != null && carrier.IsCarrying) { carrier.DepositToVault(bb.myVault); } } } } /// /// AttackEnemyAction — Engage nearest enemy. Prioritizes bag carriers and low-HP targets. /// Respects team engagement limits (max 2 AI per enemy). /// 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; } } /// /// DefendVaultAction — Rush to own vault when under attack. /// Highest priority interrupt action. /// 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(); 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); } } } /// /// PatrolAction — Wander randomly via NavMesh when no other objective. /// Lowest-priority fallback action. /// 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; } } /// /// RetreatAction — Fall back to vault when low health. /// Deposits any carried bags along the way. /// 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(); 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 } } } /// /// EscortCarrierAction — Follow and protect an ally who is carrying bags. /// Maintains distance, engages threats approaching the carrier. /// 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(); } } } } /// /// InterceptCarrierAction — Chase and attack an enemy who is carrying bags. /// Highest combat priority. /// 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); } }