Client LuaCsForBarotrauma
AIObjectiveFixLeak.cs
3 using FarseerPhysics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
12  public override Identifier Identifier { get; set; } = "fix leak".ToIdentifier();
13  public override bool ForceRun => true;
14  public override bool KeepDivingGearOn => true;
15  protected override bool AllowInFriendlySubs => true;
16  protected override bool AllowInAnySub => true;
17  protected override bool AllowWhileHandcuffed => false;
18 
19  public Gap Leak { get; private set; }
20 
21  private AIObjectiveGetItem getWeldingTool;
22  private AIObjectiveContainItem refuelObjective;
23  private AIObjectiveGoTo gotoObjective;
24  private AIObjectiveOperateItem operateObjective;
25 
26  public readonly bool isPriority;
27 
28  public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false) : base (character, objectiveManager, priorityModifier)
29  {
30  Leak = leak;
31  this.isPriority = isPriority;
32  }
33 
34  protected override bool CheckObjectiveSpecific() => Leak.Open <= 0 || Leak.Removed;
35 
36  protected override float GetPriority()
37  {
38  if (!IsAllowed)
39  {
41  return Priority;
42  }
43  float coopMultiplier = 1;
44  foreach (var c in Character.CharacterList)
45  {
46  if (!HumanAIController.IsActive(c)) { continue; }
47  if (c.TeamID != character.TeamID) { continue; }
48  if (c == character) { continue; }
49  if (c.IsPlayer) { continue; }
50  if (c.AIController is HumanAIController otherAI )
51  {
52  if (otherAI.ObjectiveManager.GetFirstActiveObjective<AIObjectiveFixLeak>() is AIObjectiveFixLeak fixLeak)
53  {
54  if (fixLeak.Leak == Leak)
55  {
56  // Ignore leaks that others are already targeting
57  Priority = 0;
58  return Priority;
59  }
60  if (fixLeak.Leak.FlowTargetHull == Leak.FlowTargetHull)
61  {
62  // Reduce the priority of leaks that others should be targeting
63  coopMultiplier = 0.1f;
64  break;
65  }
66  }
67  }
68  }
69  float reduction = isPriority ? 1 : 2;
70  float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
71  if (operateObjective != null && objectiveManager.GetFirstActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
72  {
73  // Prioritize leaks that we are already fixing
74  Priority = maxPriority;
75  }
76  else
77  {
78  float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
79  float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
80  // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
81  // If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
82  float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
83  if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
84  {
85  // Double the distance when the leak can be accessed from the current hull.
86  distanceFactor *= 2;
87  }
88  float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
89  float devotion = CumulatedDevotion / 100;
90  Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier * coopMultiplier), 0, 1));
91  }
92  return Priority;
93  }
94 
95  protected override void Act(float deltaTime)
96  {
97  var weldingTool = character.Inventory.FindItemByTag("weldingequipment".ToIdentifier(), true);
98  var repairTool = weldingTool?.GetComponent<RepairTool>();
99  if (weldingTool == null)
100  {
101  TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment".ToIdentifier(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
102  onAbandon: () =>
103  {
104  if (character.IsOnPlayerTeam && objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>())
105  {
106  character.Speak(TextManager.Get("dialogcannotfindweldingequipment").Value, null, 0.0f, "dialogcannotfindweldingequipment".ToIdentifier(), 10.0f);
107  }
108  Abandon = true;
109  },
110  onCompleted: () => RemoveSubObjective(ref getWeldingTool));
111  return;
112  }
113  else
114  {
115  if (repairTool == null)
116  {
117 #if DEBUG
118  DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no RepairTool component but is tagged as a welding tool");
119 #endif
120  Abandon = true;
121  return;
122  }
123  if (weldingTool.OwnInventory == null && repairTool.RequiredItems.Any(r => r.Key == RelatedItem.RelationType.Contained))
124  {
125 #if DEBUG
126  DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no proper inventory");
127 #endif
128  Abandon = true;
129  return;
130  }
131  if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 0.0f))
132  {
133  TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, Tags.WeldingFuel, weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
134  {
135  RemoveExisting = true
136  },
137  onAbandon: () =>
138  {
139  Abandon = true;
140  ReportWeldingFuelTankCount();
141  },
142  onCompleted: () =>
143  {
144  RemoveSubObjective(ref refuelObjective);
145  ReportWeldingFuelTankCount();
146  });
147 
148  void ReportWeldingFuelTankCount()
149  {
150  if (character.Submarine != Submarine.MainSub) { return; }
151  int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 1);
152  if (remainingOxygenTanks == 0)
153  {
154  character.Speak(TextManager.Get("DialogOutOfWeldingFuel").Value, null, 0.0f, "outofweldingfuel".ToIdentifier(), 30.0f);
155  }
156  else if (remainingOxygenTanks < 4)
157  {
158  character.Speak(TextManager.Get("DialogLowOnWeldingFuel").Value, null, 0.0f, "lowonweldingfuel".ToIdentifier(), 30.0f);
159  }
160  }
161  return;
162  }
163  }
164  if (subObjectives.Any()) { return; }
166  // TODO: use the collider size/reach?
167  if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150)
168  {
170  }
171  float reach = CalculateReach(repairTool, character);
172  bool canOperate = toLeak.LengthSquared() < reach * reach;
173  if (canOperate)
174  {
175  TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak)
176  {
177  // Use an empty filter to override the default
178  EndNodeFilter = n => true
179  },
180  onAbandon: () => Abandon = true,
181  onCompleted: () =>
182  {
183  if (CheckObjectiveSpecific()) { IsCompleted = true; }
184  else
185  {
186  // Failed to operate. Probably too far.
187  Abandon = true;
188  }
189  });
190  }
191  else
192  {
193  TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager)
194  {
195  UseDistanceRelativeToAimSourcePos = true,
196  CloseEnough = reach,
197  DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak".ToIdentifier() : Identifier.Empty,
198  TargetName = Leak.FlowTargetHull?.DisplayName,
199  requiredCondition = () =>
200  Leak.Submarine == character.Submarine &&
201  Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
202  endNodeFilter = IsSuitableEndNode,
203  // The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
204  // Only report about contextual targets.
205  SpeakCannotReachCondition = () => isPriority && !CheckObjectiveSpecific()
206  },
207  onAbandon: () =>
208  {
209  if (CheckObjectiveSpecific()) { IsCompleted = true; }
210  else if ((Leak.WorldPosition - character.AnimController.AimSourceWorldPos).LengthSquared() > MathUtils.Pow(reach * 2, 2))
211  {
212  // Too far
213  Abandon = true;
214  }
215  else
216  {
217  // We are close, try again.
218  RemoveSubObjective(ref gotoObjective);
219  }
220  },
221  onCompleted: () => RemoveSubObjective(ref gotoObjective));
222 
223  bool IsSuitableEndNode(PathNode n)
224  {
225  if (n.Waypoint.CurrentHull is null) { return false; }
226  if (n.Waypoint.CurrentHull.ConnectedGaps.Contains(Leak)) { return true; }
227  // Accept also nodes located in the linked hulls (multi-hull rooms)
228  return Leak.linkedTo.Any(e => e is Hull h && h.linkedTo.Contains(n.Waypoint.CurrentHull));
229  }
230  }
231  }
232 
233  public override void Reset()
234  {
235  base.Reset();
236  getWeldingTool = null;
237  refuelObjective = null;
238  gotoObjective = null;
239  operateObjective = null;
240  }
241 
242  public static float CalculateReach(RepairTool repairTool, Character character)
243  {
244  float armLength = ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
245  // This is an approximation, because we don't know the exact reach until the pose is taken.
246  // And even then the actual range depends on the direction we are aiming to.
247  // Found out that without any multiplier the value (209) is often too short.
248  return repairTool.Range + armLength * 2;
249  }
250  }
251 }
static float CalculateReach(RepairTool repairTool, Character character)
override bool CheckObjectiveSpecific()
Should return whether the objective is completed or not.
AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier=1, bool isPriority=false)
override void Act(float deltaTime)
static float GetLeakSeverity(Gap leak)
float Priority
Final priority value after all calculations.
const float LowestOrderPriority
Maximum priority of an order given to the character (rightmost order in the crew list)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
static bool IsActive(Character c)
Item FindItemByTag(Identifier tag, bool recursive=false)