5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Graphics;
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
18 private static readonly HashSet<Submarine> visibleSubs =
new HashSet<Submarine>();
20 private static double prevCullTime;
25 private const float CullInterval = 0.25f;
29 private const int CullMargin = 50;
33 private const int CullMoveThreshold = 50;
38 camView =
new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2);
44 camView.Y += camView.Height;
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)
62 sub.VisibleBorders.X + (
int)sub.WorldPosition.X,
63 sub.VisibleBorders.Y + (
int)sub.WorldPosition.Y,
64 sub.VisibleBorders.Width,
65 sub.VisibleBorders.Height);
73 if (visibleEntities ==
null)
79 visibleEntities.Clear();
84 if (entity ==
null || entity.
Removed) {
continue; }
87 if (!visibleSubs.Contains(entity.
Submarine)) {
continue; }
89 if (entity.
IsVisible(camView)) { visibleEntities.Add(entity); }
92 prevCullArea = camView;
93 prevCullTime = Timing.TotalTime;
103 visibleEntities?.Remove(entity);
106 public static void Draw(SpriteBatch spriteBatch,
bool editing =
false)
110 foreach (
MapEntity e
in entitiesToRender)
112 e.
Draw(spriteBatch, editing);
116 public static void DrawFront(SpriteBatch spriteBatch,
bool editing =
false, Predicate<MapEntity> predicate =
null)
120 foreach (
MapEntity e
in entitiesToRender)
124 if (predicate !=
null)
126 if (!predicate(e)) {
continue; }
129 e.
Draw(spriteBatch, editing,
false);
137 worldBorders.Location += sub.WorldPosition.ToPoint();
138 worldBorders.Y = -worldBorders.Y;
140 GUI.DrawRectangle(spriteBatch, worldBorders, Color.White,
false, 0, 5);
142 if (sub.SubBody ==
null || sub.subBody.PositionBuffer.Count < 2)
continue;
144 Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
145 prevPos.Y = -prevPos.Y;
147 for (
int i = 1; i < sub.subBody.PositionBuffer.Count; i++)
149 Vector2 currPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[i].Position);
150 currPos.Y = -currPos.Y;
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);
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)
169 depthSortedDamageable.Clear();
172 foreach (
MapEntity e
in entitiesToRender)
176 if (predicate !=
null)
178 if (!predicate(e)) {
continue; }
180 float drawDepth = structure.GetDrawDepth();
182 while (i < depthSortedDamageable.Count)
184 float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
185 if (otherDrawDepth < drawDepth) {
break; }
188 depthSortedDamageable.Insert(i, structure);
192 foreach (
Structure s
in depthSortedDamageable)
194 s.
DrawDamage(spriteBatch, damageEffect, editing);
196 if (damageEffect !=
null)
198 damageEffect.Parameters[
"aCutoff"].SetValue(0.0f);
199 damageEffect.Parameters[
"cCutoff"].SetValue(0.0f);
204 public static void DrawPaintedColors(SpriteBatch spriteBatch,
bool editing =
false, Predicate<MapEntity> predicate =
null)
208 foreach (
MapEntity e
in entitiesToRender)
212 if (hull.SupportsPaintedColors)
214 if (predicate !=
null)
216 if (!predicate(e)) {
continue; }
218 hull.DrawSectionColors(spriteBatch);
224 public static void DrawBack(SpriteBatch spriteBatch,
bool editing =
false, Predicate<MapEntity> predicate =
null)
228 foreach (
MapEntity e
in entitiesToRender)
232 if (predicate !=
null)
234 if (!predicate(e))
continue;
237 e.
Draw(spriteBatch, editing,
true);
241 public static void DrawGrid(SpriteBatch spriteBatch,
int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter,
float alpha = 1.0f)
243 Vector2 topLeft = roundedGridCenter - Vector2.One *
GridSize * gridCells / 2;
244 Vector2 bottomRight = roundedGridCenter + Vector2.One *
GridSize * gridCells / 2;
246 for (
int i = 0; i < gridCells; i++)
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;
251 float normalizedDistX = Math.Abs(i + distFromGridX - gridCells / 2) / (gridCells / 2);
252 float normalizedDistY = Math.Abs(i - distFromGridY - gridCells / 2) / (gridCells / 2);
254 float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
255 float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistX);
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);
269 [Obsolete(
"Use MiniMap.CreateMiniMap()")]
276 float aspectRatio = worldBorders.Width / (float)worldBorders.Height;
277 float parentAspectRatio = parent.
Rect.Width / (
float)parent.
Rect.Height;
282 (parentAspectRatio > aspectRatio ?
new Vector2(aspectRatio / parentAspectRatio, 1.0f) :
new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
286 UserData =
"hullcontainer"
291 HashSet<Hull> hullList =
Hull.
HullList.Where(hull => hull.Submarine ==
this || connectedSubs.Contains(hull.Submarine)).Where(hull => !ignoreOutpost ||
IsEntityFoundOnThisSub(hull,
true)).ToHashSet();
293 Dictionary<Hull, HashSet<Hull>> combinedHulls =
new Dictionary<Hull, HashSet<Hull>>();
295 foreach (
Hull hull
in hullList)
297 if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) {
continue; }
299 List<Hull> linkedHulls =
new List<Hull>();
302 linkedHulls.Remove(hull);
304 foreach (
Hull linkedHull
in linkedHulls)
306 if (!combinedHulls.ContainsKey(hull))
308 combinedHulls.Add(hull,
new HashSet<Hull>());
311 combinedHulls[hull].Add(linkedHull);
315 foreach (
Hull hull
in hullList)
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);
322 bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull));
324 if (hideHull) {
continue; }
326 Color color = Color.DarkCyan * 0.8f;
328 var hullFrame =
new GUIFrame(
new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style:
"MiniMapRoom", color: color)
333 new GUIFrame(
new RectTransform(Vector2.One, hullFrame.RectTransform), style:
"ScanLines", color: color);
336 foreach (var (mainHull, linkedHulls) in combinedHulls)
338 MiniMapHullData data =
ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
340 Vector2 relativeHullPos =
new Vector2(
341 (data.Bounds.X - worldBorders.X) / worldBorders.Width,
342 (worldBorders.Y - data.Bounds.Y) / worldBorders.Height);
344 Vector2 relativeHullSize =
new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height);
346 Color color = Color.DarkCyan * 0.8f;
351 foreach (var (r, _) in data.RectDatas)
353 float y = r.Y - -r.Height,
356 if (y > highestY) { highestY = y; }
357 if (x > highestX) { highestX = x; }
360 HashSet<GUIFrame> frames =
new HashSet<GUIFrame>();
362 foreach (var (snappredRect, hull) in data.RectDatas)
364 RectangleF rect = snappredRect;
365 rect.Height = -rect.Height;
366 rect.Y -= rect.Height;
368 var (parentW, parentH) = hullContainer.Rect.Size.ToVector2();
369 Vector2 size =
new Vector2(rect.Width / parentW, rect.Height / parentH);
371 Vector2 pos =
new Vector2(rect.X / parentW, rect.Y / parentH);
373 GUIFrame hullFrame =
new GUIFrame(
new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style:
"ScanLinesSeamless", color: color)
376 UVOffset =
new Vector2(highestX - rect.X, highestY - rect.Y)
379 frames.Add(hullFrame);
382 new GUICustomComponent(
new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, (spriteBatch, component) =>
384 foreach (List<Vector2> list in data.Polygon)
386 spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.Color, 2f);
388 }, (deltaTime, component) =>
390 if (component.Parent.Rect.Size != data.ParentSize)
402 if (pointsOfInterest !=
null)
404 foreach (
Entity entity
in pointsOfInterest)
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)
411 CanBeFocused =
false,
422 Dictionary<Hull, Rectangle> rects =
new Dictionary<Hull, Rectangle>();
424 worldRect.Y = -worldRect.Y;
426 rects.Add(mainHull, worldRect);
428 foreach (
Hull hull
in linkedHulls)
433 worldRect =
Rectangle.Union(worldRect, rect);
434 rects.Add(hull, rect);
437 worldRect.Y = -worldRect.Y;
439 List<RectangleF> normalizedRects =
new List<RectangleF>();
440 List<Hull> hullRefs =
new List<Hull>();
441 foreach (var (hull, rect) in rects)
446 var (posX, posY) =
new Vector2(
447 (wRect.X - worldBorders.X) / (
float)worldBorders.Width,
448 (worldBorders.Y - wRect.Y) / (
float)worldBorders.Height);
450 var (scaleX, scaleY) =
new Vector2(wRect.Width / (
float)worldBorders.Width, wRect.Height / (
float)worldBorders.Height);
452 RectangleF newRect =
new RectangleF(posX * parentRect.Width, posY * parentRect.Height, scaleX * parentRect.Width, scaleY * parentRect.Height);
454 normalizedRects.Add(newRect);
458 ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
460 List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
462 List<List<Vector2>> scaledPolygon =
new List<List<Vector2>>();
464 foreach (List<Vector2> list
in polygon)
466 var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
467 float sizeX = polySizeX - 1f,
468 sizeY = polySizeY - 1f;
470 scaledPolygon.Add(ToolBox.ScalePolygon(list,
new Vector2(sizeX / polySizeX, sizeY / polySizeY)));
473 return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
478 List<string> errorMsgs =
new List<string>();
485 errorMsgs.Add(TextManager.Get(
"NoHullsWarning").Value);
491 (Info.OutpostModuleInfo?.ModuleFlags.Any(f => f !=
"hallwayvertical" && f !=
"hallwayhorizontal") ??
true))
497 errorMsgs.Add(TextManager.Get(
"NoWaypointsWarning").Value);
505 errorMsgs.Add(TextManager.Get(
"WaterInHullsWarning").Value);
512 HashSet<ContainerTagPrefab> missingContainerTags =
new();
513 foreach (var prefab
in ContainerTagPrefab.Prefabs)
515 if (!prefab.IsRecommendedForSub(
this) || !prefab.WarnIfLess) {
continue; }
517 int count =
Item.
ItemList.Count(i => i.HasTag(prefab.Identifier));
518 if (count < prefab.RecommendedAmount)
520 missingContainerTags.Add(prefab);
524 if (missingContainerTags.Any())
526 StringBuilder sb =
new();
528 foreach (var tag
in missingContainerTags)
530 sb.AppendLine($
"- {tag.Name}");
532 if (missingContainerTags.Count > count && count >= 3)
534 var moreIndicator = TextManager.GetWithVariable(
535 "upgradeuitooltip.moreindicator",
537 (missingContainerTags.Count - count).ToString()).Value;
538 sb.AppendLine(moreIndicator);
543 errorMsgs.Add(TextManager.GetWithVariable(
544 "ContainerTagUI.CountWarning",
546 sb.ToString()).Value);
555 if (item.GetComponent<
Vent>() ==
null) {
continue; }
560 errorMsgs.Add(TextManager.Get(
"DisconnectedVentsWarning").Value);
570 Dictionary<Hull, float> hullOxygenFlow =
new Dictionary<Hull, float>();
572 foreach (var linkedTo
in item.
linkedTo)
574 if (linkedTo is not
Item linkedItem || linkedItem.GetComponent<
Vent>() is not
Vent vent) {
continue; }
575 if (vent.Item.CurrentHull ==
null)
577 vent.Item.FindHull();
578 if (vent.Item.CurrentHull ==
null) {
continue; }
580 float oxygenFlow = oxygenGenerator.GetVentOxygenFlow(vent);
581 if (!hullOxygenFlow.ContainsKey(vent.Item.CurrentHull))
583 hullOxygenFlow[vent.Item.CurrentHull] = oxygenFlow;
587 hullOxygenFlow[vent.Item.CurrentHull] += oxygenFlow;
590 foreach ((
Hull hull,
float oxygenFlow) in hullOxygenFlow)
594 errorMsgs.Add(TextManager.GetWithVariable(
"LowOxygenOutputWarning",
"[roomname]",
604 errorMsgs.Add(TextManager.Get(
"NoHumanSpawnpointWarning").Value);
612 errorMsgs.Add(TextManager.Get(
"NoCargoSpawnpointWarning").Value);
616 if (
Item.
ItemList.None(it => it.GetComponent<
Pump>() !=
null && it.HasTag(Tags.Ballast)))
620 errorMsgs.Add(TextManager.Get(
"NoBallastTagsWarning").Value);
624 if (
Item.
ItemList.None(it => it.HasTag(Tags.HiddenItemContainer)))
628 errorMsgs.Add(TextManager.Get(
"NoHiddenContainersWarning").Value);
632 if (Info.Dimensions.X * Physics.DisplayToRealWorldRatio > 80 ||
633 Info.Dimensions.Y * Physics.DisplayToRealWorldRatio > 32)
637 errorMsgs.Add(TextManager.Get(
"TooLargeForEndGameWarning").Value);
647 if (junctionBox ==
null) {
continue; }
649 item.
linkedTo.Count(lt => lt is
Gap || (lt is
Item it2 && it2.GetComponent<
Door>() !=
null)) +
654 if (doorLinks + wireCount > item.
Connections[i].MaxWires)
656 errorMsgs.Add(TextManager.GetWithVariables(
"InsufficientFreeConnectionsWarning",
657 (
"[doorcount]", doorLinks.ToString()),
658 (
"[freeconnectioncount]", (item.
Connections[i].MaxWires - wireCount).ToString())).Value);
665 if (
Gap.
GapList.Any(g => g.linkedTo.Count == 0))
669 errorMsgs.Add(TextManager.Get(
"NonLinkedGapsWarning").Value);
674 float entityCountWarningThreshold = 0.75f;
680 errorMsgs.Add(TextManager.Get(
"subeditor.itemcountwarning").Value);
689 errorMsgs.Add(TextManager.Get(
"subeditor.structurecountwarning").Value);
698 errorMsgs.Add(TextManager.Get(
"subeditor.wallcountwarning").Value);
707 errorMsgs.Add(TextManager.Get(
"subeditor.lightcountwarning").Value);
716 errorMsgs.Add(TextManager.Get(
"subeditor.shadowcastinglightswarning").Value);
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)));
727 var text =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), textListBox.Content.RectTransform),
string.Join(
"\n\n", errorMsgs), wrap:
true)
737 msgBox.
Buttons[0].OnClicked += (button, obj) =>
753 if (Vector2.Distance(e.
Position, HiddenSubPosition) > 20000)
758 item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f);
765 if (Vector2.Distance(e.
Position, HiddenSubPosition) > 20000)
768 TextManager.Get(
"Warning"),
769 TextManager.Get(
"FarAwayEntitiesWarning"),
770 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
772 msgBox.Buttons[0].OnClicked += (btn, obj) =>
777 msgBox.Buttons[0].OnClicked += msgBox.Close;
778 msgBox.Buttons[1].OnClicked += msgBox.Close;
793 int disabledItemLightCount = 0;
797 disabledItemLightCount += item.GetComponents<Items.
Components.LightComponent>().Count();
804 int disabledItemLightCount = 0;
808 disabledItemLightCount += item.GetComponents<Items.
Components.LightComponent>().Count();
810 return GameMain.
LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount;
818 Vector2 worldGridPos = VectorToWorldGrid(position, sub, round);
831 while (index < subBody.PositionBuffer.Count && sendingTime > subBody.PositionBuffer[index].Timestamp)
836 subBody.PositionBuffer.Insert(index, posInfo);
844 SetLayerEnabled(layerIdentifier, enabled);
Vector2 ScreenToWorld(Vector2 coords)
virtual Vector2 WorldPosition
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
List< GUIButton > Buttons
static SubEditorScreen SubEditorScreen
static Lights.LightManager LightManager
static List< Gap > GapList
LocalizedString DisplayName
static readonly List< Hull > HullList
const float OxygenConsumptionSpeed
List< Connection > Connections
Inventory ParentInventory
static readonly List< Item > ItemList
List< ItemComponent > Components
static void GetLinkedHulls(Hull hull, List< Hull > linkedHulls)
float CollapseEffectStrength
static readonly List< MapEntity > MapEntityList
virtual bool DrawBelowWater
override Vector2 Position
readonly List< MapEntity > linkedTo
virtual bool DrawOverWater
virtual bool IsVisible(Rectangle worldView)
virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
PosInfo ClientRead(IReadMessage msg, float sendingTime, string parentDebugName)
void DrawDamage(SpriteBatch spriteBatch, Effect damageEffect, bool editing)
static List< Structure > WallList
static List< WarningType > SuppressedWarnings
const int MaxShadowCastingLights
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 int GetShadowCastingLightCount()
override Vector2? WorldPosition
static void ForceVisibilityRecheck()
static void Draw(SpriteBatch spriteBatch, bool editing=false)
static float DamageEffectCutoff
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 CullEntities(Camera cam)
static List< Submarine > Loaded
static void DrawBack(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
static readonly Vector2 GridSize
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)
static int GetLightCount()
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
static Color DamageEffectColor
static List< WayPoint > WayPointList
Identifier ReadIdentifier()
Interface for entities that handle ServerNetObject.ENTITY_POSITION