Client LuaCsForBarotrauma
AIObjectiveFindDivingGear.cs
3 using System.Collections.Generic;
4 using System.Linq;
5 
6 namespace Barotrauma
7 {
9  {
10  public override Identifier Identifier { get; set; } = "find diving gear".ToIdentifier();
11  public override string DebugTag => $"{Identifier} ({gearTag})";
12  public override bool ForceRun => true;
13  public override bool KeepDivingGearOn => true;
14  public override bool AbandonWhenCannotCompleteSubObjectives => false;
15  protected override bool AllowWhileHandcuffed => false;
16 
17  private readonly Identifier gearTag;
18 
19  private AIObjectiveGetItem getDivingGear;
20  private AIObjectiveContainItem getOxygen;
21  private Item targetItem;
22  private int? oxygenSourceSlotIndex;
23 
24  public const float MIN_OXYGEN = 10;
25 
26  protected override bool CheckObjectiveSpecific() =>
27  targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
28 
29  public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
30  {
31  gearTag = needsDivingSuit ? Tags.HeavyDivingGear : Tags.LightDivingGear;
32  }
33 
34  protected override void Act(float deltaTime)
35  {
36  TrySetTargetItem(character.Inventory.FindItem(it => it.HasTag(gearTag) && IsSuitablePressureProtection(it, gearTag, character), true));
37  if (targetItem == null && gearTag == Tags.LightDivingGear)
38  {
39  TrySetTargetItem(character.Inventory.FindItem(
40  it => it.HasTag(Tags.HeavyDivingGear) && IsSuitablePressureProtection(it, Tags.HeavyDivingGear, character), recursive: true));
41  }
42  if (targetItem == null ||
43  !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
44  targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
45  {
46  bool mustFindMorePressureProtection =
49  it => it.HasTag(Tags.HeavyDivingGear) && !IsSuitablePressureProtection(it, Tags.HeavyDivingGear, character), recursive: true) != null;
50  TryAddSubObjective(ref getDivingGear, () =>
51  {
52  if (targetItem == null && character.IsOnPlayerTeam)
53  {
54  character.Speak(TextManager.Get("DialogGetDivingGear").Value, null, 0.0f, "getdivinggear".ToIdentifier(), 30.0f);
55  }
56  var getItemObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true)
57  {
59  AllowToFindDivingGear = false,
60  AllowDangerousPressure = true,
61  EquipSlotType = InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head,
62  Wear = true
63  };
64  if (gearTag == Tags.HeavyDivingGear)
65  {
66  if (mustFindMorePressureProtection)
67  {
68  //if we're looking for a suit specifically because the current suit isn't enough,
69  //let's ignore unsuitable suits altogether...
70  getItemObjective.ItemFilter = it => IsSuitablePressureProtection(it, gearTag, character);
71  }
72  else
73  {
74  //...Otherwise it's fine to give a very small priority
75  //to inadequate suits (a suit not adequate for the depth is better than no suit)
76  getItemObjective.GetItemPriority = it => IsSuitablePressureProtection(it, gearTag, character) ? 1000.0f : 1.0f;
77  }
78  getItemObjective.GetItemPriority = it =>
79  {
80  if (IsSuitablePressureProtection(it, gearTag, character))
81  {
82  return 1000.0f;
83  }
84  else
85  {
86  //if we're looking for a suit specifically because the current suit isn't enough,
87  //let's ignore unsuitable suits altogether. Otherwise it's fine to give a very small priority
88  //to inadequate suits (a suit not adequate for the depth is better than no suit)
89  return mustFindMorePressureProtection ? 0.0f : 1.0f;
90  }
91  };
92  }
93  return getItemObjective;
94  },
95  onAbandon: () =>
96  {
97  if (mustFindMorePressureProtection) { objectiveManager.FailedToFindDivingGearForDepth = true; }
98  Abandon = true;
99  },
100  onCompleted: () =>
101  {
102  RemoveSubObjective(ref getDivingGear);
103  if (gearTag == Tags.HeavyDivingGear && HumanAIController.HasItem(character, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
104  {
105  foreach (Item mask in masks)
106  {
107  if (mask != targetItem)
108  {
109  character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
110  }
111  }
112  }
113  });
114  }
115  else
116  {
117  float min = GetMinOxygen(character);
118  if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => IsSuitableContainedOxygenSource(it)))
119  {
120  TryAddSubObjective(ref getOxygen, () =>
121  {
122  if (character.IsOnPlayerTeam)
123  {
124  if (HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: min))
125  {
126  character.Speak(TextManager.Get("dialogswappingoxygentank").Value, null, 0, "swappingoxygentank".ToIdentifier(), 30.0f);
127  if (character.Inventory.FindAllItems(i => i.HasTag(Tags.OxygenSource) && i.Condition > min, recursive: true).Count == 1)
128  {
129  character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
130  }
131  }
132  else
133  {
134  character.Speak(TextManager.Get("DialogGetOxygenTank").Value, null, 0, "getoxygentank".ToIdentifier(), 30.0f);
135  }
136  }
137  var container = targetItem.GetComponent<ItemContainer>();
138  var objective = new AIObjectiveContainItem(character, Tags.OxygenSource, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
139  {
140  AllowToFindDivingGear = false,
141  AllowDangerousPressure = true,
142  ConditionLevel = MIN_OXYGEN,
143  RemoveExistingWhenNecessary = true,
144  TargetSlot = oxygenSourceSlotIndex
145  };
146  if (container.HasSubContainers)
147  {
148  objective.TargetSlot = container.FindSuitableSubContainerIndex(Tags.OxygenSource);
149  }
150  // Only remove the oxygen source being replaced
151  objective.RemoveExistingPredicate = i => objective.IsInTargetSlot(i);
152  return objective;
153  },
154  onAbandon: () =>
155  {
156  getOxygen = null;
157  int remainingTanks = ReportOxygenTankCount();
158  // Try to seek any oxygen sources, even if they have minimal amount of oxygen.
159  TryAddSubObjective(ref getOxygen, () =>
160  {
161  return new AIObjectiveContainItem(character, Tags.OxygenSource, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
162  {
163  AllowToFindDivingGear = false,
164  AllowDangerousPressure = true,
165  RemoveExisting = true
166  };
167  },
168  onAbandon: () =>
169  {
170  Abandon = true;
171  if (remainingTanks > 0 && !HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: 0.01f))
172  {
173  character.Speak(TextManager.Get("dialogcantfindtoxygen").Value, null, 0, "cantfindoxygen".ToIdentifier(), 30.0f);
174  }
175  },
176  onCompleted: () => RemoveSubObjective(ref getOxygen));
177  },
178  onCompleted: () =>
179  {
180  RemoveSubObjective(ref getOxygen);
181  ReportOxygenTankCount();
182  });
183 
184  int ReportOxygenTankCount()
185  {
186  if (character.Submarine != Submarine.MainSub) { return 1; }
187  int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.OxygenSource) && i.Condition > 1);
188  if (remainingOxygenTanks == 0)
189  {
190  character.Speak(TextManager.Get("DialogOutOfOxygenTanks").Value, null, 0.0f, "outofoxygentanks".ToIdentifier(), 30.0f);
191  }
192  else if (remainingOxygenTanks < 10)
193  {
194  character.Speak(TextManager.Get("DialogLowOnOxygenTanks").Value, null, 0.0f, "lowonoxygentanks".ToIdentifier(), 30.0f);
195  }
196  return remainingOxygenTanks;
197  }
198  }
199  }
200  }
201 
202  public static bool IsSuitablePressureProtection(Item item, Identifier tag, Character character)
203  {
204  if (tag == Tags.HeavyDivingGear)
205  {
206  float realWorldDepth = Level.Loaded?.GetRealWorldDepth(character.WorldPosition.Y) ?? 0.0f;
207  if (item.GetComponent<Wearable>() is not { } wearable || wearable.PressureProtection < realWorldDepth + Steering.PressureWarningThreshold)
208  {
209  return false;
210  }
211  }
212  return true;
213  }
214 
215 
216  private bool IsSuitableContainedOxygenSource(Item item)
217  {
218  return
219  item != null &&
220  item.HasTag(Tags.OxygenSource) &&
221  item.Condition > 0 &&
222  (oxygenSourceSlotIndex == null || item.ParentInventory.IsInSlot(item, oxygenSourceSlotIndex.Value));
223  }
224 
225  private void TrySetTargetItem(Item item)
226  {
227  if (targetItem == item) { return; }
228  targetItem = item;
229  if (targetItem != null)
230  {
231  oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(Tags.OxygenSource);
232  }
233  else
234  {
235  oxygenSourceSlotIndex = null;
236  }
237  }
238 
239  public override void Reset()
240  {
241  base.Reset();
242  getDivingGear = null;
243  getOxygen = null;
244  targetItem = null;
245  oxygenSourceSlotIndex = null;
246  }
247 
248  public static float GetMinOxygen(Character character)
249  {
250  // Seek oxygen that has at least 10% condition left, if we are inside a friendly sub.
251  // The margin helps us to survive, because we might need some oxygen before we can find more oxygen.
252  // When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks.
253  float min = 0.01f;
254  float minOxygen = character.IsInFriendlySub ? MIN_OXYGEN : min;
255  if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag(Tags.OxygenSource) && i.ConditionPercentage >= minOxygen))
256  {
257  // There's a valid oxygen tank in the inventory -> no need to swap the tank too early.
258  minOxygen = min;
259  }
260  return minOxygen;
261  }
262  }
263 }
static float GetMinOxygen(Character character)
AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier=1)
static bool IsSuitablePressureProtection(Item item, Identifier tag, Character character)
override bool CheckObjectiveSpecific()
Should return whether the objective is completed or not.
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
virtual Vector2 WorldPosition
Definition: Entity.cs:49
static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable< Item > items, Identifier containedTag=default, float conditionPercentage=0, bool requireEquipped=false, bool recursive=true, Func< Item, bool > predicate=null)
Note: uses a single list for matching items. The item is reused each time when the method is called....
Item FindItem(Func< Item, bool > predicate, bool recursive)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
int? FindSuitableSubContainerIndex(Identifier itemTagOrIdentifier)
Returns the index of the first slot whose restrictions match the specified tag or identifier
const float PressureWarningThreshold
How many units before crush depth the pressure warning is shown
readonly float PressureProtection
How deep down does the item protect from pressure? Determined by status effects.
float GetRealWorldDepth(float worldPositionY)
Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav termina...