Client LuaCsForBarotrauma
AIObjectiveRescueAll.cs
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 
6 namespace Barotrauma
7 {
9  {
10  public override Identifier Identifier { get; set; } = "rescue all".ToIdentifier();
11  public override bool ForceRun => true;
12  public override bool InverseTargetPriority => true;
13  protected override bool AllowOutsideSubmarine => true;
14  protected override bool AllowInAnySub => true;
15 
16  private readonly HashSet<Character> charactersWithMinorInjuries = new HashSet<Character>();
17 
18  private const float vitalityThreshold = 75;
19  private const float vitalityThresholdForOrders = 90;
20  public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
21  {
22  if (manager == null)
23  {
24  return vitalityThreshold;
25  }
26  else
27  {
28  return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? vitalityThresholdForOrders : vitalityThreshold;
29  }
30  }
31 
32  public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
33  : base(character, objectiveManager, priorityModifier) { }
34 
35  protected override bool IsValidTarget(Character target)
36  {
37  if (!IsValidTarget(target, character, out bool ignoredasMinorWounds))
38  {
39  if (ignoredasMinorWounds)
40  {
41  //the target might be at a low enough health to be considered a valid target,
42  //but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
43  // -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
44  if (character.IsOnPlayerTeam && target != character && !charactersWithMinorInjuries.Contains(target))
45  {
46  // But only speak about targets when we are not already actively treating, in which case we should be speaking about the current target.
47  if (objectiveManager.GetFirstActiveObjective<AIObjectiveRescue>() == null)
48  {
49  charactersWithMinorInjuries.Add(target);
50  character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
51  delay: 1.0f,
52  identifier: $"notreatableafflictions{target.Name}".ToIdentifier(),
53  minDurationBetweenSimilar: 10.0f);
54  }
55  }
56  }
57  return false;
58  }
59  return true;
60  }
61 
62  protected override IEnumerable<Character> GetList() => Character.CharacterList;
63 
64  protected override float GetTargetPriority()
65  {
66  if (Targets.None()) { return 100; }
67  if (!objectiveManager.IsOrder(this))
68  {
69  if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsMedic, onlyActive: true, onlyConnectedSubs: true))
70  {
71  // Don't do anything if there's a medic on board actively treating and we are not a medic
72  return 100;
73  }
74  }
75  float worstCondition = Targets.Min(t => GetVitalityFactor(t));
76  if (Targets.Contains(character))
77  {
78  // Targeting self -> higher prio
79  if (character.Bleeding > 10)
80  {
81  // Enforce the highest priority when bleeding out.
82  worstCondition = 0;
83  }
84  // Boost the priority when wounded.
85  worstCondition /= 2;
86  }
87  return worstCondition;
88  }
89 
90  public static float GetVitalityFactor(Character character)
91  {
92  float vitality = 100;
93  vitality -= character.Bleeding * 2;
94  vitality += Math.Min(character.Oxygen, 0);
95  foreach (Affliction affliction in GetTreatableAfflictions(character, ignoreTreatmentThreshold: true))
96  {
97  float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
98  vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
100  {
101  vitality -= affliction.Strength;
102  }
103  else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
104  {
105  vitality -= affliction.Strength;
106  }
107  }
108  return Math.Clamp(vitality, 0, 100);
109  }
110 
111  public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold)
112  {
113  var allAfflictions = character.CharacterHealth.GetAllAfflictions();
114  foreach (Affliction affliction in allAfflictions)
115  {
116  if (affliction.Prefab.IsBuff) { continue; }
117  if (!affliction.Prefab.HasTreatments) { continue; }
118  if (!ignoreTreatmentThreshold)
119  {
120  //other afflictions of the same type increase the "treatability"
121  // e.g. we might want to ignore burns below 5%, but not if the character has them on all limbs
122  float totalAfflictionStrength = character.CharacterHealth.GetTotalAdjustedAfflictionStrength(affliction);
123  if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
124  }
125  if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
126  yield return affliction;
127  }
128  }
129 
130  protected override AIObjective ObjectiveConstructor(Character target)
131  => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
132 
133  protected override void OnObjectiveCompleted(AIObjective objective, Character target)
134  => HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
135 
136  public static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds)
137  {
138  ignoredAsMinorWounds = false;
139  if (target == null || target.IsDead || target.Removed) { return false; }
140  if (target.IsInstigator) { return false; }
141  if (target.IsPet) { return false; }
142  if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
143  bool isBelowTreatmentThreshold;
144  float vitalityFactor;
145  if (character.AIController is HumanAIController humanAI)
146  {
147  if (!IsValidTargetForAI(target, humanAI)) { return false; }
148  vitalityFactor = GetVitalityFactor(target);
149  isBelowTreatmentThreshold = vitalityFactor < GetVitalityThreshold(humanAI.ObjectiveManager, character, target);
150  }
151  else
152  {
153  vitalityFactor = GetVitalityFactor(target);
154  isBelowTreatmentThreshold = vitalityFactor < vitalityThreshold;
155  }
156  bool hasTreatableAfflictions = GetTreatableAfflictions(target, ignoreTreatmentThreshold: false).Any();
157  bool isValidTarget = isBelowTreatmentThreshold && hasTreatableAfflictions;
158  if (!isValidTarget)
159  {
160  ignoredAsMinorWounds = hasTreatableAfflictions || vitalityFactor < 100;
161  }
162  return isValidTarget;
163  }
164 
165  private static bool IsValidTargetForAI(Character target, HumanAIController humanAI)
166  {
167  Character character = humanAI.Character;
168  if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
169  {
170  if (!character.IsMedic && target != character)
171  {
172  // Don't allow to treat others autonomously, unless we are a medic
173  return false;
174  }
175  // Ignore unsafe hulls, unless ordered
176  if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
177  {
178  return false;
179  }
180  }
181  if (character.Submarine != null)
182  {
183  // Don't allow going into another sub, unless it's connected and of the same team and type.
184  if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
185  }
186  else if (target.Submarine != null)
187  {
188  // We are outside, but the target is inside.
189  return false;
190  }
191  if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
192  {
193  // Ignore all concious targets that are currently fighting, fleeing, or treating characters
194  if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
195  targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
196  targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
197  {
198  return false;
199  }
200  }
201  if (target.CurrentHull != null)
202  {
203  // Don't go into rooms that have enemies
204  if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
205  }
206  return character.GetDamageDoneByAttacker(target) <= 0;
207  }
208  }
209 }
An objective that creates specific kinds of subobjectives for specific types of targets,...
override void OnObjectiveCompleted(AIObjective objective, Character target)
static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds)
static IEnumerable< Affliction > GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold)
override IEnumerable< Character > GetList()
List of all possible items of the specified type. Used for filtering the removed objectives.
override float GetTargetPriority()
Returns a priority value based on the current targets (e.g. high prio when there's lots of severe fir...
static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier=1)
static float GetVitalityFactor(Character character)
override bool IsValidTarget(Character target)
override AIObjective ObjectiveConstructor(Character target)
float GetVitalityDecrease(CharacterHealth characterHealth)
Definition: Affliction.cs:171
virtual float Strength
Definition: Affliction.cs:31
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
readonly bool IsBuff
If set to true, the game will recognize this affliction as a buff. This means, among other things,...
ImmutableHashSet< Identifier > IgnoreTreatmentIfAfflictedBy
Bots will not try to treat the affliction if the character has any of these afflictions
bool HasTreatments
Can this affliction be treated with some item?
static readonly Identifier ParalysisType
readonly Identifier AfflictionType
Arbitrary string that is used to identify the type of the affliction.
readonly float TreatmentThreshold
How strong the affliction needs to be before bots attempt to treat it. Also effects when the afflicti...
static readonly Identifier PoisonType
Submarine Submarine
Definition: Entity.cs:53
bool IsTrueForAnyCrewMember(Func< Character, bool > predicate, bool onlyActive=true, bool onlyConnectedSubs=false)
Including the player characters in the same team.
static bool IsFriendly(Character me, Character other, bool onlySameTeam=false)
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)