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;
99  if (affliction.Strength > affliction.Prefab.TreatmentThreshold)
100  {
102  {
103  vitality -= affliction.Strength;
104  }
105  else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
106  {
107  vitality -= affliction.Strength;
108  }
109  else if (affliction.Prefab == AfflictionPrefab.HuskInfection)
110  {
111  vitality -= affliction.Strength;
112  }
113  }
114  }
115  return Math.Clamp(vitality, 0, 100);
116  }
117 
118  public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold)
119  {
120  var allAfflictions = character.CharacterHealth.GetAllAfflictions();
121  foreach (Affliction affliction in allAfflictions)
122  {
123  if (affliction.Prefab.IsBuff) { continue; }
124  if (!affliction.Prefab.HasTreatments) { continue; }
125  if (!ignoreTreatmentThreshold)
126  {
127  //other afflictions of the same type increase the "treatability"
128  // e.g. we might want to ignore burns below 5%, but not if the character has them on all limbs
129  float totalAfflictionStrength = character.CharacterHealth.GetTotalAdjustedAfflictionStrength(affliction);
130  if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
131  }
132  if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
133  yield return affliction;
134  }
135  }
136 
137  protected override AIObjective ObjectiveConstructor(Character target)
138  => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
139 
140  protected override void OnObjectiveCompleted(AIObjective objective, Character target)
141  => HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
142 
143  public static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds)
144  {
145  ignoredAsMinorWounds = false;
146  if (target == null || target.IsDead || target.Removed) { return false; }
147  if (target.IsInstigator) { return false; }
148  if (target.IsPet) { return false; }
149  if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
150  bool isBelowTreatmentThreshold;
151  float vitalityFactor;
152  if (character.AIController is HumanAIController humanAI)
153  {
154  if (!IsValidTargetForAI(target, humanAI)) { return false; }
155  vitalityFactor = GetVitalityFactor(target);
156  isBelowTreatmentThreshold = vitalityFactor < GetVitalityThreshold(humanAI.ObjectiveManager, character, target);
157  }
158  else
159  {
160  vitalityFactor = GetVitalityFactor(target);
161  isBelowTreatmentThreshold = vitalityFactor < vitalityThreshold;
162  }
163  bool hasTreatableAfflictions = GetTreatableAfflictions(target, ignoreTreatmentThreshold: false).Any();
164  bool isValidTarget = isBelowTreatmentThreshold && hasTreatableAfflictions;
165  if (!isValidTarget)
166  {
167  ignoredAsMinorWounds = hasTreatableAfflictions || vitalityFactor < 100;
168  }
169  return isValidTarget;
170  }
171 
172  private static bool IsValidTargetForAI(Character target, HumanAIController humanAI)
173  {
174  Character character = humanAI.Character;
175  if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
176  {
177  if (!character.IsMedic && target != character)
178  {
179  // Don't allow to treat others autonomously, unless we are a medic
180  return false;
181  }
182  // Ignore unsafe hulls, unless ordered
183  if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
184  {
185  return false;
186  }
187  }
188  if (character.Submarine != null)
189  {
190  // Don't allow going into another sub, unless it's connected and of the same team and type.
191  if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; }
192  }
193  else if (target.Submarine != null)
194  {
195  // We are outside, but the target is inside.
196  return false;
197  }
198  if (target != character && target.IsBot && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
199  {
200  // Ignore all concious targets that are currently fighting, fleeing, or treating characters
201  if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
202  targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
203  targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
204  {
205  return false;
206  }
207  }
208  if (target.CurrentHull != null)
209  {
210  // Don't go into rooms that have enemies
211  if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
212  }
213  return character.GetDamageDoneByAttacker(target) <= 0;
214  }
215  }
216 }
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
static AfflictionPrefab HuskInfection
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)