3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
7 using System.Globalization;
22 private Vector4 colorVector4;
44 colorVector4 =
new Vector4(value.R / 255.0f, value.G / 255.0f, value.B / 255.0f, value.A / 255.0f);
56 PerlinNoise.GetPerlin(
Rect.X / 1000.0f,
Rect.Y / 1000.0f),
57 PerlinNoise.GetPerlin(
Rect.Y / 1000.0f + 0.5f,
Rect.X / 1000.0f + 0.5f));
61 GrimeSprite = DecalManager.GrimeSprites[$
"{nameof(GrimeSprite)}{index % DecalManager.GrimeSpriteCount}"].
Sprite;
65 public BackgroundSection(Rectangle rect, ushort index,
float colorStrength, Color color, ushort rowIndex)
67 System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
76 PerlinNoise.GetPerlin(
Rect.X / 1000.0f,
Rect.Y / 1000.0f),
77 PerlinNoise.GetPerlin(
Rect.Y / 1000.0f + 0.5f,
Rect.X / 1000.0f + 0.5f));
81 GrimeSprite = DecalManager.GrimeSprites[$
"{nameof(GrimeSprite)}{index % DecalManager.GrimeSpriteCount}"].
Sprite;
87 if (Color == color) {
return false; }
102 if (Color == to) {
return false; }
103 colorVector4 = Vector4.Lerp(colorVector4, to.ToVector4(), amount);
104 color =
new Color(colorVector4);
116 public readonly
static List<Hull>
HullList =
new List<Hull>();
117 public readonly
static List<EntityGrid>
EntityGrids =
new List<EntityGrid>();
139 public readonly Dictionary<Identifier, SerializableProperty>
properties;
155 private float lethalPressure;
157 private float surface;
158 private float waterVolume;
159 private float pressure;
161 private float oxygen;
167 private float[] waveY;
168 private float[] waveVel;
170 private float[] leftDelta;
171 private float[] rightDelta;
175 private readonly List<Decal> decals =
new List<Decal>();
180 public override string Name =>
"Hull";
188 private readonly HashSet<Identifier> moduleTags =
new HashSet<Identifier>();
195 private string roomName;
199 get {
return roomName; }
202 if (roomName == value) {
return; }
214 private Color ambientLight;
219 get {
return ambientLight; }
222 ambientLight = value;
224 lastAmbientLightEditTime = Timing.TotalTime;
239 if (value.Width !=
rect.Width)
241 int arraySize = (int)Math.Ceiling((
float)value.Width /
WaveWidth + 1);
242 waveY =
new float[arraySize];
243 waveVel =
new float[arraySize];
244 leftDelta =
new float[arraySize];
245 rightDelta =
new float[arraySize];
259 drawSurface = surface;
263 CreateBackgroundSections();
274 get {
return lethalPressure; }
275 set { lethalPressure = MathHelper.Clamp(value, 0.0f, 100.0f); }
280 get {
return new Vector2(
rect.Width,
rect.Height); }
291 get {
return surface; }
301 get {
return waterVolume; }
304 if (!MathUtils.IsValid(value)) {
return; }
307 if (waterVolume > 0.0f)
317 get {
return oxygen; }
320 if (!MathUtils.IsValid(value))
return;
321 oxygen = MathHelper.Clamp(value, 0.0f,
Volume);
327 private bool ForceAsWetRoom =>
328 roomName !=
null && (
329 roomName.Contains(
"ballast", StringComparison.OrdinalIgnoreCase) ||
330 roomName.Contains(
"bilge", StringComparison.OrdinalIgnoreCase) ||
331 roomName.Contains(
"airlock", StringComparison.OrdinalIgnoreCase) ||
332 roomName.Contains(
"dockingport", StringComparison.OrdinalIgnoreCase));
334 private bool isWetRoom;
335 [
Editable,
Serialize(
false,
IsPropertySaveable.Yes, description:
"It's normal for this hull to be filled with water. If the room name contains 'ballast', 'bilge', or 'airlock', you can't disable this setting.")]
338 get {
return isWetRoom; }
349 private bool avoidStaying;
350 [
Editable,
Serialize(
false,
IsPropertySaveable.Yes, description:
"Bots avoid staying here, but they are still allowed to access the room when needed and go through it. Forced true for wet rooms.")]
353 get {
return avoidStaying ||
IsWetRoom; }
356 avoidStaying = value;
368 get {
return Volume <= 0.0f ? 100.0f : oxygen /
Volume * 100.0f; }
374 get {
return rect.Width *
rect.Height; }
379 get {
return pressure; }
380 set { pressure = value; }
385 get {
return waveY; }
390 get {
return waveVel; }
400 private readonly HashSet<int> pendingSectionUpdates =
new HashSet<int>();
412 private const int sectorWidth = 4;
413 private const int sectorHeight = 4;
415 private const float minColorStrength = 0.0f;
416 private const float maxColorStrength = 0.7f;
418 private bool networkUpdatePending;
419 private float networkUpdateTimer;
452 public Hull(Rectangle rectangle)
477 int arraySize = (int)Math.Ceiling((
float)rectangle.Width /
WaveWidth + 1);
478 waveY =
new float[arraySize];
479 waveVel =
new float[arraySize];
480 leftDelta =
new float[arraySize];
481 rightDelta =
new float[arraySize];
485 if (submarine?.Info !=
null && !submarine.
Info.
IsWreck)
489 MinSightRange = 1000,
490 MaxSightRange = 5000,
497 if (submarine ==
null || !submarine.
Loading)
503 CreateBackgroundSections();
509 DebugConsole.Log(
"Created hull (" +
ID +
")");
545 if (!property.Value.Attributes.OfType<
Editable>().Any()) {
continue; }
546 clone.SerializableProperties[
property.Key].TrySetValue(clone, property.Value.GetValue(
this));
549 clone.lastAmbientLightEditTime = 0.0;
556 var newGrid =
new EntityGrid(worldRect, 200.0f);
563 var newGrid =
new EntityGrid(submarine, 200.0f);
567 if (hull.
Submarine == submarine && !hull.
IdFreed) { newGrid.InsertEntity(hull); }
575 foreach (Identifier tag
in tags)
586 if (lowerPickedBody !=
null)
594 CeilingHeight = ConvertUnits.ToDisplayUnits(upperPickedPos.Y - lowerPickedPos.Y);
599 DetermineIsAirlock();
603 lastAmbientLightEditTime = 0.0;
611 if (grid.
Submarine != submarine)
continue;
613 rect.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
617 rect.Location += MathUtils.ToPoint(submarine.HiddenSubPosition);
630 index = (int)MathHelper.Clamp(index, 0, waveY.Length - 1);
634 public override void Move(Vector2 amount,
bool ignoreContacts =
true)
636 if (!MathUtils.IsValid(amount))
638 DebugConsole.ThrowError($
"Attempted to move a hull by an invalid amount ({amount})\n{Environment.StackTrace.CleanupStackTrace()}");
642 rect.X += (int)amount.X;
643 rect.Y += (
int)amount.Y;
653 drawSurface = surface;
669 List<FireSource> fireSourcesToRemove =
new List<FireSource>(
FireSources);
671 foreach (
FireSource fireSource
in fireSourcesToRemove)
701 List<FireSource> fireSourcesToRemove =
new List<FireSource>(
FireSources);
702 foreach (
FireSource fireSource
in fireSourcesToRemove)
734 public Decal AddDecal(UInt32 decalId, Vector2 worldPosition,
float scale,
bool isNetworkEvent,
int? spriteIndex =
null)
742 var decal = DecalManager.Prefabs.Find(p => p.UintIdentifier == decalId);
745 DebugConsole.ThrowError($
"Could not find a decal prefab with the UInt identifier {decalId}!");
748 return AddDecal(decal.Name, worldPosition, scale, isNetworkEvent, spriteIndex);
752 public Decal AddDecal(
string decalName, Vector2 worldPosition,
float scale,
bool isNetworkEvent,
int? spriteIndex =
null)
762 var decal = DecalManager.CreateDecal(decalName, scale, worldPosition,
this, spriteIndex);
775 #region Shared network write
781 for (
int i = 0; i < Math.Min(
FireSources.Count, 16); i++)
784 Vector2 normalizedPos =
new Vector2(
785 (fireSource.Position.X -
rect.X) /
rect.Width,
786 (fireSource.Position.Y - (
rect.Y -
rect.Height)) /
rect.Height);
788 msg.
WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
789 msg.
WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
794 private void SharedBackgroundSectionsWrite(
IWriteMessage msg, in BackgroundSectionsEventData backgroundSectionsEventData)
796 int sectorToUpdate = backgroundSectionsEventData.SectorStartIndex;
800 for (
int i = start; i < end; i++)
808 #region Shared network read
817 +
new Vector2(0, -hull.
Rect.Height)
818 + normalizedPosition * hull.
Rect.Size.ToVector2();
819 Size = normalizedSize * hull.
Rect.Width;
823 private void SharedStatusRead(
IReadMessage msg, out
float newWaterVolume, out NetworkFireSource[] newFireSources)
828 newFireSources =
new NetworkFireSource[fireSourceCount];
829 for (
int i = 0; i < fireSourceCount; i++)
831 float x = MathHelper.Clamp(msg.
ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
832 float y = MathHelper.Clamp(msg.
ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
834 newFireSources[i] =
new NetworkFireSource(
this,
new Vector2(x, y), size);
838 private readonly
struct BackgroundSectionNetworkUpdate
840 public readonly
int SectionIndex;
841 public readonly Color Color;
842 public readonly
float ColorStrength;
843 public BackgroundSectionNetworkUpdate(
int sectionIndex, Color color,
float colorStrength)
845 SectionIndex = sectionIndex;
847 ColorStrength = colorStrength;
851 private void SharedBackgroundSectionRead(
IReadMessage msg, Action<BackgroundSectionNetworkUpdate> action, out
int sectorToUpdate)
856 for (
int i = start; i < end; i++)
861 action(
new BackgroundSectionNetworkUpdate(i, color, colorStrength));
870 UpdateProjSpecific(deltaTime, cam);
891 foreach (
Decal decal
in decals)
898 for (
int i = decals.Count - 1; i >= 0; i--)
900 var decal = decals[i];
901 if (decal.FadeTimer >= decal.LifeTime || decal.BaseAlpha <= 0.001f)
905 decalUpdatePending =
true;
919 lethalPressure = 0.0f;
924 if (waterDepth < 1.0f)
931 surface = Math.Max(MathHelper.Lerp(
934 deltaTime * 10.0f),
rect.Y -
rect.Height);
936 for (
int i = 0; i < waveY.Length; i++)
939 waveY[i] = waveY[i] + waveVel[i];
942 if (surface + waveY[i] >
rect.Y)
944 float excess = (surface + waveY[i]) -
rect.Y;
946 waveVel[i] = waveVel[i] * -0.5f;
949 else if (surface + waveY[i] <
rect.Y -
rect.Height)
951 float excess = (surface + waveY[i]) - (
rect.Y -
rect.Height);
953 waveVel[i] = waveVel[i] * -0.5f;
958 waveVel[i] = waveVel[i] + a;
962 for (
int j = 0; j < 2; j++)
964 for (
int i = 1; i < waveY.Length - 1; i++)
966 leftDelta[i] =
WaveSpread * (waveY[i] - waveY[i - 1]);
967 waveVel[i - 1] += leftDelta[i];
969 rightDelta[i] =
WaveSpread * (waveY[i] - waveY[i + 1]);
970 waveVel[i + 1] += rightDelta[i];
984 if (surface > gap.
Rect.Y || surface < gap.
Rect.Y - gap.
Rect.Height) {
continue; }
988 float otherSurfaceY = hull2.surface;
989 if (otherSurfaceY > gap.
Rect.Y || otherSurfaceY < gap.
Rect.Y - gap.
Rect.Height) {
continue; }
991 float surfaceDiff = (surface - otherSurfaceY) * gap.
Open;
992 for (
int j = 0; j < 2; j++)
994 rightDelta[waveY.Length - 1] =
WaveSpread * (hull2.waveY[0] - waveY[waveY.Length - 1] - surfaceDiff) * 0.5f;
995 waveVel[waveY.Length - 1] += rightDelta[waveY.Length - 1];
996 waveY[waveY.Length - 1] += rightDelta[waveY.Length - 1];
998 hull2.leftDelta[0] =
WaveSpread * (waveY[waveY.Length - 1] - hull2.waveY[0] + surfaceDiff) * 0.5f;
999 hull2.waveVel[0] += hull2.leftDelta[0];
1000 hull2.waveY[0] += hull2.leftDelta[0];
1003 if (surfaceDiff < 32.0f)
1006 hull2.waveY[0] = surfaceDiff * 0.5f;
1007 waveY[waveY.Length - 1] = -surfaceDiff * 0.5f;
1013 for (
int j = 0; j < 2; j++)
1015 for (
int i = 1; i < waveY.Length - 1; i++)
1017 waveY[i - 1] += leftDelta[i];
1018 waveY[i + 1] += rightDelta[i];
1022 if (waterVolume <
Volume)
1026 float waterVolumeFactor = Math.Max((100.0f -
WaterPercentage) / 10.0f, 1.0f);
1033 if (drawSurface >
rect.Y -
rect.Height + 1) {
return; }
1035 for (
int i = 1; i < waveY.Length - 1; i++)
1037 if (waveY[i] > 0.1f) {
return; }
1045 partial
void UpdateProjSpecific(
float deltaTime,
Camera cam);
1055 var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.
Position) / 1000, 1f);
1057 if (force.LengthSquared() > 0.01f)
1059 item.body.ApplyForce(force);
1064 public void Extinguish(
float deltaTime,
float amount, Vector2 position,
bool extinguishRealFires =
true,
bool extinguishFakeFires =
true)
1066 if (extinguishRealFires)
1070 FireSources[i].Extinguish(deltaTime, amount, position);
1073 if (extinguishFakeFires)
1091 private readonly HashSet<Hull> adjacentHulls =
new HashSet<Hull>();
1092 public IEnumerable<Hull>
GetConnectedHulls(
bool includingThis,
int? searchDepth =
null,
bool ignoreClosedGaps =
false)
1094 adjacentHulls.Clear();
1096 searchDepth ??= 100;
1097 GetAdjacentHulls(adjacentHulls, ref startStep, searchDepth.Value, ignoreClosedGaps);
1098 if (!includingThis) { adjacentHulls.Remove(
this); }
1099 return adjacentHulls;
1102 private void GetAdjacentHulls(HashSet<Hull> connectedHulls, ref
int step,
int searchDepth,
bool ignoreClosedGaps =
false)
1104 connectedHulls.Add(
this);
1105 if (step > searchDepth) {
return; }
1108 if (ignoreClosedGaps && g.Open <= 0.0f) {
continue; }
1109 for (
int i = 0; i < 2 && i < g.linkedTo.Count; i++)
1111 if (g.linkedTo[i] is
Hull hull && !connectedHulls.Contains(hull))
1114 hull.GetAdjacentHulls(connectedHulls, ref step, searchDepth, ignoreClosedGaps);
1124 public float GetApproximateDistance(Vector2 startPos, Vector2 endPos,
Hull targetHull,
float maxDistance,
float distanceMultiplierPerClosedDoor = 0)
1126 return GetApproximateHullDistance(startPos, endPos,
new HashSet<Hull>(), targetHull, 0.0f, maxDistance, distanceMultiplierPerClosedDoor);
1129 private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls,
Hull target,
float distance,
float maxDistance,
float distanceMultiplierFromDoors = 0)
1131 if (distance >= maxDistance) {
return float.MaxValue; }
1134 return distance + Vector2.Distance(startPos, endPos);
1137 connectedHulls.Add(
this);
1141 float distanceMultiplier = 1;
1142 if (g.ConnectedDoor !=
null && !g.ConnectedDoor.IsBroken)
1145 if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.PredictedState.HasValue) ||
1147 (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
1149 if (g.ConnectedDoor.OpenState < 0.1f)
1151 if (distanceMultiplierFromDoors <= 0) {
continue; }
1152 distanceMultiplier *= distanceMultiplierFromDoors;
1156 else if (g.Open <= 0.0f)
1161 for (
int i = 0; i < 2 && i < g.linkedTo.Count; i++)
1163 if (g.linkedTo[i] is
Hull hull && !connectedHulls.Contains(hull))
1165 float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position) * distanceMultiplier, maxDistance);
1166 if (dist <
float.MaxValue)
1174 return float.MaxValue;
1184 public static Hull FindHull(Vector2 position,
Hull guess =
null,
bool useWorldCoordinates =
true,
bool inclusive =
true)
1193 if (
Submarine.
RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive))
1205 if (useWorldCoordinates)
1208 borders.Location +=
new Point((
int)worldPos.X, (
int)worldPos.Y);
1215 const float padding = 128.0f;
1216 if (position.X < borders.X - padding || position.X > borders.Right + padding ||
1217 position.Y > borders.Y + padding || position.Y < borders.Y - borders.Height - padding)
1222 Vector2 transformedPosition = position;
1223 if (useWorldCoordinates && entityGrid.
Submarine !=
null)
1227 var entities = entityGrid.
GetEntities(transformedPosition);
1228 if (entities ==
null) {
continue; }
1229 foreach (
Hull hull
in entities)
1248 if (guess !=
null &&
HullList.Contains(guess))
1250 if (
Submarine.
RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive))
return guess;
1272 Hull h = c.CurrentHull;
1273 HullList.ForEach(j => j.Visible =
false);
1274 List<Hull> visibleHulls;
1277 visibleHulls =
HullList.FindAll(j => j.CanSeeOther(
null,
false));
1281 visibleHulls =
HullList.FindAll(j => h.CanSeeOther(j,
true));
1283 visibleHulls.ForEach(j => j.Visible =
true);
1292 private bool CanSeeOther(
Hull other,
bool allowIndirect =
true)
1294 if (other ==
this)
return true;
1298 bool retVal =
false;
1302 List<Hull> otherHulls =
HullList.FindAll(h => h.ConnectedGaps.Contains(g) && h !=
this);
1303 retVal = otherHulls.Any(h => h == other);
1304 if (!retVal && allowIndirect) retVal = otherHulls.Any(h => h.CanSeeOther(other,
false));
1305 if (retVal)
return true;
1312 if (g.ConnectedDoor !=
null && !
HullList.Any(h => h.ConnectedGaps.Contains(g) && h !=
this))
return true;
1314 List<MapEntity> structures =
MapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(
Rect));
1315 return structures.Any(st => !(st as Structure).CastShadow);
1322 List<string> roomItems =
new List<string>();
1326 if (item.GetComponent<Items.
Components.Reactor>() !=
null) roomItems.Add(
"reactor");
1327 if (item.GetComponent<Items.
Components.Engine>() !=
null) roomItems.Add(
"engine");
1328 if (item.GetComponent<Items.
Components.Steering>() !=
null) roomItems.Add(
"steering");
1329 if (item.GetComponent<Items.
Components.Sonar>() !=
null) roomItems.Add(
"sonar");
1330 if (item.
HasTag(Tags.Ballast)) roomItems.Add(
"ballast");
1333 if (roomItems.Contains(
"reactor"))
1334 return "RoomName.ReactorRoom";
1335 else if (roomItems.Contains(
"engine"))
1336 return "RoomName.EngineRoom";
1337 else if (roomItems.Contains(
"steering") && roomItems.Contains(
"sonar"))
1338 return "RoomName.CommandRoom";
1339 else if (roomItems.Contains(
"ballast"))
1340 return "RoomName.Ballast";
1344 if (moduleFlags !=
null && moduleFlags.Any() &&
1347 if (moduleFlags.Contains(Tags.Airlock) &&
1348 ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor !=
null))
1350 return "RoomName.Airlock";
1355 if (
ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor !=
null))
1357 return "RoomName.Airlock";
1364 if (
rect.Y -
rect.Height / 2 > subRect.Y + subRect.Height * 0.66f)
1365 roomPos = Alignment.Top;
1366 else if (
rect.Y -
rect.Height / 2 > subRect.Y + subRect.Height * 0.33f)
1367 roomPos = Alignment.CenterY;
1369 roomPos = Alignment.Bottom;
1371 if (
rect.Center.X < subRect.X + subRect.Width * 0.33f)
1372 roomPos |= Alignment.Left;
1373 else if (
rect.Center.X < subRect.X + subRect.Width * 0.66f)
1374 roomPos |= Alignment.CenterX;
1376 roomPos |= Alignment.Right;
1378 return "RoomName.Sub" + roomPos.ToString();
1384 private void DetermineIsAirlock()
1386 if (
RoomName !=
null &&
RoomName.Contains(
"airlock", StringComparison.OrdinalIgnoreCase))
1393 var airlockTag =
"airlock".ToIdentifier();
1394 foreach (Item item
in Item.ItemList)
1396 if (item.CurrentHull !=
this && item.HasTag(airlockTag))
1414 if (gap.ConnectedDoor ==
null) {
continue; }
1415 if (gap.IsRoomToRoom) {
continue; }
1416 if (!gap.ConnectedDoor.CanBeTraversed && (character ==
null || !gap.ConnectedDoor.HasAccess(character))) {
continue; }
1422 #region BackgroundSections
1423 private void CreateBackgroundSections()
1425 int sectionWidth, sectionHeight;
1430 yBackgroundMax =
rect.Height / sectionHeight;
1437 for (
int y = 0; y < yBackgroundMax; y++)
1442 int sector = (
int)Math.Floor(index / (
float)sectorWidth - xSectors * y) + y / sectorHeight * (
int)Math.Ceiling(xSectors);
1457 if (worldPosition.X < worldRect.X || worldPosition.X > worldRect.Right) {
continue; }
1458 if (worldPosition.Y > worldRect.Y || worldPosition.Y < worldRect.Y - worldRect.Height) {
continue; }
1469 Vector2 relativePosition =
new Vector2(worldPosition.X - subOffset.X -
rect.X, worldPosition.Y - subOffset.Y -
rect.Y);
1474 if (yIndex < 0 || yIndex >= yBackgroundMax) {
return null; }
1490 if (xMax < 0) { yield
break; }
1493 if (yMin >= yBackgroundMax) { yield
break; }
1495 if (yMax < 0) { yield
break; }
1497 for (
int x = xMin; x <= xMax; x++)
1499 for (
int y = yMin; y <= yMax; y++)
1514 bool sectionUpdated = isCleaning;
1517 if (section.
Color != color.Value && strength.HasValue)
1524 if (section.
LerpColor(color.Value, changeSpeed)) { sectionUpdated =
true; }
1528 if (section.
SetColor(color.Value)) { sectionUpdated =
true; }
1532 if (strength !=
null)
1535 if (previous != -1f)
1540 sectionUpdated =
true;
1542 RefreshAveragePaintedColor();
1547 networkUpdatePending =
true;
1550 serverUpdateDelay = 0.5f;
1555 private void RefreshAveragePaintedColor()
1557 Vector4 avgColor = Vector4.Zero;
1560 avgColor += anySection.Color.ToVector4();
1573 if (strength !=
null)
1576 if (previous != -1f)
1587 bool decalsCleaned =
false;
1588 foreach (
Decal decal
in decals)
1592 decal.
Clean(cleanVal);
1593 decalsCleaned =
true;
1595 decalUpdatePending =
true;
1597 pendingDecalUpdates.Add(decal);
1598 networkUpdatePending =
true;
1603 if (section.ColorStrength == 0 && !decalsCleaned) {
return; }
1629 hull.linkedToID =
new List<ushort>();
1631 hull.ParseLinks(element, idRemap);
1633 string originalAmbientLight = element.
GetAttributeString(
"originalambientlight",
null);
1634 if (!
string.IsNullOrWhiteSpace(originalAmbientLight))
1636 hull.OriginalAmbientLight = XMLExtensions.ParseColor(originalAmbientLight,
false);
1639 foreach (var subElement
in element.Elements())
1641 switch (subElement.Name.ToString().ToLowerInvariant())
1644 string id = subElement.GetAttributeString(
"id",
"");
1645 Vector2 pos = subElement.GetAttributeVector2(
"pos", Vector2.Zero);
1646 float scale = subElement.GetAttributeFloat(
"scale", 1.0f);
1647 float timer = subElement.GetAttributeFloat(
"timer", 1.0f);
1648 float baseAlpha = subElement.GetAttributeFloat(
"alpha", 1.0f);
1649 var decal = hull.
AddDecal(
id, pos + hull.
WorldRect.Location.ToVector2(), scale,
true);
1653 decal.BaseAlpha = baseAlpha;
1656 case "ballastflorabehavior":
1657 Identifier identifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
1661 hull.
BallastFlora =
new BallastFloraBehavior(hull, prefab, Vector2.Zero);
1668 string backgroundSectionStr = element.GetAttributeString(
"backgroundsections",
"");
1669 if (!
string.IsNullOrEmpty(backgroundSectionStr))
1671 string[] backgroundSectionStrSplit = backgroundSectionStr.Split(
';');
1672 foreach (
string str
in backgroundSectionStrSplit)
1674 string[] backgroundSectionData = str.Split(
':');
1675 if (backgroundSectionData.Length != 3) {
continue; }
1676 Color color = XMLExtensions.ParseColor(backgroundSectionData[1]);
1677 if (
int.TryParse(backgroundSectionData[0], out
int index) &&
1678 float.TryParse(backgroundSectionData[2], NumberStyles.Any, CultureInfo.InvariantCulture, out
float strength))
1684 hull.RefreshAveragePaintedColor();
1687 if (element.GetAttribute(
"oxygen") ==
null) { hull.
Oxygen = hull.
Volume; }
1692 public override XElement
Save(XElement parentElement)
1696 string errorMsg =
"Error - tried to save a hull that's not a part of any submarine.\n" + Environment.StackTrace.CleanupStackTrace();
1697 DebugConsole.ThrowError(errorMsg);
1698 GameAnalyticsManager.AddErrorEventOnce(
"Hull.Save:WorldHull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
1702 XElement element =
new XElement(
"Hull");
1705 new XAttribute(
"ID",
ID),
1706 new XAttribute(
"rect",
1710 new XAttribute(
"water", waterVolume)
1715 var saveableLinked =
linkedTo.Where(l => l.ShouldBeSaved && (l.Removed ==
Removed)).ToList();
1716 element.Add(
new XAttribute(
"linked",
string.Join(
",", saveableLinked.Select(l => l.ID.ToString()))));
1721 element.Add(
new XAttribute(
"originalambientlight", XMLExtensions.ColorToString(
OriginalAmbientLight.Value)));
1728 "backgroundsections",
1729 string.Join(
';',
BackgroundSections.Where(b => b.ColorStrength > 0.01f).Select(b => b.Index +
":" + XMLExtensions.ColorToString(b.Color) +
":" + b.ColorStrength.ToString(
"G", CultureInfo.InvariantCulture)))));
1732 foreach (
Decal decal
in decals)
1735 new XElement(
"decal",
1738 new XAttribute(
"scale", decal.
Scale.ToString(
"G", CultureInfo.InvariantCulture)),
1739 new XAttribute(
"timer", decal.
FadeTimer.ToString(
"G", CultureInfo.InvariantCulture)),
1740 new XAttribute(
"alpha", decal.
BaseAlpha.ToString(
"G", CultureInfo.InvariantCulture))
1747 parentElement.Add(element);
1753 return $
"{base.ToString()} ({DisplayName ?? "unnamed
"}, {(Submarine?.Info?.Name ?? "no sub
")})";
bool LerpColor(Color to, float amount)
BackgroundSection(Rectangle rect, ushort index, ushort rowIndex)
float SetColorStrength(float colorStrength)
BackgroundSection(Rectangle rect, ushort index, float colorStrength, Color color, ushort rowIndex)
Color GetStrengthAdjustedColor()
bool SetColor(Color color)
static BallastFloraPrefab Find(Identifier identifier)
Affliction GetAffliction(string identifier, bool allowLimbAfflictions=true)
CharacterHealth CharacterHealth
static Character? Controlled
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
Rectangle GetAttributeRect(string key, in Rectangle def)
XAttribute? GetAttribute(string name)
readonly DecalPrefab Prefab
void Update(float deltaTime)
bool AffectsSection(BackgroundSection section)
Vector2 NonClampedPosition
readonly Submarine Submarine
void InsertEntity(MapEntity entity)
List< MapEntity > GetEntities(Vector2 position)
void RemoveEntity(MapEntity entity)
const ushort NullEntityID
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
static void UpdateAll(List< FireSource > fireSources, float deltaTime)
static NetworkMember NetworkMember
static void UpdateHulls()
Hull(Rectangle rectangle, Submarine submarine, ushort id=Entity.NullEntityID)
override void OnMapLoaded()
const float OxygenDistributionSpeed
void AddFireSource(FireSource fireSource)
const float PressureBuildUpSpeed
How fast the pressure in the hull builds up when there's a gap leading outside
static float WaveStiffness
LocalizedString DisplayName
static void DetectItemVisibility(Character c=null)
void IncreaseSectionColorOrStrength(BackgroundSection section, Color? color, float? strength, bool requiresUpdate, bool isCleaning)
bool IsGreen
Returns true if the green component of the background color is twice as bright as the red and blue....
override string ToString()
override MapEntity Clone()
const int BackgroundSectionsPerNetworkEvent
readonly Dictionary< Identifier, SerializableProperty > properties
IEnumerable< Identifier > OutpostModuleTags
Inherited flags from outpost generation.
override XElement Save(XElement parentElement)
int GetWaveIndex(float xPos)
BackgroundSection GetBackgroundSection(Vector2 worldPosition)
bool SupportsPaintedColors
IEnumerable< BackgroundSection > GetBackgroundSectionsViaContaining(Rectangle rectArea)
override void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
const float PressureDropSpeed
How fast the pressure in the hull goes back to normal when it's no longer full of water
Color? OriginalAmbientLight
void SetModuleTags(IEnumerable< Identifier > tags)
void Extinguish(float deltaTime, float amount, Vector2 position, bool extinguishRealFires=true, bool extinguishFakeFires=true)
float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor=0)
Approximate distance from this hull to the target hull, moving through open gaps without passing thro...
static Hull GetCleanTarget(Vector2 worldPosition)
Dictionary< Identifier, SerializableProperty > SerializableProperties
static EntityGrid GenerateEntityGrid(Submarine submarine)
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
List< DummyFireSource > FakeFireSources
const float OxygenDeteriorationSpeed
void RemoveFire(FireSource fire)
bool IsBlue
Returns true if the blue component of the background color is twice as bright as the red and green....
const int MaxDecalsPerHull
static readonly List< EntityGrid > EntityGrids
static EntityGrid GenerateEntityGrid(Rectangle worldRect)
readonly List< Gap > ConnectedGaps
BallastFloraBehavior BallastFlora
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
bool LeadsOutside(Character character)
Does this hull have any doors leading outside?
void SetSectionColorOrStrength(BackgroundSection section, Color? color, float? strength)
static Rectangle GetBorders()
static readonly List< Hull > HullList
static Hull FindHullUnoptimized(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any). The difference to FindHul...
const float OxygenConsumptionSpeed
override void Move(Vector2 amount, bool ignoreContacts=true)
void AddToGrid(Submarine submarine)
static float WaveDampening
bool DoesSectionMatch(int index, int row)
Color AveragePaintedColor
Average color of the background sections
int FireCount
Can be used by conditionals
IEnumerable< Hull > GetConnectedHulls(bool includingThis, int? searchDepth=null, bool ignoreClosedGaps=false)
bool IsRed
Returns true if the red component of the background color is twice as bright as the blue and green....
void ApplyFlowForces(float deltaTime, Item item)
override void Update(float deltaTime, Camera cam)
static Hull Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
const int BackgroundSectionSize
Decal AddDecal(string decalName, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
Hull(Rectangle rectangle)
List< BackgroundSection > BackgroundSections
void CleanSection(BackgroundSection section, float cleanVal, bool updateRequired)
List< FireSource > FireSources
int GetWaveIndex(Vector2 position)
ushort GetOffsetId(XElement element)
static void UpdateHulls()
goes through every item and re-checks which hull they are in
bool HasTag(Identifier tag)
static readonly List< Item > ItemList
List< ItemComponent > Components
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
static readonly List< MapEntity > MapEntityList
override Vector2 Position
readonly List< MapEntity > linkedTo
override Vector2 SimPosition
IEnumerable< Identifier > ModuleFlags
readonly Identifier Identifier
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
static void StoreCommand(Command command)
static bool IsSubEditor()
override Vector2? WorldPosition
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
Vector2 HiddenSubPosition
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
override Vector2? Position
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
static Vector2 LastPickedPosition
OutpostModuleInfo OutpostModuleInfo
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)
void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
void WriteUInt32(UInt32 val)
NetworkFireSource(Hull hull, Vector2 normalizedPosition, float normalizedSize)
readonly Vector2 Position