Client LuaCsForBarotrauma
AIObjectiveRepairItem.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Linq;
6 
7 namespace Barotrauma
8 {
10  {
11  public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
12 
13  protected override bool AllowInFriendlySubs => true;
14  public override bool KeepDivingGearOn => Item?.CurrentHull == null;
15  protected override bool AllowWhileHandcuffed => false;
16 
17  public Item Item { get; private set; }
18 
19  private AIObjectiveGoTo goToObjective;
20  private AIObjectiveContainItem refuelObjective;
21  private RepairTool repairTool;
22 
23  private const float WaitTimeBeforeRepair = 0.5f;
24  private float waitTimer;
25 
26  private bool IsRepairing() => IsRepairing(character, Item);
27  private readonly bool isPriority;
28 
29  public static bool IsRepairing(Character character, Item item) => character.SelectedItem == item && item.Repairables.Any(r => r.CurrentFixer == character);
30 
31  public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
32  : base(character, objectiveManager, priorityModifier)
33  {
34  Item = item;
35  this.isPriority = isPriority;
36  }
37 
38  protected override float GetPriority()
39  {
40  if (!IsAllowed) { HandleDisallowed(); }
42  {
43  Abandon = true;
44  }
45  if (Abandon)
46  {
47  if (IsRepairing())
48  {
49  Item.Repairables.ForEach(r => r.StopRepairing(character));
50  }
51  return Priority;
52  }
54  {
55  Priority = 0;
56  IsCompleted = true;
57  }
59  {
60  Priority = 0;
61  }
62  else
63  {
64  float distanceFactor = 1;
65  if (!isPriority && Item.CurrentHull != character.CurrentHull)
66  {
67  distanceFactor = GetDistanceFactor(Item.WorldPosition, factorAtMaxDistance: 0.25f, verticalDistanceMultiplier: 5, maxDistance: 4000);
68  }
69  float requiredSuccessFactor = objectiveManager.HasOrder<AIObjectiveRepairItems>() ? 0 : AIObjectiveRepairItems.RequiredSuccessFactor;
70  float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor) / 100;
71  bool isSelected = IsRepairing();
72  float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
73  float devotion = (CumulatedDevotion + selectedBonus) / 100;
74  float reduction = isPriority ? 1 : isSelected ? 2 : 3;
75  float max = AIObjectiveManager.LowestOrderPriority - reduction;
76  float highestWeight = -1;
77  foreach (Identifier tag in Item.Prefab.Tags)
78  {
79  if (JobPrefab.ItemRepairPriorities.TryGetValue(tag, out float weight) && weight > highestWeight)
80  {
81  highestWeight = weight;
82  }
83  }
84  if (highestWeight == -1)
85  {
86  // Predefined weight not found.
87  highestWeight = 1;
88  }
89  Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * highestWeight * PriorityModifier), 0, 1));
90  }
91  return Priority;
92  }
93 
94  protected override bool CheckObjectiveState()
95  {
97  if (character.IsOnPlayerTeam && IsCompleted && IsRepairing())
98  {
99  character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "itemrepaired".ToIdentifier(), 10.0f);
100  }
101  return IsCompleted;
102  }
103 
104  protected override void Act(float deltaTime)
105  {
106  // Only continue when the get item sub objectives have been completed.
107  if (subObjectives.Any()) { return; }
108  foreach (Repairable repairable in Item.Repairables)
109  {
110  if (!repairable.HasRequiredItems(character, false))
111  {
112  //make sure we have all the items required to fix the target item
113  foreach (var kvp in repairable.RequiredItems)
114  {
115  foreach (RelatedItem requiredItem in kvp.Value)
116  {
117  var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, equip: true)
118  {
119  AllowVariants = requiredItem.AllowVariants
120  };
121  if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
122  {
124  {
125  getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair").Value, null, 0.0f, "dialogcannotfindrequireditemtorepair".ToIdentifier(), 10.0f);
126  }
127  }
128  subObjectives.Add(getItemObjective);
129  }
130  }
131  return;
132  }
133  }
134  if (repairTool == null)
135  {
136  FindRepairTool();
137  }
138  if (repairTool != null)
139  {
140  if (repairTool.RequiredItems.TryGetValue(RelatedItem.RelationType.Contained, out var requiredItems))
141  {
142  if (repairTool.Item.OwnInventory == null)
143  {
144 #if DEBUG
145  DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"{repairTool}\" has no proper inventory.");
146 #endif
147  Abandon = true;
148  return;
149  }
150  RelatedItem item = null;
151  Item fuel = null;
152  foreach (RelatedItem requiredItem in requiredItems)
153  {
154  item = requiredItem;
155  fuel = repairTool.Item.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
156  if (fuel != null) { break; }
157  }
158  if (fuel == null)
159  {
160  RemoveSubObjective(ref goToObjective);
161  TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
162  {
163  RemoveExisting = true
164  },
165  onCompleted: () => RemoveSubObjective(ref refuelObjective),
166  onAbandon: () => Abandon = true);
167  return;
168  }
169  }
170  }
171  if (character.CanInteractWith(Item, out _, checkLinked: false))
172  {
173  waitTimer += deltaTime;
174  if (waitTimer < WaitTimeBeforeRepair) { return; }
175 
177 
178  bool repairThroughRepairInterface = false;
179  foreach (Repairable repairable in Item.Repairables)
180  {
181  if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
182  {
183  // Someone else is repairing the target. Abandon the objective if the other is better at this than us.
184  Abandon = repairable.CurrentFixer.IsPlayer || repairable.DegreeOfSuccess(character) < repairable.DegreeOfSuccess(repairable.CurrentFixer);
185  }
186  if (!Abandon)
187  {
188  if (character.SelectedItem != Item)
189  {
190  if (Item.TryInteract(character, forceUseKey: true) ||
191  Item.TryInteract(character, forceSelectKey: true))
192  {
194  repairThroughRepairInterface = true;
195  }
196  else
197  {
198  Abandon = true;
199  }
200  }
201  CheckPreviousCondition(deltaTime);
202  }
203  if (Abandon)
204  {
205  if (character.IsOnPlayerTeam && IsRepairing())
206  {
207  character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
208  }
209  repairable.StopRepairing(character);
210  }
211  else if (repairable.CurrentFixer != character)
212  {
213  repairable.StartRepairing(character, Repairable.FixActions.Repair);
214  }
215  else
216  {
217  repairThroughRepairInterface = true;
218  }
219  break;
220  }
221 
222  if (!repairThroughRepairInterface && repairTool != null && !Abandon)
223  {
224  OperateRepairTool(deltaTime);
225  }
226  }
227  else
228  {
229  waitTimer = 0.0f;
230  RemoveSubObjective(ref refuelObjective);
231  // If cannot reach the item, approach it.
232  TryAddSubObjective(ref goToObjective,
233  constructor: () =>
234  {
235  var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
236  {
237  TargetName = Item.Name,
238  SpeakCannotReachCondition = () => isPriority
239  };
240  if (repairTool != null)
241  {
242  objective.CloseEnough = AIObjectiveFixLeak.CalculateReach(repairTool, character);
243  }
244  return objective;
245  },
246  onAbandon: () =>
247  {
248  Abandon = true;
249  if (character.IsOnPlayerTeam && IsRepairing())
250  {
251  character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
252  }
253  });
254  }
255  }
256 
257  private const float conditionCheckDelay = 1;
258  private float conditionCheckTimer;
259  private float previousCondition;
260  private void CheckPreviousCondition(float deltaTime)
261  {
262  if (Item == null || Item.Removed) { return; }
263  conditionCheckTimer -= deltaTime;
264  if (conditionCheckTimer > 0) { return; }
265  conditionCheckTimer = conditionCheckDelay;
266  if (previousCondition > -1 && Item.Condition < previousCondition)
267  {
268  // If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
269  Abandon = true;
270  }
271  else
272  {
273  // If the previous condition is not yet stored or if it's valid (greater or equal to current condition), save the condition for the next check here.
274  previousCondition = Item.Condition;
275  }
276  }
277 
278  private void FindRepairTool()
279  {
280  foreach (Repairable repairable in Item.Repairables)
281  {
282  foreach (var kvp in repairable.RequiredItems)
283  {
284  foreach (RelatedItem requiredItem in kvp.Value)
285  {
286  foreach (var item in character.Inventory.AllItems)
287  {
288  if (requiredItem.MatchesItem(item))
289  {
290  repairTool = item.GetComponent<RepairTool>();
291  }
292  }
293  }
294  }
295  }
296  }
297 
298  private void OperateRepairTool(float deltaTime)
299  {
301  if (character.Submarine != null)
302  {
304  }
305  if (repairTool.Item.RequireAimToUse)
306  {
307  character.SetInput(InputType.Aim, false, true);
308  }
309  Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position;
310  if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2))
311  {
312  // Too close -> steer away
314  }
315  else
316  {
318  }
319  if (VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
320  {
321  repairTool.Use(deltaTime, character);
322  }
323  }
324 
325  public override void Reset()
326  {
327  base.Reset();
328  goToObjective = null;
329  refuelObjective = null;
330  repairTool = null;
331  }
332  }
333 }
void FaceTarget(ISpatialEntity target)
static float CalculateReach(RepairTool repairTool, Character character)
float Priority
Final priority value after all calculations.
static float GetDistanceFactor(Vector2 selfPos, Vector2 targetWorldPos, float factorAtMaxDistance, float verticalDistanceMultiplier=3, float maxDistance=10000.0f, float factorAtMinDistance=1.0f)
Get a normalized value representing how close the target position is. The value is a rough estimation...
const float LowestOrderPriority
Maximum priority of an order given to the character (rightmost order in the crew list)
static bool IsRepairing(Character character, Item item)
override void Act(float deltaTime)
override bool CheckObjectiveState()
Should return whether the objective is completed or not.
AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier=1, bool isPriority=false)
override float GetTargetPriority()
Returns a priority value based on the current targets (e.g. high prio when there's lots of severe fir...
void SetInput(InputType inputType, bool hit, bool held)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Submarine Submarine
Definition: Entity.cs:53
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
bool IgnoreByAI(Character character)
bool TryInteract(Character user, bool ignoreRequiredItems=false, bool forceSelectKey=false, bool forceUseKey=false)
bool RequireAimToUse
If true, the user has to hold the "aim" key before use is registered. False by default.
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
override ImmutableHashSet< Identifier > Tags
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
float DegreeOfSuccess(Character character)
Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
Dictionary< RelatedItem.RelationType, List< RelatedItem > > RequiredItems
static IReadOnlyDictionary< Identifier, float > ItemRepairPriorities
Tag -> priority.
float TransformedRotation
Takes flipping (Dir) into account.
void SteeringManual(float deltaTime, Vector2 velocity)