Client LuaCsForBarotrauma
SubmarinePreview.cs
2 using Barotrauma.IO;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 using System.Collections.Immutable;
9 using System.Globalization;
10 using System.Linq;
11 using System.Threading.Tasks;
12 using System.Xml.Linq;
13 
14 namespace Barotrauma
15 {
16  sealed class SubmarinePreview : IDisposable
17  {
18  private readonly SubmarineInfo submarineInfo;
19 
20  private SpriteRecorder spriteRecorder;
21  private Camera camera;
22  private Task loadTask;
23  private (Vector2 Min, Vector2 Max) bounds;
24 
25  private volatile bool isDisposed;
26 
27  private GUIFrame previewFrame;
28 
29  private sealed class LoadedHull
30  {
31  public UInt16 ID;
32  public readonly ImmutableList<UInt16> LinkedHulls;
33  public readonly Rectangle Rect;
34  public readonly Identifier NameIdentifier;
35 
36  public LoadedHull(XElement element)
37  {
38  ID = (ushort)element.GetAttributeInt("id", Entity.NullEntityID);
39  NameIdentifier = element.GetAttributeIdentifier("roomname", "");
40  Rect = element.GetAttributeRect("rect", Rectangle.Empty);
41  Rect.Y = -Rect.Y;
42  LinkedHulls = element.GetAttributeUshortArray("linked", Array.Empty<ushort>()).ToImmutableList();
43  }
44  }
45 
46  private sealed class HullCollection
47  {
48  public readonly List<LoadedHull> Hulls = new List<LoadedHull>();
49  public readonly List<Rectangle> Rects = new List<Rectangle>();
50  public readonly LocalizedString Name;
51 
52  public HullCollection(LoadedHull hull)
53  {
54  Name = TextManager.Get(hull.NameIdentifier).Fallback(hull.NameIdentifier.ToString());
55  AddHull(hull);
56  }
57 
58  public void AddHull(LoadedHull hull)
59  {
60  Hulls.Add(hull);
61  Rects.Add(hull.Rect);
62  }
63 
64  private bool Contains(UInt16 hullId)
65  {
66  return Hulls.Any(h => h.ID == hullId);
67  }
68 
69  public bool IsLinkedTo(HullCollection other)
70  {
71  return
72  Hulls.Any(h => h.LinkedHulls.Any(id => other.Contains(id))) ||
73  other.Hulls.Any(h => h.LinkedHulls.Any(id => Contains(id)));
74  }
75  }
76 
77  private readonly struct Door
78  {
79  public readonly Rectangle Rect;
80 
81  public Door(Rectangle rect)
82  {
83  rect.Y = -rect.Y;
84  Rect = rect;
85  }
86  }
87 
88  private readonly List<HullCollection> hullCollections = new List<HullCollection>();
89  private readonly List<Door> doors;
90 
91  private static SubmarinePreview instance = null;
92 
93  public static void Create(SubmarineInfo submarineInfo)
94  {
95  Close();
96  instance = new SubmarinePreview(submarineInfo);
97  }
98 
99  public static void Close()
100  {
101  instance?.Dispose(); instance = null;
102  }
103 
104  private SubmarinePreview(SubmarineInfo subInfo)
105  {
106  camera = new Camera();
107  submarineInfo = subInfo;
108  spriteRecorder = new SpriteRecorder();
109  isDisposed = false;
110  loadTask = null;
111 
112  hullCollections = new List<HullCollection>();
113  doors = new List<Door>();
114 
115  previewFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
116  new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, previewFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
117 
118  new GUIButton(new RectTransform(Vector2.One, previewFrame.RectTransform), "", style: null)
119  {
120  OnClicked = (btn, obj) => { Dispose(); return false; }
121  };
122 
123  var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center));
124  int innerPadding = GUI.IntScale(100f);
125  var innerPadded = new GUIFrame(new RectTransform(new Point(innerFrame.Rect.Width - innerPadding, innerFrame.Rect.Height - innerPadding), previewFrame.RectTransform, Anchor.Center), style: null)
126  {
127  OutlineColor = Color.Black,
128  OutlineThickness = 2
129  };
130 
131  GUITextBlock titleText = null;
132  GUIListBox specsContainer = null;
133 
134  new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center),
135  (spriteBatch, component) =>
136  {
137  if (isDisposed) { return; }
138  camera.UpdateTransform(interpolate: true, updateListener: false);
139  Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2);
140  RenderSubmarine(spriteBatch, drawRect, component);
141  },
142  (deltaTime, component) =>
143  {
144  if (isDisposed) { return; }
145  bool isMouseOnComponent = GUI.MouseOn == component;
146  camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
147  if (isMouseOnComponent &&
148  (PlayerInput.MidButtonHeld() || PlayerInput.PrimaryMouseButtonHeld()))
149  {
150  Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
151  moveSpeed.X = -moveSpeed.X;
152  camera.Position += moveSpeed;
153  }
154 
155  if (titleText != null && specsContainer != null)
156  {
157  specsContainer.Visible = GUI.IsMouseOn(titleText);
158  }
159  if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
160  {
161  Dispose();
162  }
163  });
164 
165  var topContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), innerPadded.RectTransform, Anchor.TopLeft), style: null)
166  {
167  Color = Color.Black * 0.65f
168  };
169  var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 5f / 7f), topContainer.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft);
170 
171  titleText = new GUITextBlock(new RectTransform(new Vector2(0.95f, 1f), topLayout.RectTransform), subInfo.DisplayName, font: GUIStyle.LargeFont);
172  new GUIButton(new RectTransform(new Vector2(0.05f, 1f), topLayout.RectTransform), TextManager.Get("Close"))
173  {
174  OnClicked = (btn, obj) => { Dispose(); return false; }
175  };
176 
177  specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) })
178  {
179  CurrentSelectMode = GUIListBox.SelectMode.None,
180  Color = Color.Black * 0.65f,
181  ScrollBarEnabled = false,
182  ScrollBarVisible = false,
183  Spacing = GUI.IntScale(5)
184  };
185  subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font,
186  includeTitle: false,
187  includeDescription: true);
188  int width = specsContainer.Rect.Width;
189  void recalculateSpecsContainerHeight()
190  {
191  int totalSize = 0;
192  var children = specsContainer.Content.Children.Where(c => c.Visible);
193  foreach (GUIComponent child in children)
194  {
195  totalSize += child.Rect.Height;
196  }
197  totalSize += specsContainer.Content.CountChildren * specsContainer.Spacing;
198  if (specsContainer.PadBottom)
199  {
200  GUIComponent last = specsContainer.Content.Children.LastOrDefault();
201  if (last != null)
202  {
203  totalSize += specsContainer.Rect.Height - last.Rect.Height;
204  }
205  }
206  specsContainer.RectTransform.Resize(new Point(width, totalSize), true);
207  specsContainer.RecalculateChildren();
208  }
209  //hell
210  recalculateSpecsContainerHeight();
211  specsContainer.Content.GetAllChildren<GUITextBlock>().ForEach(c =>
212  {
213  var firstChild = c.Children.FirstOrDefault() as GUITextBlock;
214  if (firstChild != null)
215  {
216  firstChild.CalculateHeightFromText(); firstChild.SetTextPos();
217  c.RectTransform.MinSize = new Point(0, firstChild.Rect.Height);
218  }
219  c.CalculateHeightFromText(); c.SetTextPos();
220  });
221  recalculateSpecsContainerHeight();
222 
223  TaskPool.Add(nameof(GeneratePreviewMeshes), GeneratePreviewMeshes(), _ =>
224  {
225  if (isDisposed) { return; }
226  // Reset the camera's position on the main thread,
227  // because the Camera class is not thread-safe and
228  // it's possible for its state to not get updated
229  // properly if done within a task
230  camera.Position = (bounds.Min + bounds.Max) * (0.5f, -0.5f);
231  Vector2 span2d = bounds.Max - bounds.Min;
232  Vector2 scaledSpan2d = span2d / camera.Resolution.ToVector2();
233  float scaledSpan = Math.Max(scaledSpan2d.X, scaledSpan2d.Y);
234  camera.MinZoom = Math.Min(0.1f, 0.4f / scaledSpan);
235  camera.Zoom = 0.7f / scaledSpan;
236  camera.StopMovement();
237  camera.UpdateTransform(interpolate: false, updateListener: false);
238  });
239  }
240 
241  public static void AddToGUIUpdateList()
242  {
243  instance?.previewFrame?.AddToGUIUpdateList();
244  }
245 
246  public Task GeneratePreviewMeshes()
247  {
248  if (loadTask != null) { throw new InvalidOperationException("Tried to start SubmarinePreview loadTask more than once!"); }
249  loadTask = Task.Run(GeneratePreviewMeshesInternal);
250  return loadTask;
251  }
252 
253  private async Task GeneratePreviewMeshesInternal()
254  {
255  await Task.Yield();
256  spriteRecorder.Begin(SpriteSortMode.BackToFront);
257 
258  HashSet<int> toIgnore = new HashSet<int>();
259  HashSet<int> wires = new HashSet<int>();
260 
261  foreach (var subElement in submarineInfo.SubmarineElement.Elements())
262  {
263  switch (subElement.Name.LocalName.ToLowerInvariant())
264  {
265  case "item":
266  foreach (var component in subElement.Elements())
267  {
268  switch (component.Name.LocalName.ToLowerInvariant())
269  {
270  case "itemcontainer":
271  ExtractItemContainerIds(component, toIgnore);
272  break;
273  case "connectionpanel":
274  ExtractConnectionPanelLinks(component, wires);
275  break;
276  }
277  }
278  break;
279  }
280  if (isDisposed) { return; }
281  await Task.Yield();
282  }
283 
284  var wireNodes = new List<XElement>();
285  List<LoadedHull> loadedHulls = new List<LoadedHull>();
286  foreach (var subElement in submarineInfo.SubmarineElement.Elements())
287  {
288  if (subElement.GetAttributeBool("hiddeningame", false)) { continue; }
289  switch (subElement.Name.LocalName.ToLowerInvariant())
290  {
291  case "structure":
292  case "item":
293  var id = subElement.GetAttributeInt("ID", 0);
294  if (wires.Contains(id))
295  {
296  wireNodes.Add(subElement);
297  }
298  else if (!toIgnore.Contains(id))
299  {
300  BakeMapEntity(subElement);
301  }
302  break;
303  case "hull":
304  Identifier identifier = subElement.GetAttributeIdentifier("roomname", "");
305  if (!identifier.IsEmpty)
306  {
307  loadedHulls.Add(new LoadedHull(subElement));
308  }
309  break;
310  }
311  if (isDisposed) { return; }
312  await Task.Yield();
313  }
314 
315  //List<HullCollection> tempHullCollections = new List<HullCollection>();
316  foreach (LoadedHull hull in loadedHulls)
317  {
318  hullCollections.Add(new HullCollection(hull));
319  }
320 
321  bool intersectionFound;
322  do
323  {
324  intersectionFound = false;
325  for (int i = 0; i < hullCollections.Count; i++)
326  {
327  for (int j = i + 1; j < hullCollections.Count; j++)
328  {
329  var collection1 = hullCollections[i];
330  var collection2 = hullCollections[j];
331  if (collection1.IsLinkedTo(collection2))
332  {
333  collection2.Hulls.ForEach(h => collection1.AddHull(h));
334  hullCollections.Remove(collection2);
335  intersectionFound = true;
336  break;
337  }
338  }
339  if (intersectionFound) { break; }
340  }
341  } while (intersectionFound);
342 
343  bounds = (spriteRecorder.Min, spriteRecorder.Max);
344  wireNodes.ForEach(BakeWireNodes);
345 
346  spriteRecorder.End();
347  }
348 
349  private static void ExtractItemContainerIds(XElement component, HashSet<int> ids)
350  {
351  string containedString = component.GetAttributeString("contained", "");
352  string[] itemIdStrings = containedString.Split(',');
353  for (int i = 0; i < itemIdStrings.Length; i++)
354  {
355  foreach (string idStr in itemIdStrings[i].Split(';'))
356  {
357  if (!int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out int id)) { continue; }
358  if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
359  }
360  }
361  }
362 
363  private static void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
364  {
365  var pins = component.Elements("input").Concat(component.Elements("output"));
366  foreach (var pin in pins)
367  {
368  var links = pin.Elements("link");
369  foreach (var link in links)
370  {
371  int id = link.GetAttributeInt("w", 0);
372  if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
373  }
374  }
375  }
376 
377  private void BakeWireNodes(XElement element)
378  {
379  var prefabIdentifier = element.GetAttributeIdentifier("identifier", "");
380  if (prefabIdentifier.IsEmpty) { return; }
381  if (!ItemPrefab.Prefabs.TryGet(prefabIdentifier, out var prefab)) { return; }
382 
383  var prefabWireComponentElement = prefab.ConfigElement.GetChildElement("wire");
384  if (prefabWireComponentElement is null) { return; }
385 
386  var wireComponent = element.GetChildElement("wire");
387  if (wireComponent is null) { return; }
388 
389  var color = element.GetAttributeColor("spritecolor") ?? Color.White;
390 
391  var nodes = Wire.ExtractNodes(wireComponent).ToImmutableArray();
392  var wireSprite = Wire.ExtractWireSprite(prefab.ConfigElement);
393 
394  var useSpriteDepth = element.GetAttributeBool("usespritedepth", false);
395  var depth =
396  useSpriteDepth
397  ? element.GetAttributeFloat("spritedepth", 1.0f)
398  : wireSprite.Depth;
399 
400  var width = prefabWireComponentElement.GetAttributeFloat("width", 0.3f);
401 
402  for (int i = 0; i < nodes.Length - 1; i++)
403  {
404  var line = (Start: nodes[i], End: nodes[i + 1]);
405  var wireSegment = new Wire.WireSection(line.Start, line.End);
406  wireSegment.Draw(spriteRecorder, wireSprite, color, Vector2.Zero, depth, width);
407  }
408  }
409 
410  private void BakeMapEntity(XElement element)
411  {
412  Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
413  if (identifier.IsEmpty) { return; }
414  Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
415  if (rect.Equals(Rectangle.Empty)) { return; }
416 
417  float depth = element.GetAttributeFloat("spritedepth", 1f);
418  bool flippedX = element.GetAttributeBool("flippedx", false);
419  bool flippedY = element.GetAttributeBool("flippedy", false);
420 
421  float scale = element.GetAttributeFloat("scale", 1f);
422  Color color = element.GetAttributeColor("spritecolor", Color.White);
423 
424  float rotationRad = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0f));
425 
426  MapEntityPrefab prefab;
427  if (element.NameAsIdentifier() == "item"
428  && ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip))
429  {
430  prefab = ip;
431  }
432  else
433  {
434  prefab = MapEntityPrefab.FindByIdentifier(identifier);
435  }
436  if (prefab == null) { return; }
437 
438  flippedX &= prefab.CanSpriteFlipX;
439  flippedY &= prefab.CanSpriteFlipY;
440 
441  SpriteEffects spriteEffects = SpriteEffects.None;
442  if (flippedX)
443  {
444  spriteEffects |= SpriteEffects.FlipHorizontally;
445  }
446  if (flippedY)
447  {
448  spriteEffects |= SpriteEffects.FlipVertically;
449  }
450 
451  var prevEffects = prefab.Sprite.effects;
452  prefab.Sprite.effects ^= spriteEffects;
453 
454  bool overrideSprite = false;
455  ItemPrefab itemPrefab = prefab as ItemPrefab;
456  if (itemPrefab != null)
457  {
458  BakeItemComponents(itemPrefab, rect, color, scale, rotationRad, depth, out overrideSprite);
459  }
460 
461  if (!overrideSprite)
462  {
463  if (prefab is StructurePrefab structurePrefab)
464  {
465  ParseUpgrades(structurePrefab.ConfigElement, ref scale);
466 
467  if (!prefab.ResizeVertical)
468  {
469  rect.Height = (int)(rect.Height * scale / prefab.Scale);
470  }
471  if (!prefab.ResizeHorizontal)
472  {
473  rect.Width = (int)(rect.Width * scale / prefab.Scale);
474  }
475  var textureScale = element.GetAttributeVector2("texturescale", Vector2.One);
476 
477  Vector2 backGroundOffset = Vector2.Zero;
478 
479  Vector2 textureOffset = element.GetAttributeVector2("textureoffset", Vector2.Zero);
480 
481  textureOffset = Structure.UpgradeTextureOffset(
482  targetSize: rect.Size.ToVector2(),
483  originalTextureOffset: textureOffset,
484  submarineInfo: submarineInfo,
485  sourceRect: prefab.Sprite.SourceRect,
486  scale: textureScale * scale,
487  flippedX: flippedX,
488  flippedY: flippedY);
489 
490  backGroundOffset = new Vector2(
491  MathUtils.PositiveModulo(-textureOffset.X, prefab.Sprite.SourceRect.Width * textureScale.X * scale),
492  MathUtils.PositiveModulo(-textureOffset.Y, prefab.Sprite.SourceRect.Height * textureScale.Y * scale));
493 
494  prefab.Sprite.DrawTiled(
495  spriteBatch: spriteRecorder,
496  position: new Vector2(rect.X + rect.Width / 2, -(rect.Y - rect.Height / 2)),
497  targetSize: rect.Size.ToVector2(),
498  origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
499  color: color,
500  startOffset: backGroundOffset,
501  textureScale: textureScale * scale,
502  depth: depth,
503  rotation: rotationRad);
504  }
505  else if (itemPrefab != null)
506  {
507  bool usePrefabValues = element.GetAttributeBool("isoverride", false) != itemPrefab.IsOverride;
508  if (usePrefabValues)
509  {
510  scale = itemPrefab.ConfigElement.GetAttributeFloat(scale, "scale", "Scale");
511  }
512 
513  ParseUpgrades(itemPrefab.ConfigElement, ref scale);
514 
515  if (prefab.ResizeVertical || prefab.ResizeHorizontal)
516  {
517  if (!prefab.ResizeHorizontal)
518  {
519  rect.Width = (int)(prefab.Sprite.size.X * scale);
520  }
521  if (!prefab.ResizeVertical)
522  {
523  rect.Height = (int)(prefab.Sprite.size.Y * scale);
524  }
525 
526  var spritePos = rect.Center.ToVector2();
527  //spritePos.Y = rect.Height - spritePos.Y;
528 
529  prefab.Sprite.DrawTiled(
530  spriteRecorder,
531  rect.Location.ToVector2() * new Vector2(1f, -1f),
532  rect.Size.ToVector2(),
533  color: color,
534  textureScale: Vector2.One * scale,
535  depth: depth);
536 
537  foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
538  {
539  float offsetState = 0f;
540  Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
541  if (flippedX) { offset.X = -offset.X; }
542  if (flippedY) { offset.Y = -offset.Y; }
543  decorativeSprite.Sprite.DrawTiled(spriteRecorder,
544  new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
545  rect.Size.ToVector2(), color: color,
546  textureScale: Vector2.One * scale,
547  depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
548  }
549  }
550  else
551  {
552  rect.Width = (int)(rect.Width * scale / prefab.Scale);
553  rect.Height = (int)(rect.Height * scale / prefab.Scale);
554 
555  var spritePos = rect.Center.ToVector2();
556  spritePos.Y -= rect.Height;
557  //spritePos.Y = rect.Height - spritePos.Y;
558 
559  prefab.Sprite.Draw(
560  spriteRecorder,
561  spritePos * new Vector2(1f, -1f),
562  color,
563  prefab.Sprite.Origin,
564  rotationRad,
565  scale,
566  prefab.Sprite.effects, depth);
567 
568  foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
569  {
570  float rotationState = 0f; float offsetState = 0f;
571  float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
572  Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
573  if (flippedX) { offset.X = -offset.X; }
574  if (flippedY) { offset.Y = -offset.Y; }
575  float throwAway = 0.0f;
576  decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
577  rotationRad + rot, decorativeSprite.GetScale(ref throwAway, 0f) * scale, prefab.Sprite.effects,
578  depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
579  }
580  }
581  }
582  }
583 
584  prefab.Sprite.effects = prevEffects;
585  }
586 
587  private void BakeItemComponents(
588  ItemPrefab prefab,
589  Rectangle rect, Color color,
590  float scale, float rotationRad, float depth,
591  out bool overrideSprite)
592  {
593  overrideSprite = false;
594 
595  float relativeScale = scale / prefab.Scale;
596  foreach (var subElement in prefab.ConfigElement.Elements())
597  {
598  switch (subElement.Name.LocalName.ToLowerInvariant())
599  {
600  case "turret":
601  Sprite barrelSprite = null;
602  Sprite railSprite = null;
603  foreach (var turretSubElem in subElement.Elements())
604  {
605  switch (turretSubElem.Name.ToString().ToLowerInvariant())
606  {
607  case "barrelsprite":
608  barrelSprite = new Sprite(turretSubElem);
609  break;
610  case "railsprite":
611  railSprite = new Sprite(turretSubElem);
612  break;
613  }
614  }
615 
616  Vector2 barrelPos = subElement.GetAttributeVector2("barrelpos", Vector2.Zero);
617  Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2);
618  var transformedBarrelPos = MathUtils.RotatePoint(
619  relativeBarrelPos,
620  rotationRad);
621 
622  Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale);
623  drawPos.Y = -drawPos.Y;
624 
625  railSprite?.Draw(spriteRecorder,
626  drawPos,
627  color,
628  rotationRad, scale,
629  SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth));
630 
631  barrelSprite?.Draw(spriteRecorder,
632  drawPos,
633  color,
634  rotationRad, scale,
635  SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth));
636 
637  break;
638  case "door":
639  var scaledRect = rect with { Size = (rect.Size.ToVector2() * relativeScale).ToPoint() };
640 
641  doors.Add(new Door(scaledRect));
642 
643  var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
644  if (doorSpriteElem != null)
645  {
646  string texturePath = doorSpriteElem.GetAttributeStringUnrestricted("texture", "");
647  Vector2 pos = scaledRect.Location.ToVector2() * new Vector2(1f, -1f);
648  if (subElement.GetAttributeBool("horizontal", false))
649  {
650  pos.Y += (float)scaledRect.Height * 0.5f;
651  }
652  else
653  {
654  pos.X += (float)scaledRect.Width * 0.5f;
655  }
656  Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
657  spriteRecorder.Draw(doorSprite.Texture, pos,
658  new Rectangle((int)doorSprite.SourceRect.X,
659  (int)doorSprite.SourceRect.Y,
660  (int)doorSprite.size.X, (int)doorSprite.size.Y),
661  color, 0.0f, doorSprite.Origin, new Vector2(scale), SpriteEffects.None, doorSprite.Depth);
662  }
663  break;
664  case "ladder":
665  var backgroundSprElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("backgroundsprite", StringComparison.OrdinalIgnoreCase));
666  if (backgroundSprElem != null)
667  {
668  Sprite backgroundSprite = new Sprite(backgroundSprElem);
669  backgroundSprite.DrawTiled(spriteRecorder,
670  new Vector2(rect.Left, -rect.Top) - backgroundSprite.Origin * scale,
671  new Vector2(backgroundSprite.size.X * scale, rect.Height), color: color,
672  textureScale: Vector2.One * scale,
673  depth: depth + 0.1f);
674  }
675  break;
676  }
677  }
678  }
679 
680  private void ParseUpgrades(XElement prefabConfigElement, ref float scale)
681  {
682  foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
683  {
684  var upgradeVersion = new Version(upgrade.GetAttributeString("gameversion", "0.0.0.0"));
685  if (upgradeVersion >= submarineInfo.GameVersion)
686  {
687  string scaleModifier = upgrade.GetAttributeString("scale", "*1");
688 
689  if (scaleModifier.StartsWith("*"))
690  {
691  if (float.TryParse(scaleModifier.Substring(1), NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
692  {
693  scale *= parsedScale;
694  }
695  }
696  else
697  {
698  if (float.TryParse(scaleModifier, NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
699  {
700  scale = parsedScale;
701  }
702  }
703  }
704  }
705  }
706 
707  private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle, GUIComponent component)
708  {
709  if (spriteRecorder == null) { return; }
710 
711  GUI.DrawRectangle(spriteBatch, scissorRectangle, new Color(0.051f, 0.149f, 0.271f, 1.0f), isFilled: true);
712 
713  if (!spriteRecorder.ReadyToRender)
714  {
715  LocalizedString waitText = !loadTask.IsCompleted ?
716  TextManager.Get("generatingsubmarinepreview", "loading") :
717  (loadTask.Exception?.ToString() ?? "Task completed without marking as ready to render");
718  Vector2 origin = (GUIStyle.Font.MeasureString(waitText) * 0.5f);
719  origin.X = MathF.Round(origin.X);
720  origin.Y = MathF.Round(origin.Y);
721  GUIStyle.Font.DrawString(
722  spriteBatch,
723  waitText,
724  scissorRectangle.Center.ToVector2(),
725  Color.White,
726  0f,
727  origin,
728  1f,
729  SpriteEffects.None,
730  0f);
731  return;
732  }
733  spriteBatch.End();
734 
735  var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
736  GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
737  var prevRasterizerState = GameMain.Instance.GraphicsDevice.RasterizerState;
738  GameMain.Instance.GraphicsDevice.RasterizerState = GameMain.ScissorTestEnable;
739 
740  spriteRecorder.Render(camera);
741 
742  var mousePos = camera.ScreenToWorld(PlayerInput.MousePosition);
743  mousePos.Y = -mousePos.Y;
744 
745  spriteBatch.Begin(SpriteSortMode.BackToFront, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
746  GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
747  foreach (var hullCollection in hullCollections)
748  {
749  bool mouseOver = false;
750  if (GUI.MouseOn == null || GUI.MouseOn == component)
751  {
752  foreach (var rect in hullCollection.Rects)
753  {
754  mouseOver = rect.Contains(mousePos);
755  if (mouseOver) { break; }
756  }
757  }
758 
759  foreach (var rect in hullCollection.Rects)
760  {
761  GUI.DrawRectangle(spriteBatch, rect, mouseOver ? Color.Red : Color.Blue, depth: mouseOver ? 0.45f : 0.5f, thickness: (mouseOver ? 4f : 2f) / camera.Zoom);
762  }
763 
764  if (mouseOver)
765  {
766  LocalizedString str = hullCollection.Name;
767  Vector2 strSize = GUIStyle.Font.MeasureString(str) / camera.Zoom;
768  Vector2 padding = new Vector2(30, 30) / camera.Zoom;
769  Vector2 shift = new Vector2(10, 0) / camera.Zoom;
770 
771  GUI.DrawRectangle(spriteBatch, mousePos + shift, strSize + padding, Color.Black, isFilled: true, depth: 0.25f);
772  GUIStyle.Font.DrawString(spriteBatch, str, mousePos + shift + (strSize + padding) * 0.5f, Color.White, 0f, strSize * camera.Zoom * 0.5f, 1f / camera.Zoom, SpriteEffects.None, 0f);
773  }
774  }
775  foreach (var door in doors)
776  {
777  GUI.DrawRectangle(spriteBatch, door.Rect, GUIStyle.Green * 0.5f, isFilled: true, depth: 0.4f);
778  }
779  spriteBatch.End();
780 
781  GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
782  GameMain.Instance.GraphicsDevice.RasterizerState = prevRasterizerState;
783  spriteBatch.Begin(SpriteSortMode.Deferred);
784  }
785 
786  public void Dispose()
787  {
788  if (previewFrame != null)
789  {
790  previewFrame.RectTransform.Parent = null;
791  previewFrame = null;
792  }
793  spriteRecorder?.Dispose(); spriteRecorder = null;
794  camera = null;
795  isDisposed = true;
796  }
797  }
798 }
float? Zoom
Definition: Camera.cs:78
void MoveCamera(float deltaTime, bool allowMove=true, bool allowZoom=true, bool allowInput=true, bool? followSub=null)
Definition: Camera.cs:255
Matrix Transform
Definition: Camera.cs:136
Point Resolution
Definition: Camera.cs:120
float MinZoom
Definition: Camera.cs:38
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
void UpdateTransform(bool interpolate=true, bool updateListener=true)
Definition: Camera.cs:199
Vector2 Position
Definition: Camera.cs:398
void StopMovement()
Definition: Camera.cs:389
const ushort NullEntityID
Definition: Entity.cs:14
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
RectTransform RectTransform
void Draw(ISpriteBatch spriteBatch, Sprite wireSprite, Color color, Vector2 offset, float depth, float width=0.3f)
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
RectTransform?? Parent
void Begin(SpriteSortMode sortMode)
void CreateSpecsWindow(GUIListBox parent, GUIFont font, bool includeTitle=true, bool includeClass=true, bool includeDescription=false, bool includeCrushDepth=false)
static void Create(SubmarineInfo submarineInfo)
@ Structure
Structures and hulls, but also items (for backwards support)!