Client LuaCsForBarotrauma
ShipCommandManager.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
10  {
11  public readonly Character character;
13 
14  private bool active;
15  public bool Active
16  {
17  get { return active; }
18  set
19  {
20  active = value ? TryInitializeShipCommandManager() : value;
21  }
22  }
23 
25  {
26  get;
27  private set;
28  }
29 
31  {
32  get;
33  private set;
34  }
35 
36  private Steering steering;
37  public readonly List<Vector2> patrolPositions = new List<Vector2>();
38  public enum NavigationStates
39  {
40  Inactive,
41  Patrol,
43  }
44 
45  public NavigationStates NavigationState { get; private set; } = NavigationStates.Inactive;
46 
47  float navigationTimer = 0f;
48  private readonly float navigationInterval = 4f;
49 
50  float timeUntilRam;
51  private const float RamTimerMax = 17.5f;
52 
53  public readonly List<ShipIssueWorker> ShipIssueWorkers = new List<ShipIssueWorker>();
54  public const float MinimumIssueThreshold = 10f;
55  private const float IssueDevotionBuffer = 5f;
56 
57  private float decisionTimer = 6f;
58  private readonly float decisionInterval = 6f;
59 
60  private float timeSinceLastCommandDecision;
61  private float timeSinceLastNavigation;
62 
63  public readonly List<Character> AlliedCharacters = new List<Character>();
64  public readonly List<Character> EnemyCharacters = new List<Character>();
65 
66  private readonly List<ShipIssueWorker> attendedIssues = new List<ShipIssueWorker>();
67  private readonly List<ShipIssueWorker> availableIssues = new List<ShipIssueWorker>();
68  private readonly List<ShipGlobalIssue> shipGlobalIssues = new List<ShipGlobalIssue>();
69 
71  {
72  this.character = character;
74  }
75 
76  public void Update(float deltaTime)
77  {
78  if (!Active || character.IsHandcuffed) { return; }
79  decisionTimer -= deltaTime;
80  if (decisionTimer <= 0.0f)
81  {
82  UpdateCommandDecision(timeSinceLastCommandDecision);
83  decisionTimer = decisionInterval * Rand.Range(0.8f, 1.2f);
84  timeSinceLastCommandDecision = decisionTimer;
85  }
86 
87  navigationTimer -= deltaTime;
88  if (navigationTimer <= 0.0f)
89  {
90  UpdateNavigation(timeSinceLastNavigation);
91  navigationTimer = navigationInterval * Rand.Range(0.8f, 1.2f);
92  timeSinceLastNavigation = navigationTimer;
93  }
94  }
95 
96  public static void ShipCommandLog(string text)
97  {
98  if (GameSettings.CurrentConfig.VerboseLogging)
99  {
100  DebugConsole.NewMessage(text);
101  }
102  }
103 
104  static bool WithinRange(float range, float distanceSquared)
105  {
106  return range * range > distanceSquared;
107  }
108 
109  void UpdateNavigation(float timeSinceLastUpdate)
110  {
111  if (steering == null || EnemySubmarine == null)
112  {
113  return;
114  }
115 
116  float distanceSquaredEnemy = Vector2.DistanceSquared(CommandedSubmarine.WorldPosition, EnemySubmarine.WorldPosition);
117 
118  if (NavigationState != NavigationStates.Aggressive)
119  {
120  if (WithinRange(7000f, distanceSquaredEnemy))
121  {
122 #if DEBUG
123  ShipCommandLog("Ship " + CommandedSubmarine + " was within the aggro range of " + EnemySubmarine);
124 #endif
125  NavigationState = NavigationStates.Aggressive;
126  }
127  else if (WithinRange(40000f, distanceSquaredEnemy))
128  {
130  }
131  }
132 
133  if (NavigationState == NavigationStates.Aggressive)
134  {
136  if (WithinRange(8500f, distanceSquaredEnemy) && !WithinRange(1500f, distanceSquaredEnemy)) // if we are within enemy ship's range for ramTimerMax, try to ram them instead (if we're not already very close)
137  {
138  if (steering.AIRamTimer > 0f)
139  {
140 #if DEBUG
141  ShipCommandLog("Ship " + CommandedSubmarine + " was still ramming, " + steering.AIRamTimer + " left");
142 #endif
143  }
144  else
145  {
146  timeUntilRam -= timeSinceLastUpdate;
147 #if DEBUG
148  ShipCommandLog("Ship " + CommandedSubmarine + " was close enough to ram, " + timeUntilRam + " left until ramming");
149 #endif
150 
151  if (timeUntilRam <= 0f)
152  {
153 #if DEBUG
154  ShipCommandLog("Ship " + CommandedSubmarine + " is attempting to ram!");
155 #endif
156  steering.AIRamTimer = 50f;
157  timeUntilRam = RamTimerMax * Rand.Range(0.9f, 1.1f);
158  }
159  }
160  }
161  else
162  {
163  steering.AIRamTimer = 0f;
164  timeUntilRam = RamTimerMax * Rand.Range(0.9f, 1.1f);
165  }
166  }
167  else if (patrolPositions.Any())
168  {
169  float distanceSquaredPatrol = Vector2.DistanceSquared(CommandedSubmarine.WorldPosition, patrolPositions.First());
170 
171  if (WithinRange(7000f, distanceSquaredPatrol))
172  {
173  Vector2 lastPosition = patrolPositions.First();
174  patrolPositions.RemoveAt(0);
175  patrolPositions.Add(lastPosition);
176  }
177  steering.AITacticalTarget = patrolPositions.First();
178  }
179  }
180 
182  {
184  }
185 
186  void UpdateCommandDecision(float timeSinceLastUpdate)
187  {
188 
189 #if DEBUG
190  ShipCommandLog("Updating command for character " + character);
191 #endif
192 
193  shipGlobalIssues.ForEach(c => c.CalculateGlobalIssue());
194 
195  AlliedCharacters.Clear();
196  EnemyCharacters.Clear();
197 
198  bool isEmergency = false;
199 
200  foreach (Character potentialCharacter in Character.CharacterList)
201  {
202  if (!HumanAIController.IsActive(potentialCharacter)) { continue; }
203 
204  if (HumanAIController.IsFriendly(character, potentialCharacter, true) && potentialCharacter.AIController is HumanAIController)
205  {
206  if (AbleToTakeOrder(potentialCharacter))
207  {
208  AlliedCharacters.Add(potentialCharacter);
209  }
210  }
211  else
212  {
213  EnemyCharacters.Add(potentialCharacter);
214  if (potentialCharacter.Submarine == CommandedSubmarine) // if enemies are on board, don't issue normal orders anymore
215  {
216  isEmergency = true;
217  }
218  }
219  }
220 
221  attendedIssues.Clear();
222  availableIssues.Clear();
223 
224  foreach (ShipIssueWorker shipIssueWorker in ShipIssueWorkers)
225  {
226  float importance = shipIssueWorker.CalculateImportance(isEmergency);
227  if (shipIssueWorker.OrderAttendedTo(timeSinceLastUpdate))
228  {
229 #if DEBUG
230  ShipCommandLog("Current importance for " + shipIssueWorker + " was " + importance + " and it was already being attended by " + shipIssueWorker.OrderedCharacter);
231 #endif
232  InsertIssue(shipIssueWorker, attendedIssues);
233  }
234  else
235  {
236 #if DEBUG
237  ShipCommandLog("Current importance for " + shipIssueWorker + " was " + importance + " and it is not attended to");
238 #endif
239  shipIssueWorker.RemoveOrder();
240  InsertIssue(shipIssueWorker, availableIssues);
241  }
242  }
243 
244  static void InsertIssue(ShipIssueWorker issue, List<ShipIssueWorker> list)
245  {
246  int index = 0;
247  while (index < list.Count && list[index].Importance > issue.Importance)
248  {
249  index++;
250  }
251  list.Insert(index, issue);
252  }
253 
254  ShipIssueWorker mostImportantIssue = availableIssues.FirstOrDefault();
255 
256  float bestValue = 0f;
257  Character bestCharacter = null;
258 
259  if (mostImportantIssue != null && mostImportantIssue.Importance >= MinimumIssueThreshold)
260  {
261  IEnumerable<Character> bestCharacters = CrewManager.GetCharactersSortedForOrder(mostImportantIssue.SuggestedOrder, AlliedCharacters, character, true);
262 
263  foreach (Character orderedCharacter in bestCharacters)
264  {
265  float issueApplicability = mostImportantIssue.Importance;
266 
267  // prefer not to switch if not qualified
268  issueApplicability *= mostImportantIssue.SuggestedOrder.AppropriateJobs.Contains(orderedCharacter.Info.Job.Prefab.Identifier) ? 1f : 0.75f;
269 
270  ShipIssueWorker occupiedIssue = attendedIssues.FirstOrDefault(i => i.OrderedCharacter == orderedCharacter);
271 
272  if (occupiedIssue != null)
273  {
274  if (occupiedIssue.GetType() == mostImportantIssue.GetType() && mostImportantIssue is ShipIssueWorkerGlobal && occupiedIssue is ShipIssueWorkerGlobal)
275  {
276  continue;
277  }
278 
279  // reverse redundancy to ensure certain issues can be switched over easily (operating weapons)
280  if (mostImportantIssue.AllowEasySwitching && occupiedIssue.AllowEasySwitching)
281  {
282  issueApplicability /= mostImportantIssue.CurrentRedundancy;
283  }
284 
285  // give slight preference if not qualified for current job
286  issueApplicability += occupiedIssue.SuggestedOrder.AppropriateJobs.Contains(orderedCharacter.Info.Job.Prefab.Identifier) ? 0 : 7.5f;
287 
288  // prefer not to switch orders unless considerably more important
289  issueApplicability -= IssueDevotionBuffer;
290 
291  if (issueApplicability + IssueDevotionBuffer < occupiedIssue.Importance)
292  {
293  continue;
294  }
295  }
296 
297  // prefer first one in bestCharacters in tiebreakers
298  if (issueApplicability > bestValue)
299  {
300  bestValue = issueApplicability;
301  bestCharacter = orderedCharacter;
302  }
303  }
304  }
305 
306  if (bestCharacter != null && mostImportantIssue != null)
307  {
308 #if DEBUG
309  ShipCommandLog("Setting " + mostImportantIssue + " for character " + bestCharacter);
310 #endif
311  mostImportantIssue.SetOrder(bestCharacter);
312  }
313  else // if we didn't give an order, let's try to dismiss someone instead
314  {
315  foreach (ShipIssueWorker shipIssueWorker in ShipIssueWorkers)
316  {
317  if (shipIssueWorker.Importance <= 0f && shipIssueWorker.OrderAttendedTo())
318  {
319 #if DEBUG
320  ShipCommandLog("Dismissing " + shipIssueWorker + " for character " + shipIssueWorker.OrderedCharacter);
321 #endif
322  var order = new Order(OrderPrefab.Dismissal, null).WithManualPriority(3).WithOrderGiver(character);
323  shipIssueWorker.OrderedCharacter.SetOrder(order, isNewOrder: true);
324  shipIssueWorker.RemoveOrder();
325  break;
326  }
327  }
328  }
329  }
330 
331  bool TryInitializeShipCommandManager()
332  {
334 
335  if (CommandedSubmarine == null)
336  {
337  DebugConsole.ThrowError("TryInitializeShipCommandManager failed: CommandedSubmarine was null for character " + character);
338  return false;
339  }
340 
341  EnemySubmarine = Submarine.MainSubs[0] == CommandedSubmarine ? Submarine.MainSubs[1] : Submarine.MainSubs[0];
342 
343  if (EnemySubmarine == null)
344  {
345  DebugConsole.ThrowError("TryInitializeShipCommandManager failed: EnemySubmarine was null for character " + character);
346  return false;
347  }
348 
349  timeUntilRam = RamTimerMax * Rand.Range(0.9f, 1.1f);
350 
351  ShipIssueWorkers.Clear();
352 
353  if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag(Tags.Reactor) && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
354  {
355  var order = new Order(OrderPrefab.Prefabs["operatereactor"], "powerup".ToIdentifier(), reactor.Item, reactor);
356  ShipIssueWorkers.Add(new ShipIssueWorkerPowerUpReactor(this, order));
357  }
358 
359  if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag(Tags.NavTerminal) && !i.NonInteractable) is Item nav && nav.GetComponent<Steering>() is Steering steeringComponent)
360  {
361  steering = steeringComponent;
362  var order = new Order(OrderPrefab.Prefabs["steer"], "navigatetactical".ToIdentifier(), nav, steeringComponent);
363  ShipIssueWorkers.Add(new ShipIssueWorkerSteer(this, order));
364  }
365 
366  foreach (Item item in CommandedSubmarine.GetItems(true).FindAll(i => i.HasTag(Tags.Turret) && !i.HasTag(Tags.Hardpoint)))
367  {
368  var order = new Order(OrderPrefab.Prefabs["operateweapons"], item, item.GetComponent<Turret>());
369  ShipIssueWorkers.Add(new ShipIssueWorkerOperateWeapons(this, order));
370  }
371 
372  int crewSizeModifier = 2;
373  // these issueworkers revolve around a singular, shared issue, which is injected into them to prevent redundant calculations
374  ShipGlobalIssueFixLeaks shipGlobalIssueFixLeaks = new ShipGlobalIssueFixLeaks(this);
375  for (int i = 0; i < crewSizeModifier; i++)
376  {
377  var order = OrderPrefab.Prefabs["fixleaks"].CreateInstance(OrderPrefab.OrderTargetType.Entity);
378  ShipIssueWorkers.Add(new ShipIssueWorkerFixLeaks(this, order, shipGlobalIssueFixLeaks));
379  }
380  shipGlobalIssues.Add(shipGlobalIssueFixLeaks);
381 
382  ShipGlobalIssueRepairSystems shipGlobalIssueRepairSystems = new ShipGlobalIssueRepairSystems(this);
383  for (int i = 0; i < crewSizeModifier; i++)
384  {
385  var order = OrderPrefab.Prefabs["repairsystems"].CreateInstance(OrderPrefab.OrderTargetType.Entity);
386  ShipIssueWorkers.Add(new ShipIssueWorkerRepairSystems(this, order, shipGlobalIssueRepairSystems));
387  }
388  shipGlobalIssues.Add(shipGlobalIssueRepairSystems);
389 
390  return true;
391  }
392  }
393 }
Submarine Submarine
Definition: Entity.cs:53
static bool IsActive(Character c)
readonly HumanAIController humanAIController
ShipCommandManager(Character character)
void Update(float deltaTime)
readonly List< Character > EnemyCharacters
static void ShipCommandLog(string text)
readonly List< Vector2 > patrolPositions
readonly List< ShipIssueWorker > ShipIssueWorkers
bool AbleToTakeOrder(Character character)
readonly List< Character > AlliedCharacters
List< Item > GetItems(bool alsoFromConnectedSubs)