Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Map/Submarine.cs
4 using FarseerPhysics;
5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Graphics;
7 using System;
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
10 using System.Linq;
11 using System.Text;
12 
13 namespace Barotrauma
14 {
15  partial class Submarine : Entity, IServerPositionSync
16  {
17  //drawing ----------------------------------------------------
18  private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>();
19 
20  private static double prevCullTime;
21  private static Rectangle prevCullArea;
25  private const float CullInterval = 0.25f;
29  private const int CullMargin = 50;
33  private const int CullMoveThreshold = 50;
34 
35  public static void CullEntities(Camera cam)
36  {
37  Rectangle camView = cam.WorldView;
38  camView = new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2);
39 
41  {
42  //force everything to be visible when the collapse effect (which moves everything to a single point) is active
43  camView = Rectangle.Union(AbsRect(camView.Location.ToVector2(), camView.Size.ToVector2()), new Rectangle(Point.Zero, Level.Loaded.Size));
44  camView.Y += camView.Height;
45  }
46 
47  if (Math.Abs(camView.X - prevCullArea.X) < CullMoveThreshold &&
48  Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold &&
49  Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold &&
50  Math.Abs(camView.Bottom - prevCullArea.Bottom) < CullMoveThreshold &&
51  prevCullTime > Timing.TotalTime - CullInterval)
52  {
53  return;
54  }
55 
56  visibleSubs.Clear();
57  foreach (Submarine sub in Loaded)
58  {
59  if (Level.Loaded != null && sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; }
60 
61  Rectangle worldBorders = new Rectangle(
62  sub.VisibleBorders.X + (int)sub.WorldPosition.X,
63  sub.VisibleBorders.Y + (int)sub.WorldPosition.Y,
64  sub.VisibleBorders.Width,
65  sub.VisibleBorders.Height);
66 
67  if (RectsOverlap(worldBorders, camView))
68  {
69  visibleSubs.Add(sub);
70  }
71  }
72 
73  if (visibleEntities == null)
74  {
75  visibleEntities = new List<MapEntity>(MapEntity.MapEntityList.Count);
76  }
77  else
78  {
79  visibleEntities.Clear();
80  }
81 
82  foreach (MapEntity entity in MapEntity.MapEntityList)
83  {
84  if (entity == null || entity.Removed) { continue; }
85  if (entity.Submarine != null)
86  {
87  if (!visibleSubs.Contains(entity.Submarine)) { continue; }
88  }
89  if (entity.IsVisible(camView)) { visibleEntities.Add(entity); }
90  }
91 
92  prevCullArea = camView;
93  prevCullTime = Timing.TotalTime;
94  }
95 
96  public static void ForceVisibilityRecheck()
97  {
98  prevCullTime = 0;
99  }
100 
101  public static void ForceRemoveFromVisibleEntities(MapEntity entity)
102  {
103  visibleEntities?.Remove(entity);
104  }
105 
106  public static void Draw(SpriteBatch spriteBatch, bool editing = false)
107  {
108  var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
109 
110  foreach (MapEntity e in entitiesToRender)
111  {
112  e.Draw(spriteBatch, editing);
113  }
114  }
115 
116  public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
117  {
118  var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
119 
120  foreach (MapEntity e in entitiesToRender)
121  {
122  if (!e.DrawOverWater) { continue; }
123 
124  if (predicate != null)
125  {
126  if (!predicate(e)) { continue; }
127  }
128 
129  e.Draw(spriteBatch, editing, false);
130  }
131 
132  if (GameMain.DebugDraw)
133  {
134  foreach (Submarine sub in Loaded)
135  {
136  Rectangle worldBorders = sub.Borders;
137  worldBorders.Location += sub.WorldPosition.ToPoint();
138  worldBorders.Y = -worldBorders.Y;
139 
140  GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
141 
142  if (sub.SubBody == null || sub.subBody.PositionBuffer.Count < 2) continue;
143 
144  Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
145  prevPos.Y = -prevPos.Y;
146 
147  for (int i = 1; i < sub.subBody.PositionBuffer.Count; i++)
148  {
149  Vector2 currPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[i].Position);
150  currPos.Y = -currPos.Y;
151 
152  GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 10, (int)currPos.Y - 10, 20, 20), Color.Blue * 0.6f, true, 0.01f);
153  GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.5f, 0, 5);
154 
155  prevPos = currPos;
156  }
157  }
158  }
159  }
160 
161  public static float DamageEffectCutoff;
162  public static Color DamageEffectColor;
163 
164  private static readonly List<Structure> depthSortedDamageable = new List<Structure>();
165  public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
166  {
167  var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
168 
169  depthSortedDamageable.Clear();
170 
171  //insertion sort according to draw depth
172  foreach (MapEntity e in entitiesToRender)
173  {
174  if (e is Structure structure && structure.DrawDamageEffect)
175  {
176  if (predicate != null)
177  {
178  if (!predicate(e)) { continue; }
179  }
180  float drawDepth = structure.GetDrawDepth();
181  int i = 0;
182  while (i < depthSortedDamageable.Count)
183  {
184  float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
185  if (otherDrawDepth < drawDepth) { break; }
186  i++;
187  }
188  depthSortedDamageable.Insert(i, structure);
189  }
190  }
191 
192  foreach (Structure s in depthSortedDamageable)
193  {
194  s.DrawDamage(spriteBatch, damageEffect, editing);
195  }
196  if (damageEffect != null)
197  {
198  damageEffect.Parameters["aCutoff"].SetValue(0.0f);
199  damageEffect.Parameters["cCutoff"].SetValue(0.0f);
200  DamageEffectCutoff = 0.0f;
201  }
202  }
203 
204  public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
205  {
206  var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
207 
208  foreach (MapEntity e in entitiesToRender)
209  {
210  if (e is Hull hull)
211  {
212  if (hull.SupportsPaintedColors)
213  {
214  if (predicate != null)
215  {
216  if (!predicate(e)) { continue; }
217  }
218  hull.DrawSectionColors(spriteBatch);
219  }
220  }
221  }
222  }
223 
224  public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
225  {
226  var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
227 
228  foreach (MapEntity e in entitiesToRender)
229  {
230  if (!e.DrawBelowWater) continue;
231 
232  if (predicate != null)
233  {
234  if (!predicate(e)) continue;
235  }
236 
237  e.Draw(spriteBatch, editing, true);
238  }
239  }
240 
241  public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f)
242  {
243  Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2;
244  Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2;
245 
246  for (int i = 0; i < gridCells; i++)
247  {
248  float distFromGridX = (MathUtils.RoundTowardsClosest(gridCenter.X, GridSize.X) - gridCenter.X) / GridSize.X;
249  float distFromGridY = (MathUtils.RoundTowardsClosest(gridCenter.Y, GridSize.Y) - gridCenter.Y) / GridSize.Y;
250 
251  float normalizedDistX = Math.Abs(i + distFromGridX - gridCells / 2) / (gridCells / 2);
252  float normalizedDistY = Math.Abs(i - distFromGridY - gridCells / 2) / (gridCells / 2);
253 
254  float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
255  float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistX);
256 
257  GUI.DrawLine(spriteBatch,
258  new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y),
259  new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y),
260  Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3);
261  GUI.DrawLine(spriteBatch,
262  new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY),
263  new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY),
264  Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3);
265  }
266  }
267 
268  // TODO remove
269  [Obsolete("Use MiniMap.CreateMiniMap()")]
270  public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null, bool ignoreOutpost = false)
271  {
272  Rectangle worldBorders = GetDockedBorders();
273  worldBorders.Location += WorldPosition.ToPoint();
274 
275  //create a container that has the same "aspect ratio" as the sub
276  float aspectRatio = worldBorders.Width / (float)worldBorders.Height;
277  float parentAspectRatio = parent.Rect.Width / (float)parent.Rect.Height;
278 
279  float scale = 0.9f;
280 
281  GUIFrame hullContainer = new GUIFrame(new RectTransform(
282  (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
283  parent.RectTransform, Anchor.Center),
284  style: null)
285  {
286  UserData = "hullcontainer"
287  };
288 
289  var connectedSubs = GetConnectedSubs();
290 
291  HashSet<Hull> hullList = Hull.HullList.Where(hull => hull.Submarine == this || connectedSubs.Contains(hull.Submarine)).Where(hull => !ignoreOutpost || IsEntityFoundOnThisSub(hull, true)).ToHashSet();
292 
293  Dictionary<Hull, HashSet<Hull>> combinedHulls = new Dictionary<Hull, HashSet<Hull>>();
294 
295  foreach (Hull hull in hullList)
296  {
297  if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
298 
299  List<Hull> linkedHulls = new List<Hull>();
300  MiniMap.GetLinkedHulls(hull, linkedHulls);
301 
302  linkedHulls.Remove(hull);
303 
304  foreach (Hull linkedHull in linkedHulls)
305  {
306  if (!combinedHulls.ContainsKey(hull))
307  {
308  combinedHulls.Add(hull, new HashSet<Hull>());
309  }
310 
311  combinedHulls[hull].Add(linkedHull);
312  }
313  }
314 
315  foreach (Hull hull in hullList)
316  {
317  Vector2 relativeHullPos = new Vector2(
318  (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width,
319  (worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height);
320  Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height);
321 
322  bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull));
323 
324  if (hideHull) { continue; }
325 
326  Color color = Color.DarkCyan * 0.8f;
327 
328  var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color)
329  {
330  UserData = hull
331  };
332 
333  new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: color);
334  }
335 
336  foreach (var (mainHull, linkedHulls) in combinedHulls)
337  {
338  MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
339 
340  Vector2 relativeHullPos = new Vector2(
341  (data.Bounds.X - worldBorders.X) / worldBorders.Width,
342  (worldBorders.Y - data.Bounds.Y) / worldBorders.Height);
343 
344  Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height);
345 
346  Color color = Color.DarkCyan * 0.8f;
347 
348  float highestY = 0f,
349  highestX = 0f;
350 
351  foreach (var (r, _) in data.RectDatas)
352  {
353  float y = r.Y - -r.Height,
354  x = r.X;
355 
356  if (y > highestY) { highestY = y; }
357  if (x > highestX) { highestX = x; }
358  }
359 
360  HashSet<GUIFrame> frames = new HashSet<GUIFrame>();
361 
362  foreach (var (snappredRect, hull) in data.RectDatas)
363  {
364  RectangleF rect = snappredRect;
365  rect.Height = -rect.Height;
366  rect.Y -= rect.Height;
367 
368  var (parentW, parentH) = hullContainer.Rect.Size.ToVector2();
369  Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH);
370  // TODO this won't be required if we some day switch RectTransform to use RectangleF
371  Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH);
372 
373  GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color)
374  {
375  UserData = hull,
376  UVOffset = new Vector2(highestX - rect.X, highestY - rect.Y)
377  };
378 
379  frames.Add(hullFrame);
380  }
381 
382  new GUICustomComponent(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, (spriteBatch, component) =>
383  {
384  foreach (List<Vector2> list in data.Polygon)
385  {
386  spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.Color, 2f);
387  }
388  }, (deltaTime, component) =>
389  {
390  if (component.Parent.Rect.Size != data.ParentSize)
391  {
392  data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
393  }
394  })
395  {
396  UserData = frames,
397  Color = color,
398  CanBeFocused = false
399  };
400  }
401 
402  if (pointsOfInterest != null)
403  {
404  foreach (Entity entity in pointsOfInterest)
405  {
406  Vector2 relativePos = new Vector2(
407  (entity.WorldPosition.X - worldBorders.X) / worldBorders.Width,
408  (worldBorders.Y - entity.WorldPosition.Y) / worldBorders.Height);
409  new GUIFrame(new RectTransform(new Point(1, 1), hullContainer.RectTransform) { RelativeOffset = relativePos }, style: null)
410  {
411  CanBeFocused = false,
412  UserData = entity
413  };
414  }
415  }
416  }
417 
418  public static MiniMapHullData ConstructLinkedHulls(Hull mainHull, HashSet<Hull> linkedHulls, GUIComponent parent, Rectangle worldBorders)
419  {
420  Rectangle parentRect = parent.Rect;
421 
422  Dictionary<Hull, Rectangle> rects = new Dictionary<Hull, Rectangle>();
423  Rectangle worldRect = mainHull.WorldRect;
424  worldRect.Y = -worldRect.Y;
425 
426  rects.Add(mainHull, worldRect);
427 
428  foreach (Hull hull in linkedHulls)
429  {
430  Rectangle rect = hull.WorldRect;
431  rect.Y = -rect.Y;
432 
433  worldRect = Rectangle.Union(worldRect, rect);
434  rects.Add(hull, rect);
435  }
436 
437  worldRect.Y = -worldRect.Y;
438 
439  List<RectangleF> normalizedRects = new List<RectangleF>();
440  List<Hull> hullRefs = new List<Hull>();
441  foreach (var (hull, rect) in rects)
442  {
443  Rectangle wRect = rect;
444  wRect.Y = -wRect.Y;
445 
446  var (posX, posY) = new Vector2(
447  (wRect.X - worldBorders.X) / (float)worldBorders.Width,
448  (worldBorders.Y - wRect.Y) / (float)worldBorders.Height);
449 
450  var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height);
451 
452  RectangleF newRect = new RectangleF(posX * parentRect.Width, posY * parentRect.Height, scaleX * parentRect.Width, scaleY * parentRect.Height);
453 
454  normalizedRects.Add(newRect);
455  hullRefs.Add(hull);
456  }
457 
458  ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
459 
460  List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
461 
462  List<List<Vector2>> scaledPolygon = new List<List<Vector2>>();
463 
464  foreach (List<Vector2> list in polygon)
465  {
466  var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
467  float sizeX = polySizeX - 1f,
468  sizeY = polySizeY - 1f;
469 
470  scaledPolygon.Add(ToolBox.ScalePolygon(list, new Vector2(sizeX / polySizeX, sizeY / polySizeY)));
471  }
472 
473  return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
474  }
475 
476  public void CheckForErrors()
477  {
478  List<string> errorMsgs = new List<string>();
479  List<SubEditorScreen.WarningType> warnings = new List<SubEditorScreen.WarningType>();
480 
481  if (!Hull.HullList.Any())
482  {
483  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoWaypoints))
484  {
485  errorMsgs.Add(TextManager.Get("NoHullsWarning").Value);
486  warnings.Add(SubEditorScreen.WarningType.NoHulls);
487  }
488  }
489 
490  if (Info.Type != SubmarineType.OutpostModule ||
491  (Info.OutpostModuleInfo?.ModuleFlags.Any(f => f != "hallwayvertical" && f != "hallwayhorizontal") ?? true))
492  {
493  if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
494  {
495  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoWaypoints))
496  {
497  errorMsgs.Add(TextManager.Get("NoWaypointsWarning").Value);
498  warnings.Add(SubEditorScreen.WarningType.NoWaypoints);
499  }
500  }
501  }
502 
503  if (Hull.HullList.Any(h => h.WaterVolume > 0.0f))
504  {
505  errorMsgs.Add(TextManager.Get("WaterInHullsWarning").Value);
506  warnings.Add(SubEditorScreen.WarningType.WaterInHulls);
507  Hull.ShowHulls = true;
508  }
509 
510  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NotEnoughContainers))
511  {
512  HashSet<ContainerTagPrefab> missingContainerTags = new();
513  foreach (var prefab in ContainerTagPrefab.Prefabs)
514  {
515  if (!prefab.IsRecommendedForSub(this) || !prefab.WarnIfLess) { continue; }
516 
517  int count = Item.ItemList.Count(i => i.HasTag(prefab.Identifier));
518  if (count < prefab.RecommendedAmount)
519  {
520  missingContainerTags.Add(prefab);
521  }
522  }
523 
524  if (missingContainerTags.Any())
525  {
526  StringBuilder sb = new();
527  int count = 0;
528  foreach (var tag in missingContainerTags)
529  {
530  sb.AppendLine($"- {tag.Name}");
531  count++;
532  if (missingContainerTags.Count > count && count >= 3)
533  {
534  var moreIndicator = TextManager.GetWithVariable(
535  "upgradeuitooltip.moreindicator",
536  "[amount]",
537  (missingContainerTags.Count - count).ToString()).Value;
538  sb.AppendLine(moreIndicator);
539  break;
540  }
541  }
542 
543  errorMsgs.Add(TextManager.GetWithVariable(
544  "ContainerTagUI.CountWarning",
545  "[tags]",
546  sb.ToString()).Value);
547  warnings.Add(SubEditorScreen.WarningType.NotEnoughContainers);
548  }
549  }
550 
551  if (Info.Type == SubmarineType.Player)
552  {
553  foreach (Item item in Item.ItemList)
554  {
555  if (item.GetComponent<Vent>() == null) { continue; }
556  if (!item.linkedTo.Any())
557  {
558  if (!IsWarningSuppressed(SubEditorScreen.WarningType.DisconnectedVents))
559  {
560  errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning").Value);
561  warnings.Add(SubEditorScreen.WarningType.DisconnectedVents);
562  }
563  break;
564  }
565  }
566  foreach (Item item in Item.ItemList)
567  {
568  if (item.GetComponent<OxygenGenerator>() is not OxygenGenerator oxygenGenerator) { continue; }
569 
570  Dictionary<Hull, float> hullOxygenFlow = new Dictionary<Hull, float>();
571 
572  foreach (var linkedTo in item.linkedTo)
573  {
574  if (linkedTo is not Item linkedItem || linkedItem.GetComponent<Vent>() is not Vent vent) { continue; }
575  if (vent.Item.CurrentHull == null)
576  {
577  vent.Item.FindHull();
578  if (vent.Item.CurrentHull == null) { continue; }
579  }
580  float oxygenFlow = oxygenGenerator.GetVentOxygenFlow(vent);
581  if (!hullOxygenFlow.ContainsKey(vent.Item.CurrentHull))
582  {
583  hullOxygenFlow[vent.Item.CurrentHull] = oxygenFlow;
584  }
585  else
586  {
587  hullOxygenFlow[vent.Item.CurrentHull] += oxygenFlow;
588  }
589  }
590  foreach ((Hull hull, float oxygenFlow) in hullOxygenFlow)
591  {
592  if (oxygenFlow < Hull.OxygenConsumptionSpeed)
593  {
594  errorMsgs.Add(TextManager.GetWithVariable("LowOxygenOutputWarning", "[roomname]",
595  hull.DisplayName).Value);
596  warnings.Add(SubEditorScreen.WarningType.LowOxygenOutputWarning);
597  }
598  }
599  }
600  if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human))
601  {
602  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHumanSpawnpoints))
603  {
604  errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning").Value);
605  warnings.Add(SubEditorScreen.WarningType.NoHumanSpawnpoints);
606  }
607  }
608  if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null)
609  {
610  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoCargoSpawnpoints))
611  {
612  errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning").Value);
613  warnings.Add(SubEditorScreen.WarningType.NoCargoSpawnpoints);
614  }
615  }
616  if (Item.ItemList.None(it => it.GetComponent<Pump>() != null && it.HasTag(Tags.Ballast)))
617  {
618  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoBallastTag))
619  {
620  errorMsgs.Add(TextManager.Get("NoBallastTagsWarning").Value);
621  warnings.Add(SubEditorScreen.WarningType.NoBallastTag);
622  }
623  }
624  if (Item.ItemList.None(it => it.HasTag(Tags.HiddenItemContainer)))
625  {
626  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHiddenContainers))
627  {
628  errorMsgs.Add(TextManager.Get("NoHiddenContainersWarning").Value);
629  warnings.Add(SubEditorScreen.WarningType.NoHiddenContainers);
630  }
631  }
632  if (Info.Dimensions.X * Physics.DisplayToRealWorldRatio > 80 ||
633  Info.Dimensions.Y * Physics.DisplayToRealWorldRatio > 32)
634  {
635  if (!IsWarningSuppressed(SubEditorScreen.WarningType.TooLargeForEndGame))
636  {
637  errorMsgs.Add(TextManager.Get("TooLargeForEndGameWarning").Value);
638  warnings.Add(SubEditorScreen.WarningType.TooLargeForEndGame);
639  }
640  }
641  }
642  else if (Info.Type == SubmarineType.OutpostModule)
643  {
644  foreach (Item item in Item.ItemList)
645  {
646  var junctionBox = item.GetComponent<PowerTransfer>();
647  if (junctionBox == null) { continue; }
648  int doorLinks =
649  item.linkedTo.Count(lt => lt is Gap || (lt is Item it2 && it2.GetComponent<Door>() != null)) +
650  Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2));
651  for (int i = 0; i < item.Connections.Count; i++)
652  {
653  int wireCount = item.Connections[i].Wires.Count;
654  if (doorLinks + wireCount > item.Connections[i].MaxWires)
655  {
656  errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
657  ("[doorcount]", doorLinks.ToString()),
658  ("[freeconnectioncount]", (item.Connections[i].MaxWires - wireCount).ToString())).Value);
659  break;
660  }
661  }
662  }
663  }
664 
665  if (Gap.GapList.Any(g => g.linkedTo.Count == 0))
666  {
667  if (!IsWarningSuppressed(SubEditorScreen.WarningType.NonLinkedGaps))
668  {
669  errorMsgs.Add(TextManager.Get("NonLinkedGapsWarning").Value);
670  warnings.Add(SubEditorScreen.WarningType.NonLinkedGaps);
671  }
672  }
673 
674  float entityCountWarningThreshold = 0.75f;
675 
676  if (Item.ItemList.Count > SubEditorScreen.MaxItems * entityCountWarningThreshold)
677  {
678  if (!IsWarningSuppressed(SubEditorScreen.WarningType.ItemCount))
679  {
680  errorMsgs.Add(TextManager.Get("subeditor.itemcountwarning").Value);
681  warnings.Add(SubEditorScreen.WarningType.ItemCount);
682  }
683  }
684 
685  if ((MapEntity.MapEntityList.Count - Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count) > SubEditorScreen.MaxStructures * entityCountWarningThreshold)
686  {
687  if (!IsWarningSuppressed(SubEditorScreen.WarningType.StructureCount))
688  {
689  errorMsgs.Add(TextManager.Get("subeditor.structurecountwarning").Value);
690  warnings.Add(SubEditorScreen.WarningType.StructureCount);
691  }
692  }
693 
694  if (Structure.WallList.Count > SubEditorScreen.MaxStructures * entityCountWarningThreshold)
695  {
696  if (!IsWarningSuppressed(SubEditorScreen.WarningType.WallCount))
697  {
698  errorMsgs.Add(TextManager.Get("subeditor.wallcountwarning").Value);
699  warnings.Add(SubEditorScreen.WarningType.WallCount);
700  }
701  }
702 
703  if (GetLightCount() > SubEditorScreen.MaxLights * entityCountWarningThreshold)
704  {
705  if (!IsWarningSuppressed(SubEditorScreen.WarningType.LightCount))
706  {
707  errorMsgs.Add(TextManager.Get("subeditor.lightcountwarning").Value);
708  warnings.Add(SubEditorScreen.WarningType.LightCount);
709  }
710  }
711 
712  if (GetShadowCastingLightCount() > SubEditorScreen.MaxShadowCastingLights * entityCountWarningThreshold)
713  {
714  if (!IsWarningSuppressed(SubEditorScreen.WarningType.ShadowCastingLightCount))
715  {
716  errorMsgs.Add(TextManager.Get("subeditor.shadowcastinglightswarning").Value);
717  warnings.Add(SubEditorScreen.WarningType.ShadowCastingLightCount);
718  }
719  }
720 
721  if (errorMsgs.Any())
722  {
723  GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("Warning"), string.Empty, new Vector2(0.25f, 0.0f), minSize: new Point(GUI.IntScale(650), GUI.IntScale(650)));
724  if (warnings.Any())
725  {
726  var textListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), msgBox.Content.RectTransform));
727  var text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textListBox.Content.RectTransform), string.Join("\n\n", errorMsgs), wrap: true)
728  {
729  CanBeFocused = false
730  };
731  text.RectTransform.MinSize = new Point(0, (int)text.TextSize.Y);
732 
733  Point size = msgBox.RectTransform.NonScaledSize;
734  GUITickBox suppress = new GUITickBox(new RectTransform(new Vector2(1f, 0.33f), msgBox.Content.RectTransform), TextManager.Get("editor.suppresswarnings"));
735  msgBox.RectTransform.NonScaledSize = new Point(size.X, size.Y + suppress.RectTransform.NonScaledSize.Y);
736 
737  msgBox.Buttons[0].OnClicked += (button, obj) =>
738  {
739  if (suppress.Selected)
740  {
741  foreach (SubEditorScreen.WarningType warning in warnings.Where(warning => !SubEditorScreen.SuppressedWarnings.Contains(warning)))
742  {
743  SubEditorScreen.SuppressedWarnings.Add(warning);
744  }
745  }
746  return true;
747  };
748  }
749  }
750 
751  foreach (MapEntity e in MapEntity.MapEntityList)
752  {
753  if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
754  {
755  //move disabled items (wires, items inside containers) inside the sub
756  if (e is Item item && item.body != null && !item.body.Enabled)
757  {
758  item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f);
759  }
760  }
761  }
762 
763  foreach (MapEntity e in MapEntity.MapEntityList)
764  {
765  if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
766  {
767  var msgBox = new GUIMessageBox(
768  TextManager.Get("Warning"),
769  TextManager.Get("FarAwayEntitiesWarning"),
770  new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
771 
772  msgBox.Buttons[0].OnClicked += (btn, obj) =>
773  {
775  return true;
776  };
777  msgBox.Buttons[0].OnClicked += msgBox.Close;
778  msgBox.Buttons[1].OnClicked += msgBox.Close;
779 
780  break;
781 
782  }
783  }
784 
785  bool IsWarningSuppressed(SubEditorScreen.WarningType type)
786  {
787  return SubEditorScreen.SuppressedWarnings.Contains(type);
788  }
789  }
790 
791  public static int GetLightCount()
792  {
793  int disabledItemLightCount = 0;
794  foreach (Item item in Item.ItemList)
795  {
796  if (item.ParentInventory == null) { continue; }
797  disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
798  }
799  return GameMain.LightManager.Lights.Count() - disabledItemLightCount;
800  }
801 
802  public static int GetShadowCastingLightCount()
803  {
804  int disabledItemLightCount = 0;
805  foreach (Item item in Item.ItemList)
806  {
807  if (item.ParentInventory == null) { continue; }
808  disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
809  }
810  return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount;
811  }
812 
813  public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos = null, bool round = false)
814  {
815  Vector2 position = mousePos ?? PlayerInput.MousePosition;
816  position = cam.ScreenToWorld(position);
817 
818  Vector2 worldGridPos = VectorToWorldGrid(position, sub, round);
819 
820  return worldGridPos;
821  }
822 
823  public void ClientReadPosition(IReadMessage msg, float sendingTime)
824  {
825  var posInfo = PhysicsBody.ClientRead(msg, sendingTime, parentDebugName: Info.Name);
826  msg.ReadPadBits();
827 
828  if (posInfo != null)
829  {
830  int index = 0;
831  while (index < subBody.PositionBuffer.Count && sendingTime > subBody.PositionBuffer[index].Timestamp)
832  {
833  index++;
834  }
835 
836  subBody.PositionBuffer.Insert(index, posInfo);
837  }
838  }
839 
840  public void ClientEventRead(IReadMessage msg, float sendingTime)
841  {
842  Identifier layerIdentifier = msg.ReadIdentifier();
843  bool enabled = msg.ReadBoolean();
844  SetLayerEnabled(layerIdentifier, enabled);
845  }
846  }
847 }
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
Vector2 Position
Definition: Camera.cs:398
Rectangle WorldView
Definition: Camera.cs:123
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
virtual Rectangle Rect
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
List< GUIButton > Buttons
GUILayoutGroup Content
override bool Selected
Definition: GUITickBox.cs:18
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static Lights.LightManager LightManager
Definition: GameMain.cs:78
static bool DebugDraw
Definition: GameMain.cs:29
static readonly List< Hull > HullList
static readonly List< Item > ItemList
static readonly List< MapEntity > MapEntityList
virtual bool IsVisible(Rectangle worldView)
virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
PosInfo ClientRead(IReadMessage msg, float sendingTime, string parentDebugName)
Point?? MinSize
Min size in pixels. Does not affect scaling.
Point NonScaledSize
Size before scale multiplications.
void DrawDamage(SpriteBatch spriteBatch, Effect damageEffect, bool editing)
static List< WarningType > SuppressedWarnings
static void DrawFront(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
void ClientEventRead(IReadMessage msg, float sendingTime)
static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing=false, Predicate< MapEntity > predicate=null)
static void Draw(SpriteBatch spriteBatch, bool editing=false)
static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha=1.0f)
void CreateMiniMap(GUIComponent parent, IEnumerable< Entity > pointsOfInterest=null, bool ignoreOutpost=false)
static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos=null, bool round=false)
void ClientReadPosition(IReadMessage msg, float sendingTime)
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
static void ForceRemoveFromVisibleEntities(MapEntity entity)
static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true)
static MiniMapHullData ConstructLinkedHulls(Hull mainHull, HashSet< Hull > linkedHulls, GUIComponent parent, Rectangle worldBorders)
static void DrawBack(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
static Rectangle AbsRect(Vector2 pos, Vector2 size)
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
Interface for entities that handle ServerNetObject.ENTITY_POSITION