using UnityEngine; /// /// CollectBagAction — Navigate to nearest unclaimed available/dropped cash box. /// Prioritizes hot-drop boxes (bonus value) and closer boxes. /// Respects TeamBlackboard claims to prevent clustering. /// public class CollectBagAction : AIAction { public override string Name => "CollectBag"; public override float Score(AIBlackboard bb, TeamBlackboard tb) { // Can't collect if dead or already full if (bb.isDead) return 0f; if (bb.carriedBagCount >= 3) return 0f; // No bags nearby int totalNearby = bb.nearbyAvailableBags.Count + bb.nearbyDroppedBags.Count; if (totalNearby == 0) return 0f; float score = 0.5f; // Base desire to collect // Bonus for hot-drop bags (time-sensitive) for (int i = 0; i < bb.nearbyDroppedBags.Count; i++) { if (bb.nearbyDroppedBags[i].IsHotDrop) { score += 0.2f; break; } } // Distance factor — closer bags = higher score float distanceFactor = InverseCurve(bb.nearestBagDistance, 25f); score *= (0.5f + distanceFactor * 0.5f); // Less desire to collect when already carrying (deposit instead) if (bb.carriedBagCount >= 2) score *= 0.3f; else if (bb.carriedBagCount >= 1) score *= 0.6f; // Reduce if nearby enemies (risky to collect) if (bb.nearbyEnemyCount > 0 && bb.nearestEnemyDistance < 8f) score *= 0.6f; // Low health = less collecting score *= (0.3f + bb.healthPercent * 0.7f); return Mathf.Clamp01(score); } public override void Execute(AIBrain brain, AIBlackboard bb, TeamBlackboard tb) { // Find best bag (prefer dropped hot-drops, then closest available) CashBag bestBag = null; float bestScore = float.MinValue; // Check dropped bags first (higher priority) for (int i = 0; i < bb.nearbyDroppedBags.Count; i++) { CashBag bag = bb.nearbyDroppedBags[i]; if (bag == null || bag.IsCarried) continue; if (tb.IsBagClaimed(bag, brain)) continue; float dist = Vector3.Distance(brain.transform.position, bag.transform.position); float bagScore = bag.GetEffectiveValue() / Mathf.Max(dist, 1f); if (bag.IsHotDrop) bagScore *= 2f; // Big bonus for hot drops if (bagScore > bestScore) { bestScore = bagScore; bestBag = bag; } } // Then available bags if (bestBag == null) { for (int i = 0; i < bb.nearbyAvailableBags.Count; i++) { CashBag bag = bb.nearbyAvailableBags[i]; if (bag == null || bag.IsCarried) continue; if (tb.IsBagClaimed(bag, brain)) continue; float dist = Vector3.Distance(brain.transform.position, bag.transform.position); float bagScore = bag.Value / Mathf.Max(dist, 1f); if (bagScore > bestScore) { bestScore = bagScore; bestBag = bag; } } } if (bestBag == null) { // No unclaimed bags, go idle return; } // Claim and navigate tb.TryClaimBag(bestBag, brain); bb.targetBag = bestBag; brain.NavigateTo(bestBag.transform.position); } public override void OnExit(AIBrain brain, AIBlackboard bb, TeamBlackboard tb) { tb.ReleaseBagClaim(brain); bb.targetBag = null; } }