Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/WayPoint.cs
2 using FarseerPhysics;
3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Xml.Linq;
10 
11 namespace Barotrauma
12 {
13  [Flags]
14  public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32, Disabled = 64 };
15 
16  partial class WayPoint : MapEntity
17  {
18  public static List<WayPoint> WayPointList = new List<WayPoint>();
19 
20  public static bool ShowWayPoints = true, ShowSpawnPoints = true;
21 
22  public const float LadderWaypointInterval = 75.0f;
23 
24  protected SpawnType spawnType;
25  private string[] idCardTags;
26  private ushort ladderId;
27  public Ladder Ladders;
28  public Structure Stairs;
29 
30  private HashSet<Identifier> tags;
31 
32  public bool IsObstructed;
33 
34  public bool IsInWater => CurrentHull == null || CurrentHull.Surface > Position.Y;
35 
36  // Waypoints linked to doors are traversable, unless they are obstructed, because we filter them out in the setter of Gap.Open.
37  // The only way to add the open gaps should be by calling OnGapStateSchanged.
38  public bool IsTraversable => !IsObstructed && (openGaps == null || openGaps.Count == 0 || IsInWater);
39 
40  private HashSet<Gap> openGaps;
46  public void OnGapStateChanged(bool open, Gap gap)
47  {
48  openGaps ??= new HashSet<Gap>();
49  if (open)
50  {
51  openGaps.Add(gap);
52  }
53  else
54  {
55  openGaps.Remove(gap);
56  }
57  }
58 
59  private ushort gapId;
61  {
62  get;
63  set;
64  }
65 
67  {
68  get { return ConnectedGap?.ConnectedDoor; }
69  }
70 
71  public Hull CurrentHull { get; private set; }
72 
74  public RuinGeneration.Ruin Ruin;
75 
77  {
78  get { return spawnType; }
79  set { spawnType = value; }
80  }
81 
82  public Point ExitPointSize { get; private set; }
83 
85  (int)WorldPosition.X - ExitPointSize.X / 2, (int)WorldPosition.Y + ExitPointSize.Y / 2,
87 
88  public Action<WayPoint> OnLinksChanged { get; set; }
89 
90  public override string Name
91  {
92  get
93  {
94  return spawnType == SpawnType.Path ? "WayPoint" : "SpawnPoint";
95  }
96  }
97 
98  public string IdCardDesc { get; private set; }
99  public string[] IdCardTags
100  {
101  get { return idCardTags; }
102  private set
103  {
104  idCardTags = value;
105  for (int i = 0; i < idCardTags.Length; i++)
106  {
107  idCardTags[i] = idCardTags[i].Trim().ToLowerInvariant();
108  }
109  }
110  }
111 
112  public IEnumerable<Identifier> Tags => tags;
113 
114  public JobPrefab AssignedJob { get; private set; }
115 
116  public WayPoint(Vector2 position, SpawnType spawnType, Submarine submarine, Gap gap = null)
117  : this(new Rectangle((int)position.X - 3, (int)position.Y + 3, 6, 6), submarine)
118  {
119  this.spawnType = spawnType;
120  ConnectedGap = gap;
121  }
122 
123  public WayPoint(MapEntityPrefab prefab, Rectangle rectangle)
124  : this (rectangle, Submarine.MainSub)
125  {
126  if (prefab.Identifier.Contains("spawn"))
127  {
128  spawnType = SpawnType.Human;
129  }
130  else
131  {
132  SpawnType = SpawnType.Path;
133  }
134 
135 #if CLIENT
137  {
138  SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
139  }
140 #endif
141  }
142 
143  public enum Type
144  {
145  WayPoint,
146  SpawnPoint
147  }
148 
149  public WayPoint(Rectangle newRect, Submarine submarine)
150  : this (Type.WayPoint, newRect, submarine)
151  {
152  }
153 
154  public WayPoint(Type type, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
155  : base (type is Type.WayPoint
156  ? CoreEntityPrefab.WayPointPrefab
157  : CoreEntityPrefab.SpawnPointPrefab, submarine, id)
158  {
159  rect = newRect;
160  idCardTags = Array.Empty<string>();
161  tags = new HashSet<Identifier>();
162 
163 #if CLIENT
164  if (iconSprites == null)
165  {
166  iconSprites = new Dictionary<string, Sprite>()
167  {
168  { "Path", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,0,128,128)) },
169  { "Human", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,0,128,128)) },
170  { "Enemy", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256,0,128,128)) },
171  { "Cargo", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) },
172  { "Corpse", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512,0,128,128)) },
173  { "Ladder", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,128,128,128)) },
174  { "Door", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,128,128,128)) },
175  { "Submarine", new Sprite("Content/UI/CommandUIBackground.png", new Rectangle(0,896,128,128)) },
176  { "ExitPoint", new Sprite("Content/UI/CommandUIBackground.png", new Rectangle(0,896,128,128)) }
177  };
178  }
179 #endif
180 
181  InsertToList();
182  WayPointList.Add(this);
183 
184  DebugConsole.Log("Created waypoint (" + ID + ")");
185 
186  FindHull();
187  }
188 
189  public override MapEntity Clone()
190  {
191  var clone = new WayPoint(rect, Submarine)
192  {
194  idCardTags = idCardTags,
195  tags = tags,
198  };
199 
200  return clone;
201  }
202 
203  public static bool GenerateSubWaypoints(Submarine submarine)
204  {
205  if (!Hull.HullList.Any())
206  {
207  DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
208  return false;
209  }
210 
211  List<WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path);
212  foreach (WayPoint wayPoint in existingWaypoints)
213  {
214  wayPoint.Remove();
215  }
216 
217  //find all open doors and temporarily activate their bodies to prevent visibility checks
218  //from ignoring the doors and generating waypoint connections that go straight through the door
219  List<Door> openDoors = new List<Door>();
220  foreach (Item item in Item.ItemList)
221  {
222  var door = item.GetComponent<Door>();
223  if (door != null && !door.Body.Enabled)
224  {
225  openDoors.Add(door);
226  door.Body.Enabled = true;
227  }
228  }
229  bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin".ToIdentifier());
230  float diffFromHullEdge = 50;
231  float minDist = 100.0f;
232  float heightFromFloor = 110.0f;
233  float hullMinHeight = 100;
234 
235  var removals = new HashSet<WayPoint>();
236  foreach (Hull hull in Hull.HullList)
237  {
238  if (isFlooded)
239  {
240  diffFromHullEdge = 75;
241  var hullWaypoints = new List<WayPoint>();
242  float top = hull.Rect.Y;
243  float bottom = hull.Rect.Y - hull.Rect.Height;
244  if (hull.Rect.Width < 300 || hull.Rect.Height < 300)
245  {
246  // For narrow hulls, create one line of waypoints either horizontally or vertically
247  if (hull.Rect.Width > hull.Rect.Height)
248  {
249  // Horizontal
250  float y = hull.Rect.Y - hull.Rect.Height / 2;
251  for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
252  {
253  hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
254  }
255  }
256  else
257  {
258  // Vertical
259  float x = hull.Rect.X + hull.Rect.Width / 2;
260  for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
261  {
262  hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
263  }
264  }
265  }
266  if (hullWaypoints.None())
267  {
268  // Try to create a grid-like network of waypoints
269  for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
270  {
271  for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist)
272  {
273  hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine));
274  }
275  }
276  if (hullWaypoints.None())
277  {
278  // If that fails, just create one waypoint at the center.
279  hullWaypoints.Add(new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height / 2), SpawnType.Path, submarine));
280  }
281  foreach (WayPoint wp in hullWaypoints)
282  {
283  foreach (Structure wall in Structure.WallList)
284  {
285  if (wall.HasBody)
286  {
287  // Remove waypoints that are too close/inside the walls.
288  Rectangle rect = wall.Rect;
289  rect.Inflate(10, 10);
290  if (rect.ContainsWorld(wp.Position))
291  {
292  removals.Add(wp);
293  }
294  }
295  }
296  }
297  }
298  // Connect the waypoints
299  foreach (var wayPoint in hullWaypoints)
300  {
301  for (int dir = -1; dir <= 1; dir += 2)
302  {
303  WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.9f, minDist));
304  if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
305  {
306  wayPoint.ConnectTo(closest);
307  }
308  closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(minDist, minDist * 1.9f));
309  if (closest != null && closest.CurrentHull == wayPoint.CurrentHull)
310  {
311  wayPoint.ConnectTo(closest);
312  }
313  }
314  }
315  }
316  else
317  {
318  if (hull.Rect.Height < hullMinHeight) { continue; }
319  // Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
320  Body floor = null;
321  for (int i = 0; i < 5; i++)
322  {
323  float horizontalOffset = 0;
324  switch (i)
325  {
326  case 1:
327  horizontalOffset = hull.RectWidth * 0.2f;
328  break;
329  case 2:
330  horizontalOffset = hull.RectWidth * 0.4f;
331  break;
332  case 3:
333  horizontalOffset = -hull.RectWidth * 0.2f;
334  break;
335  case 4:
336  horizontalOffset = -hull.RectWidth * 0.4f;
337  break;
338  }
339  horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
340  Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
341  floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
342  if (floor != null) { break; }
343  }
344  if (floor == null) { continue; }
345  float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
346  if (hull.Rect.Width < diffFromHullEdge * 3.0f)
347  {
348  new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
349  }
350  else
351  {
352  WayPoint previousWaypoint = null;
353  for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
354  {
355  var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
356  // Too close to stairs, will be assigned as a stair point -> remove
357  if (wayPoint.FindStairs() != null)
358  {
359  removals.Add(wayPoint);
360  continue;
361  }
362  if (previousWaypoint != null)
363  {
364  wayPoint.ConnectTo(previousWaypoint);
365  }
366  previousWaypoint = wayPoint;
367  }
368  if (previousWaypoint == null)
369  {
370  // Ensure that we always create at least one waypoint per hull.
371  new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
372  }
373  }
374  }
375  }
376 
377  // Platforms
378  foreach (Structure platform in Structure.WallList)
379  {
380  if (!platform.IsPlatform) { continue; }
381  float waypointHeight = heightFromFloor;
382  WayPoint prevWaypoint = null;
383  for (float x = platform.Rect.X + diffFromHullEdge; x <= platform.Rect.Right - diffFromHullEdge; x += minDist)
384  {
385  WayPoint wayPoint = new WayPoint(new Vector2(x, platform.Rect.Y + waypointHeight), SpawnType.Path, submarine);
386  if (prevWaypoint != null)
387  {
388  wayPoint.ConnectTo(prevWaypoint);
389  }
390  // If the waypoint is close to hull waypoints, remove it.
391  if (wayPoint != null)
392  {
393  for (int dir = -1; dir <= 1; dir += 2)
394  {
395  if (wayPoint.FindClosest(dir, horizontalSearch: true, tolerance: new Vector2(minDist, heightFromFloor), ignored: prevWaypoint.ToEnumerable()) != null)
396  {
397  wayPoint.Remove();
398  wayPoint = null;
399  break;
400  }
401  }
402  }
403  prevWaypoint = wayPoint;
404  }
405  }
406 
407  float outSideWaypointInterval = 100.0f;
408  if (!isFlooded && submarine.Info.Type != SubmarineType.OutpostModule)
409  {
410  List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>();
411 
412  Rectangle borders = Hull.GetBorders();
413  int originalWidth = borders.Width;
414  int originalHeight = borders.Height;
415  borders.X -= Math.Min(500, originalWidth / 4);
416  borders.Y += Math.Min(500, originalHeight / 4);
417  borders.Width += Math.Min(1500, originalWidth / 2);
418  borders.Height += Math.Min(1000, originalHeight / 2);
419  borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
420 
421  if (borders.Width <= outSideWaypointInterval * 2)
422  {
423  borders.Inflate(outSideWaypointInterval * 2 - borders.Width, 0);
424  }
425 
426  if (borders.Height <= outSideWaypointInterval * 2)
427  {
428  int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height;
429  borders.Y += inflateAmount / 2;
430  borders.Height += inflateAmount;
431  }
432 
433  WayPoint[,] cornerWaypoint = new WayPoint[2, 2];
434  for (int i = 0; i < 2; i++)
435  {
436  for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval)
437  {
438  var wayPoint = new WayPoint(
439  new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
440  SpawnType.Path, submarine);
441 
442  outsideWaypoints.Add((wayPoint, i));
443 
444  if (x == borders.X + outSideWaypointInterval)
445  {
446  cornerWaypoint[i, 0] = wayPoint;
447  }
448  else
449  {
450  wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]);
451  }
452  }
453 
454  cornerWaypoint[i, 1] = WayPointList[WayPointList.Count - 1];
455  }
456 
457  for (int i = 0; i < 2; i++)
458  {
459  WayPoint wayPoint = null;
460  for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval)
461  {
462  wayPoint = new WayPoint(
463  new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
464  SpawnType.Path, submarine);
465 
466  outsideWaypoints.Add((wayPoint, i));
467 
468  if (y == borders.Y - borders.Height)
469  {
470  wayPoint.ConnectTo(cornerWaypoint[1, i]);
471  }
472  else
473  {
474  wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]);
475  }
476  }
477 
478  wayPoint.ConnectTo(cornerWaypoint[0, i]);
479  }
480 
481  Vector2 center = ConvertUnits.ToSimUnits(submarine.HiddenSubPosition);
482  float halfHeight = ConvertUnits.ToSimUnits(borders.Height / 2);
483  // Try to move the waypoints so that they are near the walls, roughly following the shape of the sub.
484  foreach (var wayPoint in outsideWaypoints)
485  {
486  WayPoint wp = wayPoint.Item1;
487  float xDiff = center.X - wp.SimPosition.X;
488  Vector2 targetPos = new Vector2(center.X - xDiff * 0.5f, center.Y);
489  Body wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
490  if (wall == null)
491  {
492  // Try again, and shoot to the center now. It happens with some subs that the first, offset raycast don't hit the walls.
493  targetPos = new Vector2(center.X - xDiff, center.Y);
494  wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
495  }
496  if (wall != null)
497  {
498  float distanceFromWall = 1;
499  if (xDiff > 0 && !submarine.Info.HasTag(SubmarineTag.Shuttle))
500  {
501  // We don't want to move the waypoints near the tail too close to the engine.
502  float yDist = Math.Abs(center.Y - wp.SimPosition.Y);
503  distanceFromWall = MathHelper.Lerp(1, 3, MathUtils.InverseLerp(halfHeight, 0, yDist));
504  }
505  Vector2 newPos = Submarine.LastPickedPosition + Submarine.LastPickedNormal * distanceFromWall;
506  wp.rect = new Rectangle(ConvertUnits.ToDisplayUnits(newPos).ToPoint(), wp.rect.Size);
507  wp.FindHull();
508  }
509  }
510  // Remove unwanted points
511  WayPoint previous = null;
512  float tooClose = outSideWaypointInterval / 2;
513  foreach (var wayPoint in outsideWaypoints)
514  {
515  WayPoint wp = wayPoint.Item1;
516  if (wp.CurrentHull != null ||
517  Submarine.PickBody(wp.SimPosition, wp.SimPosition + Vector2.Normalize(center - wp.SimPosition) * 0.1f, collisionCategory: Physics.CollisionWall | Physics.CollisionItem, customPredicate: f => !(f.Body.UserData is Submarine), allowInsideFixture: true) != null)
518  {
519  // Remove waypoints that got inside/too near the sub.
520  removals.Add(wp);
521  previous = wp;
522  continue;
523  }
524  foreach (var otherWayPoint in outsideWaypoints)
525  {
526  WayPoint otherWp = otherWayPoint.Item1;
527  if (otherWp == wp) { continue; }
528  if (removals.Contains(otherWp)) { continue; }
529  float sqrDist = Vector2.DistanceSquared(wp.Position, otherWp.Position);
530  // Remove waypoints that are too close to each other.
531  if (!removals.Contains(previous) && sqrDist < tooClose * tooClose)
532  {
533  removals.Add(wp);
534  }
535  }
536  previous = wp;
537  }
538  foreach (WayPoint wp in removals)
539  {
540  outsideWaypoints.RemoveAll(w => w.Item1 == wp);
541  }
542  removals.ForEach(wp => wp.Remove());
543  for (int i = 0; i < outsideWaypoints.Count; i++)
544  {
545  WayPoint current = outsideWaypoints[i].Item1;
546  if (current.linkedTo.Count(l => !removals.Contains(l)) > 1) { continue; }
547  WayPoint next = null;
548  int maxConnections = 2;
549  float tooFar = outSideWaypointInterval * 5;
550  for (int j = 0; j < maxConnections; j++)
551  {
552  if (current.linkedTo.Count >= maxConnections) { break; }
553  tooFar /= current.linkedTo.Count(l => !removals.Contains(l));
554  next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp.Item1 != next && wp.Item1.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.Item1.linkedTo.Count < 2 && wp.Item2 < i);
555  if (next != null)
556  {
557  current.ConnectTo(next);
558  }
559  }
560  }
561  }
562  removals.ForEach(wp => wp.Remove());
563  removals.Clear();
564  // Stairs
565  foreach (MapEntity mapEntity in MapEntityList.ToList())
566  {
567  if (!(mapEntity is Structure structure)) { continue; }
568  if (structure.StairDirection == Direction.None) { continue; }
569  WayPoint[] stairPoints = new WayPoint[3];
570  float margin = -32;
571 
572  stairPoints[0] = new WayPoint(new Vector2(
573  structure.Rect.X + 5,
574  structure.Rect.Y - (structure.StairDirection == Direction.Left ? margin : structure.Rect.Height - 100)), SpawnType.Path, submarine);
575 
576  stairPoints[1] = new WayPoint(new Vector2(
577  structure.Rect.Right - 5,
578  structure.Rect.Y - (structure.StairDirection == Direction.Left ? structure.Rect.Height - 100 : margin)), SpawnType.Path, submarine);
579 
580  for (int i = 0; i < 2; i++)
581  {
582  for (int dir = -1; dir <= 1; dir += 2)
583  {
584  WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.5f, minDist / 2));
585  if (closest == null) { continue; }
586  stairPoints[i].ConnectTo(closest);
587  }
588  }
589 
590  stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine);
591  stairPoints[0].ConnectTo(stairPoints[2]);
592  stairPoints[2].ConnectTo(stairPoints[1]);
593  stairPoints.ForEach(wp => wp.FindStairs());
594  }
595 
596  // Ladders
597  foreach (Item item in Item.ItemList)
598  {
599  var ladders = item.GetComponent<Ladder>();
600  if (ladders == null) { continue; }
601 
602  Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10);
603  List<(WayPoint wp, bool connectHullPoints)> ladderPoints = new List<(WayPoint, bool)>
604  {
605  (new WayPoint(bottomPoint, SpawnType.Path, submarine), true)
606  };
607 
608  List<Body> ignoredBodies = new List<Body>();
609  // Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either.
610  // Start point is where the bots normally grasp the ladder when they stand on ground.
611  WayPoint lowestPoint = ladderPoints[0].wp;
612  WayPoint prevPoint = lowestPoint;
613  Vector2 prevPos = prevPoint.SimPosition;
614  Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies,
615  collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform | Physics.CollisionStairs,
616  customPredicate: f => !(f.Body.UserData is Submarine));
617  float startHeight = ground != null ? ConvertUnits.ToDisplayUnits(ground.Position.Y) : bottomPoint.Y;
618  startHeight += heightFromFloor;
619  WayPoint startPoint = lowestPoint;
620  Vector2 nextPos = new Vector2(item.Rect.Center.X, startHeight);
621  // Don't create the start point if it's too close to the lowest point or if it's outside of the sub.
622  // If we skip creating the start point, the lowest point is used instead.
623  if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null)
624  {
625  startPoint = new WayPoint(nextPos, SpawnType.Path, submarine);
626  ladderPoints.Add((startPoint, true));
627  if (lowestPoint != null)
628  {
629  startPoint.ConnectTo(lowestPoint);
630  }
631  prevPoint = startPoint;
632  prevPos = prevPoint.SimPosition;
633  }
634  for (float y = startPoint.Position.Y + LadderWaypointInterval; y < item.Rect.Y - 1.0f; y += LadderWaypointInterval)
635  {
636  //first check if there's a door in the way
637  //(we need to create a waypoint linked to the door for NPCs to open it)
638  Body pickedBody = Submarine.PickBody(
639  ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)),
640  prevPos, ignoredBodies, Physics.CollisionWall, false,
641  (Fixture f) => f.Body.UserData is Item pickedItem && pickedItem.GetComponent<Door>() != null);
642 
643  Door pickedDoor = null;
644  if (pickedBody != null)
645  {
646  pickedDoor = (pickedBody?.UserData as Item).GetComponent<Door>();
647  }
648  else
649  {
650  //no door, check for platforms/walls
651  pickedBody = Submarine.PickBody(
652  ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false,
653  (Fixture f) => f.Body.UserData is Structure);
654  }
655 
656  if (pickedBody != null)
657  {
658  ignoredBodies.Add(pickedBody);
659  }
660 
661  if (pickedDoor != null)
662  {
663  WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine);
664  ladderPoints.Add((newPoint, true));
665  newPoint.ConnectedGap = pickedDoor.LinkedGap;
666  // TODO: Prevent the waypoint below being too close to the door
667  newPoint.ConnectTo(prevPoint);
668  prevPoint = newPoint;
669  prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height));
670  // Adjust y to prevent waypoints clamping up together
671  y = Math.Max(pickedDoor.Item.Position.Y, y);
672  }
673  else
674  {
675  Vector2 pos = pickedBody == null ? new Vector2(startPoint.Position.X, y) :
676  ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor;
677  WayPoint newPoint = new WayPoint(pos, SpawnType.Path, submarine);
678  ladderPoints.Add((newPoint, pickedBody != null));
679  newPoint.ConnectTo(prevPoint);
680  prevPoint = newPoint;
681  prevPos = ConvertUnits.ToSimUnits(newPoint.Position);
682  if (pickedBody != null)
683  {
684  // Adjust y to prevent waypoints clamping up together
685  y = Math.Max(newPoint.Position.Y, y);
686  }
687  }
688  }
689 
690  // Cap
691  if (prevPoint.rect.Y < item.Rect.Y - 40)
692  {
693  WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
694  ladderPoints.Add((wayPoint, true));
695  wayPoint.ConnectTo(prevPoint);
696  }
697 
698  // Connect ladder waypoints to hull points at the right and left side
699  var ladderWaypoints = ladderPoints.Select(lp => lp.wp);
700  foreach (var ladderPoint in ladderPoints)
701  {
702  var wp = ladderPoint.wp;
703  wp.Ladders = ladders;
704  if (!ladderPoint.connectHullPoints) { continue; }
705  bool isHatch = wp.ConnectedGap != null && !wp.ConnectedGap.IsRoomToRoom;
706  for (int dir = -1; dir <= 1; dir += 2)
707  {
708  WayPoint closest = isHatch ?
709  wp.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderWaypoints) :
710  wp.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderWaypoints);
711  if (closest == null) { continue; }
712  wp.ConnectTo(closest);
713  }
714  }
715  }
716 
717  // Another ladder pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
718  foreach (Item item in Item.ItemList)
719  {
720  var ladders = item.GetComponent<Ladder>();
721  if (ladders == null) { continue; }
722  var wps = WayPointList.Where(wp => wp.Ladders == ladders).OrderByDescending(wp => wp.Rect.Y);
723  WayPoint cap = wps.First();
724  WayPoint above = cap.FindClosest(1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders);
725  above?.ConnectTo(cap);
726  WayPoint bottom = wps.Last();
727  WayPoint below = bottom.FindClosest(-1, horizontalSearch: false, tolerance: new Vector2(25, 50), filter: wp => wp.Ladders != null && wp.Ladders != ladders);
728  below?.ConnectTo(bottom);
729  }
730 
731  foreach (Gap gap in Gap.GapList)
732  {
733  if (gap.IsHorizontal)
734  {
735  if ( isFlooded)
736  {
737  // Too small to swim through
738  if (gap.Rect.Height < 50) { continue; }
739  }
740  else
741  {
742  // Too small to walk through
743  if (gap.Rect.Height < hullMinHeight) { continue; }
744  }
745 
746  Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor);
747  if (isFlooded)
748  {
749  pos.Y = gap.Rect.Y - gap.Rect.Height / 2;
750  }
751  var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
752  // The closest waypoint can be quite far if the gap is at an exterior door.
753  Vector2 tolerance = gap.IsRoomToRoom && !isFlooded ? new Vector2(150, 70) : new Vector2(1000, 1000);
754  for (int dir = -1; dir <= 1; dir += 2)
755  {
756  WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody);
757  if (closest != null)
758  {
759  wayPoint.ConnectTo(closest);
760  }
761  }
762  }
763  else
764  {
765  // Create waypoints on vertical gaps on the outer walls, also hatches.
766  if (!isFlooded && (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull))) { continue; }
767  // Too small to swim through
768  if (gap.Rect.Width < 50.0f) { continue; }
769  Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2);
770  // Some hatches are created in the block above where we handle the ladder waypoints. So we need to check for duplicates.
771  if (WayPointList.Any(wp => wp.ConnectedGap == gap)) { continue; }
772  var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap);
773  Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull);
774  int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y);
775  WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
776  if (closest != null)
777  {
778  wayPoint.ConnectTo(closest);
779  }
780  if (isFlooded)
781  {
782  closest = wayPoint.FindClosest(-dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100));
783  if (closest != null)
784  {
785  wayPoint.ConnectTo(closest);
786  }
787  }
788  // Link to outside
789  for (dir = -1; dir <= 1; dir += 2)
790  {
791  closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null);
792  if (closest != null)
793  {
794  wayPoint.ConnectTo(closest);
795  }
796  }
797  }
798  }
799 
800  var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && w.linkedTo.None());
801  foreach (WayPoint wp in orphans)
802  {
803  wp.Remove();
804  }
805 
806  foreach (WayPoint wp in WayPointList)
807  {
808  if (wp.SpawnType == SpawnType.Path && wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
809  {
810  DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color.");
811  }
812  }
813 
814  //re-disable the bodies of the doors that are supposed to be open
815  foreach (Door door in openDoors)
816  {
817  door.Body.Enabled = false;
818  }
819 
820  return true;
821  }
822 
823  private WayPoint FindClosestOutside(IEnumerable<(WayPoint, int)> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<(WayPoint, int), bool> filter = null)
824  {
825  float closestDist = 0;
826  WayPoint closest = null;
827  foreach (var wayPoint in waypointList)
828  {
829  WayPoint wp = wayPoint.Item1;
830  if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
831  // Ignore if already linked
832  if (linkedTo.Contains(wp)) { continue; }
833  if (ignored != null && ignored.Contains(wp)) { continue; }
834  if (filter != null && !filter(wayPoint)) { continue; }
835  float sqrDist = Vector2.DistanceSquared(Position, wp.Position);
836  if (sqrDist > tolerance * tolerance) { continue; }
837  if (closest == null || sqrDist < closestDist)
838  {
839  var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
840  if (body != null && body != ignoredBody && !(body.UserData is Submarine))
841  {
842  if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
843  {
844  continue;
845  }
846  }
847  closestDist = sqrDist;
848  closest = wp;
849  }
850  }
851  return closest;
852  }
853 
854  private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<WayPoint, bool> filter = null)
855  {
856  if (dir != -1 && dir != 1) { return null; }
857 
858  float closestDist = 0.0f;
859  WayPoint closest = null;
860 
861  foreach (WayPoint wp in WayPointList)
862  {
863  if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
864 
865  float xDiff = wp.Position.X - Position.X;
866  float yDiff = wp.Position.Y - Position.Y;
867  float xDist = Math.Abs(xDiff);
868  float yDist = Math.Abs(yDiff);
869  if (tolerance.X < xDist) { continue; }
870  if (tolerance.Y < yDist) { continue; }
871 
872  float dist = 0.0f;
873  float diff = 0.0f;
874  if (horizontalSearch)
875  {
876  diff = xDiff;
877  dist = xDist + yDist / 5.0f;
878  }
879  else
880  {
881  diff = yDiff;
882  dist = yDist + xDist / 5.0f;
883  //prefer ladder waypoints when moving vertically
884  if (wp.Ladders != null) { dist *= 0.5f; }
885  }
886 
887  if (Math.Sign(diff) != dir) { continue; }
888  // Ignore if already linked
889  if (linkedTo.Contains(wp)) { continue; }
890  if (ignored != null && ignored.Contains(wp)) { continue; }
891  if (filter != null && !filter(wp)) { continue; }
892 
893  if (closest == null || dist < closestDist)
894  {
895  var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
896  if (body != null && body != ignoredBody && !(body.UserData is Submarine))
897  {
898  if (body.UserData is Structure)
899  {
900  continue;
901  }
902  if (body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall) && body.UserData is Item i && i.GetComponent<Door>() != null)
903  {
904  continue;
905  }
906  }
907 
908  closestDist = dist;
909  closest = wp;
910  }
911  }
912 
913  return closest;
914  }
915 
916  public void ConnectTo(WayPoint wayPoint2)
917  {
918  System.Diagnostics.Debug.Assert(this != wayPoint2);
919  if (!linkedTo.Contains(wayPoint2))
920  {
921  linkedTo.Add(wayPoint2);
922  OnLinksChanged?.Invoke(this);
923  }
924  if (!wayPoint2.linkedTo.Contains(this))
925  {
926  wayPoint2.linkedTo.Add(this);
927  wayPoint2.OnLinksChanged?.Invoke(wayPoint2);
928  }
929  }
930 
931  public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null, bool ignoreSubmarine = false)
932  {
933  return WayPointList.GetRandom(wp =>
934  (ignoreSubmarine || wp.Submarine == sub) &&
935  //checking for the disabled flag is not strictly necessary because we check for equality of the spawn type,
936  //but lets do that anyway in case we change the handling of the spawn type at some point
937  !wp.spawnType.HasFlag(SpawnType.Disabled) &&
938  wp.spawnType == spawnType &&
939  (spawnPointTag.IsNullOrEmpty() || wp.Tags.Any(t => t == spawnPointTag)) &&
940  (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
941  useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced);
942  }
943 
944  public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
945  {
946  List<WayPoint> subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine);
947  subWayPoints.Shuffle();
948 
949  List<WayPoint> unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human);
950 
951  WayPoint[] assignedWayPoints = new WayPoint[crew.Count];
952 
953  for (int i = 0; i < crew.Count; i++ )
954  {
955  //try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job
956  for (int n = 0; n < unassignedWayPoints.Count; n++)
957  {
958  if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) { continue; }
959  assignedWayPoints[i] = unassignedWayPoints[n];
960  unassignedWayPoints.RemoveAt(n);
961 
962  break;
963  }
964  }
965 
966  //go through the crewmembers that don't have a spawnpoint yet (if any)
967  for (int i = 0; i < crew.Count; i++)
968  {
969  if (assignedWayPoints[i] != null) { continue; }
970 
971  //try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else
972  foreach (WayPoint wp in subWayPoints)
973  {
974  if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) { continue; }
975 
976  assignedWayPoints[i] = wp;
977  break;
978  }
979  if (assignedWayPoints[i] != null) { continue; }
980 
981  //try to assign a spawnpoint that isn't meant for any specific job
982  var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.AssignedJob == null);
983  if (nonJobSpecificPoints.Any())
984  {
985  assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.ServerAndClient)];
986  }
987 
988  if (assignedWayPoints[i] != null) { continue; }
989 
990  //everything else failed -> just give a random spawnpoint inside the sub
991  assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, useSyncedRand: true);
992  }
993 
994  for (int i = 0; i < assignedWayPoints.Length; i++)
995  {
996  if (assignedWayPoints[i] == null)
997  {
998  DebugConsole.AddWarning("Couldn't find a waypoint for " + crew[i].Name + "!");
999  assignedWayPoints[i] = WayPointList[0];
1000  }
1001  }
1002 
1003  return assignedWayPoints;
1004  }
1005 
1006  public void FindHull()
1007  {
1009 #if CLIENT
1010  //we may not be able to find the hull with the optimized method in the sub editor if new hulls have been added, use the unoptimized method
1012  {
1014  }
1015 #endif
1016  }
1017 
1018  public override void OnMapLoaded()
1019  {
1020  if (Submarine == null)
1021  {
1022  // Don't try to connect waypoints that are not linked to any submarines to hulls, stairs, gaps etc.
1023  // Used to cause weird pathfinding errors on some outpost modules, because the waypoints of the main path or side path got linked to a hull in the outpost.
1024  return;
1025  }
1026  InitializeLinks();
1027  FindHull();
1028  FindStairs();
1029  }
1030 
1031  private Structure FindStairs()
1032  {
1033  Stairs = null;
1034  Body pickedBody = Submarine.PickBody(SimPosition, SimPosition - new Vector2(0, 1.2f), null, Physics.CollisionStairs);
1035  if (pickedBody != null && pickedBody.UserData is Structure structure && structure.StairDirection != Direction.None)
1036  {
1037  Stairs = structure;
1038  }
1039  return Stairs;
1040  }
1041 
1042  public void InitializeLinks()
1043  {
1044  if (gapId > 0)
1045  {
1046  ConnectedGap = FindEntityByID(gapId) as Gap;
1047  gapId = 0;
1048  }
1049  if (ladderId > 0)
1050  {
1051  if (FindEntityByID(ladderId) is Item ladderItem) { Ladders = ladderItem.GetComponent<Ladder>(); }
1052  ladderId = 0;
1053  }
1054  }
1055 
1056  public static WayPoint Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
1057  {
1058  Rectangle rect = new Rectangle(
1059  int.Parse(element.GetAttribute("x").Value),
1060  int.Parse(element.GetAttribute("y").Value),
1061  (int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
1062 
1063  Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
1064  WayPoint w = new WayPoint(spawnType == SpawnType.Path ? Type.WayPoint : Type.SpawnPoint, rect, submarine, idRemap.GetOffsetId(element))
1065  {
1066  spawnType = spawnType,
1067  Layer = element.GetAttributeString(nameof(Layer), null)
1068  };
1069 
1070  string idCardDescString = element.GetAttributeString("idcarddesc", "");
1071  if (!string.IsNullOrWhiteSpace(idCardDescString))
1072  {
1073  w.IdCardDesc = idCardDescString;
1074  }
1075  string idCardTagString = element.GetAttributeString("idcardtags", "");
1076  if (!string.IsNullOrWhiteSpace(idCardTagString))
1077  {
1078  w.IdCardTags = idCardTagString.Split(',');
1079  }
1080 
1081  w.ExitPointSize = element.GetAttributePoint("exitpointsize", Point.Zero);
1082 
1083  w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToHashSet();
1084 
1085  Identifier jobIdentifier = element.GetAttributeIdentifier("job", Identifier.Empty);
1086  if (!jobIdentifier.IsEmpty)
1087  {
1088  w.AssignedJob = JobPrefab.Get(jobIdentifier);
1089  }
1090 
1091  w.linkedToID = new List<ushort>();
1092  w.ladderId = idRemap.GetOffsetId(element.GetAttributeInt("ladders", 0));
1093  w.gapId = idRemap.GetOffsetId(element.GetAttributeInt("gap", 0));
1094 
1095  int i = 0;
1096  while (element.GetAttribute("linkedto" + i) != null)
1097  {
1098  int srcId = int.Parse(element.GetAttribute("linkedto" + i).Value);
1099  int destId = idRemap.GetOffsetId(srcId);
1100  if (destId > 0)
1101  {
1102  w.linkedToID.Add((ushort)destId);
1103  }
1104  else
1105  {
1106  w.unresolvedLinkedToID ??= new List<ushort>();
1107  w.unresolvedLinkedToID.Add((ushort)srcId);
1108  }
1109  i += 1;
1110  }
1111  return w;
1112  }
1113 
1114  public override XElement Save(XElement parentElement)
1115  {
1116  if (!ShouldBeSaved) return null;
1117  XElement element = new XElement("WayPoint");
1118 
1119  element.Add(new XAttribute("ID", ID),
1120  new XAttribute("x", (int)(rect.X - Submarine.HiddenSubPosition.X)),
1121  new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)),
1122  new XAttribute("spawn", spawnType),
1123  new XAttribute(nameof(Layer), Layer ?? string.Empty));
1124  if (SpawnType == SpawnType.ExitPoint)
1125  {
1126  element.Add(new XAttribute("exitpointsize", XMLExtensions.PointToString(ExitPointSize)));
1127  }
1128 
1129  if (!string.IsNullOrWhiteSpace(IdCardDesc)) element.Add(new XAttribute("idcarddesc", IdCardDesc));
1130  if (idCardTags.Length > 0)
1131  {
1132  element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags)));
1133  }
1134  if (tags.Count > 0)
1135  {
1136  element.Add(new XAttribute("tags", string.Join(",", tags)));
1137  }
1138 
1139  if (AssignedJob != null) element.Add(new XAttribute("job", AssignedJob.Identifier));
1140  if (ConnectedGap != null) element.Add(new XAttribute("gap", ConnectedGap.ID));
1141  if (Ladders != null) element.Add(new XAttribute("ladders", Ladders.Item.ID));
1142 
1143  parentElement.Add(element);
1144 
1145  if (linkedTo != null)
1146  {
1147  int i = 0;
1148  foreach (MapEntity e in linkedTo)
1149  {
1150  if (!e.ShouldBeSaved || (e.Removed != Removed)) { continue; }
1151  if (e.Submarine?.Info.Type != Submarine?.Info.Type) { continue; }
1152  element.Add(new XAttribute("linkedto" + i, e.ID));
1153  i += 1;
1154  }
1155  }
1156 
1157  return element;
1158  }
1159 
1160  public override void ShallowRemove()
1161  {
1162  base.ShallowRemove();
1163  WayPointList.Remove(this);
1164  }
1165 
1166  public override void Remove()
1167  {
1168  base.Remove();
1169  CurrentHull = null;
1170  ConnectedGap = null;
1171  Tunnel = null;
1172  Ruin = null;
1173  Stairs = null;
1174  Ladders = null;
1175  OnLinksChanged = null;
1176  WayPointList.Remove(this);
1177  }
1178  }
1179 }
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
Point GetAttributePoint(string key, in Point def)
int GetAttributeInt(string key, int def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
static void RemoveAll()
Definition: Entity.cs:212
Submarine Submarine
Definition: Entity.cs:53
const ushort NullEntityID
Definition: Entity.cs:14
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static readonly List< Hull > HullList
static Hull FindHullUnoptimized(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any). The difference to FindHul...
ushort GetOffsetId(XElement element)
Definition: IdRemap.cs:86
static readonly List< Item > ItemList
JobPrefab Prefab
Definition: Job.cs:18
static readonly List< MapEntity > MapEntityList
Prefab(ContentFile file, Identifier identifier)
Definition: Prefab.cs:40
readonly Identifier Identifier
Definition: Prefab.cs:34
static void StoreCommand(Command command)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
static WayPoint Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
WayPoint(MapEntityPrefab prefab, Rectangle rectangle)
WayPoint(Rectangle newRect, Submarine submarine)
WayPoint(Type type, Rectangle newRect, Submarine submarine, ushort id=Entity.NullEntityID)
void OnGapStateChanged(bool open, Gap gap)
Only called by a Gap when the state changes. So in practice used like an event callback,...
static WayPoint GetRandom(SpawnType spawnType=SpawnType.Human, JobPrefab assignedJob=null, Submarine sub=null, bool useSyncedRand=false, string spawnPointTag=null, bool ignoreSubmarine=false)
WayPoint(Vector2 position, SpawnType spawnType, Submarine submarine, Gap gap=null)
override void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
static bool GenerateSubWaypoints(Submarine submarine)
override XElement Save(XElement parentElement)
static WayPoint[] SelectCrewSpawnPoints(List< CharacterInfo > crew, Submarine submarine)