using Barotrauma.Extensions; using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; namespace Barotrauma { partial class Submarine : Entity, IServerPositionSync { //drawing ---------------------------------------------------- private static readonly HashSet visibleSubs = new HashSet(); private static double prevCullTime; private static Rectangle prevCullArea; /// /// Interval at which we force culled entites to be updated, regardless if the camera has moved /// private const float CullInterval = 0.25f; /// /// Margin applied around the view area when culling entities (i.e. entities that are this far outside the view are still considered visible) /// private const int CullMargin = 50; /// /// Update entity culling when any corner of the view has moved more than this /// private const int CullMoveThreshold = 50; public static void CullEntities(Camera cam) { Rectangle camView = cam.WorldView; camView = new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2); if (Level.Loaded?.Renderer?.CollapseEffectStrength is > 0.0f) { //force everything to be visible when the collapse effect (which moves everything to a single point) is active camView = Rectangle.Union(AbsRect(camView.Location.ToVector2(), camView.Size.ToVector2()), new Rectangle(Point.Zero, Level.Loaded.Size)); camView.Y += camView.Height; } if (Math.Abs(camView.X - prevCullArea.X) < CullMoveThreshold && Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold && Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold && Math.Abs(camView.Bottom - prevCullArea.Bottom) < CullMoveThreshold && prevCullTime > Timing.TotalTime - CullInterval) { return; } visibleSubs.Clear(); foreach (Submarine sub in Loaded) { if (Level.Loaded != null && sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; } Rectangle worldBorders = new Rectangle( sub.VisibleBorders.X + (int)sub.WorldPosition.X, sub.VisibleBorders.Y + (int)sub.WorldPosition.Y, sub.VisibleBorders.Width, sub.VisibleBorders.Height); if (RectsOverlap(worldBorders, camView)) { visibleSubs.Add(sub); } } if (visibleEntities == null) { visibleEntities = new List(MapEntity.MapEntityList.Count); } else { visibleEntities.Clear(); } foreach (MapEntity entity in MapEntity.MapEntityList) { if (entity == null || entity.Removed) { continue; } if (entity.Submarine != null) { if (!visibleSubs.Contains(entity.Submarine)) { continue; } } if (entity.IsVisible(camView)) { visibleEntities.Add(entity); } } prevCullArea = camView; prevCullTime = Timing.TotalTime; } public static void ForceVisibilityRecheck() { prevCullTime = 0; } public static void ForceRemoveFromVisibleEntities(MapEntity entity) { visibleEntities?.Remove(entity); } public static void Draw(SpriteBatch spriteBatch, bool editing = false) { var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; foreach (MapEntity e in entitiesToRender) { e.Draw(spriteBatch, editing); } } public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null) { var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; foreach (MapEntity e in entitiesToRender) { if (!e.DrawOverWater) { continue; } if (predicate != null) { if (!predicate(e)) { continue; } } e.Draw(spriteBatch, editing, false); } if (GameMain.DebugDraw) { foreach (Submarine sub in Loaded) { Rectangle worldBorders = sub.Borders; worldBorders.Location += (sub.DrawPosition + sub.HiddenSubPosition).ToPoint(); worldBorders.Y = -worldBorders.Y; GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5); if (sub.SubBody == null || sub.subBody.PositionBuffer.Count < 2) continue; Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position); prevPos.Y = -prevPos.Y; for (int i = 1; i < sub.subBody.PositionBuffer.Count; i++) { Vector2 currPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[i].Position); currPos.Y = -currPos.Y; GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 10, (int)currPos.Y - 10, 20, 20), Color.Blue * 0.6f, true, 0.01f); GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.5f, 0, 5); prevPos = currPos; } } } } public static float DamageEffectCutoff; private static readonly List depthSortedDamageable = new List(); public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate predicate = null) { var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; depthSortedDamageable.Clear(); //insertion sort according to draw depth foreach (MapEntity e in entitiesToRender) { if (e is Structure structure && structure.DrawDamageEffect) { if (predicate != null) { if (!predicate(e)) { continue; } } float drawDepth = structure.GetDrawDepth(); int i = 0; while (i < depthSortedDamageable.Count) { float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth(); if (otherDrawDepth < drawDepth) { break; } i++; } depthSortedDamageable.Insert(i, structure); } } foreach (Structure s in depthSortedDamageable) { s.DrawDamage(spriteBatch, damageEffect, editing); } } public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null) { var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; foreach (MapEntity e in entitiesToRender) { if (e is Hull hull) { if (hull.SupportsPaintedColors) { if (predicate != null) { if (!predicate(e)) { continue; } } hull.DrawSectionColors(spriteBatch); } } } } public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null) { var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList; foreach (MapEntity e in entitiesToRender) { if (!e.DrawBelowWater) continue; if (predicate != null) { if (!predicate(e)) continue; } e.Draw(spriteBatch, editing, true); } } public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f, Color? color = null) { Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2; Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2; for (int i = 0; i < gridCells; i++) { float middleIndex = (gridCells - 1) / 2.0f; float normalizedPos = Math.Abs((i - middleIndex) / middleIndex); float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedPos); float expandY = expandX; Color lineColor = color ?? Color.White; GUI.DrawLine(spriteBatch, new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y), new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y), lineColor * (1.0f - normalizedPos) * alpha, depth: 0.6f, width: 3); GUI.DrawLine(spriteBatch, new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY), new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY), lineColor * (1.0f - normalizedPos) * alpha, depth: 0.6f, width: 3); } } // TODO remove [Obsolete("Use MiniMap.CreateMiniMap()")] public void CreateMiniMap(GUIComponent parent, IEnumerable pointsOfInterest = null, bool ignoreOutpost = false) { Rectangle worldBorders = GetDockedBorders(); worldBorders.Location += WorldPosition.ToPoint(); //create a container that has the same "aspect ratio" as the sub float aspectRatio = worldBorders.Width / (float)worldBorders.Height; float parentAspectRatio = parent.Rect.Width / (float)parent.Rect.Height; float scale = 0.9f; GUIFrame hullContainer = new GUIFrame(new RectTransform( (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, parent.RectTransform, Anchor.Center), style: null) { UserData = "hullcontainer" }; var connectedSubs = GetConnectedSubs(); HashSet hullList = Hull.HullList.Where(hull => hull.Submarine == this || connectedSubs.Contains(hull.Submarine)).Where(hull => !ignoreOutpost || IsEntityFoundOnThisSub(hull, true)).ToHashSet(); Dictionary> combinedHulls = new Dictionary>(); foreach (Hull hull in hullList) { if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; } List linkedHulls = new List(); hull.GetLinkedHulls(linkedHulls); linkedHulls.Remove(hull); foreach (Hull linkedHull in linkedHulls) { if (!combinedHulls.ContainsKey(hull)) { combinedHulls.Add(hull, new HashSet()); } combinedHulls[hull].Add(linkedHull); } } foreach (Hull hull in hullList) { Vector2 relativeHullPos = new Vector2( (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, (worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height); Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height); bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull)); if (hideHull) { continue; } Color color = Color.DarkCyan * 0.8f; var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color) { UserData = hull }; new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: color); } foreach (var (mainHull, linkedHulls) in combinedHulls) { MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); Vector2 relativeHullPos = new Vector2( (data.Bounds.X - worldBorders.X) / worldBorders.Width, (worldBorders.Y - data.Bounds.Y) / worldBorders.Height); Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height); Color color = Color.DarkCyan * 0.8f; float highestY = 0f, highestX = 0f; foreach (var (r, _) in data.RectDatas) { float y = r.Y - -r.Height, x = r.X; if (y > highestY) { highestY = y; } if (x > highestX) { highestX = x; } } HashSet frames = new HashSet(); foreach (var (snappredRect, hull) in data.RectDatas) { RectangleF rect = snappredRect; rect.Height = -rect.Height; rect.Y -= rect.Height; var (parentW, parentH) = hullContainer.Rect.Size.ToVector2(); Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH); // TODO this won't be required if we some day switch RectTransform to use RectangleF Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH); GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color) { UserData = hull, UVOffset = new Vector2(highestX - rect.X, highestY - rect.Y) }; frames.Add(hullFrame); } new GUICustomComponent(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, (spriteBatch, component) => { foreach (List list in data.Polygon) { spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.Color, 2f); } }, (deltaTime, component) => { if (component.Parent.Rect.Size != data.ParentSize) { data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); } }) { UserData = frames, Color = color, CanBeFocused = false }; } if (pointsOfInterest != null) { foreach (Entity entity in pointsOfInterest) { Vector2 relativePos = new Vector2( (entity.WorldPosition.X - worldBorders.X) / worldBorders.Width, (worldBorders.Y - entity.WorldPosition.Y) / worldBorders.Height); new GUIFrame(new RectTransform(new Point(1, 1), hullContainer.RectTransform) { RelativeOffset = relativePos }, style: null) { CanBeFocused = false, UserData = entity }; } } } public static MiniMapHullData ConstructLinkedHulls(Hull mainHull, HashSet linkedHulls, GUIComponent parent, Rectangle worldBorders) { Rectangle parentRect = parent.Rect; Dictionary rects = new Dictionary(); Rectangle worldRect = mainHull.WorldRect; worldRect.Y = -worldRect.Y; rects.Add(mainHull, worldRect); foreach (Hull hull in linkedHulls) { Rectangle rect = hull.WorldRect; rect.Y = -rect.Y; worldRect = Rectangle.Union(worldRect, rect); rects.Add(hull, rect); } worldRect.Y = -worldRect.Y; List normalizedRects = new List(); List hullRefs = new List(); foreach (var (hull, rect) in rects) { Rectangle wRect = rect; wRect.Y = -wRect.Y; var (posX, posY) = new Vector2( (wRect.X - worldBorders.X) / (float)worldBorders.Width, (worldBorders.Y - wRect.Y) / (float)worldBorders.Height); var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height); RectangleF newRect = new RectangleF(posX * parentRect.Width, posY * parentRect.Height, scaleX * parentRect.Width, scaleY * parentRect.Height); normalizedRects.Add(newRect); hullRefs.Add(hull); } ImmutableArray snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1); List> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles); List> scaledPolygon = new List>(); foreach (List list in polygon) { var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list); float sizeX = polySizeX - 1f, sizeY = polySizeY - 1f; scaledPolygon.Add(ToolBox.ScalePolygon(list, new Vector2(sizeX / polySizeX, sizeY / polySizeY))); } return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray()); } public void CheckForErrors() { List errorMsgs = new List(); List warnings = new List(); if (!Hull.HullList.Any()) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoWaypoints)) { errorMsgs.Add(TextManager.Get("NoHullsWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoHulls); } } if (Info.Type != SubmarineType.OutpostModule || (Info.OutpostModuleInfo?.ModuleFlags.Any(f => f != "hallwayvertical" && f != "hallwayhorizontal") ?? true)) { if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoWaypoints)) { errorMsgs.Add(TextManager.Get("NoWaypointsWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoWaypoints); } } } if (Hull.HullList.Any(h => h.WaterVolume > 0.0f)) { errorMsgs.Add(TextManager.Get("WaterInHullsWarning").Value); warnings.Add(SubEditorScreen.WarningType.WaterInHulls); Hull.ShowHulls = true; } if (Info.IsWreck) { Point vanillaBrainSize = new Point(204, 204); if (WreckAI.GetPotentialBrainRooms(this, WreckAIConfig.GetRandom(), minSize: vanillaBrainSize).None()) { errorMsgs.Add(TextManager.Get("NoSuitableBrainRoomsWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoSuitableBrainRooms); } } if (!IsWarningSuppressed(SubEditorScreen.WarningType.NotEnoughContainers)) { HashSet missingContainerTags = new(); foreach (var prefab in ContainerTagPrefab.Prefabs) { if (!prefab.IsRecommendedForSub(this) || !prefab.WarnIfLess) { continue; } int count = Item.ItemList.Count(i => i.HasTag(prefab.Identifier)); if (count < prefab.RecommendedAmount) { missingContainerTags.Add(prefab); } } if (missingContainerTags.Any()) { StringBuilder sb = new(); int count = 0; foreach (var tag in missingContainerTags) { sb.AppendLine($"- {tag.Name}"); count++; if (missingContainerTags.Count > count && count >= 3) { var moreIndicator = TextManager.GetWithVariable( "upgradeuitooltip.moreindicator", "[amount]", (missingContainerTags.Count - count).ToString()).Value; sb.AppendLine(moreIndicator); break; } } errorMsgs.Add(TextManager.GetWithVariable( "ContainerTagUI.CountWarning", "[tags]", sb.ToString()).Value); warnings.Add(SubEditorScreen.WarningType.NotEnoughContainers); } } if (Info.Type == SubmarineType.Player) { foreach (Item item in Item.ItemList) { if (item.GetComponent() == null) { continue; } if (!item.linkedTo.Any()) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.DisconnectedVents)) { errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning").Value); warnings.Add(SubEditorScreen.WarningType.DisconnectedVents); } break; } } foreach (Item item in Item.ItemList) { if (item.GetComponent() is not OxygenGenerator oxygenGenerator) { continue; } oxygenGenerator.GetVents(); Dictionary hullOxygenFlow = new Dictionary(); foreach (var linkedTo in item.linkedTo) { if (linkedTo is not Item linkedItem || linkedItem.GetComponent() is not Vent vent) { continue; } if (vent.Item.CurrentHull == null) { vent.Item.FindHull(); if (vent.Item.CurrentHull == null) { continue; } } float oxygenFlow = oxygenGenerator.GetVentOxygenFlow(vent); if (!hullOxygenFlow.ContainsKey(vent.Item.CurrentHull)) { hullOxygenFlow[vent.Item.CurrentHull] = oxygenFlow; } else { hullOxygenFlow[vent.Item.CurrentHull] += oxygenFlow; } } foreach ((Hull hull, float oxygenFlow) in hullOxygenFlow) { if (oxygenFlow < Hull.OxygenConsumptionSpeed) { errorMsgs.Add(TextManager.GetWithVariable("LowOxygenOutputWarning", "[roomname]", hull.DisplayName).Value); warnings.Add(SubEditorScreen.WarningType.LowOxygenOutputWarning); } } } if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human)) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHumanSpawnpoints)) { errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoHumanSpawnpoints); } } if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoCargoSpawnpoints)) { errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoCargoSpawnpoints); } } if (Item.ItemList.None(it => it.GetComponent() != null && it.HasTag(Tags.Ballast))) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoBallastTag)) { errorMsgs.Add(TextManager.Get("NoBallastTagsWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoBallastTag); } } if (Item.ItemList.None(it => it.HasTag(Tags.HiddenItemContainer))) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHiddenContainers)) { errorMsgs.Add(TextManager.Get("NoHiddenContainersWarning").Value); warnings.Add(SubEditorScreen.WarningType.NoHiddenContainers); } } if (Info.Dimensions.X * Physics.DisplayToRealWorldRatio > 80 || Info.Dimensions.Y * Physics.DisplayToRealWorldRatio > 32) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.TooLargeForEndGame)) { errorMsgs.Add(TextManager.Get("TooLargeForEndGameWarning").Value); warnings.Add(SubEditorScreen.WarningType.TooLargeForEndGame); } } } else if (Info.Type == SubmarineType.OutpostModule) { foreach (Item item in Item.ItemList) { var junctionBox = item.GetComponent(); if (junctionBox == null) { continue; } int doorLinks = item.linkedTo.Count(lt => lt is Gap || (lt is Item it2 && it2.GetComponent() != null)) + Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2)); for (int i = 0; i < item.Connections.Count; i++) { int wireCount = item.Connections[i].Wires.Count; if (doorLinks + wireCount > item.Connections[i].MaxWires) { errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", ("[doorcount]", doorLinks.ToString()), ("[freeconnectioncount]", (item.Connections[i].MaxWires - wireCount).ToString())).Value); warnings.Add(SubEditorScreen.WarningType.InsufficientFreeConnectionsWarning); break; } } } } if (Gap.GapList.Any(g => g.linkedTo.Count == 0)) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.NonLinkedGaps)) { errorMsgs.Add(TextManager.Get("NonLinkedGapsWarning").Value); warnings.Add(SubEditorScreen.WarningType.NonLinkedGaps); } } float entityCountWarningThreshold = 0.75f; if (Item.ItemList.Count > SubEditorScreen.MaxItems * entityCountWarningThreshold) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.ItemCount)) { errorMsgs.Add(TextManager.Get("subeditor.itemcountwarning").Value); warnings.Add(SubEditorScreen.WarningType.ItemCount); } } if ((MapEntity.MapEntityList.Count - Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count) > SubEditorScreen.MaxStructures * entityCountWarningThreshold) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.StructureCount)) { errorMsgs.Add(TextManager.Get("subeditor.structurecountwarning").Value); warnings.Add(SubEditorScreen.WarningType.StructureCount); } } if (Structure.WallList.Count > SubEditorScreen.MaxStructures * entityCountWarningThreshold) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.WallCount)) { errorMsgs.Add(TextManager.Get("subeditor.wallcountwarning").Value); warnings.Add(SubEditorScreen.WarningType.WallCount); } } if (GetLightCount() > SubEditorScreen.MaxLights * entityCountWarningThreshold) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.LightCount)) { errorMsgs.Add(TextManager.Get("subeditor.lightcountwarning").Value); warnings.Add(SubEditorScreen.WarningType.LightCount); } } if (GetShadowCastingLightCount() > SubEditorScreen.MaxShadowCastingLights * entityCountWarningThreshold) { if (!IsWarningSuppressed(SubEditorScreen.WarningType.ShadowCastingLightCount)) { errorMsgs.Add(TextManager.Get("subeditor.shadowcastinglightswarning").Value); warnings.Add(SubEditorScreen.WarningType.ShadowCastingLightCount); } } if (errorMsgs.Any()) { GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("Warning"), string.Empty, new Vector2(0.25f, 0.0f), minSize: new Point(GUI.IntScale(650), GUI.IntScale(650))); if (warnings.Any()) { var textListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), msgBox.Content.RectTransform)); var text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textListBox.Content.RectTransform), string.Join("\n\n", errorMsgs), wrap: true) { CanBeFocused = false }; text.RectTransform.MinSize = new Point(0, (int)text.TextSize.Y); Point size = msgBox.RectTransform.NonScaledSize; GUITickBox suppress = new GUITickBox(new RectTransform(new Vector2(1f, 0.33f), msgBox.Content.RectTransform), TextManager.Get("editor.suppresswarnings")); msgBox.RectTransform.NonScaledSize = new Point(size.X, size.Y + suppress.RectTransform.NonScaledSize.Y); msgBox.Buttons[0].OnClicked += (button, obj) => { if (suppress.Selected) { foreach (SubEditorScreen.WarningType warning in warnings.Where(warning => !SubEditorScreen.SuppressedWarnings.Contains(warning))) { SubEditorScreen.SuppressedWarnings.Add(warning); } } return true; }; } } foreach (MapEntity e in MapEntity.MapEntityList) { if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) { //move disabled items (wires, items inside containers) inside the sub if (e is Item item && item.body != null && !item.body.Enabled) { item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f); } } } foreach (MapEntity e in MapEntity.MapEntityList) { if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) { var msgBox = new GUIMessageBox( TextManager.Get("Warning"), TextManager.Get("FarAwayEntitiesWarning"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.SubEditorScreen.Cam.Position = e.WorldPosition; return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked += msgBox.Close; break; } } bool IsWarningSuppressed(SubEditorScreen.WarningType type) { return SubEditorScreen.SuppressedWarnings.Contains(type); } } public static int GetLightCount() { int disabledItemLightCount = 0; foreach (Item item in Item.ItemList) { if (item.ParentInventory == null) { continue; } disabledItemLightCount += item.GetComponents().Count(); } return GameMain.LightManager.Lights.Count() - disabledItemLightCount; } public static int GetShadowCastingLightCount() { int disabledItemLightCount = 0; foreach (Item item in Item.ItemList) { if (item.ParentInventory == null) { continue; } disabledItemLightCount += item.GetComponents().Count(); } return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount; } public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos = null, bool round = false) { Vector2 position = mousePos ?? PlayerInput.MousePosition; position = cam.ScreenToWorld(position); Vector2 worldGridPos = VectorToWorldGrid(position, sub, round); return worldGridPos; } public void ClientReadPosition(IReadMessage msg, float sendingTime) { var posInfo = PhysicsBody.ClientRead(msg, sendingTime, parentDebugName: Info.Name); msg.ReadPadBits(); if (posInfo != null) { int index = 0; while (index < subBody.PositionBuffer.Count && sendingTime > subBody.PositionBuffer[index].Timestamp) { index++; } subBody.PositionBuffer.Insert(index, posInfo); } } public void ClientEventRead(IReadMessage msg, float sendingTime) { Identifier layerIdentifier = msg.ReadIdentifier(); bool enabled = msg.ReadBoolean(); SetLayerEnabled(layerIdentifier, enabled); } } }