Unstable 1.1.14.0
This commit is contained in:
@@ -92,6 +92,11 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private bool flash;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a debris particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool debris;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a underwater bubble particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
@@ -120,12 +125,15 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// List of item tags that the explosion ignores when applying fire effects.
|
||||
/// </summary>
|
||||
private readonly string[] ignoreFireEffectsForTags;
|
||||
private readonly Identifier[] ignoreFireEffectsForTags;
|
||||
|
||||
/// <summary>
|
||||
/// When set to true, the explosion don't deal less damage when the target is behind a solid object.
|
||||
/// </summary>
|
||||
private readonly bool ignoreCover;
|
||||
public bool IgnoreCover
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How long the light source created by the explosion lasts.
|
||||
@@ -185,11 +193,12 @@ namespace Barotrauma
|
||||
this.EmpStrength = empStrength;
|
||||
BallastFloraDamage = ballastFloraStrength;
|
||||
sparks = true;
|
||||
debris = true;
|
||||
shockwave = true;
|
||||
smoke = true;
|
||||
flames = true;
|
||||
underwaterBubble = true;
|
||||
ignoreFireEffectsForTags = Array.Empty<string>();
|
||||
ignoreFireEffectsForTags = Array.Empty<Identifier>();
|
||||
}
|
||||
|
||||
public Explosion(ContentXElement element, string parentDebugName)
|
||||
@@ -205,13 +214,14 @@ namespace Barotrauma
|
||||
flames = element.GetAttributeBool("flames", showEffects);
|
||||
underwaterBubble = element.GetAttributeBool("underwaterbubble", showEffects);
|
||||
smoke = element.GetAttributeBool("smoke", showEffects);
|
||||
debris = element.GetAttributeBool("debris", false);
|
||||
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", showEffects);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames && showEffects);
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
ignoreFireEffectsForTags = element.GetAttributeIdentifierArray("ignorefireeffectsfortags", Array.Empty<Identifier>());
|
||||
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
IgnoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
OnlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
OnlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
|
||||
@@ -243,6 +253,7 @@ namespace Barotrauma
|
||||
shockwave = false;
|
||||
smoke = false;
|
||||
flash = false;
|
||||
debris = false;
|
||||
flames = false;
|
||||
underwaterBubble = false;
|
||||
}
|
||||
@@ -470,7 +481,7 @@ namespace Barotrauma
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
|
||||
//solid obstacles between the explosion and the limb reduce the effect of the explosion
|
||||
if (!ignoreCover)
|
||||
if (!IgnoreCover)
|
||||
{
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
}
|
||||
@@ -583,7 +594,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly List<Structure> damagedStructureList = new List<Structure>();
|
||||
private static readonly Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
|
||||
/// <summary>
|
||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||
@@ -591,37 +601,30 @@ namespace Barotrauma
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable<Submarine> ignoredSubmarines = null, bool emitWallDamageParticles = true)
|
||||
{
|
||||
float dist = 600.0f;
|
||||
damagedStructureList.Clear();
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
damagedStructures.Clear();
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
if (entity is not Structure structure) { continue; }
|
||||
if (ignoredSubmarines != null && entity.Submarine != null && ignoredSubmarines.Contains(entity.Submarine)) { continue; }
|
||||
if (ignoredSubmarines != null && structure.Submarine != null && ignoredSubmarines.Contains(structure.Submarine)) { continue; }
|
||||
|
||||
if (structure.HasBody &&
|
||||
!structure.IsPlatform &&
|
||||
Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f)
|
||||
{
|
||||
damagedStructureList.Add(structure);
|
||||
}
|
||||
}
|
||||
|
||||
damagedStructures.Clear();
|
||||
foreach (Structure structure in damagedStructureList)
|
||||
{
|
||||
for (int i = 0; i < structure.SectionCount; i++)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
|
||||
structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles);
|
||||
|
||||
if (damagedStructures.ContainsKey(structure))
|
||||
for (int i = 0; i < structure.SectionCount; i++)
|
||||
{
|
||||
damagedStructures[structure] += damage * distFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
damagedStructures.Add(structure, damage * distFactor);
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
|
||||
structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles);
|
||||
|
||||
if (damagedStructures.ContainsKey(structure))
|
||||
{
|
||||
damagedStructures[structure] += damage * distFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
damagedStructures.Add(structure, damage * distFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,7 +720,7 @@ namespace Barotrauma
|
||||
if (body.UserData is Item item)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null && !door.IsBroken) { damageMultiplier *= 0.01f; }
|
||||
if (door != null && !door.IsOpen && !door.IsBroken) { damageMultiplier *= 0.01f; }
|
||||
}
|
||||
else if (body.UserData is Structure structure)
|
||||
{
|
||||
|
||||
@@ -577,8 +577,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure = 0.0f;
|
||||
hull2.LethalPressure = 0.0f;
|
||||
hull1.LethalPressure -= Hull.PressureDropSpeed * deltaTime;
|
||||
hull2.LethalPressure -= Hull.PressureDropSpeed * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -642,7 +642,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : 10.0f) * deltaTime;
|
||||
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -657,7 +657,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
|
||||
{
|
||||
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : 10.0f) * deltaTime;
|
||||
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -140,6 +142,16 @@ namespace Barotrauma
|
||||
get { return properties; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How fast the pressure in the hull builds up when there's a gap leading outside
|
||||
/// </summary>
|
||||
public const float PressureBuildUpSpeed = 15.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How fast the pressure in the hull goes back to normal when it's no longer full of water
|
||||
/// </summary>
|
||||
public const float PressureDropSpeed = 10.0f;
|
||||
|
||||
private float lethalPressure;
|
||||
|
||||
private float surface;
|
||||
@@ -310,6 +322,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAirlock { get; private set; }
|
||||
|
||||
private bool ForceAsWetRoom =>
|
||||
roomName != null && (
|
||||
roomName.Contains("ballast", StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -404,6 +418,26 @@ namespace Barotrauma
|
||||
private bool networkUpdatePending;
|
||||
private float networkUpdateTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Average color of the background sections
|
||||
/// </summary>
|
||||
public Color AveragePaintedColor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the red component of the background color is twice as bright as the blue and green. Can be used by StatusEffects.
|
||||
/// </summary>
|
||||
public bool IsRed => ColorExtensions.IsRedDominant(AveragePaintedColor, minimumAlpha: 100);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the green component of the background color is twice as bright as the red and blue. Can be used by StatusEffects.
|
||||
/// </summary>
|
||||
public bool IsGreen => ColorExtensions.IsGreenDominant(AveragePaintedColor, minimumAlpha: 100);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the blue component of the background color is twice as bright as the red and green. Can be used by StatusEffects.
|
||||
/// </summary>
|
||||
public bool IsBlue => ColorExtensions.IsBlueDominant(AveragePaintedColor, minimumAlpha: 100);
|
||||
|
||||
public List<FireSource> FireSources { get; private set; }
|
||||
|
||||
public List<DummyFireSource> FakeFireSources { get; private set; }
|
||||
@@ -556,7 +590,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
|
||||
|
||||
|
||||
DetermineIsAirlock();
|
||||
|
||||
BallastFlora?.OnMapLoaded();
|
||||
#if CLIENT
|
||||
lastAmbientLightEditTime = 0.0;
|
||||
@@ -980,7 +1016,7 @@ namespace Barotrauma
|
||||
|
||||
if (waterVolume < Volume)
|
||||
{
|
||||
LethalPressure -= 10.0f * deltaTime;
|
||||
LethalPressure -= PressureDropSpeed * deltaTime;
|
||||
if (WaterVolume <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -1264,7 +1300,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (g.ConnectedDoor != null && !HullList.Any(h => h.ConnectedGaps.Contains(g) && h != this)) return true;
|
||||
}
|
||||
List<MapEntity> structures = mapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
|
||||
List<MapEntity> structures = MapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
|
||||
return structures.Any(st => !(st as Structure).CastShadow);
|
||||
}
|
||||
return false;
|
||||
@@ -1280,7 +1316,7 @@ namespace Barotrauma
|
||||
if (item.GetComponent<Items.Components.Engine>() != null) roomItems.Add("engine");
|
||||
if (item.GetComponent<Items.Components.Steering>() != null) roomItems.Add("steering");
|
||||
if (item.GetComponent<Items.Components.Sonar>() != null) roomItems.Add("sonar");
|
||||
if (item.HasTag("ballast")) roomItems.Add("ballast");
|
||||
if (item.HasTag(Tags.Ballast)) roomItems.Add("ballast");
|
||||
}
|
||||
|
||||
if (roomItems.Contains("reactor"))
|
||||
@@ -1297,7 +1333,7 @@ namespace Barotrauma
|
||||
if (moduleFlags != null && moduleFlags.Any() &&
|
||||
(Submarine.Info.Type == SubmarineType.OutpostModule || Submarine.Info.Type == SubmarineType.Outpost))
|
||||
{
|
||||
if (moduleFlags.Contains("airlock".ToIdentifier()) &&
|
||||
if (moduleFlags.Contains(Tags.Airlock) &&
|
||||
ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null))
|
||||
{
|
||||
return "RoomName.Airlock";
|
||||
@@ -1334,23 +1370,26 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Is this hull or any of the items inside it tagged as "airlock"?
|
||||
/// </summary>
|
||||
public bool IsTaggedAirlock()
|
||||
private void DetermineIsAirlock()
|
||||
{
|
||||
if (RoomName != null && RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
IsAirlock = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var airlockTag = "airlock".ToIdentifier();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != this && item.HasTag("airlock"))
|
||||
if (item.CurrentHull != this && item.HasTag(airlockTag))
|
||||
{
|
||||
return true;
|
||||
IsAirlock = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
IsAirlock = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1489,6 +1528,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
sectionUpdated = true;
|
||||
}
|
||||
RefreshAveragePaintedColor();
|
||||
}
|
||||
|
||||
if (sectionUpdated && GameMain.NetworkMember != null && requiresUpdate)
|
||||
@@ -1501,6 +1541,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshAveragePaintedColor()
|
||||
{
|
||||
Vector4 avgColor = Vector4.Zero;
|
||||
foreach (var anySection in BackgroundSections)
|
||||
{
|
||||
avgColor += anySection.Color.ToVector4();
|
||||
}
|
||||
avgColor /= BackgroundSections.Count;
|
||||
AveragePaintedColor = new Color(avgColor);
|
||||
}
|
||||
|
||||
public void SetSectionColorOrStrength(BackgroundSection section, Color? color, float? strength)
|
||||
{
|
||||
if (color != null)
|
||||
@@ -1619,6 +1670,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
hull.RefreshAveragePaintedColor();
|
||||
|
||||
SerializableProperty.DeserializeProperties(hull, element);
|
||||
if (element.GetAttribute("oxygen") == null) { hull.Oxygen = hull.Volume; }
|
||||
@@ -1687,7 +1739,7 @@ namespace Barotrauma
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Name ?? "unnamed"})";
|
||||
return $"{base.ToString()} ({DisplayName ?? "unnamed"}, {(Submarine?.Info?.Name ?? "no sub")})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Barotrauma
|
||||
|
||||
public Point StartPos, EndPos;
|
||||
|
||||
public bool DisplayOnSonar;
|
||||
public readonly HashSet<Mission> MissionsToDisplayOnSonar = new HashSet<Mission>();
|
||||
|
||||
public readonly CaveGenerationParams CaveGenerationParams;
|
||||
|
||||
@@ -334,13 +334,15 @@ namespace Barotrauma
|
||||
LevelGenParams,
|
||||
Size,
|
||||
GenStart,
|
||||
TunnelGen,
|
||||
TunnelGen1,
|
||||
TunnelGen2,
|
||||
AbyssGen,
|
||||
CaveGen,
|
||||
VoronoiGen,
|
||||
VoronoiGen2,
|
||||
VoronoiGen3,
|
||||
Ruins,
|
||||
Outposts,
|
||||
FloatingIce,
|
||||
LevelBodies,
|
||||
IceSpires,
|
||||
@@ -512,7 +514,6 @@ namespace Barotrauma
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
SetEqualityCheckValue(LevelGenStage.LevelGenParams, unchecked((int)GenerationParams.UintIdentifier));
|
||||
SetEqualityCheckValue(LevelGenStage.Size, borders.Width ^ borders.Height << 16);
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen);
|
||||
|
||||
LevelObjectManager = new LevelObjectManager();
|
||||
|
||||
@@ -574,7 +575,7 @@ namespace Barotrauma
|
||||
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y));
|
||||
endExitPosition = new Point(endPosition.X, borders.Bottom);
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen);
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen1);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate the initial nodes for the main path and smaller tunnels
|
||||
@@ -670,15 +671,15 @@ namespace Barotrauma
|
||||
CalculateTunnelDistanceField(null);
|
||||
GenerateSeaFloorPositions();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.AbyssGen);
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen2);
|
||||
|
||||
GenerateAbyssArea();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.CaveGen);
|
||||
GenerateEqualityCheckValue(LevelGenStage.AbyssGen);
|
||||
|
||||
GenerateCaves(mainPath);
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen);
|
||||
GenerateEqualityCheckValue(LevelGenStage.CaveGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate voronoi sites
|
||||
@@ -784,7 +785,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen2);
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// construct the voronoi graph and cells
|
||||
@@ -895,7 +896,7 @@ namespace Barotrauma
|
||||
startPosition.X = (int)pathCells[0].Site.Coord.X;
|
||||
startExitPosition.X = startPosition.X;
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen3);
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen2);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// remove unnecessary cells and create some holes at the bottom of the level
|
||||
@@ -933,8 +934,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<Point> ruinPositions = new List<Point>();
|
||||
int ruinCount = GenerationParams.RuinCount;
|
||||
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireRuin) ?? false)
|
||||
int ruinCount = GenerationParams.UseRandomRuinCount()
|
||||
? Rand.Range(GenerationParams.MinRuinCount, GenerationParams.MaxRuinCount + 1, Rand.RandSync.ServerAndClient)
|
||||
: GenerationParams.RuinCount;
|
||||
|
||||
bool hasRuinMissions = GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireRuin) ?? false;
|
||||
if (hasRuinMissions)
|
||||
{
|
||||
ruinCount = Math.Max(ruinCount, 1);
|
||||
}
|
||||
@@ -1131,7 +1136,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.Ruins);
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen3);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create some ruins
|
||||
@@ -1141,10 +1146,10 @@ namespace Barotrauma
|
||||
for (int i = 0; i < ruinPositions.Count; i++)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed) + i);
|
||||
GenerateRuin(ruinPositions[i], mirror);
|
||||
GenerateRuin(ruinPositions[i], mirror, hasRuinMissions);
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.FloatingIce);
|
||||
GenerateEqualityCheckValue(LevelGenStage.Ruins);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create floating ice chunks
|
||||
@@ -1176,7 +1181,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.LevelBodies);
|
||||
GenerateEqualityCheckValue(LevelGenStage.FloatingIce);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// generate the bodies and rendered triangles of the cells
|
||||
@@ -1281,7 +1286,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.IceSpires);
|
||||
GenerateEqualityCheckValue(LevelGenStage.LevelBodies);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create ice spires
|
||||
@@ -1294,6 +1299,8 @@ namespace Barotrauma
|
||||
if (spire != null) { ExtraWalls.Add(spire); };
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.IceSpires);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// connect side paths and cave branches to their parents
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -1316,7 +1323,7 @@ namespace Barotrauma
|
||||
|
||||
CreateOutposts();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.TopAndBottom);
|
||||
GenerateEqualityCheckValue(LevelGenStage.Outposts);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// top barrier & sea floor
|
||||
@@ -1325,7 +1332,8 @@ namespace Barotrauma
|
||||
TopBarrier = GameMain.World.CreateEdge(
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
||||
|
||||
//for debugging purposes
|
||||
TopBarrier.UserData = "topbarrier";
|
||||
TopBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f);
|
||||
TopBarrier.BodyType = BodyType.Static;
|
||||
TopBarrier.CollisionCategories = Physics.CollisionLevel;
|
||||
@@ -1358,15 +1366,23 @@ namespace Barotrauma
|
||||
CreateWrecks();
|
||||
CreateBeaconStation();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.PlaceLevelObjects);
|
||||
GenerateEqualityCheckValue(LevelGenStage.TopAndBottom);
|
||||
|
||||
LevelObjectManager.PlaceObjects(this, GenerationParams.LevelObjectAmount);
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenerateItems);
|
||||
GenerateEqualityCheckValue(LevelGenStage.PlaceLevelObjects);
|
||||
|
||||
GenerateItems();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.Finish);
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.IsOutpost)
|
||||
{
|
||||
OutpostGenerator.PowerUpOutpost(sub);
|
||||
}
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenerateItems);
|
||||
|
||||
#if CLIENT
|
||||
backgroundCreatureManager.SpawnCreatures(this, GenerationParams.BackgroundCreatureAmount);
|
||||
@@ -1384,7 +1400,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//initialize MapEntities that aren't in any sub (e.g. items inside ruins)
|
||||
MapEntity.MapLoaded(MapEntity.mapEntityList.FindAll(me => me.Submarine == null), false);
|
||||
MapEntity.MapLoaded(MapEntity.MapEntityList.FindAll(me => me.Submarine == null), false);
|
||||
|
||||
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
@@ -1409,6 +1425,7 @@ namespace Barotrauma
|
||||
GameMain.Server.EntityEventManager.Clear();
|
||||
#endif
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.Finish);
|
||||
//assign an ID to make entity events work
|
||||
//ID = FindFreeID();
|
||||
Generating = false;
|
||||
@@ -1949,7 +1966,8 @@ namespace Barotrauma
|
||||
BottomBarrier = GameMain.World.CreateEdge(
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
||||
|
||||
//for debugging purposes
|
||||
BottomBarrier.UserData = "bottombarrier";
|
||||
BottomBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, BottomPos)), 0.0f);
|
||||
BottomBarrier.BodyType = BodyType.Static;
|
||||
BottomBarrier.CollisionCategories = Physics.CollisionLevel;
|
||||
@@ -2055,23 +2073,56 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRuin(Point ruinPos, bool mirror)
|
||||
private void GenerateRuin(Point ruinPos, bool mirror, bool requireMissionReadyRuin)
|
||||
{
|
||||
var ruinGenerationParams = RuinGenerationParams.RuinParams.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
float GetWeight(RuinGenerationParams p)
|
||||
{
|
||||
if (p.PreferredDifficulty == -1) { return 100; }
|
||||
float diff = Math.Abs(Difficulty - p.PreferredDifficulty) / 100;
|
||||
float weight = MathUtils.Pow(1 - diff, 10);
|
||||
return Math.Max(weight, 0);
|
||||
}
|
||||
IEnumerable<RuinGenerationParams> possibleRuinGenerationParams = RuinGenerationParams.RuinParams;
|
||||
if (requireMissionReadyRuin)
|
||||
{
|
||||
possibleRuinGenerationParams = possibleRuinGenerationParams.Where(p => p.IsMissionReady);
|
||||
}
|
||||
if (possibleRuinGenerationParams.Multiple())
|
||||
{
|
||||
// Sort by weight and choose from the closest 25% of the candidates.
|
||||
// Prevents choosing from the "wrong" end, which would otherwise be possible (yet rare), because we use a weighted random for the pick.
|
||||
possibleRuinGenerationParams = possibleRuinGenerationParams
|
||||
/* the prefabs aren't in a consistent order, so we need to sort them first to ensure the clients and server choose the same one */
|
||||
.OrderByDescending(p => p.UintIdentifier)
|
||||
.OrderByDescending(GetWeight)
|
||||
.Take((int)Math.Max(Math.Round(possibleRuinGenerationParams.Count() / 4f), 1));
|
||||
}
|
||||
var selectedRuinGenerationParams = possibleRuinGenerationParams.GetRandomByWeight(GetWeight, randSync: Rand.RandSync.ServerAndClient);
|
||||
if (selectedRuinGenerationParams == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to generate alien ruins. Could not find any RuinGenerationParameters!");
|
||||
return;
|
||||
}
|
||||
DebugConsole.NewMessage($"Creating alien ruins using {selectedRuinGenerationParams.Identifier} (preferred difficulty: {selectedRuinGenerationParams.PreferredDifficulty}, current difficulty {Difficulty})", color: Color.Yellow);
|
||||
|
||||
LocationType locationType = StartLocation?.Type;
|
||||
if (locationType == null)
|
||||
{
|
||||
locationType = LocationType.Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
if (ruinGenerationParams.AllowedLocationTypes.Any())
|
||||
if (selectedRuinGenerationParams.AllowedLocationTypes.Any())
|
||||
{
|
||||
locationType = LocationType.Prefabs.Where(lt =>
|
||||
ruinGenerationParams.AllowedLocationTypes.Any(allowedType =>
|
||||
selectedRuinGenerationParams.AllowedLocationTypes.Any(allowedType =>
|
||||
allowedType == "any" || lt.Identifier == allowedType)).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
|
||||
var ruin = new Ruin(this, ruinGenerationParams, locationType, ruinPos, mirror);
|
||||
var ruin = new Ruin(this, selectedRuinGenerationParams, locationType, ruinPos, mirror);
|
||||
if (ruin.Submarine != null)
|
||||
{
|
||||
SetLinkedSubCrushDepth(ruin.Submarine);
|
||||
}
|
||||
|
||||
Ruins.Add(ruin);
|
||||
var tooClose = GetTooCloseCells(ruinPos.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 4);
|
||||
|
||||
@@ -3671,7 +3722,7 @@ namespace Barotrauma
|
||||
}
|
||||
tempSW.Stop();
|
||||
Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)");
|
||||
sub.SetPosition(spawnPoint);
|
||||
sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false);
|
||||
wreckPositions.Add(sub, positions);
|
||||
blockedRects.Add(sub, rects);
|
||||
return sub;
|
||||
@@ -3985,11 +4036,13 @@ namespace Barotrauma
|
||||
|
||||
SubmarineInfo outpostInfo;
|
||||
Submarine outpost = null;
|
||||
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
|
||||
|
||||
SubmarineInfo preSelectedOutpost = isStart ? preSelectedStartOutpost : preSelectedEndOutpost;
|
||||
if (preSelectedOutpost == null)
|
||||
{
|
||||
if (LevelData.OutpostGenerationParamsExist)
|
||||
{
|
||||
Location location = i == 0 ? StartLocation : EndLocation;
|
||||
Location location = isStart ? StartLocation : EndLocation;
|
||||
OutpostGenerationParams outpostGenerationParams = null;
|
||||
if (LevelData.ForceOutpostGenerationParams != null)
|
||||
{
|
||||
@@ -4027,7 +4080,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (string categoryToHide in locationType.HideEntitySubcategories)
|
||||
{
|
||||
foreach (MapEntity entityToHide in MapEntity.mapEntityList.Where(me => me.Submarine == outpost && (me.Prefab?.HasSubCategory(categoryToHide) ?? false)))
|
||||
foreach (MapEntity entityToHide in MapEntity.MapEntityList.Where(me => me.Submarine == outpost && (me.Prefab?.HasSubCategory(categoryToHide) ?? false)))
|
||||
{
|
||||
entityToHide.HiddenInGame = true;
|
||||
}
|
||||
@@ -4048,7 +4101,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading a pre-selected outpost for the {(isStart ? "start" : "end")} of the level...");
|
||||
outpostInfo = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
|
||||
outpostInfo = preSelectedOutpost;
|
||||
outpostInfo.Type = SubmarineType.Outpost;
|
||||
outpost = new Submarine(outpostInfo);
|
||||
}
|
||||
@@ -4132,6 +4185,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
outpost.SetPosition(spawnPos, forceUndockFromStaticSubmarines: false);
|
||||
SetLinkedSubCrushDepth(outpost);
|
||||
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
@@ -4178,7 +4232,7 @@ namespace Barotrauma
|
||||
ContentFile contentFile = null;
|
||||
if (!string.IsNullOrEmpty(GenerationParams.ForceBeaconStation))
|
||||
{
|
||||
contentFile = beaconStationFiles.FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation);
|
||||
contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation);
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead...");
|
||||
@@ -4266,77 +4320,75 @@ namespace Barotrauma
|
||||
beaconSonar.Item.CreateServerEvent(beaconSonar);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
else if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (!(GameMain.NetworkMember?.IsClient ?? false))
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation?.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation?.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
}
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
}
|
||||
|
||||
//remove wires
|
||||
float removeWireMinDifficulty = 20.0f;
|
||||
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (removeWireProbability > 0.0f && allowDisconnectedWires)
|
||||
//remove wires
|
||||
float removeWireMinDifficulty = 20.0f;
|
||||
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (removeWireProbability > 0.0f && allowDisconnectedWires)
|
||||
{
|
||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||
{
|
||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire.Locked) { continue; }
|
||||
if (wire.Connections[0] != null && (wire.Connections[0].Item.NonInteractable || wire.Connections[0].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
{
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire.Locked) { continue; }
|
||||
if (wire.Connections[0] != null && (wire.Connections[0].Item.NonInteractable || wire.Connections[0].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
continue;
|
||||
}
|
||||
if (wire.Connections[1] != null && (wire.Connections[1].Item.NonInteractable || wire.Connections[1].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Rand.Range(0f, 1.0f, Rand.RandSync.Unsynced) < removeWireProbability)
|
||||
{
|
||||
foreach (Connection connection in wire.Connections)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (wire.Connections[1] != null && (wire.Connections[1].Item.NonInteractable || wire.Connections[1].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Rand.Range(0f, 1.0f, Rand.RandSync.Unsynced) < removeWireProbability)
|
||||
{
|
||||
foreach (Connection connection in wire.Connections)
|
||||
if (connection != null)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
#if SERVER
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowDamagedWalls)
|
||||
if (allowDamagedWalls)
|
||||
{
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
{
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
{
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
}
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
{
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
{
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
}
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetLinkedSubCrushDepth(BeaconStation);
|
||||
}
|
||||
|
||||
public bool CheckBeaconActive()
|
||||
@@ -4345,7 +4397,15 @@ namespace Barotrauma
|
||||
return beaconSonar.Voltage > beaconSonar.MinVoltage && beaconSonar.CurrentMode == Sonar.Mode.Active;
|
||||
}
|
||||
|
||||
private bool IsModeStartOutpostCompatible()
|
||||
private void SetLinkedSubCrushDepth(Submarine parentSub)
|
||||
{
|
||||
foreach (var connectedSub in parentSub.GetConnectedSubs())
|
||||
{
|
||||
connectedSub.RealWorldCrushDepth = Math.Max(connectedSub.RealWorldCrushDepth, GetRealWorldDepth(0) + 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsModeStartOutpostCompatible()
|
||||
{
|
||||
#if CLIENT
|
||||
return GameMain.GameSession?.GameMode is CampaignMode || GameMain.GameSession?.GameMode is TutorialMode || GameMain.GameSession?.GameMode is TestGameMode;
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Barotrauma
|
||||
if (eventSet is null) { continue; }
|
||||
int count = childElement.GetAttributeInt("count", 0);
|
||||
if (count < 1) { continue; }
|
||||
FinishedEvents.Add(eventSet, count);
|
||||
FinishedEvents.TryAdd(eventSet, count);
|
||||
}
|
||||
|
||||
static EventSet FindSetRecursive(EventSet parentSet, Identifier setIdentifier)
|
||||
|
||||
@@ -502,9 +502,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public bool UseRandomRuinCount() => MinRuinCount >= 0 && MaxRuinCount > 0;
|
||||
|
||||
public int GetMaxRuinCount() => UseRandomRuinCount() ? MaxRuinCount : RuinCount;
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The number of alien ruins in the level. Ignored, if both MinRuinCount and MaxRuinCount are defined."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int RuinCount { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "The minimum number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinRuinCount { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "The maximum number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxRuinCount { get; set; }
|
||||
|
||||
// TODO: Move the wreck parameters under a separate class?
|
||||
#region Wreck parameters
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
|
||||
+10
-9
@@ -20,6 +20,8 @@ namespace Barotrauma
|
||||
private List<LevelObject> updateableObjects;
|
||||
private List<LevelObject>[,] objectGrid;
|
||||
|
||||
const float ParallaxStrength = 0.0001f;
|
||||
|
||||
public float GlobalForceDecreaseTimer
|
||||
{
|
||||
get;
|
||||
@@ -237,7 +239,6 @@ namespace Barotrauma
|
||||
PlaceObject(prefab, spawnPosition, level, cave);
|
||||
if (amount > prefab.MaxCount && objects.Count > prefab.MaxCount)
|
||||
{
|
||||
bool maxReached = false;
|
||||
int objectCount = 0;
|
||||
for (int j = 0; j < objects.Count; j++)
|
||||
{
|
||||
@@ -406,11 +407,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z / 10000.0f;
|
||||
float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z / 10000.0f;
|
||||
float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z * ParallaxStrength;
|
||||
float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z * ParallaxStrength;
|
||||
|
||||
float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z / 10000.0f - level.BottomPos;
|
||||
float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z / 10000.0f - level.BottomPos;
|
||||
float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z * ParallaxStrength - level.BottomPos;
|
||||
float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z * ParallaxStrength - level.BottomPos;
|
||||
|
||||
if (newObject.Triggers != null)
|
||||
{
|
||||
@@ -465,7 +466,7 @@ namespace Barotrauma
|
||||
for (int y = yStart; y <= yEnd; y++)
|
||||
{
|
||||
var list = objectGrid[x, y];
|
||||
if (objectGrid[x, y] == null) { list = objectGrid[x, y] = new List<LevelObject>(); }
|
||||
if (list == null) { objectGrid[x, y] = list = new List<LevelObject>(); }
|
||||
|
||||
//insertion sort in ascending order (= prefer rendering objects in front)
|
||||
int drawOrderIndex = 0;
|
||||
@@ -478,9 +479,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Microsoft.Xna.Framework.Point GetGridIndices(Vector2 worldPosition)
|
||||
public static Point GetGridIndices(Vector2 worldPosition)
|
||||
{
|
||||
return new Microsoft.Xna.Framework.Point(
|
||||
return new Point(
|
||||
(int)Math.Floor(worldPosition.X / GridSize),
|
||||
(int)Math.Floor((worldPosition.Y - Level.Loaded.BottomPos) / GridSize));
|
||||
}
|
||||
@@ -521,7 +522,7 @@ namespace Barotrauma
|
||||
return objectsInRange;
|
||||
}
|
||||
|
||||
private static List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType, bool checkFlags = true)
|
||||
private static List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
|
||||
{
|
||||
List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
|
||||
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
|
||||
|
||||
@@ -366,6 +366,7 @@ namespace Barotrauma
|
||||
private void LoadElements(LevelObjectPrefabsFile file, ContentXElement element, int parentTriggerIndex)
|
||||
{
|
||||
int propertyOverrideCount = 0;
|
||||
//load sprites first, OverrideProperties may need them (defaulting to the default sprite if no override is defined)
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -384,6 +385,12 @@ namespace Barotrauma
|
||||
case "deformablesprite":
|
||||
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "overridecommonness":
|
||||
Identifier levelType = subElement.GetAttributeIdentifier("leveltype", Identifier.Empty);
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
|
||||
@@ -27,6 +27,9 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
public override string Name => "RuinGenerationParams";
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Are these params designed to be used for alien ruins targeted by missions. If false, the params are ignored when there's any missions targeting ruins."), Editable]
|
||||
public bool IsMissionReady { get; set; }
|
||||
|
||||
public RuinGenerationParams(ContentXElement element, RuinConfigFile file) : base(element, file) { }
|
||||
|
||||
public static void SaveAll()
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
//prevent the ruin from extending above the level "ceiling"
|
||||
position.Y = Math.Min(level.Size.Y - (Submarine.Borders.Height / 2) - 100, position.Y);
|
||||
Submarine.SetPosition(position.ToVector2());
|
||||
Submarine.SetPosition(position.ToVector2(), forceUndockFromStaticSubmarines: false);
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
|
||||
@@ -398,7 +398,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles)
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign &&
|
||||
(campaign.PurchasedLostShuttles || campaign.PurchasedLostShuttlesInLatestSave))
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
|
||||
@@ -69,9 +69,11 @@ namespace Barotrauma
|
||||
public int LocationTypeChangeCooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Is some mission blocking this location from changing its type?
|
||||
/// Is some mission blocking this location from changing its type, or have location type changes been forcibly disabled on the location?
|
||||
/// </summary>
|
||||
public bool LocationTypeChangesBlocked => availableMissions.Any(m => m.Prefab.BlockLocationTypeChanges);
|
||||
public bool LocationTypeChangesBlocked => DisallowLocationTypeChanges || availableMissions.Any(m => m.Prefab.BlockLocationTypeChanges);
|
||||
|
||||
public bool DisallowLocationTypeChanges;
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
@@ -1146,6 +1148,7 @@ namespace Barotrauma
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (takenItems.Any(it => it.Matches(item) && it.OriginalID == item.ID)) { continue; }
|
||||
if (item.IsSalvageMissionItem) { continue; }
|
||||
if (item.OriginalModuleIndex < 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to register a non-outpost item as being taken from the outpost.");
|
||||
@@ -1417,7 +1420,7 @@ namespace Barotrauma
|
||||
|
||||
public void Reset(CampaignMode campaign)
|
||||
{
|
||||
if (Type != OriginalType)
|
||||
if (Type != OriginalType && !DisallowLocationTypeChanges)
|
||||
{
|
||||
ChangeType(campaign, OriginalType);
|
||||
PendingLocationTypeChange = null;
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
public bool AllowAsBiomeGate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can this location type be used in the random, non-campaign levels that don't take place in any specific zone
|
||||
/// </summary>
|
||||
@@ -120,6 +122,7 @@ namespace Barotrauma
|
||||
UsePortraitInRandomLoadingScreens = element.GetAttributeBool(nameof(UsePortraitInRandomLoadingScreens), true);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
AllowAsBiomeGate = element.GetAttributeBool(nameof(AllowAsBiomeGate), true);
|
||||
AllowInRandomLevels = element.GetAttributeBool(nameof(AllowInRandomLevels), true);
|
||||
|
||||
ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
|
||||
|
||||
@@ -614,13 +614,20 @@ namespace Barotrauma
|
||||
Connections[i].Locations[0].MapPosition.X < Connections[i].Locations[1].MapPosition.X ?
|
||||
Connections[i].Locations[0] :
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
|
||||
if (!AllowAsBiomeGate(leftMostLocation.Type))
|
||||
{
|
||||
leftMostLocation.ChangeType(
|
||||
campaign,
|
||||
LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"),
|
||||
LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => AllowAsBiomeGate(lt)),
|
||||
createStores: false);
|
||||
}
|
||||
static bool AllowAsBiomeGate(LocationType lt)
|
||||
{
|
||||
//checking for "abandoned" is not strictly necessary here because it's now configured to not be allowed as a biome gate
|
||||
//but might be better to keep it for backwards compatibility (previously we relied only on that check)
|
||||
return lt.HasOutpost && lt.Identifier != "abandoned" && lt.AllowAsBiomeGate;
|
||||
}
|
||||
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
|
||||
@@ -784,6 +791,28 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(Connections.All(c => c.Biome != null));
|
||||
}
|
||||
|
||||
private Location GetPreviousToEndLocation()
|
||||
{
|
||||
Location previousToEndLocation = null;
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Biome.IsEndBiome && (previousToEndLocation == null || location.MapPosition.X > previousToEndLocation.MapPosition.X))
|
||||
{
|
||||
previousToEndLocation = location;
|
||||
}
|
||||
}
|
||||
return previousToEndLocation;
|
||||
}
|
||||
|
||||
private void ForceLocationTypeToNone(CampaignMode campaign, Location location)
|
||||
{
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
location.ChangeType(campaign, locationType, createStores: false);
|
||||
}
|
||||
location.DisallowLocationTypeChanges = true;
|
||||
}
|
||||
|
||||
private void CreateEndLocation(CampaignMode campaign)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
@@ -800,15 +829,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Location previousToEndLocation = null;
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Biome.IsEndBiome && (previousToEndLocation == null || location.MapPosition.X > previousToEndLocation.MapPosition.X))
|
||||
{
|
||||
previousToEndLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
var previousToEndLocation = GetPreviousToEndLocation();
|
||||
if (endLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
endLocations = new List<Location>() { endLocation };
|
||||
@@ -833,10 +854,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(campaign, locationType, createStores: false);
|
||||
}
|
||||
ForceLocationTypeToNone(campaign, previousToEndLocation);
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
@@ -1555,6 +1573,7 @@ namespace Barotrauma
|
||||
if (index < 0) { return null; }
|
||||
return Locations[index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Discover(Location location)
|
||||
@@ -1604,6 +1623,12 @@ namespace Barotrauma
|
||||
SelectLocation(CurrentLocation.Connections[0].OtherLocation(CurrentLocation));
|
||||
}
|
||||
}
|
||||
|
||||
var previousToEndLocation = GetPreviousToEndLocation();
|
||||
if (previousToEndLocation != null)
|
||||
{
|
||||
ForceLocationTypeToNone(campaign, previousToEndLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
|
||||
@@ -110,7 +110,13 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
radiationAffliction ??= new Affliction(AfflictionPrefab.RadiationSickness, Params.RadiationDamageAmount);
|
||||
if (radiationAffliction == null)
|
||||
{
|
||||
float radiationStrengthChange = AfflictionPrefab.RadiationSickness.Effects.FirstOrDefault()?.StrengthChange ?? 0.0f;
|
||||
radiationAffliction = new Affliction(
|
||||
AfflictionPrefab.RadiationSickness,
|
||||
(Params.RadiationDamageAmount - radiationStrengthChange) * Params.RadiationDamageDelay);
|
||||
}
|
||||
|
||||
radiationTimer = Params.RadiationDamageDelay;
|
||||
|
||||
@@ -120,11 +126,9 @@ namespace Barotrauma
|
||||
|
||||
if (IsEntityRadiated(character))
|
||||
{
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
AttackResult attackResult = limb.AddDamage(limb.SimPosition, radiationAffliction.ToEnumerable(), playSound: false);
|
||||
character.CharacterHealth.ApplyDamage(limb, attackResult);
|
||||
}
|
||||
var limb = character.AnimController.MainLimb;
|
||||
AttackResult attackResult = limb.AddDamage(limb.SimPosition, radiationAffliction.ToEnumerable(), playSound: false);
|
||||
character.CharacterHealth.ApplyDamage(limb, attackResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
{
|
||||
abstract partial class MapEntity : Entity, ISpatialEntity
|
||||
{
|
||||
public static List<MapEntity> mapEntityList = new List<MapEntity>();
|
||||
public readonly static List<MapEntity> MapEntityList = new List<MapEntity>();
|
||||
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
@@ -82,8 +82,8 @@ namespace Barotrauma
|
||||
{
|
||||
get { return isHighlighted || ExternalHighlight; }
|
||||
set
|
||||
{
|
||||
if (value != IsHighlighted)
|
||||
{
|
||||
if (value != isHighlighted)
|
||||
{
|
||||
isHighlighted = value;
|
||||
CheckIsHighlighted();
|
||||
@@ -531,32 +531,32 @@ namespace Barotrauma
|
||||
{
|
||||
if (Sprite == null)
|
||||
{
|
||||
mapEntityList.Add(this);
|
||||
MapEntityList.Add(this);
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < mapEntityList.Count)
|
||||
while (i < MapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
if (mapEntityList[i - 1]?.Prefab == Prefab)
|
||||
if (MapEntityList[i - 1]?.Prefab == Prefab)
|
||||
{
|
||||
mapEntityList.Insert(i, this);
|
||||
MapEntityList.Insert(i, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
i = 0;
|
||||
while (i < mapEntityList.Count)
|
||||
while (i < MapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
Sprite existingSprite = mapEntityList[i - 1].Sprite;
|
||||
Sprite existingSprite = MapEntityList[i - 1].Sprite;
|
||||
if (existingSprite == null) { continue; }
|
||||
if (existingSprite.Texture == this.Sprite.Texture) { break; }
|
||||
}
|
||||
#endif
|
||||
mapEntityList.Insert(i, this);
|
||||
MapEntityList.Insert(i, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -566,7 +566,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
mapEntityList.Remove(this);
|
||||
MapEntityList.Remove(this);
|
||||
|
||||
if (aiTarget != null) aiTarget.Remove();
|
||||
}
|
||||
@@ -575,7 +575,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
mapEntityList.Remove(this);
|
||||
MapEntityList.Remove(this);
|
||||
|
||||
#if CLIENT
|
||||
Submarine.ForceRemoveFromVisibleEntities(this);
|
||||
@@ -638,6 +638,7 @@ namespace Barotrauma
|
||||
sw.Restart();
|
||||
#endif
|
||||
Powered.UpdatePower(deltaTime);
|
||||
Item.UpdatePendingConditionUpdates(deltaTime);
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
item.Update(deltaTime, cam);
|
||||
@@ -689,6 +690,20 @@ namespace Barotrauma
|
||||
{
|
||||
IdRemap idRemap = new IdRemap(parentElement, idOffset);
|
||||
|
||||
bool containsHiddenContainers = false;
|
||||
bool hiddenContainerCreated = false;
|
||||
MTRandom hiddenContainerRNG = new MTRandom(ToolBox.StringToInt(submarine.Info.Name));
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
if (element.NameAsIdentifier() != "Item") { continue; }
|
||||
var tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
|
||||
if (tags.Contains(Tags.HiddenItemContainer))
|
||||
{
|
||||
containsHiddenContainers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<MapEntity> entities = new List<MapEntity>();
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
@@ -713,10 +728,11 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
Identifier replacementIdentifier = Identifier.Empty;
|
||||
if (t == typeof(Structure))
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
StructurePrefab structurePrefab = Structure.FindPrefab(name, identifier);
|
||||
if (structurePrefab == null)
|
||||
{
|
||||
@@ -727,6 +743,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (t == typeof(Item) && !containsHiddenContainers && identifier == "vent" &&
|
||||
submarine.Info.Type == SubmarineType.Player && !submarine.Info.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
if (!hiddenContainerCreated)
|
||||
{
|
||||
DebugConsole.AddWarning($"There are no hidden containers such as loose vents or loose panels in the submarine \"{submarine.Info.Name}\". Certain traitor events require these to function properly. Converting one of the vents to a loose vent...");
|
||||
}
|
||||
if (!hiddenContainerCreated || hiddenContainerRNG.NextDouble() < 0.2)
|
||||
{
|
||||
replacementIdentifier = "loosevent".ToIdentifier();
|
||||
containsHiddenContainers = true;
|
||||
hiddenContainerCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -741,7 +771,12 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element.FromPackage(null), submarine, idRemap });
|
||||
var newElement = element.FromPackage(null);
|
||||
if (!replacementIdentifier.IsEmpty)
|
||||
{
|
||||
newElement.SetAttributeValue("identifier", replacementIdentifier.ToString());
|
||||
}
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { newElement, submarine, idRemap });
|
||||
if (newEntity != null)
|
||||
{
|
||||
entities.Add((MapEntity)newEntity);
|
||||
@@ -774,9 +809,9 @@ namespace Barotrauma
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i].mapLoadedCalled || entities[i].Removed) { continue; }
|
||||
if (entities[i] is LinkedSubmarine)
|
||||
if (entities[i] is LinkedSubmarine sub)
|
||||
{
|
||||
linkedSubs.Add((LinkedSubmarine)entities[i]);
|
||||
linkedSubs.Add(sub);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -795,6 +830,35 @@ namespace Barotrauma
|
||||
{
|
||||
linkedSub.OnMapLoaded();
|
||||
}
|
||||
|
||||
CreateDroppedStacks(entities);
|
||||
}
|
||||
|
||||
private static void CreateDroppedStacks(List<MapEntity> entities)
|
||||
{
|
||||
const float MaxDist = 10.0f;
|
||||
List<Item> itemsInStack = new List<Item>();
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i] is not Item item1 || item1.Prefab.MaxStackSize <= 1 || item1.body is not { Enabled: true }) { continue; }
|
||||
itemsInStack.Clear();
|
||||
itemsInStack.Add(item1);
|
||||
for (int j = i + 1; j < entities.Count; j++)
|
||||
{
|
||||
if (entities[j] is not Item item2) { continue; }
|
||||
if (item1.Prefab != item2.Prefab) { continue; }
|
||||
if (item2.body is not { Enabled: true }) { continue; }
|
||||
if (item2.DroppedStack.Any()) { continue; }
|
||||
if (Math.Abs(item1.Position.X - item2.Position.X) > MaxDist) { continue; }
|
||||
if (Math.Abs(item1.Position.Y - item2.Position.Y) > MaxDist) { continue; }
|
||||
itemsInStack.Add(item2);
|
||||
}
|
||||
if (itemsInStack.Count > 1)
|
||||
{
|
||||
item1.CreateDroppedStack(itemsInStack, allowClientExecute: true);
|
||||
DebugConsole.Log($"Merged x{itemsInStack.Count} of {item1.Name} into a dropped stack.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitializeLoadedLinks(IEnumerable<MapEntity> entities)
|
||||
|
||||
@@ -158,11 +158,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (name.IsNullOrEmpty()) { throw new ArgumentException($"{nameof(name)} must not be null or empty"); }
|
||||
|
||||
return Find(prefab =>
|
||||
prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
prefab.OriginalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
(prefab.Aliases != null &&
|
||||
prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))));
|
||||
var matches =
|
||||
List.Where(prefab =>
|
||||
prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
prefab.OriginalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
(prefab.Aliases != null &&
|
||||
prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))));
|
||||
//if there's multiple matches, prefer ones that aren't hidden in menus and base items
|
||||
//(hidden ones and variants are often e.g. some kind of special ones used in an event or versions unlockable with talents)
|
||||
if (matches.Count() > 1)
|
||||
{
|
||||
var bestMatch =
|
||||
matches.FirstOrDefault(prefab => !prefab.HideInMenus) ??
|
||||
matches.FirstOrDefault(prefab => prefab is ItemPrefab ip && ip.VariantOf.IsEmpty);
|
||||
if (bestMatch != null) { return bestMatch; }
|
||||
}
|
||||
return matches.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static MapEntityPrefab FindByIdentifier(Identifier identifier)
|
||||
@@ -210,6 +221,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool HideInMenus { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool HideInEditors { get; protected set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public string Subcategory { get; protected set; }
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes, description: "The closer to the current level difficulty this value is, the higher the probability of choosing these generation params are. Defaults to -1, which means we use the current difficulty."), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
public int PreferredDifficulty
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(10, IsPropertySaveable.Yes, description: "Total number of modules in the outpost."), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
public int TotalModuleCount
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -130,7 +129,7 @@ namespace Barotrauma
|
||||
//remove linked subs too
|
||||
if (otherSub.Submarine == sub) { connectedSubs.Add(otherSub); }
|
||||
}
|
||||
List<MapEntity> entities = MapEntity.mapEntityList.FindAll(e => connectedSubs.Contains(e.Submarine));
|
||||
List<MapEntity> entities = MapEntity.MapEntityList.FindAll(e => connectedSubs.Contains(e.Submarine));
|
||||
entities.ForEach(e => e.Remove());
|
||||
foreach (Submarine otherSub in connectedSubs)
|
||||
{
|
||||
@@ -201,7 +200,14 @@ namespace Barotrauma
|
||||
|
||||
selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None));
|
||||
selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag);
|
||||
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams);
|
||||
|
||||
AppendToModule(
|
||||
selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags,
|
||||
selectedModules,
|
||||
locationType,
|
||||
allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams,
|
||||
allowDifferentLocationType: remainingTries == 1);
|
||||
|
||||
if (pendingModuleFlags.Any(flag => flag != "none"))
|
||||
{
|
||||
if (!allowInvalidOutpost)
|
||||
@@ -442,7 +448,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
AlignLadders(selectedModules, entities);
|
||||
PowerUpOutpost(entities.SelectMany(e => e.Value));
|
||||
if (generationParams.MaxWaterPercentage > 0.0f)
|
||||
{
|
||||
foreach (var entity in allEntities)
|
||||
@@ -533,67 +538,67 @@ namespace Barotrauma
|
||||
/// Attaches additional modules to all the available gaps of the given module,
|
||||
/// and continues recursively through the attached modules until all the pending module types have been placed.
|
||||
/// </summary>
|
||||
/// <param name="currentModule">The module to attach to</param>
|
||||
/// <param name="availableModules">Which modules we can choose from</param>
|
||||
/// <param name="pendingModuleFlags">Which types of modules we still need in the outpost</param>
|
||||
/// <param name="currentModule">The module to attach to.</param>
|
||||
/// <param name="availableModules">Which modules we can choose from.</param>
|
||||
/// <param name="pendingModuleFlags">Which types of modules we still need in the outpost.</param>
|
||||
/// <param name="selectedModules">The modules we've already selected to be used in the outpost.</param>
|
||||
/// <param name="locationType">The type of the location we're generating the outpost for.</param>
|
||||
/// <param name="tryReplacingCurrentModule">If we fail to append to the current module, should we try replacing it with something else and see if we can append to it then?</param>
|
||||
/// <param name="allowExtendBelowInitialModule">Is the module allowed to be placed further down than the initial module (usually the airlock module)?</param>
|
||||
/// <param name="allowDifferentLocationType">If we fail to find a module suitable for the location type, should we use a module that's meant for a different location type instead?</param>
|
||||
private static bool AppendToModule(PlacedModule currentModule,
|
||||
List<SubmarineInfo> availableModules,
|
||||
List<Identifier> pendingModuleFlags,
|
||||
List<PlacedModule> selectedModules,
|
||||
LocationType locationType,
|
||||
bool retry = true,
|
||||
bool allowExtendBelowInitialModule = false)
|
||||
bool tryReplacingCurrentModule = true,
|
||||
bool allowExtendBelowInitialModule = false,
|
||||
bool allowDifferentLocationType = false)
|
||||
{
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
|
||||
List<PlacedModule> placedModules = new List<PlacedModule>();
|
||||
for (int i = 0; i < 2; i++)
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
|
||||
{
|
||||
//try placing a module meant for this location type first, and if that fails, try choosing whatever fits
|
||||
bool allowDifferentLocationType = i > 0;
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (DisallowBelowAirlock(allowExtendBelowInitialModule, gapPosition, currentModule)) { continue; }
|
||||
|
||||
PlacedModule newModule = null;
|
||||
//try appending to the current module if possible
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (DisallowBelowAirlock(allowExtendBelowInitialModule, gapPosition, currentModule)) { continue; }
|
||||
|
||||
PlacedModule newModule = null;
|
||||
//try appending to the current module if possible
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
}
|
||||
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't append to current module, try one of the other placed modules
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (otherModule == currentModule) { continue; }
|
||||
foreach (OutpostModuleInfo.GapPosition otherGapPosition in
|
||||
GapPositions.Where(g => !otherModule.UsedGapPositions.HasFlag(g) && otherModule.Info.OutpostModuleInfo.GapPositions.HasFlag(g)))
|
||||
{
|
||||
if (DisallowBelowAirlock(allowExtendBelowInitialModule, otherGapPosition, otherModule)) { continue; }
|
||||
newModule = AppendModule(otherModule, GetOpposingGapPosition(otherGapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newModule != null) { break; }
|
||||
}
|
||||
}
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
}
|
||||
}
|
||||
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't append to current module, try one of the other placed modules
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (otherModule == currentModule) { continue; }
|
||||
foreach (OutpostModuleInfo.GapPosition otherGapPosition in
|
||||
GapPositions.Where(g => !otherModule.UsedGapPositions.HasFlag(g) && otherModule.Info.OutpostModuleInfo.GapPositions.HasFlag(g)))
|
||||
{
|
||||
if (DisallowBelowAirlock(allowExtendBelowInitialModule, otherGapPosition, otherModule)) { continue; }
|
||||
newModule = AppendModule(otherModule, GetOpposingGapPosition(otherGapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newModule != null) { break; }
|
||||
}
|
||||
}
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
}
|
||||
|
||||
//couldn't place a module anywhere, we're probably fucked!
|
||||
if (placedModules.Count == 0 && retry && currentModule.PreviousModule != null && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule))
|
||||
if (placedModules.Count == 0 && tryReplacingCurrentModule && currentModule.PreviousModule != null && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule))
|
||||
{
|
||||
//try to replace the previously placed module with something else that we can append to
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -607,7 +612,7 @@ namespace Barotrauma
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType: true);
|
||||
assertAllPreviousModulesPresent();
|
||||
if (currentModule == null) { break; }
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, tryReplacingCurrentModule: false, allowExtendBelowInitialModule, allowDifferentLocationType))
|
||||
{
|
||||
assertAllPreviousModulesPresent();
|
||||
return true;
|
||||
@@ -618,7 +623,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (PlacedModule placedModule in placedModules)
|
||||
{
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: allowExtendBelowInitialModule);
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType, tryReplacingCurrentModule: true, allowExtendBelowInitialModule, allowDifferentLocationType);
|
||||
}
|
||||
return placedModules.Count > 0;
|
||||
|
||||
@@ -929,10 +934,8 @@ namespace Barotrauma
|
||||
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
if (allowDifferentLocationType)
|
||||
if (allowDifferentLocationType && modulesWithCorrectFlags.Any())
|
||||
{
|
||||
if (modulesWithCorrectFlags.Any())
|
||||
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(modulesWithCorrectFlags.ToList(), modulesWithCorrectFlags.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
@@ -1353,44 +1356,38 @@ namespace Barotrauma
|
||||
|
||||
if (startWaypoint.WorldPosition.X > endWaypoint.WorldPosition.X)
|
||||
{
|
||||
var temp = startWaypoint;
|
||||
startWaypoint = endWaypoint;
|
||||
endWaypoint = temp;
|
||||
(endWaypoint, startWaypoint) = (startWaypoint, endWaypoint);
|
||||
}
|
||||
|
||||
if (hallwayLength > 100 && isHorizontal)
|
||||
{
|
||||
//if the hallway is longer than 100 pixels, generate some waypoints inside it
|
||||
//for vertical hallways this isn't necessarily, it's done as a part of the ladder generation in AlignLadders
|
||||
WayPoint prevWayPoint = startWaypoint;
|
||||
WayPoint firstWayPoint = null;
|
||||
for (float x = leftHull.Rect.Right + 50; x < rightHull.Rect.X - 50; x += 100.0f)
|
||||
{
|
||||
var newWayPoint = new WayPoint(new Vector2(x, hullBounds.Y + 110.0f), SpawnType.Path, sub);
|
||||
firstWayPoint ??= newWayPoint;
|
||||
prevWayPoint.linkedTo.Add(newWayPoint);
|
||||
newWayPoint.linkedTo.Add(prevWayPoint);
|
||||
prevWayPoint = newWayPoint;
|
||||
}
|
||||
if (firstWayPoint != null)
|
||||
{
|
||||
firstWayPoint.linkedTo.Add(startWaypoint);
|
||||
startWaypoint.linkedTo.Add(firstWayPoint);
|
||||
}
|
||||
if (prevWayPoint != null)
|
||||
{
|
||||
prevWayPoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(prevWayPoint);
|
||||
}
|
||||
}
|
||||
|
||||
WayPoint closestWaypoint = null;
|
||||
float closestDistSqr = 30.0f * 30.0f;
|
||||
foreach (WayPoint waypoint in WayPoint.WayPointList)
|
||||
else
|
||||
{
|
||||
if (waypoint == startWaypoint) { continue; }
|
||||
float dist = Vector2.DistanceSquared(waypoint.WorldPosition, startWaypoint.WorldPosition);
|
||||
if (dist < closestDistSqr)
|
||||
{
|
||||
closestWaypoint = waypoint;
|
||||
closestDistSqr = dist;
|
||||
}
|
||||
}
|
||||
if (closestWaypoint != null)
|
||||
{
|
||||
startWaypoint.linkedTo.Add(closestWaypoint);
|
||||
closestWaypoint.linkedTo.Add(startWaypoint);
|
||||
startWaypoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(startWaypoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1444,7 +1441,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (MapEntity me in entities[module])
|
||||
{
|
||||
if (!(me is Gap gap)) { continue; }
|
||||
if (me is not Gap gap) { continue; }
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null && !door.UseBetweenOutpostModules) { continue; }
|
||||
if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap))
|
||||
@@ -1524,15 +1521,38 @@ namespace Barotrauma
|
||||
|
||||
static void RemoveLinkedEntity(MapEntity linked)
|
||||
{
|
||||
if (linked is Item linkedItem && linkedItem.Connections != null)
|
||||
if (linked is Item linkedItem)
|
||||
{
|
||||
foreach (Connection connection in linkedItem.Connections)
|
||||
if (linkedItem.Connections != null)
|
||||
{
|
||||
foreach (Wire w in connection.Wires.ToArray())
|
||||
foreach (Connection connection in linkedItem.Connections)
|
||||
{
|
||||
w?.Item.Remove();
|
||||
foreach (Wire w in connection.Wires.ToArray())
|
||||
{
|
||||
w?.Item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
//if we end up removing a ladder, remove its waypoints too
|
||||
if (linkedItem.GetComponent<Ladder>() is Ladder ladder)
|
||||
{
|
||||
var ladderWaypoints = WayPoint.WayPointList.FindAll(wp => wp.Ladders == ladder);
|
||||
foreach (var ladderWaypoint in ladderWaypoints)
|
||||
{
|
||||
//got through all waypoints linked to the ladder waypoints, and link them together
|
||||
//so we don't end up breaking up any paths by removing the ladder waypoints
|
||||
for (int i = 0; i < ladderWaypoint.linkedTo.Count; i++)
|
||||
{
|
||||
if (ladderWaypoint.linkedTo[i] is not WayPoint waypoint1 || waypoint1.Ladders == ladder) { continue; }
|
||||
for (int j = i + 1; j < ladderWaypoint.linkedTo.Count; j++)
|
||||
{
|
||||
if (ladderWaypoint.linkedTo[j] is not WayPoint waypoint2 || waypoint2.Ladders == ladder) { continue; }
|
||||
waypoint1.ConnectTo(waypoint2);
|
||||
}
|
||||
}
|
||||
}
|
||||
ladderWaypoints.ForEach(wp => wp.Remove());
|
||||
}
|
||||
}
|
||||
linked.Remove();
|
||||
}
|
||||
@@ -1610,11 +1630,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static void PowerUpOutpost(IEnumerable<MapEntity> entities)
|
||||
public static void PowerUpOutpost(Submarine sub)
|
||||
{
|
||||
foreach (Entity e in entities)
|
||||
//create a copy of the list, because EntitySpawner may not exist yet we're generating the level,
|
||||
//which can cause items to be removed/instantiated directly
|
||||
var entities = MapEntity.MapEntityList.Where(me => me.Submarine == sub).ToList();
|
||||
|
||||
foreach (MapEntity e in entities)
|
||||
{
|
||||
if (!(e is Item item)) { continue; }
|
||||
if (e is not Item item) { continue; }
|
||||
var reactor = item.GetComponent<Reactor>();
|
||||
if (reactor != null)
|
||||
{
|
||||
@@ -1626,9 +1650,9 @@ namespace Barotrauma
|
||||
for (int i = 0; i < 600; i++)
|
||||
{
|
||||
Powered.UpdatePower((float)Timing.Step);
|
||||
foreach (Entity e in entities)
|
||||
foreach (MapEntity e in entities)
|
||||
{
|
||||
if (!(e is Item item) || item.GetComponent<Powered>() == null) { continue; }
|
||||
if (e is not Item item || item.GetComponent<Powered>() == null) { continue; }
|
||||
item.Update((float)Timing.Step, GameMain.GameScreen.Cam);
|
||||
}
|
||||
}
|
||||
@@ -1683,7 +1707,7 @@ namespace Barotrauma
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
var npc = Character.Create(CharacterPrefab.HumanSpeciesName, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
var npc = Character.Create(characterInfo.SpeciesName, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, setSubmarine: true);
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
npc.HumanPrefab = humanPrefab;
|
||||
|
||||
@@ -81,8 +81,6 @@ namespace Barotrauma
|
||||
get { return base.Prefab.Sprite; }
|
||||
}
|
||||
|
||||
public bool IsExteriorWall { get; private set; } = true;
|
||||
|
||||
public bool IsPlatform
|
||||
{
|
||||
get { return Prefab.Platform; }
|
||||
@@ -406,8 +404,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CheckIsExteriorWall();
|
||||
|
||||
#if CLIENT
|
||||
convexHulls?.ForEach(x => x.Move(amount));
|
||||
|
||||
@@ -700,41 +696,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckIsExteriorWall()
|
||||
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
|
||||
{
|
||||
if (!HasBody)
|
||||
{
|
||||
IsExteriorWall = false;
|
||||
return;
|
||||
}
|
||||
Vector2[] corners = new Vector2[4];
|
||||
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
|
||||
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
|
||||
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
|
||||
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
|
||||
|
||||
Vector2 point1 = WorldPosition + BodyOffset * Scale;
|
||||
//point1 = MathUtils.RotatePointAroundTarget(WorldPosition, point1, BodyRotation);
|
||||
Vector2 point2 = point1;
|
||||
|
||||
Vector2 normal = new Vector2(
|
||||
(float)-Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation));
|
||||
|
||||
float thickness = IsHorizontal ?
|
||||
(BodyHeight > 0 ? BodyHeight : rect.Height) :
|
||||
(BodyWidth > 0 ? BodyWidth : rect.Width);
|
||||
|
||||
point1 += normal * (thickness / 2 + 16);
|
||||
point2 -= normal * (thickness / 2 + 16);
|
||||
|
||||
IsExteriorWall =
|
||||
Hull.FindHullUnoptimized(point1, null, useWorldCoordinates: true) == null ||
|
||||
Hull.FindHullUnoptimized(point2, null, useWorldCoordinates: true) == null;
|
||||
#if CLIENT
|
||||
if (convexHulls != null)
|
||||
{
|
||||
foreach (ConvexHull ch in convexHulls)
|
||||
{
|
||||
ch.IsExteriorWall = IsExteriorWall;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return corners;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -742,9 +712,9 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static Structure GetAttachTarget(Vector2 worldPosition)
|
||||
{
|
||||
foreach (MapEntity mapEntity in mapEntityList)
|
||||
foreach (MapEntity mapEntity in MapEntityList)
|
||||
{
|
||||
if (mapEntity is not Structure structure) { continue; }
|
||||
if (!(mapEntity is Structure structure)) { continue; }
|
||||
if (!structure.Prefab.AllowAttachItems) { continue; }
|
||||
if (structure.Bodies != null && structure.Bodies.Count > 0) { continue; }
|
||||
Rectangle worldRect = mapEntity.WorldRect;
|
||||
@@ -1273,7 +1243,7 @@ namespace Barotrauma
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
private void CreateWallDamageExplosion(Gap gap, Character attacker)
|
||||
private static void CreateWallDamageExplosion(Gap gap, Character attacker)
|
||||
{
|
||||
const float explosionRange = 750.0f;
|
||||
float explosionStrength = gap.Open;
|
||||
@@ -1294,20 +1264,23 @@ namespace Barotrauma
|
||||
|
||||
if (explosionOnBroken == null)
|
||||
{
|
||||
explosionOnBroken = new Explosion(explosionRange * gap.Open, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
|
||||
explosionOnBroken = new Explosion(explosionRange, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
|
||||
if (AfflictionPrefab.Prefabs.TryGet("lacerations".ToIdentifier(), out AfflictionPrefab lacerations))
|
||||
{
|
||||
explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(50.0f), null);
|
||||
explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(3.0f), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f), null);
|
||||
explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(3.0f), null);
|
||||
}
|
||||
explosionOnBroken.IgnoreCover = true;
|
||||
explosionOnBroken.OnlyInside = true;
|
||||
explosionOnBroken.DisableParticles();
|
||||
}
|
||||
|
||||
explosionOnBroken.Attack.Range = explosionRange * gap.Open;
|
||||
explosionOnBroken.Attack.DamageMultiplier = explosionStrength;
|
||||
explosionOnBroken.Attack.Stun = MathHelper.Clamp(explosionStrength, 0.5f, 1.0f);
|
||||
explosionOnBroken?.Explode(gap.WorldPosition, damageSource: null, attacker: attacker);
|
||||
#if CLIENT
|
||||
if (linkedHull != null)
|
||||
@@ -1669,7 +1642,6 @@ namespace Barotrauma
|
||||
{
|
||||
SetDamage(i, Sections[i].damage, createNetworkEvent: false, createExplosionEffect: false);
|
||||
}
|
||||
CheckIsExteriorWall();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
|
||||
@@ -361,11 +361,11 @@ namespace Barotrauma
|
||||
List<Pump> pumps = new List<Pump>();
|
||||
List<Item> allItems = GetItems(true);
|
||||
|
||||
bool anyHasTag = allItems.Any(i => i.HasTag("ballast"));
|
||||
bool anyHasTag = allItems.Any(i => i.HasTag(Tags.Ballast));
|
||||
|
||||
foreach (Item item in allItems)
|
||||
{
|
||||
if ((!anyHasTag || item.HasTag("ballast")) && item.GetComponent<Pump>() is { } pump)
|
||||
if ((!anyHasTag || item.HasTag(Tags.Ballast)) && item.GetComponent<Pump>() is { } pump)
|
||||
{
|
||||
pumps.Add(pump);
|
||||
}
|
||||
@@ -624,11 +624,18 @@ namespace Barotrauma
|
||||
|
||||
//math/physics stuff ----------------------------------------------------
|
||||
|
||||
public static Vector2 VectorToWorldGrid(Vector2 position)
|
||||
public static Vector2 VectorToWorldGrid(Vector2 position, bool round = false)
|
||||
{
|
||||
position.X = (float)Math.Floor(position.X / GridSize.X) * GridSize.X;
|
||||
position.Y = (float)Math.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
|
||||
|
||||
if (round)
|
||||
{
|
||||
position.X = MathF.Round(position.X / GridSize.X) * GridSize.X;
|
||||
position.Y = MathF.Round(position.Y / GridSize.Y) * GridSize.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
position.X = MathF.Floor(position.X / GridSize.X) * GridSize.X;
|
||||
position.Y = MathF.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
@@ -636,7 +643,7 @@ namespace Barotrauma
|
||||
{
|
||||
List<MapEntity> entities = onlyHulls ?
|
||||
Hull.HullList.FindAll(h => h.Submarine == this).Cast<MapEntity>().ToList() :
|
||||
MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
|
||||
MapEntity.MapEntityList.FindAll(me => me.Submarine == this);
|
||||
|
||||
//ignore items whose body is disabled (wires, items inside cabinets)
|
||||
entities.RemoveAll(e =>
|
||||
@@ -693,6 +700,22 @@ namespace Barotrauma
|
||||
return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);
|
||||
}
|
||||
|
||||
public static RectangleF AbsRectF(Vector2 pos, Vector2 size)
|
||||
{
|
||||
if (size.X < 0.0f)
|
||||
{
|
||||
pos.X += size.X;
|
||||
size.X = -size.X;
|
||||
}
|
||||
if (size.Y < 0.0f)
|
||||
{
|
||||
pos.Y += size.Y;
|
||||
size.Y = -size.Y;
|
||||
}
|
||||
|
||||
return new RectangleF(pos.X, pos.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
public static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive = false)
|
||||
{
|
||||
if (inclusive)
|
||||
@@ -721,6 +744,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RectsOverlap(RectangleF rect1, RectangleF rect2, bool inclusive = true)
|
||||
{
|
||||
if (inclusive)
|
||||
{
|
||||
return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
|
||||
rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
|
||||
}
|
||||
|
||||
return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
|
||||
rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
|
||||
}
|
||||
|
||||
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null, bool allowInsideFixture = false)
|
||||
{
|
||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.0001f)
|
||||
@@ -965,7 +1000,7 @@ namespace Barotrauma
|
||||
Item.UpdateHulls();
|
||||
|
||||
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
|
||||
List<MapEntity> subEntities = MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
|
||||
List<MapEntity> subEntities = MapEntity.MapEntityList.FindAll(me => me.Submarine == this);
|
||||
|
||||
foreach (MapEntity e in subEntities)
|
||||
{
|
||||
@@ -1047,7 +1082,7 @@ namespace Barotrauma
|
||||
|
||||
public void EnableFactionSpecificEntities(Identifier factionIdentifier)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != this) { continue; }
|
||||
|
||||
@@ -1202,7 +1237,7 @@ namespace Barotrauma
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (!item.HasTag(Tags.Ballast) && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
}
|
||||
@@ -1326,7 +1361,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList.ToList())
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine)) { continue; }
|
||||
if (!item.HasTag("cargocontainer")) { continue; }
|
||||
if (!item.HasTag(Tags.CargoContainer)) { continue; }
|
||||
if (item.NonInteractable || item.HiddenInGame) { continue; }
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
@@ -1358,22 +1393,38 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sub whose borders contain the position
|
||||
/// Finds the sub whose borders contain the position. Note that this method uses the "actual" position of the sub outside the level:
|
||||
/// only use this if the position is in a submarine's local coordinate space!
|
||||
/// </summary>
|
||||
public static Submarine FindContaining(Vector2 position)
|
||||
public static Submarine FindContainingInLocalCoordinates(Vector2 position, float inflate = 500.0f)
|
||||
{
|
||||
foreach (Submarine sub in Loaded)
|
||||
{
|
||||
Rectangle subBorders = sub.Borders;
|
||||
subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Microsoft.Xna.Framework.Point(0, sub.Borders.Height);
|
||||
|
||||
subBorders.Inflate(500.0f, 500.0f);
|
||||
|
||||
if (subBorders.Contains(position)) return sub;
|
||||
subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Point(0, sub.Borders.Height);
|
||||
subBorders.Inflate(inflate, inflate);
|
||||
if (subBorders.Contains(position)) { return sub; }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sub whose world borders contain the position.
|
||||
/// </summary>
|
||||
public static Submarine FindContaining(Vector2 worldPosition, float inflate = 500.0f)
|
||||
{
|
||||
foreach (Submarine sub in Loaded)
|
||||
{
|
||||
Rectangle worldBorders = sub.Borders;
|
||||
worldBorders.Location += sub.WorldPosition.ToPoint() - new Point(0, sub.Borders.Height);
|
||||
worldBorders.Inflate(inflate, inflate);
|
||||
if (worldBorders.Contains(worldPosition)) { return sub; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static Rectangle GetBorders(XElement submarineElement)
|
||||
{
|
||||
Vector4 bounds = Vector4.Zero;
|
||||
@@ -1430,7 +1481,7 @@ namespace Barotrauma
|
||||
HiddenSubPosition =
|
||||
new Vector2(
|
||||
//1st sub on the left side, 2nd on the right, etc
|
||||
HiddenSubPosition.X * (i % 2 == 0 ? 1 : -1),
|
||||
-HiddenSubPosition.X,
|
||||
HiddenSubPosition.Y + sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
|
||||
@@ -1479,7 +1530,7 @@ namespace Barotrauma
|
||||
center.X -= center.X % GridSize.X;
|
||||
center.Y -= center.Y % GridSize.Y;
|
||||
|
||||
RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this));
|
||||
RepositionEntities(-center, MapEntity.MapEntityList.Where(me => me.Submarine == this));
|
||||
}
|
||||
|
||||
subBody = new SubmarineBody(this, showErrorMessages);
|
||||
@@ -1497,7 +1548,7 @@ namespace Barotrauma
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
@@ -1554,16 +1605,16 @@ namespace Barotrauma
|
||||
}
|
||||
entityGrid = Hull.GenerateEntityGrid(this);
|
||||
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
|
||||
for (int i = 0; i < MapEntity.MapEntityList.Count; i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.mapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
|
||||
if (MapEntity.MapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.MapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
|
||||
}
|
||||
|
||||
Loading = false;
|
||||
|
||||
MapEntity.MapLoaded(newEntities, true);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is LinkedSubmarine linkedSub)
|
||||
@@ -1653,7 +1704,7 @@ namespace Barotrauma
|
||||
|
||||
public bool CheckFuel()
|
||||
{
|
||||
float fuel = GetItems(true).Where(i => i.HasTag("reactorfuel")).Sum(i => i.Condition);
|
||||
float fuel = GetItems(true).Where(i => i.HasTag(Tags.Fuel)).Sum(i => i.Condition);
|
||||
Info.LowFuel = fuel < 200;
|
||||
return !Info.LowFuel;
|
||||
}
|
||||
@@ -1681,7 +1732,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
|
||||
var cargoContainers = GetCargoContainers();
|
||||
int cargoCapacity = cargoContainers.Sum(c => c.container.Capacity);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
{
|
||||
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
|
||||
{
|
||||
@@ -1702,13 +1753,12 @@ namespace Barotrauma
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.PendingItemSwap?.SwappableItem?.ConnectedItemsToSwap == null) { continue; }
|
||||
foreach (var (requiredTag, swapTo) in item.PendingItemSwap.SwappableItem.ConnectedItemsToSwap)
|
||||
if (item.PendingItemSwap?.SwappableItem?.ConnectedItemsToSwap is not { } connectedItemsToSwap) { continue; }
|
||||
foreach (var (requiredTag, swapTo) in connectedItemsToSwap)
|
||||
{
|
||||
List<Item> itemsToSwap = new List<Item>();
|
||||
itemsToSwap.AddRange(item.linkedTo.Where(lt => (lt as Item)?.HasTag(requiredTag) ?? false).Cast<Item>());
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
if (item.GetComponent<ConnectionPanel>() is ConnectionPanel connectionPanel)
|
||||
{
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
@@ -1730,13 +1780,13 @@ namespace Barotrauma
|
||||
foreach (Item itemToSwap in itemsToSwap)
|
||||
{
|
||||
itemToSwap.PurchasedNewSwap = item.PurchasedNewSwap;
|
||||
if (itemPrefab != itemToSwap.Prefab) { itemToSwap.PendingItemSwap = itemPrefab; }
|
||||
if (itemPrefab != itemToSwap.Prefab) { itemToSwap.PendingItemSwap = itemPrefab; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, MapEntity> savedEntities = new Dictionary<int, MapEntity>();
|
||||
foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID))
|
||||
foreach (MapEntity e in MapEntity.MapEntityList.OrderBy(e => e.ID))
|
||||
{
|
||||
if (!e.ShouldBeSaved) { continue; }
|
||||
|
||||
@@ -1980,25 +2030,33 @@ namespace Barotrauma
|
||||
{
|
||||
var connectedWp = connection.Waypoint;
|
||||
if (connectedWp.IsObstructed || connectedWp.Ladders != null) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
|
||||
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
|
||||
if (body != null)
|
||||
bool isObstructed = wp.CurrentHull is Hull h && h.Submarine != this;
|
||||
if (!isObstructed)
|
||||
{
|
||||
if (body.UserData is Structure wall && !wall.IsPlatform || body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
|
||||
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
|
||||
if (body != null)
|
||||
{
|
||||
connectedWp.IsObstructed = true;
|
||||
wp.IsObstructed = true;
|
||||
if (!obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
if (body.UserData is Structure wall && !wall.IsPlatform || body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
{
|
||||
nodes = new HashSet<PathNode>();
|
||||
obstructedNodes.Add(otherSub, nodes);
|
||||
isObstructed = true;
|
||||
}
|
||||
nodes.Add(node);
|
||||
nodes.Add(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObstructed)
|
||||
{
|
||||
connectedWp.IsObstructed = true;
|
||||
wp.IsObstructed = true;
|
||||
if (!obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
|
||||
{
|
||||
nodes = new HashSet<PathNode>();
|
||||
obstructedNodes.Add(otherSub, nodes);
|
||||
}
|
||||
nodes.Add(node);
|
||||
nodes.Add(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2054,5 +2112,47 @@ namespace Barotrauma
|
||||
}
|
||||
return selectedContainer;
|
||||
}
|
||||
|
||||
public static Vector2 GetRelativeSimPosition(ISpatialEntity from, ISpatialEntity to, Vector2? targetWorldPos = null)
|
||||
{
|
||||
return targetWorldPos.HasValue ?
|
||||
GetRelativeSimPositionFromWorldPosition(targetWorldPos.Value, from.Submarine, to.Submarine) :
|
||||
GetRelativeSimPosition(to.SimPosition, from.Submarine, to.Submarine);
|
||||
}
|
||||
|
||||
public static Vector2 GetRelativeSimPositionFromWorldPosition(Vector2 targetWorldPos, Submarine fromSub, Submarine toSub)
|
||||
{
|
||||
Vector2 worldPos = targetWorldPos;
|
||||
if (toSub != null)
|
||||
{
|
||||
worldPos -= toSub.Position;
|
||||
}
|
||||
return GetRelativeSimPosition(ConvertUnits.ToSimUnits(worldPos), fromSub, toSub);
|
||||
}
|
||||
|
||||
public static Vector2 GetRelativeSimPosition(Vector2 targetSimPos, Submarine fromSub, Submarine toSub)
|
||||
{
|
||||
Vector2 targetPos = targetSimPos;
|
||||
if (fromSub == null && toSub != null)
|
||||
{
|
||||
// outside and targeting inside
|
||||
targetPos += toSub.SimPosition;
|
||||
}
|
||||
else if (fromSub != null && toSub == null)
|
||||
{
|
||||
// inside and targeting outside
|
||||
targetPos -= fromSub.SimPosition;
|
||||
}
|
||||
else if (fromSub != toSub)
|
||||
{
|
||||
if (fromSub != null && toSub != null)
|
||||
{
|
||||
// both inside, but in different subs
|
||||
Vector2 diff = fromSub.SimPosition - toSub.SimPosition;
|
||||
targetPos -= diff;
|
||||
}
|
||||
}
|
||||
return targetPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Barotrauma
|
||||
{
|
||||
farseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
foreach (var mapEntity in MapEntity.mapEntityList)
|
||||
foreach (var mapEntity in MapEntity.MapEntityList)
|
||||
{
|
||||
if (mapEntity.Submarine != submarine || mapEntity is not Structure wall) { continue; }
|
||||
|
||||
@@ -521,20 +521,25 @@ namespace Barotrauma
|
||||
{
|
||||
if (Submarine.LockY) { return Vector2.Zero; }
|
||||
|
||||
//calculate the buoyancy for all connected subs
|
||||
//doing it separately for each connected sub means e.g. a flooded drone barely
|
||||
//affects the buoyancy of the main sub even if there was as much water in the
|
||||
//drone as the whole ballast volume of the sub
|
||||
var connectedSubs = submarine.GetConnectedSubs();
|
||||
float waterVolume = 0.0f;
|
||||
float volume = 0.0f;
|
||||
float totalMass = connectedSubs.Sum(s => s.SubBody.Body.Mass);
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != submarine) { continue; }
|
||||
|
||||
if (hull.Submarine == null || !connectedSubs.Contains(hull.Submarine)) { continue; }
|
||||
if (hull.Submarine.PhysicsBody is not { BodyType: BodyType.Dynamic }) { continue; }
|
||||
waterVolume += hull.WaterVolume;
|
||||
volume += hull.Volume;
|
||||
volume += hull.Volume;
|
||||
}
|
||||
|
||||
float waterPercentage = volume <= 0.0f ? 0.0f : waterVolume / volume;
|
||||
|
||||
float waterPercentage = volume <= 0.0f ? 0.0f : waterVolume / volume;
|
||||
float buoyancy = NeutralBallastPercentage - waterPercentage;
|
||||
|
||||
float massRatio = Body.Mass / totalMass;
|
||||
if (buoyancy > 0.0f)
|
||||
{
|
||||
buoyancy *= 2.0f;
|
||||
@@ -543,13 +548,11 @@ namespace Barotrauma
|
||||
{
|
||||
buoyancy = Math.Max(buoyancy, -0.5f);
|
||||
}
|
||||
|
||||
if (forceUpwardsTimer > 0.0f)
|
||||
{
|
||||
buoyancy = MathHelper.Lerp(buoyancy, 0.1f, forceUpwardsTimer / ForceUpwardsDelay);
|
||||
}
|
||||
|
||||
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
|
||||
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f) * massRatio;
|
||||
}
|
||||
|
||||
public void ApplyForce(Vector2 force)
|
||||
@@ -1009,7 +1012,8 @@ namespace Barotrauma
|
||||
//stun for up to 2 second if the impact equal or higher to the maximum impact
|
||||
if (impact >= MaxCollisionImpact)
|
||||
{
|
||||
c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(3.0f).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true);
|
||||
float impactDamage = c.AnimController.GetImpactDamage(impact);
|
||||
c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ namespace Barotrauma
|
||||
removals.ForEach(wp => wp.Remove());
|
||||
removals.Clear();
|
||||
// Stairs
|
||||
foreach (MapEntity mapEntity in mapEntityList.ToList())
|
||||
foreach (MapEntity mapEntity in MapEntityList.ToList())
|
||||
{
|
||||
if (!(mapEntity is Structure structure)) { continue; }
|
||||
if (structure.StairDirection == Direction.None) { continue; }
|
||||
|
||||
Reference in New Issue
Block a user