Server LuaCsForBarotrauma
All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events
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 CheckObjectiveState() => 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 (CheckObjectiveState()) { 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 && !CheckObjectiveState()
206  },
207  onAbandon: () =>
208  {
209  if (CheckObjectiveState()) { 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)
AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier=1, bool isPriority=false)
override void Act(float deltaTime)
override bool CheckObjectiveState()
Should return whether the objective is completed or not.
static float GetLeakSeverity(Gap leak)
float Priority
Final priority value after all calculations.
Definition: AIObjective.cs:84
readonly Character character
Definition: AIObjective.cs:112
HumanAIController HumanAIController
Definition: AIObjective.cs:172
readonly List< AIObjective > subObjectives
Definition: AIObjective.cs:73
readonly AIObjectiveManager objectiveManager
Definition: AIObjective.cs:113
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
float Open
Definition: Gap.cs:66
Hull FlowTargetHull
Definition: Gap.cs:137
HumanoidAnimController AnimController
static bool IsActive(Character c)
Item FindItemByTag(Identifier tag, bool recursive=false)
readonly List< MapEntity > linkedTo
Definition: MapEntity.cs:52