public override void Update(AIObjectiveManager objectiveManager, float deltaTime) { if (character.CurrentHull == null) { currenthullSafety = 0; Priority = 5; return; } if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; } currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull); if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) { Priority -= priorityDecrease * deltaTime; } else { float dangerFactor = (100 - currenthullSafety) / 100; Priority += dangerFactor * priorityIncrease * deltaTime; } Priority = MathHelper.Clamp(Priority, 0, 100); if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted) { Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10); } }
protected override bool ShouldInterruptSubObjective(AIObjective subObjective) { if (subObjective is AIObjectiveFindSafety) { if (character.SelectedCharacter != targetCharacter) { return(true); } if (character.AnimController.InWater || targetCharacter.AnimController.InWater) { return(false); } foreach (Limb limb in targetCharacter.AnimController.Limbs) { if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) { return(false); } } return(!character.AnimController.InWater && !targetCharacter.AnimController.InWater && HumanAIController.GetHullSafety(character.CurrentHull, character) > HumanAIController.HULL_SAFETY_THRESHOLD); } return(false); }
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); } }
public Hull FindBestHull(IEnumerable <Hull> ignoredHulls = null, bool allowChangingTheSubmarine = true) { Hull bestHull = null; float bestValue = 0; foreach (Hull hull in Hull.hullList) { if (hull.Submarine == null) { continue; } if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } if (unreachable.Contains(hull)) { continue; } float hullSafety = 0; if (character.CurrentHull != null && character.Submarine != null) { // Inside if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, character); // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); hullSafety *= distanceFactor; //skip the hull if the safety is already less than the best hull //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) if (hullSafety < bestValue) { continue; } var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); if (path.Unreachable) { unreachable.Add(hull); continue; } // Each unsafe node reduces the hull safety value. // Ignore the current hull, because otherwise we couldn't find a path out. int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); hullSafety /= 1 + unsafeNodes; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { hullSafety /= 10; } } else { // Outside if (hull.RoomName != null && hull.RoomName.ToLowerInvariant().Contains("airlock")) { hullSafety = 100; } else { // TODO: could also target gaps that get us inside? foreach (Item item in Item.ItemList) { if (item.CurrentHull != hull && item.HasTag("airlock")) { hullSafety = 100; break; } } } // TODO: could we get a closest door to the outside and target the flowing hull if no airlock is found? // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); hullSafety *= distanceFactor; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC) { hullSafety /= 10; } } if (hullSafety > bestValue) { bestHull = hull; bestValue = hullSafety; } } return(bestHull); }
public Hull FindBestHull(IEnumerable <Hull> ignoredHulls = null) { Hull bestHull = null; float bestValue = 0; foreach (Hull hull in Hull.hullList) { if (hull.Submarine == null) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } float hullSafety = 0; if (character.Submarine != null && SteeringManager == PathSteering) { // Inside or outside near the sub if (unreachable.Contains(hull)) { continue; } if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, character); // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); hullSafety *= distanceFactor; // Each unsafe node reduces the hull safety value. // Ignore current hull, because otherwise the would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); if (path.Unreachable) { continue; } int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); hullSafety /= 1 + unsafeNodes; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { hullSafety /= 10; } } else { // Outside if (hull.RoomName?.ToLowerInvariant() == "airlock") { hullSafety = 100; } else { // TODO: could also target gaps that get us inside? foreach (Item item in Item.ItemList) { if (item.CurrentHull == hull && item.HasTag("airlock")) { hullSafety = 100; break; } } } // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); hullSafety *= distanceFactor; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC) { hullSafety /= 10; } } if (hullSafety > bestValue) { bestHull = hull; bestValue = hullSafety; } } return(bestHull); }
protected override void Act(float deltaTime) { if (targetCharacter == null || targetCharacter.Removed) { return; } // Unconcious target is not in a safe place -> Move to a safe place first if (targetCharacter.IsUnconscious && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { if (character.SelectedCharacter != targetCharacter) { 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)) { if (goToObjective != null && goToObjective.Target != targetCharacter) { goToObjective = null; } TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)); } else { character.SelectCharacter(targetCharacter); } } else { // Drag the character into safety if (goToObjective != null && goToObjective.Target == targetCharacter) { goToObjective = null; } if (safeHull == null) { var findSafety = objectiveManager.GetObjective <AIObjectiveFindSafety>(); if (findSafety == null) { // Ensure that we have the find safety objective (should always be the case) findSafety = new AIObjectiveFindSafety(character, objectiveManager); objectiveManager.AddObjective(findSafety); } safeHull = findSafety.FindBestHull(HumanAIController.VisibleHulls); } if (character.CurrentHull != safeHull) { TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager)); } } } if (subObjectives.Any()) { return; } if (!character.CanInteractWith(targetCharacter)) { // Go to the target and select it TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)); } else { // We can start applying treatment if (character.SelectedCharacter != targetCharacter) { 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); character.SelectCharacter(targetCharacter); } GiveTreatment(deltaTime); } }
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) { // Incapacitated target is not in a safe place -> Move to a safe place first if (targetCharacter.IsIncapacitated && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { if (character.SelectedCharacter != targetCharacter) { if (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 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 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 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); } character.SelectCharacter(targetCharacter); } GiveTreatment(deltaTime); } }
protected override void Act(float deltaTime) { if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) { Abandon = true; return; } if (targetCharacter != character) { // Unconcious target is not in a safe place -> Move to a safe place first if (targetCharacter.IsUnconscious && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { if (character.SelectedCharacter != targetCharacter) { 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 goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => RemoveSubObjective(ref goToObjective)); } else { character.SelectCharacter(targetCharacter); } } else { // Drag the character into safety if (safeHull == null) { safeHull = objectiveManager.GetObjective <AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls); } if (character.CurrentHull != safeHull) { RemoveSubObjective(ref goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => RemoveSubObjective(ref goToObjective)); } } } } if (subObjectives.Any()) { return; } if (targetCharacter != character && !character.CanInteractWith(targetCharacter)) { RemoveSubObjective(ref goToObjective); // Go to the target and select it TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => RemoveSubObjective(ref goToObjective)); } else { // We can start applying treatment if (character != targetCharacter && character.SelectedCharacter != targetCharacter) { 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); character.SelectCharacter(targetCharacter); } GiveTreatment(deltaTime); } }