799 lines
35 KiB
C#
799 lines
35 KiB
C#
using Barotrauma.Extensions;
|
|
using Barotrauma.IO;
|
|
using Barotrauma.Items.Components;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
sealed class SubmarinePreview : IDisposable
|
|
{
|
|
private readonly SubmarineInfo submarineInfo;
|
|
|
|
private SpriteRecorder spriteRecorder;
|
|
private Camera camera;
|
|
private Task loadTask;
|
|
private (Vector2 Min, Vector2 Max) bounds;
|
|
|
|
private volatile bool isDisposed;
|
|
|
|
private GUIFrame previewFrame;
|
|
|
|
private sealed class LoadedHull
|
|
{
|
|
public UInt16 ID;
|
|
public readonly ImmutableList<UInt16> LinkedHulls;
|
|
public readonly Rectangle Rect;
|
|
public readonly Identifier NameIdentifier;
|
|
|
|
public LoadedHull(XElement element)
|
|
{
|
|
ID = (ushort)element.GetAttributeInt("id", Entity.NullEntityID);
|
|
NameIdentifier = element.GetAttributeIdentifier("roomname", "");
|
|
Rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
|
Rect.Y = -Rect.Y;
|
|
LinkedHulls = element.GetAttributeUshortArray("linked", Array.Empty<ushort>()).ToImmutableList();
|
|
}
|
|
}
|
|
|
|
private sealed class HullCollection
|
|
{
|
|
public readonly List<LoadedHull> Hulls = new List<LoadedHull>();
|
|
public readonly List<Rectangle> Rects = new List<Rectangle>();
|
|
public readonly LocalizedString Name;
|
|
|
|
public HullCollection(LoadedHull hull)
|
|
{
|
|
Name = TextManager.Get(hull.NameIdentifier).Fallback(hull.NameIdentifier.ToString());
|
|
AddHull(hull);
|
|
}
|
|
|
|
public void AddHull(LoadedHull hull)
|
|
{
|
|
Hulls.Add(hull);
|
|
Rects.Add(hull.Rect);
|
|
}
|
|
|
|
private bool Contains(UInt16 hullId)
|
|
{
|
|
return Hulls.Any(h => h.ID == hullId);
|
|
}
|
|
|
|
public bool IsLinkedTo(HullCollection other)
|
|
{
|
|
return
|
|
Hulls.Any(h => h.LinkedHulls.Any(id => other.Contains(id))) ||
|
|
other.Hulls.Any(h => h.LinkedHulls.Any(id => Contains(id)));
|
|
}
|
|
}
|
|
|
|
private readonly struct Door
|
|
{
|
|
public readonly Rectangle Rect;
|
|
|
|
public Door(Rectangle rect)
|
|
{
|
|
rect.Y = -rect.Y;
|
|
Rect = rect;
|
|
}
|
|
}
|
|
|
|
private readonly List<HullCollection> hullCollections = new List<HullCollection>();
|
|
private readonly List<Door> doors;
|
|
|
|
private static SubmarinePreview instance = null;
|
|
|
|
public static void Create(SubmarineInfo submarineInfo)
|
|
{
|
|
Close();
|
|
instance = new SubmarinePreview(submarineInfo);
|
|
}
|
|
|
|
public static void Close()
|
|
{
|
|
instance?.Dispose(); instance = null;
|
|
}
|
|
|
|
private SubmarinePreview(SubmarineInfo subInfo)
|
|
{
|
|
camera = new Camera();
|
|
submarineInfo = subInfo;
|
|
spriteRecorder = new SpriteRecorder();
|
|
isDisposed = false;
|
|
loadTask = null;
|
|
|
|
hullCollections = new List<HullCollection>();
|
|
doors = new List<Door>();
|
|
|
|
previewFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
|
|
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, previewFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
|
|
|
|
new GUIButton(new RectTransform(Vector2.One, previewFrame.RectTransform), "", style: null)
|
|
{
|
|
OnClicked = (btn, obj) => { Dispose(); return false; }
|
|
};
|
|
|
|
var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center));
|
|
int innerPadding = GUI.IntScale(100f);
|
|
var innerPadded = new GUIFrame(new RectTransform(new Point(innerFrame.Rect.Width - innerPadding, innerFrame.Rect.Height - innerPadding), previewFrame.RectTransform, Anchor.Center), style: null)
|
|
{
|
|
OutlineColor = Color.Black,
|
|
OutlineThickness = 2
|
|
};
|
|
|
|
GUITextBlock titleText = null;
|
|
GUIListBox specsContainer = null;
|
|
|
|
new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center),
|
|
(spriteBatch, component) =>
|
|
{
|
|
if (isDisposed) { return; }
|
|
camera.UpdateTransform(interpolate: true, updateListener: false);
|
|
Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2);
|
|
RenderSubmarine(spriteBatch, drawRect, component);
|
|
},
|
|
(deltaTime, component) =>
|
|
{
|
|
if (isDisposed) { return; }
|
|
bool isMouseOnComponent = GUI.MouseOn == component;
|
|
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
|
|
if (isMouseOnComponent &&
|
|
(PlayerInput.MidButtonHeld() || PlayerInput.PrimaryMouseButtonHeld()))
|
|
{
|
|
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
|
|
moveSpeed.X = -moveSpeed.X;
|
|
camera.Position += moveSpeed;
|
|
}
|
|
|
|
if (titleText != null && specsContainer != null)
|
|
{
|
|
specsContainer.Visible = GUI.IsMouseOn(titleText);
|
|
}
|
|
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
|
|
{
|
|
Dispose();
|
|
}
|
|
});
|
|
|
|
var topContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), innerPadded.RectTransform, Anchor.TopLeft), style: null)
|
|
{
|
|
Color = Color.Black * 0.65f
|
|
};
|
|
var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 5f / 7f), topContainer.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
|
|
|
titleText = new GUITextBlock(new RectTransform(new Vector2(0.95f, 1f), topLayout.RectTransform), subInfo.DisplayName, font: GUIStyle.LargeFont);
|
|
new GUIButton(new RectTransform(new Vector2(0.05f, 1f), topLayout.RectTransform), TextManager.Get("Close"))
|
|
{
|
|
OnClicked = (btn, obj) => { Dispose(); return false; }
|
|
};
|
|
|
|
specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) })
|
|
{
|
|
CurrentSelectMode = GUIListBox.SelectMode.None,
|
|
Color = Color.Black * 0.65f,
|
|
ScrollBarEnabled = false,
|
|
ScrollBarVisible = false,
|
|
Spacing = GUI.IntScale(5)
|
|
};
|
|
subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font,
|
|
includeTitle: false,
|
|
includeDescription: true);
|
|
int width = specsContainer.Rect.Width;
|
|
void recalculateSpecsContainerHeight()
|
|
{
|
|
int totalSize = 0;
|
|
var children = specsContainer.Content.Children.Where(c => c.Visible);
|
|
foreach (GUIComponent child in children)
|
|
{
|
|
totalSize += child.Rect.Height;
|
|
}
|
|
totalSize += specsContainer.Content.CountChildren * specsContainer.Spacing;
|
|
if (specsContainer.PadBottom)
|
|
{
|
|
GUIComponent last = specsContainer.Content.Children.LastOrDefault();
|
|
if (last != null)
|
|
{
|
|
totalSize += specsContainer.Rect.Height - last.Rect.Height;
|
|
}
|
|
}
|
|
specsContainer.RectTransform.Resize(new Point(width, totalSize), true);
|
|
specsContainer.RecalculateChildren();
|
|
}
|
|
//hell
|
|
recalculateSpecsContainerHeight();
|
|
specsContainer.Content.GetAllChildren<GUITextBlock>().ForEach(c =>
|
|
{
|
|
var firstChild = c.Children.FirstOrDefault() as GUITextBlock;
|
|
if (firstChild != null)
|
|
{
|
|
firstChild.CalculateHeightFromText(); firstChild.SetTextPos();
|
|
c.RectTransform.MinSize = new Point(0, firstChild.Rect.Height);
|
|
}
|
|
c.CalculateHeightFromText(); c.SetTextPos();
|
|
});
|
|
recalculateSpecsContainerHeight();
|
|
|
|
TaskPool.Add(nameof(GeneratePreviewMeshes), GeneratePreviewMeshes(), _ =>
|
|
{
|
|
if (isDisposed) { return; }
|
|
// Reset the camera's position on the main thread,
|
|
// because the Camera class is not thread-safe and
|
|
// it's possible for its state to not get updated
|
|
// properly if done within a task
|
|
camera.Position = (bounds.Min + bounds.Max) * (0.5f, -0.5f);
|
|
Vector2 span2d = bounds.Max - bounds.Min;
|
|
Vector2 scaledSpan2d = span2d / camera.Resolution.ToVector2();
|
|
float scaledSpan = Math.Max(scaledSpan2d.X, scaledSpan2d.Y);
|
|
camera.MinZoom = Math.Min(0.1f, 0.4f / scaledSpan);
|
|
camera.Zoom = 0.7f / scaledSpan;
|
|
camera.StopMovement();
|
|
camera.UpdateTransform(interpolate: false, updateListener: false);
|
|
});
|
|
}
|
|
|
|
public static void AddToGUIUpdateList()
|
|
{
|
|
instance?.previewFrame?.AddToGUIUpdateList();
|
|
}
|
|
|
|
public Task GeneratePreviewMeshes()
|
|
{
|
|
if (loadTask != null) { throw new InvalidOperationException("Tried to start SubmarinePreview loadTask more than once!"); }
|
|
loadTask = Task.Run(GeneratePreviewMeshesInternal);
|
|
return loadTask;
|
|
}
|
|
|
|
private async Task GeneratePreviewMeshesInternal()
|
|
{
|
|
await Task.Yield();
|
|
spriteRecorder.Begin(SpriteSortMode.BackToFront);
|
|
|
|
HashSet<int> toIgnore = new HashSet<int>();
|
|
HashSet<int> wires = new HashSet<int>();
|
|
|
|
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
|
|
{
|
|
switch (subElement.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "item":
|
|
foreach (var component in subElement.Elements())
|
|
{
|
|
switch (component.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "itemcontainer":
|
|
ExtractItemContainerIds(component, toIgnore);
|
|
break;
|
|
case "connectionpanel":
|
|
ExtractConnectionPanelLinks(component, wires);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (isDisposed) { return; }
|
|
await Task.Yield();
|
|
}
|
|
|
|
var wireNodes = new List<XElement>();
|
|
List<LoadedHull> loadedHulls = new List<LoadedHull>();
|
|
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
|
|
{
|
|
if (subElement.GetAttributeBool("hiddeningame", false)) { continue; }
|
|
switch (subElement.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "structure":
|
|
case "item":
|
|
var id = subElement.GetAttributeInt("ID", 0);
|
|
if (wires.Contains(id))
|
|
{
|
|
wireNodes.Add(subElement);
|
|
}
|
|
else if (!toIgnore.Contains(id))
|
|
{
|
|
BakeMapEntity(subElement);
|
|
}
|
|
break;
|
|
case "hull":
|
|
Identifier identifier = subElement.GetAttributeIdentifier("roomname", "");
|
|
if (!identifier.IsEmpty)
|
|
{
|
|
loadedHulls.Add(new LoadedHull(subElement));
|
|
}
|
|
break;
|
|
}
|
|
if (isDisposed) { return; }
|
|
await Task.Yield();
|
|
}
|
|
|
|
//List<HullCollection> tempHullCollections = new List<HullCollection>();
|
|
foreach (LoadedHull hull in loadedHulls)
|
|
{
|
|
hullCollections.Add(new HullCollection(hull));
|
|
}
|
|
|
|
bool intersectionFound;
|
|
do
|
|
{
|
|
intersectionFound = false;
|
|
for (int i = 0; i < hullCollections.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < hullCollections.Count; j++)
|
|
{
|
|
var collection1 = hullCollections[i];
|
|
var collection2 = hullCollections[j];
|
|
if (collection1.IsLinkedTo(collection2))
|
|
{
|
|
collection2.Hulls.ForEach(h => collection1.AddHull(h));
|
|
hullCollections.Remove(collection2);
|
|
intersectionFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (intersectionFound) { break; }
|
|
}
|
|
} while (intersectionFound);
|
|
|
|
bounds = (spriteRecorder.Min, spriteRecorder.Max);
|
|
wireNodes.ForEach(BakeWireNodes);
|
|
|
|
spriteRecorder.End();
|
|
}
|
|
|
|
private static void ExtractItemContainerIds(XElement component, HashSet<int> ids)
|
|
{
|
|
string containedString = component.GetAttributeString("contained", "");
|
|
string[] itemIdStrings = containedString.Split(',');
|
|
for (int i = 0; i < itemIdStrings.Length; i++)
|
|
{
|
|
foreach (string idStr in itemIdStrings[i].Split(';'))
|
|
{
|
|
if (!int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out int id)) { continue; }
|
|
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
|
|
{
|
|
var pins = component.Elements("input").Concat(component.Elements("output"));
|
|
foreach (var pin in pins)
|
|
{
|
|
var links = pin.Elements("link");
|
|
foreach (var link in links)
|
|
{
|
|
int id = link.GetAttributeInt("w", 0);
|
|
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BakeWireNodes(XElement element)
|
|
{
|
|
var prefabIdentifier = element.GetAttributeIdentifier("identifier", "");
|
|
if (prefabIdentifier.IsEmpty) { return; }
|
|
if (!ItemPrefab.Prefabs.TryGet(prefabIdentifier, out var prefab)) { return; }
|
|
|
|
var prefabWireComponentElement = prefab.ConfigElement.GetChildElement("wire");
|
|
if (prefabWireComponentElement is null) { return; }
|
|
|
|
var wireComponent = element.GetChildElement("wire");
|
|
if (wireComponent is null) { return; }
|
|
|
|
var color = element.GetAttributeColor("spritecolor") ?? Color.White;
|
|
|
|
var nodes = Wire.ExtractNodes(wireComponent).ToImmutableArray();
|
|
var wireSprite = Wire.ExtractWireSprite(prefab.ConfigElement);
|
|
|
|
var useSpriteDepth = element.GetAttributeBool("usespritedepth", false);
|
|
var depth =
|
|
useSpriteDepth
|
|
? element.GetAttributeFloat("spritedepth", 1.0f)
|
|
: wireSprite.Depth;
|
|
|
|
var width = prefabWireComponentElement.GetAttributeFloat("width", 0.3f);
|
|
|
|
for (int i = 0; i < nodes.Length - 1; i++)
|
|
{
|
|
var line = (Start: nodes[i], End: nodes[i + 1]);
|
|
var wireSegment = new Wire.WireSection(line.Start, line.End);
|
|
wireSegment.Draw(spriteRecorder, wireSprite, color, Vector2.Zero, depth, width);
|
|
}
|
|
}
|
|
|
|
private void BakeMapEntity(XElement element)
|
|
{
|
|
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
|
if (identifier.IsEmpty) { return; }
|
|
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
|
if (rect.Equals(Rectangle.Empty)) { return; }
|
|
|
|
float depth = element.GetAttributeFloat("spritedepth", 1f);
|
|
bool flippedX = element.GetAttributeBool("flippedx", false);
|
|
bool flippedY = element.GetAttributeBool("flippedy", false);
|
|
|
|
float scale = element.GetAttributeFloat("scale", 1f);
|
|
Color color = element.GetAttributeColor("spritecolor", Color.White);
|
|
|
|
float rotationRad = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0f));
|
|
|
|
MapEntityPrefab prefab;
|
|
if (element.NameAsIdentifier() == "item"
|
|
&& ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip))
|
|
{
|
|
prefab = ip;
|
|
}
|
|
else
|
|
{
|
|
prefab = MapEntityPrefab.FindByIdentifier(identifier);
|
|
}
|
|
if (prefab == null) { return; }
|
|
|
|
flippedX &= prefab.CanSpriteFlipX;
|
|
flippedY &= prefab.CanSpriteFlipY;
|
|
|
|
SpriteEffects spriteEffects = SpriteEffects.None;
|
|
if (flippedX)
|
|
{
|
|
spriteEffects |= SpriteEffects.FlipHorizontally;
|
|
}
|
|
if (flippedY)
|
|
{
|
|
spriteEffects |= SpriteEffects.FlipVertically;
|
|
}
|
|
|
|
var prevEffects = prefab.Sprite.effects;
|
|
prefab.Sprite.effects ^= spriteEffects;
|
|
|
|
bool overrideSprite = false;
|
|
ItemPrefab itemPrefab = prefab as ItemPrefab;
|
|
if (itemPrefab != null)
|
|
{
|
|
BakeItemComponents(itemPrefab, rect, color, scale, rotationRad, depth, out overrideSprite);
|
|
}
|
|
|
|
if (!overrideSprite)
|
|
{
|
|
if (prefab is StructurePrefab structurePrefab)
|
|
{
|
|
ParseUpgrades(structurePrefab.ConfigElement, ref scale);
|
|
|
|
if (!prefab.ResizeVertical)
|
|
{
|
|
rect.Height = (int)(rect.Height * scale / prefab.Scale);
|
|
}
|
|
if (!prefab.ResizeHorizontal)
|
|
{
|
|
rect.Width = (int)(rect.Width * scale / prefab.Scale);
|
|
}
|
|
var textureScale = element.GetAttributeVector2("texturescale", Vector2.One);
|
|
|
|
Vector2 backGroundOffset = Vector2.Zero;
|
|
|
|
Vector2 textureOffset = element.GetAttributeVector2("textureoffset", Vector2.Zero);
|
|
|
|
textureOffset = Structure.UpgradeTextureOffset(
|
|
targetSize: rect.Size.ToVector2(),
|
|
originalTextureOffset: textureOffset,
|
|
submarineInfo: submarineInfo,
|
|
sourceRect: prefab.Sprite.SourceRect,
|
|
scale: textureScale * scale,
|
|
flippedX: flippedX,
|
|
flippedY: flippedY);
|
|
|
|
backGroundOffset = new Vector2(
|
|
MathUtils.PositiveModulo(-textureOffset.X, prefab.Sprite.SourceRect.Width * textureScale.X * scale),
|
|
MathUtils.PositiveModulo(-textureOffset.Y, prefab.Sprite.SourceRect.Height * textureScale.Y * scale));
|
|
|
|
prefab.Sprite.DrawTiled(
|
|
spriteBatch: spriteRecorder,
|
|
position: new Vector2(rect.X + rect.Width / 2, -(rect.Y - rect.Height / 2)),
|
|
targetSize: rect.Size.ToVector2(),
|
|
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
|
|
color: color,
|
|
startOffset: backGroundOffset,
|
|
textureScale: textureScale * scale,
|
|
depth: depth,
|
|
rotation: rotationRad);
|
|
}
|
|
else if (itemPrefab != null)
|
|
{
|
|
bool usePrefabValues = element.GetAttributeBool("isoverride", false) != itemPrefab.IsOverride;
|
|
if (usePrefabValues)
|
|
{
|
|
scale = itemPrefab.ConfigElement.GetAttributeFloat(scale, "scale", "Scale");
|
|
}
|
|
|
|
ParseUpgrades(itemPrefab.ConfigElement, ref scale);
|
|
|
|
if (prefab.ResizeVertical || prefab.ResizeHorizontal)
|
|
{
|
|
if (!prefab.ResizeHorizontal)
|
|
{
|
|
rect.Width = (int)(prefab.Sprite.size.X * scale);
|
|
}
|
|
if (!prefab.ResizeVertical)
|
|
{
|
|
rect.Height = (int)(prefab.Sprite.size.Y * scale);
|
|
}
|
|
|
|
var spritePos = rect.Center.ToVector2();
|
|
//spritePos.Y = rect.Height - spritePos.Y;
|
|
|
|
prefab.Sprite.DrawTiled(
|
|
spriteRecorder,
|
|
rect.Location.ToVector2() * new Vector2(1f, -1f),
|
|
rect.Size.ToVector2(),
|
|
color: color,
|
|
textureScale: Vector2.One * scale,
|
|
depth: depth);
|
|
|
|
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
|
|
{
|
|
float offsetState = 0f;
|
|
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
|
|
if (flippedX) { offset.X = -offset.X; }
|
|
if (flippedY) { offset.Y = -offset.Y; }
|
|
decorativeSprite.Sprite.DrawTiled(spriteRecorder,
|
|
new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
|
|
rect.Size.ToVector2(), color: color,
|
|
textureScale: Vector2.One * scale,
|
|
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rect.Width = (int)(rect.Width * scale / prefab.Scale);
|
|
rect.Height = (int)(rect.Height * scale / prefab.Scale);
|
|
|
|
var spritePos = rect.Center.ToVector2();
|
|
spritePos.Y -= rect.Height;
|
|
//spritePos.Y = rect.Height - spritePos.Y;
|
|
|
|
prefab.Sprite.Draw(
|
|
spriteRecorder,
|
|
spritePos * new Vector2(1f, -1f),
|
|
color,
|
|
prefab.Sprite.Origin,
|
|
rotationRad,
|
|
scale,
|
|
prefab.Sprite.effects, depth);
|
|
|
|
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
|
|
{
|
|
float rotationState = 0f; float offsetState = 0f;
|
|
float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
|
|
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
|
|
if (flippedX) { offset.X = -offset.X; }
|
|
if (flippedY) { offset.Y = -offset.Y; }
|
|
float throwAway = 0.0f;
|
|
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
|
|
rotationRad + rot, decorativeSprite.GetScale(ref throwAway, 0f) * scale, prefab.Sprite.effects,
|
|
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
prefab.Sprite.effects = prevEffects;
|
|
}
|
|
|
|
private void BakeItemComponents(
|
|
ItemPrefab prefab,
|
|
Rectangle rect, Color color,
|
|
float scale, float rotationRad, float depth,
|
|
out bool overrideSprite)
|
|
{
|
|
overrideSprite = false;
|
|
|
|
float relativeScale = scale / prefab.Scale;
|
|
foreach (var subElement in prefab.ConfigElement.Elements())
|
|
{
|
|
switch (subElement.Name.LocalName.ToLowerInvariant())
|
|
{
|
|
case "turret":
|
|
Sprite barrelSprite = null;
|
|
Sprite railSprite = null;
|
|
foreach (var turretSubElem in subElement.Elements())
|
|
{
|
|
switch (turretSubElem.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "barrelsprite":
|
|
barrelSprite = new Sprite(turretSubElem);
|
|
break;
|
|
case "railsprite":
|
|
railSprite = new Sprite(turretSubElem);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector2 barrelPos = subElement.GetAttributeVector2("barrelpos", Vector2.Zero);
|
|
Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2);
|
|
var transformedBarrelPos = MathUtils.RotatePoint(
|
|
relativeBarrelPos,
|
|
rotationRad);
|
|
|
|
Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale);
|
|
drawPos.Y = -drawPos.Y;
|
|
|
|
railSprite?.Draw(spriteRecorder,
|
|
drawPos,
|
|
color,
|
|
rotationRad, scale,
|
|
SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth));
|
|
|
|
barrelSprite?.Draw(spriteRecorder,
|
|
drawPos,
|
|
color,
|
|
rotationRad, scale,
|
|
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth));
|
|
|
|
break;
|
|
case "door":
|
|
var scaledRect = rect with { Size = (rect.Size.ToVector2() * relativeScale).ToPoint() };
|
|
|
|
doors.Add(new Door(scaledRect));
|
|
|
|
var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
|
|
if (doorSpriteElem != null)
|
|
{
|
|
string texturePath = doorSpriteElem.GetAttributeStringUnrestricted("texture", "");
|
|
Vector2 pos = scaledRect.Location.ToVector2() * new Vector2(1f, -1f);
|
|
if (subElement.GetAttributeBool("horizontal", false))
|
|
{
|
|
pos.Y += (float)scaledRect.Height * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
pos.X += (float)scaledRect.Width * 0.5f;
|
|
}
|
|
Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
|
|
spriteRecorder.Draw(doorSprite.Texture, pos,
|
|
new Rectangle((int)doorSprite.SourceRect.X,
|
|
(int)doorSprite.SourceRect.Y,
|
|
(int)doorSprite.size.X, (int)doorSprite.size.Y),
|
|
color, 0.0f, doorSprite.Origin, new Vector2(scale), SpriteEffects.None, doorSprite.Depth);
|
|
}
|
|
break;
|
|
case "ladder":
|
|
var backgroundSprElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("backgroundsprite", StringComparison.OrdinalIgnoreCase));
|
|
if (backgroundSprElem != null)
|
|
{
|
|
Sprite backgroundSprite = new Sprite(backgroundSprElem);
|
|
backgroundSprite.DrawTiled(spriteRecorder,
|
|
new Vector2(rect.Left, -rect.Top) - backgroundSprite.Origin * scale,
|
|
new Vector2(backgroundSprite.size.X * scale, rect.Height), color: color,
|
|
textureScale: Vector2.One * scale,
|
|
depth: depth + 0.1f);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ParseUpgrades(XElement prefabConfigElement, ref float scale)
|
|
{
|
|
foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
|
|
{
|
|
var upgradeVersion = new Version(upgrade.GetAttributeString("gameversion", "0.0.0.0"));
|
|
if (upgradeVersion >= submarineInfo.GameVersion)
|
|
{
|
|
string scaleModifier = upgrade.GetAttributeString("scale", "*1");
|
|
|
|
if (scaleModifier.StartsWith("*"))
|
|
{
|
|
if (float.TryParse(scaleModifier.Substring(1), NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
|
|
{
|
|
scale *= parsedScale;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (float.TryParse(scaleModifier, NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
|
|
{
|
|
scale = parsedScale;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle, GUIComponent component)
|
|
{
|
|
if (spriteRecorder == null) { return; }
|
|
|
|
GUI.DrawRectangle(spriteBatch, scissorRectangle, new Color(0.051f, 0.149f, 0.271f, 1.0f), isFilled: true);
|
|
|
|
if (!spriteRecorder.ReadyToRender)
|
|
{
|
|
LocalizedString waitText = !loadTask.IsCompleted ?
|
|
TextManager.Get("generatingsubmarinepreview", "loading") :
|
|
(loadTask.Exception?.ToString() ?? "Task completed without marking as ready to render");
|
|
Vector2 origin = (GUIStyle.Font.MeasureString(waitText) * 0.5f);
|
|
origin.X = MathF.Round(origin.X);
|
|
origin.Y = MathF.Round(origin.Y);
|
|
GUIStyle.Font.DrawString(
|
|
spriteBatch,
|
|
waitText,
|
|
scissorRectangle.Center.ToVector2(),
|
|
Color.White,
|
|
0f,
|
|
origin,
|
|
1f,
|
|
SpriteEffects.None,
|
|
0f);
|
|
return;
|
|
}
|
|
spriteBatch.End();
|
|
|
|
var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
|
|
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
|
|
var prevRasterizerState = GameMain.Instance.GraphicsDevice.RasterizerState;
|
|
GameMain.Instance.GraphicsDevice.RasterizerState = GameMain.ScissorTestEnable;
|
|
|
|
spriteRecorder.Render(camera);
|
|
|
|
var mousePos = camera.ScreenToWorld(PlayerInput.MousePosition);
|
|
mousePos.Y = -mousePos.Y;
|
|
|
|
spriteBatch.Begin(SpriteSortMode.BackToFront, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
|
|
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
|
|
foreach (var hullCollection in hullCollections)
|
|
{
|
|
bool mouseOver = false;
|
|
if (GUI.MouseOn == null || GUI.MouseOn == component)
|
|
{
|
|
foreach (var rect in hullCollection.Rects)
|
|
{
|
|
mouseOver = rect.Contains(mousePos);
|
|
if (mouseOver) { break; }
|
|
}
|
|
}
|
|
|
|
foreach (var rect in hullCollection.Rects)
|
|
{
|
|
GUI.DrawRectangle(spriteBatch, rect, mouseOver ? Color.Red : Color.Blue, depth: mouseOver ? 0.45f : 0.5f, thickness: (mouseOver ? 4f : 2f) / camera.Zoom);
|
|
}
|
|
|
|
if (mouseOver)
|
|
{
|
|
LocalizedString str = hullCollection.Name;
|
|
Vector2 strSize = GUIStyle.Font.MeasureString(str) / camera.Zoom;
|
|
Vector2 padding = new Vector2(30, 30) / camera.Zoom;
|
|
Vector2 shift = new Vector2(10, 0) / camera.Zoom;
|
|
|
|
GUI.DrawRectangle(spriteBatch, mousePos + shift, strSize + padding, Color.Black, isFilled: true, depth: 0.25f);
|
|
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);
|
|
}
|
|
}
|
|
foreach (var door in doors)
|
|
{
|
|
GUI.DrawRectangle(spriteBatch, door.Rect, GUIStyle.Green * 0.5f, isFilled: true, depth: 0.4f);
|
|
}
|
|
spriteBatch.End();
|
|
|
|
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
|
GameMain.Instance.GraphicsDevice.RasterizerState = prevRasterizerState;
|
|
spriteBatch.Begin(SpriteSortMode.Deferred);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (previewFrame != null)
|
|
{
|
|
previewFrame.RectTransform.Parent = null;
|
|
previewFrame = null;
|
|
}
|
|
spriteRecorder?.Dispose(); spriteRecorder = null;
|
|
camera = null;
|
|
isDisposed = true;
|
|
}
|
|
}
|
|
}
|