Пример #1
0
 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);
     }
 }
Пример #2
0
        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);
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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);
            }
        }
Пример #8
0
        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);
            }
        }