private void OnArrestTargetReached() { if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs) && Enemy.Stun > 0 && character.CanInteractWith(Enemy)) { if (HumanAIController.TryToMoveItem(handCuffs, Enemy.Inventory)) { handCuffs.Equip(Enemy); } else { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red); #endif } // Confiscate stolen goods. foreach (var item in Enemy.Inventory.Items) { if (item == null || item == handCuffs) { continue; } if (item.StolenDuringRound) { item.Drop(character); character.Inventory.TryPutItem(item, character, new List <InvSlotType>() { InvSlotType.Any }); } } // TODO: enable //character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f); IsCompleted = true; } }
protected override void Act(float deltaTime) { if (abortCondition != null && abortCondition()) { Abandon = true; return; } if (AllowCoolDown) { coolDownTimer -= deltaTime; } if (seekAmmunitionObjective == null && seekWeaponObjective == null) { if (Mode != CombatMode.Retreat && TryArm() && !IsEnemyDisabled) { OperateWeapon(deltaTime); } if (HoldPosition) { SteeringManager.Reset(); } else if (seekAmmunitionObjective == null && seekWeaponObjective == null) { Move(deltaTime); } switch (Mode) { case CombatMode.Offensive: if (TargetEliminated && objectiveManager.IsCurrentOrder <AIObjectiveFightIntruders>()) { character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f); } break; case CombatMode.Arrest: if (HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true)) { IsCompleted = true; } else if (Enemy.IsKnockedDown && !objectiveManager.IsCurrentObjective <AIObjectiveFightIntruders>() && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false)) { IsCompleted = true; } break; } } }
protected override void Act(float deltaTime) { if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) { Abandon = true; return; } var otherRescuer = targetCharacter.SelectedBy; if (otherRescuer != null && otherRescuer != character) { // Someone else is rescuing/holding the target. Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical"); return; } if (targetCharacter != character) { if (targetCharacter.IsIncapacitated) { // Check if the character needs more oxygen if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter)) { // Replace empty oxygen and welding fuel. if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable <Item> suits, requireEquipped: true)) { Item suit = suits.FirstOrDefault(); if (suit != null) { AIController.UnequipEmptyItems(character, suit); AIController.UnequipContainedItems(character, suit, it => it.HasTag("weldingfuel")); } } else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable <Item> masks, requireEquipped: true)) { Item mask = masks.FirstOrDefault(); if (mask != null) { AIController.UnequipEmptyItems(character, mask); AIController.UnequipContainedItems(character, mask, it => it.HasTag("weldingfuel")); } } bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0; if (ShouldRemoveDivingSuit()) { suits.ForEach(suit => suit.Drop(character)); } else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0))) { // The target has a suit equipped with an empty oxygen tank. // Can't remove the suit, because the target needs it. // If we happen to have an extra oxygen tank in the inventory, let's swap it. Item spareOxygenTank = FindOxygenTank(targetCharacter) ?? FindOxygenTank(character); if (spareOxygenTank != null) { Item suit = suits.FirstOrDefault(); if (suit != null) { // Insert the new oxygen tank TryAddSubObjective(ref replaceOxygenObjective, () => new AIObjectiveContainItem(character, spareOxygenTank, suit.GetComponent <ItemContainer>(), objectiveManager), onCompleted: () => RemoveSubObjective(ref replaceOxygenObjective), onAbandon: () => { RemoveSubObjective(ref replaceOxygenObjective); ignoreOxygen = true; if (ShouldRemoveDivingSuit()) { suits.ForEach(suit => suit.Drop(character)); } }); return; } } Item FindOxygenTank(Character c) => c.Inventory.FindItem(i => i.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && i.ConditionPercentage > 1 && i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag("diving")) == null, recursive: true); } } if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { // Incapacitated target is not in a safe place -> Move to a safe place first if (character.SelectedCharacter != targetCharacter) { if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null) { character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" }, new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); } // Go to the target and select it if (!character.CanInteractWith(targetCharacter)) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat, DialogueIdentifier = "dialogcannotreachpatient", TargetName = targetCharacter.DisplayName }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); Abandon = true; }); } else { character.SelectCharacter(targetCharacter); } } else { // Drag the character into safety if (safeHull == null) { if (findHullTimer > 0) { findHullTimer -= deltaTime; } else { safeHull = objectiveManager.GetObjective <AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls); findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); } } if (safeHull != null && character.CurrentHull != safeHull) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); safeHull = character.CurrentHull; }); } } } } } if (subObjectives.Any()) { return; } if (targetCharacter != character && !character.CanInteractWith(targetCharacter)) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); // Go to the target and select it TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat, DialogueIdentifier = "dialogcannotreachpatient", TargetName = targetCharacter.DisplayName }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); Abandon = true; }); } else { // We can start applying treatment if (character != targetCharacter && character.SelectedCharacter != targetCharacter) { if (targetCharacter.CurrentHull.DisplayName != null) { character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" }, new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f); } } GiveTreatment(deltaTime); } }
protected override void Act(float deltaTime) { if (character.LockHands) { Abandon = true; return; } targetItem = character.Inventory.FindItemByTag(gearTag, true); if (targetItem == null || !character.HasEquippedItem(targetItem)) { TryAddSubObjective(ref getDivingGear, () => { if (targetItem == null) { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); } return(new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true) { AllowStealing = true, AllowToFindDivingGear = false, AllowDangerousPressure = true }); }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getDivingGear)); } else { if (!EjectEmptyTanks(character, targetItem, out var containedItems)) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveFindDivingGear failed - the item \"" + targetItem + "\" has no proper inventory"); #endif Abandon = true; return; } if (containedItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > MIN_OXYGEN)) { // No valid oxygen source loaded. // Seek oxygen that has min 10% condition left. TryAddSubObjective(ref getOxygen, () => { if (!HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 10)) { character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); } return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true, ConditionLevel = MIN_OXYGEN }); }, onAbandon: () => { // Try to seek any oxygen sources. TryAddSubObjective(ref getOxygen, () => { return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true }); }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getOxygen)); }, onCompleted: () => RemoveSubObjective(ref getOxygen)); } } }
protected override void Act(float deltaTime) { if (character.LockHands) { Abandon = true; return; } targetItem = character.Inventory.FindItemByTag(gearTag, true); if (targetItem == null && gearTag == LIGHT_DIVING_GEAR) { targetItem = character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true); } if (targetItem == null || !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0)) { TryAddSubObjective(ref getDivingGear, () => { if (targetItem == null && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); } return(new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true) { AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _), AllowToFindDivingGear = false, AllowDangerousPressure = true, EquipSlotType = InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes, Wear = true }); }, onAbandon: () => Abandon = true, onCompleted: () => { RemoveSubObjective(ref getDivingGear); if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable <Item> masks, requireEquipped: true)) { foreach (Item mask in masks) { if (mask != targetItem) { character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot); } } } }); } else { float min = GetMinOxygen(character); if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min)) { TryAddSubObjective(ref getOxygen, () => { if (character.IsOnPlayerTeam) { if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min)) { character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f); if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1) { character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f); } } else { character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); } } return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true, ConditionLevel = MIN_OXYGEN, RemoveExisting = true }); }, onAbandon: () => { getOxygen = null; int remainingTanks = ReportOxygenTankCount(); // Try to seek any oxygen sources, even if they have minimal amount of oxygen. TryAddSubObjective(ref getOxygen, () => { return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true, RemoveExisting = true }); }, onAbandon: () => { Abandon = true; if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f)) { character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f); } }, onCompleted: () => RemoveSubObjective(ref getOxygen)); }, onCompleted: () => { RemoveSubObjective(ref getOxygen); ReportOxygenTankCount(); }); int ReportOxygenTankCount() { if (character.Submarine != Submarine.MainSub) { return(1); } int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1); if (remainingOxygenTanks == 0) { character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f); } else if (remainingOxygenTanks < 10) { character.Speak(TextManager.Get("DialogLowOnOxygenTanks"), null, 0.0f, "lowonoxygentanks", 30.0f); } return(remainingOxygenTanks); } } } }
protected override void Act(float deltaTime) { if (character.LockHands) { Abandon = true; return; } targetItem = character.Inventory.FindItemByTag(gearTag, true); if (targetItem == null || !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0)) { TryAddSubObjective(ref getDivingGear, () => { if (targetItem == null && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); } return(new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true) { AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _), AllowToFindDivingGear = false, AllowDangerousPressure = true, EquipSlotType = InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes, Wear = true }); }, onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getDivingGear)); } else { // Seek oxygen that has at least 10% condition left, if we are inside a friendly sub. // The margin helps us to survive, because we might need some oxygen before we can find more oxygen. // When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks. float min = character.Submarine != Submarine.MainSub ? 0.01f : MIN_OXYGEN; if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min)) { TryAddSubObjective(ref getOxygen, () => { if (character.IsOnPlayerTeam) { if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min)) { character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f); } else { character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); } } return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true, ConditionLevel = MIN_OXYGEN, RemoveExisting = true }); }, onAbandon: () => { getOxygen = null; int remainingTanks = ReportOxygenTankCount(); // Try to seek any oxygen sources, even if they have minimal amount of oxygen. TryAddSubObjective(ref getOxygen, () => { return(new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC) { AllowToFindDivingGear = false, AllowDangerousPressure = true, RemoveExisting = true }); }, onAbandon: () => { Abandon = true; if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f)) { character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f); } }, onCompleted: () => RemoveSubObjective(ref getOxygen)); }, onCompleted: () => { RemoveSubObjective(ref getOxygen); ReportOxygenTankCount(); }); int ReportOxygenTankCount() { if (character.Submarine != Submarine.MainSub) { return(1); } int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1); if (remainingOxygenTanks == 0) { character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f); } else if (remainingOxygenTanks < 10) { character.Speak(TextManager.Get("DialogLowOnOxygenTanks"), null, 0.0f, "lowonoxygentanks", 30.0f); } return(remainingOxygenTanks); } } } }
protected override bool Check() => HumanAIController.HasItem(character, gearTag, "oxygensource") || HumanAIController.HasItem(character, fallbackTag, "oxygensource");
protected override bool Check() => HumanAIController.HasItem(character, gearTag, out _, "oxygensource", requireEquipped: true) || HumanAIController.HasItem(character, fallbackTag, out _, "oxygensource", requireEquipped: true);
private void Engage() { if (character.LockHands || Enemy == null) { Mode = CombatMode.Retreat; SteeringManager.Reset(); return; } retreatTarget = null; RemoveSubObjective(ref retreatObjective); RemoveSubObjective(ref seekAmmunition); if (followTargetObjective != null && followTargetObjective.Target != Enemy) { RemoveFollowTarget(); } TryAddSubObjective(ref followTargetObjective, constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true, closeEnough: 50) { IgnoreIfTargetDead = true, DialogueIdentifier = "dialogcannotreachtarget", TargetName = Enemy.DisplayName }, onAbandon: () => { Abandon = true; SteeringManager.Reset(); }); if (followTargetObjective == null) { return; } if (Mode == CombatMode.Arrest && Enemy.Stun > 2) { if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs)) { if (!arrestingRegistered) { arrestingRegistered = true; followTargetObjective.Completed += OnArrestTargetReached; } followTargetObjective.CloseEnough = 100; } else { RemoveFollowTarget(); SteeringManager.Reset(); } } else if (WeaponComponent == null) { RemoveFollowTarget(); SteeringManager.Reset(); } else { followTargetObjective.CloseEnough = WeaponComponent is RangedWeapon ? 1000 : WeaponComponent is MeleeWeapon mw ? mw.Range : WeaponComponent is RepairTool rt ? rt.Range : 50; } }