Client LuaCsForBarotrauma
AIObjectiveCheckStolenItems.cs
1 #nullable enable
2 using FarseerPhysics;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
12  public override Identifier Identifier { get; set; } = "check stolen items".ToIdentifier();
13  protected override bool AllowOutsideSubmarine => false;
14  protected override bool AllowInAnySub => false;
15 
16  public float FindStolenItemsProbability = 1.0f;
17 
18  enum State
19  {
20  GotoTarget,
21  Inspect,
22  Warn,
23  Done
24  }
25 
26  private const float InspectTime = 5.0f;
27  private const float NormalWarnDelay = 5.0f;
28  private const float CriminalWarnDelay = 3.0f;
29  private float inspectTimer;
30  private float warnTimer;
31  private float currentWarnDelay;
32 
33  private State currentState;
34 
35  public readonly Character Target;
36 
37  private AIObjectiveGoTo? goToObjective;
38 
39  private readonly List<Item> stolenItems = new List<Item>();
40 
42  base(character, objectiveManager, priorityModifier)
43  {
44  Target = target;
45  InitTimers();
46  }
47 
48  protected override bool CheckObjectiveSpecific() => false;
49 
50  protected override float GetPriority()
51  {
53  {
54  // Target is climbing -> stop following the objective (soft abandon, without ignoring the target).
55  Priority = 0;
56  }
57  else if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
58  {
60  }
61  else
62  {
64  }
65  return Priority;
66  }
67 
68  public void ForceComplete()
69  {
70  IsCompleted = true;
71  }
72 
73  protected override void Act(float deltaTime)
74  {
75  switch (currentState)
76  {
77  case State.Done:
78  IsCompleted = true;
79  break;
80  case State.GotoTarget:
81  TryAddSubObjective(ref goToObjective,
82  constructor: () => new AIObjectiveGoTo(Target, character, objectiveManager, repeat: false)
83  {
84  SpeakIfFails = false
85  },
86  onCompleted: () =>
87  {
88  RemoveSubObjective(ref goToObjective);
90  {
91  // Shouldn't start inspecting characters when they climb, nor get here, because the priority should be 0,
92  // but if this still happens, we'll have to abandon the objective
93  // because it's not currently possible to hold to characters and ladders at the same time.
94  Abandon = true;
95  }
96  else
97  {
98  currentState = State.Inspect;
99  stolenItems.Clear();
100  Target.Inventory.FindAllItems(it => it.Illegitimate, recursive: true, stolenItems);
101  character.Speak(TextManager.Get(Target.IsCriminal ? "dialogcheckstolenitems.criminal" : "dialogcheckstolenitems").Value);
102  }
103  },
104  onAbandon: () =>
105  {
106  Abandon = true;
107  });
108  break;
109  case State.Inspect:
110  Inspect(deltaTime);
111  break;
112  case State.Warn:
113  Warn(deltaTime);
114  break;
115  }
116  }
117 
118  private void Inspect(float deltaTime)
119  {
120  if (inspectTimer > 0.0f)
121  {
122  Vector2 diff = Target.WorldPosition - character.WorldPosition;
123  float dist = diff.Length();
124  float maxDist = ConvertUnits.ToDisplayUnits(HumanoidAnimController.BreakFromGrabDistance);
125  if (dist > maxDist)
126  {
127  if (dist > maxDist * 2 || !character.CanSeeTarget(Target, seeThroughWindows: false))
128  {
129  //too far to reach by small manual movement, need to switch back to the earlier state
130  currentState = State.GotoTarget;
131  }
132  //move closer horizontally if the horizontal distance is the issue
133  else if (Math.Abs(diff.X) > Math.Abs(diff.Y) * 2.0f)
134  {
135  character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(MathF.Sign(Target.WorldPosition.X - character.WorldPosition.X), 0.0f));
136  }
137  else
138  {
140  }
141  return;
142  }
143  else
144  {
145  if (dist < maxDist * 0.5f) { character.AIController.SteeringManager.Reset(); }
147  }
148 
149  inspectTimer -= deltaTime;
150  if (inspectTimer < InspectTime - 1)
151  {
152  if (Math.Abs(Target.AnimController.TargetMovement.X) > 1.0f)
153  {
154  // If the target moves, tell to hold still
155  character.Speak(TextManager.Get("dialogcheckstolenitems.holdstill").Value, identifier: "holdstill".ToIdentifier(), minDurationBetweenSimilar: 3f);
156  }
157  }
158  return;
159  }
160 
162  {
163  //target not selected -> must've escaped
164  Abandon = true;
165  return;
166  }
167 
168  if (stolenItems.Any() &&
169  Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < FindStolenItemsProbability)
170  {
171  character.Speak(TextManager.Get("dialogcheckstolenitems.warn").Value);
172  currentState = State.Warn;
173  }
174  else
175  {
176  character.Speak(TextManager.Get(Target.IsCriminal ? "dialogcheckstolenitems.nostolenitems.criminal" : "dialogcheckstolenitems.nostolenitems").Value);
177  currentState = State.Done;
178  IsCompleted = true;
179  }
181  }
182 
183  private void Warn(float deltaTime)
184  {
185  if (warnTimer > 0.0f)
186  {
187  warnTimer -= deltaTime;
188  return;
189  }
190  var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == Target);
191  if (stolenItemsOnCharacter.Any())
192  {
193  character.Speak(TextManager.Get(character.IsCriminal ? "dialogcheckstolenitems.arrest.criminal" : "dialogcheckstolenitems.arrest").Value);
194  Arrest(abortWhenItemsDropped: true, allowHoldFire: true);
195  foreach (var stolenItem in stolenItemsOnCharacter)
196  {
198  }
199  }
200  else
201  {
202  character.Speak(TextManager.Get("dialogcheckstolenitems.comply").Value);
203  }
204  foreach (var item in stolenItems)
205  {
206  HumanAIController.ObjectiveManager.AddObjective(new AIObjectiveGetItem(character, item, objectiveManager, equip: false)
207  {
208  BasePriority = 10
209  });
210  }
211  currentState = State.Done;
212  IsCompleted = true;
213  }
214 
215  private void Arrest(bool abortWhenItemsDropped, bool allowHoldFire)
216  {
217  bool isCriminal = Target.IsCriminal;
218  Func<AIObjective, bool>? abortCondition = null;
219  if (abortWhenItemsDropped && !isCriminal)
220  {
221  abortCondition = obj => Target.Inventory.FindItem(it => it.Illegitimate, recursive: true) == null;
222  }
223  HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, Target, allowHoldFire: allowHoldFire && !isCriminal, speakWarnings: !isCriminal, abortCondition: abortCondition);
224  }
225 
226  public override void OnDeselected()
227  {
228  base.OnDeselected();
230  }
231 
232  public override void Reset()
233  {
234  base.Reset();
235  currentState = State.GotoTarget;
236  InitTimers();
237  }
238 
239  private void InitTimers()
240  {
241  inspectTimer = InspectTime;
242  currentWarnDelay = Target.IsCriminal ? CriminalWarnDelay : NormalWarnDelay;
243  warnTimer = currentWarnDelay;
244  }
245  }
246 }
AIObjectiveCheckStolenItems(Character character, Character target, AIObjectiveManager objectiveManager, float priorityModifier=1)
override bool CheckObjectiveSpecific()
Should return whether the objective is completed or not.
float Priority
Final priority value after all calculations.
void AddObjective(AIObjective objective)
const float LowestOrderPriority
Maximum priority of an order given to the character (rightmost order in the crew list)
float GetOrderPriority(AIObjective objective)
bool IsOrder(AIObjective objective)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
bool IsCriminal
Do the outpost security officers treat the character as a criminal? Triggers when the character has e...
bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity=null, bool seeThroughWindows=false, bool checkFacing=false)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
void AddCombatObjective(AIObjectiveCombat.CombatMode mode, Character target, float delay=0, Func< AIObjective, bool > abortCondition=null, Action onAbort=null, Action onCompleted=null, bool allowHoldFire=false, bool speakWarnings=false)
Item FindItem(Func< Item, bool > predicate, bool recursive)
List< Item > FindAllItems(Func< Item, bool > predicate=null, bool recursive=false, List< Item > list=null)
void SteeringManual(float deltaTime, Vector2 velocity)