v0.14.6.0
This commit is contained in:
@@ -25,12 +25,12 @@ namespace Barotrauma
|
||||
private readonly float screenColorRange, screenColorDuration;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
private bool playTinnitus;
|
||||
private bool applyFireEffects;
|
||||
private string[] ignoreFireEffectsForTags;
|
||||
private bool ignoreCover;
|
||||
private bool onlyInside;
|
||||
private bool onlyOutside;
|
||||
private readonly Color flashColor;
|
||||
private readonly bool playTinnitus;
|
||||
private readonly bool applyFireEffects;
|
||||
private readonly string[] ignoreFireEffectsForTags;
|
||||
private readonly bool ignoreCover;
|
||||
private readonly bool onlyInside,onlyOutside;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
@@ -81,6 +81,7 @@ namespace Barotrauma
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
|
||||
|
||||
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
|
||||
BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f);
|
||||
@@ -129,7 +130,7 @@ namespace Barotrauma
|
||||
|
||||
float displayRange = Attack.Range;
|
||||
|
||||
Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
|
||||
#if CLIENT
|
||||
@@ -395,7 +396,7 @@ namespace Barotrauma
|
||||
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;
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
|
||||
structure.AddDamage(i, damage * distFactor, attacker);
|
||||
|
||||
@@ -412,6 +413,19 @@ namespace Barotrauma
|
||||
|
||||
if (Level.Loaded != null && !MathUtils.NearlyEqual(levelWallDamage, 0.0f))
|
||||
{
|
||||
if (Level.Loaded?.LevelObjectManager != null)
|
||||
{
|
||||
foreach (var levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(worldPosition, worldRange))
|
||||
{
|
||||
if (levelObject.Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(levelObject.WorldPosition, worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) { continue; }
|
||||
levelObject.AddDamage(levelWallDamage * distFactor, 1.0f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
|
||||
{
|
||||
hull = Hull.FindHull(worldPosition, spawningHull);
|
||||
if (hull == null || worldPosition.Y < hull.WorldSurface) return;
|
||||
if (hull == null || worldPosition.Y < hull.WorldSurface) { return; }
|
||||
|
||||
#if CLIENT
|
||||
if (!isNetworkMessage && GameMain.Client != null) { return; }
|
||||
|
||||
@@ -189,6 +189,10 @@ namespace Barotrauma
|
||||
if (roomName == value) { return; }
|
||||
roomName = value;
|
||||
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
|
||||
if (!IsWetRoom && ForceAsWetRoom)
|
||||
{
|
||||
IsWetRoom = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,6 +333,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool ForceAsWetRoom =>
|
||||
roomName != null && (
|
||||
roomName.Contains("ballast", StringComparison.OrdinalIgnoreCase) ||
|
||||
roomName.Contains("bilge", StringComparison.OrdinalIgnoreCase) ||
|
||||
roomName.Contains("airlock", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private bool isWetRoom;
|
||||
[Editable, Serialize(false, true, description: "It's normal for this hull to be filled with water. If the room name contains 'ballast', 'bilge', or 'airlock', you can't disable this setting.")]
|
||||
public bool IsWetRoom
|
||||
{
|
||||
get { return isWetRoom; }
|
||||
set
|
||||
{
|
||||
isWetRoom = value;
|
||||
if (ForceAsWetRoom)
|
||||
{
|
||||
isWetRoom = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool avoidStaying;
|
||||
[Editable, Serialize(false, true, description: "Bots avoid staying here, but they are still allowed to access the room when needed and go through it. Forced true for wet rooms.")]
|
||||
public bool AvoidStaying
|
||||
{
|
||||
get { return avoidStaying || IsWetRoom; }
|
||||
set
|
||||
{
|
||||
avoidStaying = value;
|
||||
if (IsWetRoom)
|
||||
{
|
||||
avoidStaying = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float WaterPercentage => MathUtils.Percentage(WaterVolume, Volume);
|
||||
|
||||
public float OxygenPercentage
|
||||
@@ -534,6 +574,9 @@ namespace Barotrauma
|
||||
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
|
||||
|
||||
BallastFlora?.OnMapLoaded();
|
||||
#if CLIENT
|
||||
lastAmbientLightEditTime = 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void AddToGrid(Submarine submarine)
|
||||
@@ -643,6 +686,11 @@ namespace Barotrauma
|
||||
|
||||
public void AddFireSource(FireSource fireSource)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//clients aren't allowed to create fire sources in hulls whose IDs have been freed (dynamic hulls between docking ports), because they can't be synced
|
||||
if (IdFreed) { return; }
|
||||
}
|
||||
if (fireSource is DummyFireSource dummyFire)
|
||||
{
|
||||
FakeFireSources.Add(dummyFire);
|
||||
@@ -705,19 +753,22 @@ namespace Barotrauma
|
||||
|
||||
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
|
||||
|
||||
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
||||
if (FakeFireSources.Count > 0)
|
||||
{
|
||||
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
|
||||
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
||||
{
|
||||
if (FakeFireSources[i].CausedByPsychosis)
|
||||
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
FakeFireSources[i].Remove();
|
||||
if (FakeFireSources[i].CausedByPsychosis)
|
||||
{
|
||||
FakeFireSources[i].Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
FireSource.UpdateAll(FakeFireSources, deltaTime);
|
||||
}
|
||||
|
||||
FireSource.UpdateAll(FireSources, deltaTime);
|
||||
FireSource.UpdateAll(FakeFireSources, deltaTime);
|
||||
|
||||
foreach (Decal decal in decals)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
|
||||
interface IIgnorable : ISpatialEntity
|
||||
{
|
||||
bool IgnoreByAI { get; }
|
||||
bool IgnoreByAI(Character character);
|
||||
bool OrderedToBeIgnored { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma
|
||||
|
||||
public Rectangle Bounds;
|
||||
|
||||
public ItemAssemblyPrefab(string filePath)
|
||||
public ItemAssemblyPrefab(string filePath, bool allowOverwrite = false)
|
||||
{
|
||||
FilePath = filePath;
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
@@ -113,6 +113,10 @@ namespace Barotrauma
|
||||
new Rectangle(0, 0, 1, 1) :
|
||||
new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
|
||||
if (allowOverwrite && Prefabs.ContainsKey(identifier))
|
||||
{
|
||||
Prefabs.Remove(Prefabs[identifier]);
|
||||
}
|
||||
Prefabs.Add(this, doc.Root.IsOverride());
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
Cave = 0x4,
|
||||
Ruin = 0x8,
|
||||
Wreck = 0x10,
|
||||
BeaconStation = 0x20,
|
||||
BeaconStation = 0x20, // Not used anywhere
|
||||
Abyss = 0x40,
|
||||
AbyssCave = 0x80
|
||||
}
|
||||
@@ -325,9 +325,11 @@ namespace Barotrauma
|
||||
get { return LevelData.Seed; }
|
||||
}
|
||||
|
||||
|
||||
public static float? ForcedDifficulty;
|
||||
public float Difficulty
|
||||
{
|
||||
get { return LevelData.Difficulty; }
|
||||
get { return ForcedDifficulty ?? LevelData.Difficulty; }
|
||||
}
|
||||
|
||||
public LevelData.LevelType Type
|
||||
@@ -387,7 +389,7 @@ namespace Barotrauma
|
||||
|
||||
private void Generate(bool mirror)
|
||||
{
|
||||
if (Loaded != null) { Loaded.Remove(); }
|
||||
Loaded?.Remove();
|
||||
Loaded = this;
|
||||
Generating = true;
|
||||
|
||||
@@ -1152,7 +1154,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
CreateWrecks();
|
||||
CreateBeaconStation(cells);
|
||||
CreateBeaconStation();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
|
||||
@@ -1892,26 +1894,45 @@ namespace Barotrauma
|
||||
private void CalculateTunnelDistanceField(int density)
|
||||
{
|
||||
distanceField = new List<(Point point, double distance)>();
|
||||
for (int x = 0; x < Size.X; x += density)
|
||||
|
||||
if (Mirrored)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
for (int x = Size.X - 1; x >= 0; x -= density)
|
||||
{
|
||||
Point point = new Point(x, y);
|
||||
double shortestDistSqr = double.PositiveInfinity;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
{
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
|
||||
}
|
||||
addPoint(x, y);
|
||||
}
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
|
||||
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int x = 0; x < Size.X; x += density)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
{
|
||||
addPoint(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addPoint(int x, int y)
|
||||
{
|
||||
Point point = new Point(x, y);
|
||||
double shortestDistSqr = double.PositiveInfinity;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
{
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
|
||||
}
|
||||
}
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
|
||||
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
|
||||
}
|
||||
}
|
||||
|
||||
private double GetDistToTunnel(Vector2 position, Tunnel tunnel)
|
||||
@@ -2710,14 +2731,21 @@ namespace Barotrauma
|
||||
return position;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
|
||||
public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Vector2 awayPoint, float minDistFromPoint, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, filter);
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, awayPoint, minDistFromPoint, filter);
|
||||
position = pos.ToVector2();
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Func<InterestingPosition, bool> filter = null)
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter);
|
||||
position = pos.ToVector2();
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null)
|
||||
{
|
||||
if (!PositionsOfInterest.Any())
|
||||
{
|
||||
@@ -2755,6 +2783,11 @@ namespace Barotrauma
|
||||
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs);
|
||||
}
|
||||
}
|
||||
if (minDistFromPoint > 0.0f)
|
||||
{
|
||||
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), awayPoint) < minDistFromPoint * minDistFromPoint);
|
||||
}
|
||||
|
||||
if (!farEnoughPositions.Any())
|
||||
{
|
||||
string errorMsg = "Could not find a position of interest far enough from the submarines. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
@@ -2826,7 +2859,7 @@ namespace Barotrauma
|
||||
if (index < 0 || index >= bottomPositions.Count - 1) { return new Vector2(xPosition, BottomPos); }
|
||||
|
||||
float t = (xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X);
|
||||
Debug.Assert(t < 1.0f);
|
||||
Debug.Assert(t <= 1.0f);
|
||||
t = MathHelper.Clamp(t, 0.0f, 1.0f);
|
||||
|
||||
float yPos = MathHelper.Lerp(bottomPositions[index].Y, bottomPositions[index + 1].Y, t);
|
||||
@@ -2993,20 +3026,30 @@ namespace Barotrauma
|
||||
return originalTag + "_" + shortSeed;
|
||||
}
|
||||
|
||||
public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist);
|
||||
public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist);
|
||||
|
||||
public bool IsCloseToStart(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
}
|
||||
|
||||
public bool IsCloseToEnd(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
}
|
||||
|
||||
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
|
||||
{
|
||||
var tempSW = new Stopwatch();
|
||||
|
||||
// Min distance between a sub and the start/end/other sub.
|
||||
float minDistance = Sonar.DefaultSonarRange;
|
||||
float squaredMinDistance = minDistance * minDistance;
|
||||
Vector2 start = startPosition.ToVector2();
|
||||
Vector2 end = endPosition.ToVector2();
|
||||
var waypoints = WayPoint.WayPointList.Where(wp =>
|
||||
wp.Submarine == null &&
|
||||
wp.SpawnType == SpawnType.Path &&
|
||||
Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance &&
|
||||
Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList();
|
||||
!IsCloseToStart(wp.WorldPosition, minDistance) &&
|
||||
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
|
||||
|
||||
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
|
||||
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
||||
@@ -3094,12 +3137,11 @@ namespace Barotrauma
|
||||
sub.SetPosition(spawnPoint);
|
||||
wreckPositions.Add(sub, positions);
|
||||
blockedRects.Add(sub, rects);
|
||||
|
||||
return sub;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds.ToString()} (ms).", Color.Red);
|
||||
DebugConsole.NewMessage($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds} (ms).", Color.Red);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3150,7 +3192,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
var sp = spawnPoint;
|
||||
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance))
|
||||
if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < minDistance * minDistance))
|
||||
{
|
||||
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
|
||||
return false;
|
||||
@@ -3306,18 +3348,25 @@ namespace Barotrauma
|
||||
}
|
||||
wreckFiles.Shuffle(Rand.RandSync.Server);
|
||||
|
||||
int wreckCount = Math.Min(Loaded.GenerationParams.WreckCount, wreckFiles.Count);
|
||||
int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, wreckFiles.Count);
|
||||
int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, wreckFiles.Count);
|
||||
int wreckCount = Rand.Range(minWreckCount, maxWreckCount + 1, Rand.RandSync.Server);
|
||||
|
||||
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireWreck) ?? false)
|
||||
{
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
Wrecks = new List<Submarine>(wreckCount);
|
||||
for (int i = 0; i < wreckCount; i++)
|
||||
{
|
||||
ContentFile contentFile = wreckFiles[i];
|
||||
if (contentFile == null) { continue; }
|
||||
string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path);
|
||||
// For storing the translations. Used only for debugging.
|
||||
SpawnSubOnPath(wreckName, contentFile, SubmarineType.Wreck);
|
||||
}
|
||||
totalSW.Stop();
|
||||
Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)");
|
||||
Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds} (ms)");
|
||||
}
|
||||
|
||||
private bool HasStartOutpost()
|
||||
@@ -3365,11 +3414,8 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (Submarine.MainSubs.Length > 1 && Submarine.MainSubs[0] != null && Submarine.MainSubs[1] != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is PvPMode) { continue; }
|
||||
|
||||
bool isStart = (i == 0) == !Mirrored;
|
||||
if (isStart)
|
||||
{
|
||||
@@ -3527,7 +3573,7 @@ namespace Barotrauma
|
||||
{
|
||||
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
|
||||
}
|
||||
outpost.SetPosition(spawnPos);
|
||||
outpost.SetPosition(spawnPos, forceUndockFromStaticSubmarines: false);
|
||||
if ((i == 0) == !Mirrored)
|
||||
{
|
||||
StartOutpost = outpost;
|
||||
@@ -3550,7 +3596,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBeaconStation(List<VoronoiCell> mainPath)
|
||||
private void CreateBeaconStation()
|
||||
{
|
||||
if (!LevelData.HasBeaconStation) { return; }
|
||||
var beaconStationFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.BeaconStation).ToList();
|
||||
|
||||
@@ -496,8 +496,11 @@ namespace Barotrauma
|
||||
[Serialize(1, true, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int RuinCount { get; set; }
|
||||
|
||||
[Serialize(1, true, 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)]
|
||||
public int MinWreckCount { get; set; }
|
||||
|
||||
[Serialize(1, true, description: "The maximum 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)]
|
||||
public int WreckCount { get; set; }
|
||||
public int MaxWreckCount { get; set; }
|
||||
|
||||
// TODO: Move the wreck parameters under a separate class?
|
||||
#region Wreck parameters
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObject : ISpatialEntity
|
||||
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
|
||||
{
|
||||
public readonly LevelObjectPrefab Prefab;
|
||||
public Vector3 Position;
|
||||
@@ -21,6 +21,8 @@ namespace Barotrauma
|
||||
|
||||
private int spriteIndex;
|
||||
|
||||
protected bool tookDamage;
|
||||
|
||||
public LevelObjectPrefab ActivePrefab;
|
||||
|
||||
public PhysicsBody PhysicsBody
|
||||
@@ -37,11 +39,15 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsNetworkSyncing
|
||||
{
|
||||
get { return Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing); }
|
||||
get
|
||||
{
|
||||
return tookDamage || (Triggers != null && Triggers.Any(t => t.NeedsNetworkSyncing));
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
Triggers.ForEach(t => t.NeedsNetworkSyncing = false);
|
||||
tookDamage = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +56,12 @@ namespace Barotrauma
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get
|
||||
@@ -67,6 +79,10 @@ namespace Barotrauma
|
||||
|
||||
public Submarine Submarine => null;
|
||||
|
||||
public string Name => Prefab?.Name ?? "LevelObject (null)";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; } = new Dictionary<string, SerializableProperty>();
|
||||
|
||||
public Level.Cave ParentCave;
|
||||
|
||||
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
@@ -75,6 +91,7 @@ namespace Barotrauma
|
||||
Position = position;
|
||||
Scale = scale;
|
||||
Rotation = rotation;
|
||||
Health = prefab.Health;
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
|
||||
@@ -89,10 +106,13 @@ namespace Barotrauma
|
||||
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.UserData = this;
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition, -Rotation);
|
||||
PhysicsBody.BodyType = BodyType.Static;
|
||||
PhysicsBody.CollisionCategories = Physics.CollisionLevel;
|
||||
PhysicsBody.CollidesWith = Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
PhysicsBody.CollidesWith = Prefab.TakeLevelWallDamage?
|
||||
Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionProjectile :
|
||||
Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
@@ -111,6 +131,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
|
||||
if (newTrigger.PhysicsBody != null)
|
||||
{
|
||||
newTrigger.PhysicsBody.UserData = this;
|
||||
}
|
||||
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
|
||||
if (parentTriggerIndex > -1) { newTrigger.ParentTrigger = Triggers[parentTriggerIndex]; }
|
||||
Triggers.Add(newTrigger);
|
||||
@@ -135,7 +159,54 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
|
||||
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (Health <= 0.0f) { return new AttackResult(0.0f); }
|
||||
|
||||
float damage = 0.0f;
|
||||
if (Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
damage += attack.GetLevelWallDamage(deltaTime);
|
||||
}
|
||||
damage = Math.Max(Health, damage);
|
||||
AddDamage(damage, deltaTime, attacker);
|
||||
return new AttackResult(damage);
|
||||
}
|
||||
|
||||
public void AddDamage(float damage, float deltaTime, Entity attacker, bool isNetworkEvent = false)
|
||||
{
|
||||
if (Health <= 0.0f) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tookDamage |= !MathUtils.NearlyEqual(damage, 0.0f);
|
||||
Health -= damage;
|
||||
if (Health <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.Level?.LevelObjectManager != null)
|
||||
{
|
||||
GameMain.GameSession.Level.LevelObjectManager.ForceRefreshVisibleObjects = true;
|
||||
}
|
||||
#endif
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.Enabled = false;
|
||||
}
|
||||
foreach (LevelTrigger trigger in Triggers)
|
||||
{
|
||||
trigger.PhysicsBody.Enabled = false;
|
||||
foreach (StatusEffect effect in trigger.StatusEffects)
|
||||
{
|
||||
if (effect.type != ActionType.OnBroken) { continue; }
|
||||
effect.Apply(effect.type, deltaTime, attacker, this, worldPosition: WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
|
||||
{
|
||||
Vector2 emitterPos = localPosition * Scale;
|
||||
@@ -169,6 +240,10 @@ namespace Barotrauma
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (Triggers == null) { return; }
|
||||
if (Prefab.TakeLevelWallDamage)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(Health, 0.0f, Prefab.Health), 0.0f, Prefab.Health, 8);
|
||||
}
|
||||
for (int j = 0; j < Triggers.Count; j++)
|
||||
{
|
||||
if (!Triggers[j].UseNetworkSyncing) { continue; }
|
||||
|
||||
+22
-3
@@ -21,6 +21,12 @@ namespace Barotrauma
|
||||
private List<LevelObject> updateableObjects;
|
||||
private List<LevelObject>[,] objectGrid;
|
||||
|
||||
public float GlobalForceDecreaseTimer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public LevelObjectManager() : base(null, Entity.NullEntityID)
|
||||
{
|
||||
}
|
||||
@@ -130,11 +136,16 @@ namespace Barotrauma
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
float minDistance = level.Size.X * 0.2f;
|
||||
|
||||
suitableSpawnPositions.Add(prefab,
|
||||
availableSpawnPositions.Where(sp =>
|
||||
sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
sp.Length >= prefab.MinSurfaceWidth &&
|
||||
(prefab.AllowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) &&
|
||||
(prefab.AllowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) &&
|
||||
(sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
|
||||
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
@@ -422,10 +433,10 @@ namespace Barotrauma
|
||||
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
|
||||
{
|
||||
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
|
||||
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty<LevelObject>();
|
||||
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty<LevelObject>(); }
|
||||
|
||||
var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
|
||||
if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty<LevelObject>();
|
||||
if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty<LevelObject>(); }
|
||||
|
||||
minIndices.X = Math.Max(0, minIndices.X);
|
||||
minIndices.Y = Math.Max(0, minIndices.Y);
|
||||
@@ -440,6 +451,7 @@ namespace Barotrauma
|
||||
if (objectGrid[x, y] == null) { continue; }
|
||||
foreach (LevelObject obj in objectGrid[x, y])
|
||||
{
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
objectsInRange.Add(obj);
|
||||
}
|
||||
}
|
||||
@@ -484,6 +496,12 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
GlobalForceDecreaseTimer += deltaTime;
|
||||
if (GlobalForceDecreaseTimer > 1000000.0f)
|
||||
{
|
||||
GlobalForceDecreaseTimer = 0.0f;
|
||||
}
|
||||
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
@@ -496,6 +514,7 @@ namespace Barotrauma
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
|
||||
@@ -176,6 +176,20 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, true, description: "Can the object be placed near the start of the level.")]
|
||||
public bool AllowAtStart
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, true, description: "Can the object be placed near the end of the level.")]
|
||||
public bool AllowAtEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
/// <summary>
|
||||
/// Minimum length of a graph edge the object can spawn on.
|
||||
@@ -250,6 +264,27 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
|
||||
public bool TakeLevelWallDamage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HideWhenBroken
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable]
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -38,6 +38,10 @@ namespace Barotrauma
|
||||
/// Effects applied to entities that are inside the trigger
|
||||
/// </summary>
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
public IEnumerable<StatusEffect> StatusEffects
|
||||
{
|
||||
get { return statusEffects; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attacks applied to entities that are inside the trigger
|
||||
@@ -140,6 +144,11 @@ namespace Barotrauma
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public float GlobalForceDecreaseInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly TriggerForceMode forceMode;
|
||||
public TriggerForceMode ForceMode
|
||||
@@ -200,7 +209,7 @@ namespace Barotrauma
|
||||
PhysicsBody = new PhysicsBody(element, scale)
|
||||
{
|
||||
CollisionCategories = Physics.CollisionLevel,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall,
|
||||
};
|
||||
PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
|
||||
@@ -234,6 +243,7 @@ namespace Barotrauma
|
||||
ForceFluctuationInterval = element.GetAttributeFloat("forcefluctuationinterval", 0.01f);
|
||||
ForceFluctuationStrength = Math.Max(element.GetAttributeFloat("forcefluctuationstrength", 0.0f), 0.0f);
|
||||
ForceFalloff = element.GetAttributeBool("forcefalloff", true);
|
||||
GlobalForceDecreaseInterval = element.GetAttributeFloat("globalforcedecreaseinterval", 0.0f);
|
||||
|
||||
ForceVelocityLimit = ConvertUnits.ToSimUnits(element.GetAttributeFloat("forcevelocitylimit", float.MaxValue));
|
||||
string forceModeStr = element.GetAttributeString("forcemode", "Force");
|
||||
@@ -434,6 +444,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; }
|
||||
@@ -457,7 +469,13 @@ namespace Barotrauma
|
||||
|
||||
if (!UseNetworkSyncing || isNotClient)
|
||||
{
|
||||
if (ForceFluctuationStrength > 0.0f)
|
||||
if (GlobalForceDecreaseInterval > 0.0f && Level.Loaded?.LevelObjectManager != null &&
|
||||
Level.Loaded.LevelObjectManager.GlobalForceDecreaseTimer % (GlobalForceDecreaseInterval * 2) < GlobalForceDecreaseInterval)
|
||||
{
|
||||
NeedsNetworkSyncing |= currentForceFluctuation > 0.0f;
|
||||
currentForceFluctuation = 0.0f;
|
||||
}
|
||||
else if (ForceFluctuationStrength > 0.0f)
|
||||
{
|
||||
//no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers
|
||||
if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any())
|
||||
@@ -509,6 +527,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (effect.type == ActionType.OnBroken) { continue; }
|
||||
Vector2? position = null;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; }
|
||||
if (triggerer is Character character)
|
||||
@@ -533,8 +552,8 @@ namespace Barotrauma
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = new List<ISerializableEntity>();
|
||||
effect.GetNearbyTargets(worldPosition, targets);
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition);
|
||||
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition, forceUndockFromStaticSubmarines: false);
|
||||
sub.Submarine = Submarine;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace Barotrauma
|
||||
public class TakenItem
|
||||
{
|
||||
public readonly ushort OriginalID;
|
||||
public readonly ushort OriginalContainerID;
|
||||
public readonly ushort ModuleIndex;
|
||||
public readonly string Identifier;
|
||||
public readonly int OriginalContainerIndex;
|
||||
|
||||
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerID, ushort moduleIndex)
|
||||
public TakenItem(string identifier, UInt16 originalID, int originalContainerIndex, ushort moduleIndex)
|
||||
{
|
||||
OriginalID = originalID;
|
||||
OriginalContainerID = originalContainerID;
|
||||
OriginalContainerIndex = originalContainerIndex;
|
||||
ModuleIndex = moduleIndex;
|
||||
Identifier = identifier;
|
||||
}
|
||||
@@ -29,11 +29,7 @@ namespace Barotrauma
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(item.OriginalModuleIndex >= 0, "Trying to add a non-outpost item to a location's taken items");
|
||||
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
|
||||
OriginalContainerIndex = item.OriginalContainerIndex;
|
||||
OriginalID = item.ID;
|
||||
ModuleIndex = (ushort) item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
@@ -41,14 +37,14 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEqual(TakenItem obj)
|
||||
{
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerIndex == OriginalContainerIndex && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
}
|
||||
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
if (item.OriginalContainerIndex != Entity.NullEntityID)
|
||||
{
|
||||
return item.OriginalContainerID == OriginalContainerID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
return item.OriginalContainerIndex == OriginalContainerIndex && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -181,27 +177,54 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Mission SelectedMission
|
||||
private readonly List<Mission> selectedMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> SelectedMissions
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get
|
||||
{
|
||||
selectedMissions.RemoveAll(m => !availableMissions.Contains(m));
|
||||
return selectedMissions;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedMissionIndex
|
||||
public void SelectMission(Mission mission)
|
||||
{
|
||||
get
|
||||
if (!SelectedMissions.Contains(mission) && mission != null)
|
||||
{
|
||||
if (SelectedMission == null) { return -1; }
|
||||
return availableMissions.IndexOf(SelectedMission);
|
||||
selectedMissions.Add(mission);
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
public void DeselectMission(Mission mission)
|
||||
{
|
||||
selectedMissions.Remove(mission);
|
||||
}
|
||||
|
||||
|
||||
public List<int> GetSelectedMissionIndices()
|
||||
{
|
||||
List<int> selectedMissionIndices = new List<int>();
|
||||
foreach (Mission mission in SelectedMissions)
|
||||
{
|
||||
if (value < 0 || value >= AvailableMissions.Count())
|
||||
if (availableMissions.Contains(mission))
|
||||
{
|
||||
SelectedMission = null;
|
||||
return;
|
||||
selectedMissionIndices.Add(availableMissions.IndexOf(mission));
|
||||
}
|
||||
SelectedMission = availableMissions[value];
|
||||
}
|
||||
return selectedMissionIndices;
|
||||
}
|
||||
|
||||
public void SetSelectedMissionIndices(IEnumerable<int> missionIndices)
|
||||
{
|
||||
selectedMissions.Clear();
|
||||
foreach (int missionIndex in missionIndices)
|
||||
{
|
||||
if (missionIndex < 0 || missionIndex >= availableMissions.Count)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to select a mission in location \"{Name}\". Mission index out of bounds ({missionIndex}, available missions: {availableMissions.Count})");
|
||||
break;
|
||||
}
|
||||
selectedMissions.Add(availableMissions[missionIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,9 +345,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[2], out ushort containerId))
|
||||
if (!int.TryParse(takenItemSplit[2], out int containerIndex))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken container id \"{takenItemSplit[2]}\"");
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[3], out ushort moduleIndex))
|
||||
@@ -332,7 +355,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item module index \"{takenItemSplit[3]}\"");
|
||||
continue;
|
||||
}
|
||||
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerId, moduleIndex));
|
||||
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerIndex, moduleIndex));
|
||||
}
|
||||
|
||||
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", new int[0]).ToHashSet();
|
||||
@@ -415,6 +438,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{Name}\" to null.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
@@ -533,8 +562,28 @@ namespace Barotrauma
|
||||
//prefer connections that haven't been passed through, and connections with fewer available missions
|
||||
connection = ToolBox.SelectWeightedRandom(
|
||||
suitableConnections.ToList(),
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
suitableConnections.Select(c => GetConnectionWeight(this, c)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
|
||||
static float GetConnectionWeight(Location location, LocationConnection c)
|
||||
{
|
||||
float weight = c.Passed ? 1.0f : 5.0f;
|
||||
Location destination = c.OtherLocation(location);
|
||||
if (destination != null)
|
||||
{
|
||||
if (destination.MapPosition.X > location.MapPosition.X) { weight *= 2.0f; }
|
||||
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
|
||||
if (missionCount > 0)
|
||||
{
|
||||
weight /= missionCount * 2;
|
||||
}
|
||||
if (destination.IsRadiated())
|
||||
{
|
||||
weight *= 0.001f;
|
||||
}
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
return InstantiateMission(prefab, connection);
|
||||
}
|
||||
@@ -542,14 +591,14 @@ namespace Barotrauma
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
|
||||
{
|
||||
Location destination = connection.OtherLocation(this);
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination });
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub);
|
||||
mission.AdjustLevelData(connection.LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab)
|
||||
{
|
||||
var mission = prefab.Instantiate(new Location[] { this, this });
|
||||
var mission = prefab.Instantiate(new Location[] { this, this }, Submarine.MainSub);
|
||||
mission.AdjustLevelData(LevelData);
|
||||
return mission;
|
||||
}
|
||||
@@ -557,6 +606,7 @@ namespace Barotrauma
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
selectedMissions.Clear();
|
||||
if (loadedMissions != null && loadedMissions.Any())
|
||||
{
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
@@ -570,9 +620,9 @@ namespace Barotrauma
|
||||
{
|
||||
destination = Connections.First().OtherLocation(this);
|
||||
}
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination });
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination }, Submarine.MainSub);
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { SelectedMission = mission; }
|
||||
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
|
||||
}
|
||||
loadedMissions = null;
|
||||
}
|
||||
@@ -596,7 +646,7 @@ namespace Barotrauma
|
||||
public void ClearMissions()
|
||||
{
|
||||
availableMissions.Clear();
|
||||
SelectedMissionIndex = -1;
|
||||
selectedMissions.Clear();
|
||||
}
|
||||
|
||||
public bool HasOutpost()
|
||||
@@ -1107,7 +1157,7 @@ namespace Barotrauma
|
||||
{
|
||||
locationElement.Add(new XAttribute(
|
||||
"takenitems",
|
||||
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerID + ";" + it.ModuleIndex))));
|
||||
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerIndex + ";" + it.ModuleIndex))));
|
||||
}
|
||||
if (killedCharacterIdentifiers.Any())
|
||||
{
|
||||
@@ -1164,7 +1214,7 @@ namespace Barotrauma
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
new XAttribute("destinationindex", i),
|
||||
new XAttribute("selected", mission == SelectedMission)));
|
||||
new XAttribute("selected", selectedMissions.Contains(mission))));
|
||||
}
|
||||
locationElement.Add(missionsElement);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
/// From -> To
|
||||
/// </summary>
|
||||
public Action<Location, Location> OnLocationChanged;
|
||||
public Action<LocationConnection, Mission> OnMissionSelected;
|
||||
public Action<LocationConnection, IEnumerable<Mission>> OnMissionsSelected;
|
||||
|
||||
public Location EndLocation { get; private set; }
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace Barotrauma
|
||||
get { return Locations.IndexOf(SelectedLocation); }
|
||||
}
|
||||
|
||||
public int SelectedMissionIndex
|
||||
public IEnumerable<int> GetSelectedMissionIndices()
|
||||
{
|
||||
get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; }
|
||||
return SelectedConnection == null ? Enumerable.Empty<int>() : CurrentLocation.GetSelectedMissionIndices();
|
||||
}
|
||||
|
||||
public LocationConnection SelectedConnection { get; private set; }
|
||||
@@ -388,8 +388,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones];
|
||||
foreach (var connection in Connections)
|
||||
List<LocationConnection>[] connectionsBetweenZones = new List<LocationConnection>[generationParams.DifficultyZones];
|
||||
for (int i = 0; i < generationParams.DifficultyZones; i++)
|
||||
{
|
||||
connectionsBetweenZones[i] = new List<LocationConnection>();
|
||||
}
|
||||
var shuffledConnections = Connections.ToList();
|
||||
shuffledConnections.Shuffle(Rand.RandSync.Server);
|
||||
foreach (var connection in shuffledConnections)
|
||||
{
|
||||
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X);
|
||||
@@ -401,17 +407,25 @@ namespace Barotrauma
|
||||
zone1 = temp;
|
||||
}
|
||||
|
||||
if (connectionsBetweenZones[zone1] == null)
|
||||
if (generationParams.GateCount[zone1] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[zone1].Any())
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
else
|
||||
else if (generationParams.GateCount[zone1] == 1)
|
||||
{
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2))
|
||||
//if there's only one connection, place it at the center of the map
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].First().CenterPos.Y - Height / 2))
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
connectionsBetweenZones[zone1].Clear();
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1])
|
||||
{
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
@@ -421,7 +435,9 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones.Contains(Connections[i]))
|
||||
if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
@@ -756,7 +772,7 @@ namespace Barotrauma
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
public void SelectMission(int missionIndex)
|
||||
public void SelectMission(IEnumerable<int> missionIndices)
|
||||
{
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
@@ -765,23 +781,24 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
CurrentLocation.SelectedMissionIndex = missionIndex;
|
||||
|
||||
if (CurrentLocation.SelectedMission == null) { return; }
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
|
||||
if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation ||
|
||||
CurrentLocation.SelectedMission.Locations[1] != CurrentLocation)
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
@@ -977,6 +994,12 @@ namespace Barotrauma
|
||||
string prevName = location.Name;
|
||||
|
||||
var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase));
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newType.OutpostTeam != location.Type.OutpostTeam ||
|
||||
newType.HasOutpost != location.Type.HasOutpost)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,8 @@ namespace Barotrauma
|
||||
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
|
||||
|
||||
public int[] GateCount { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
|
||||
[Serialize(0.75f, true), Editable(DecimalCount = 2)]
|
||||
@@ -201,6 +203,16 @@ namespace Barotrauma
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
GateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null);
|
||||
if (GateCount == null)
|
||||
{
|
||||
GateCount = new int[DifficultyZones];
|
||||
for (int i = 0; i < DifficultyZones; i++)
|
||||
{
|
||||
GateCount[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
|
||||
@@ -81,6 +81,7 @@ namespace Barotrauma
|
||||
|
||||
if (location.Type.HasOutpost && !wasCritical && location.IsCriticallyRadiated())
|
||||
{
|
||||
location.ClearMissions();
|
||||
amountOfOutposts--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int OriginalModuleIndex = -1;
|
||||
|
||||
public UInt16 OriginalContainerID;
|
||||
public int OriginalContainerIndex = -1;
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
@@ -280,7 +280,7 @@ namespace Barotrauma
|
||||
public void ResolveLinks(IdRemap childRemap)
|
||||
{
|
||||
if (unresolvedLinkedToID == null) { return; }
|
||||
for (int i=0;i<unresolvedLinkedToID.Count;i++)
|
||||
for (int i = 0; i < unresolvedLinkedToID.Count; i++)
|
||||
{
|
||||
int srcId = unresolvedLinkedToID[i];
|
||||
int targetId = childRemap.GetOffsetId(srcId);
|
||||
@@ -420,7 +420,7 @@ namespace Barotrauma
|
||||
if (cloneItem == null) { continue; }
|
||||
|
||||
var door = cloneItem.GetComponent<Door>();
|
||||
if (door != null) { door.RefreshLinkedGap(); }
|
||||
door?.RefreshLinkedGap();
|
||||
|
||||
var cloneWire = cloneItem.GetComponent<Wire>();
|
||||
if (cloneWire == null) continue;
|
||||
@@ -509,9 +509,9 @@ namespace Barotrauma
|
||||
mapEntityList.Remove(this);
|
||||
|
||||
#if CLIENT
|
||||
if (selectedList.Contains(this))
|
||||
if (SelectedList.Contains(this))
|
||||
{
|
||||
selectedList = selectedList.FindAll(e => e != this);
|
||||
SelectedList = SelectedList.Where(e => e != this).ToHashSet();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -649,7 +649,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine, idRemap });
|
||||
if (newEntity != null) entities.Add((MapEntity)newEntity);
|
||||
if (newEntity != null)
|
||||
{
|
||||
entities.Add((MapEntity)newEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
|
||||
@@ -186,7 +186,8 @@ namespace Barotrauma
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != sub) { continue; }
|
||||
if (hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrEmpty(hull.RoomName) ||
|
||||
hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hull.RoomName = hull.CreateRoomName();
|
||||
}
|
||||
@@ -1436,6 +1437,7 @@ namespace Barotrauma
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, true);
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
npc.Prefab = humanPrefab;
|
||||
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
|
||||
{
|
||||
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
|
||||
@@ -1447,7 +1449,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
|
||||
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public Submarine Submarine => Wall.Submarine;
|
||||
public Rectangle WorldRect => Submarine == null ? rect :
|
||||
new Rectangle((int)(rect.X + Submarine.Position.X), (int)(rect.Y + Submarine.Position.Y), rect.Width, rect.Height);
|
||||
public bool IgnoreByAI => OrderedToBeIgnored;
|
||||
public bool IgnoreByAI(Character character) => OrderedToBeIgnored && character.IsOnPlayerTeam;
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public WallSection(Rectangle rect, Structure wall, float damage = 0.0f)
|
||||
@@ -53,7 +53,16 @@ namespace Barotrauma
|
||||
//dimensions of the wall sections' physics bodies (only used for debug rendering)
|
||||
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
|
||||
|
||||
public bool Indestructible;
|
||||
#if DEBUG
|
||||
[Serialize(false, true), Editable]
|
||||
#else
|
||||
[Serialize(false, true)]
|
||||
#endif
|
||||
public bool Indestructible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//sections of the wall that are supposed to be rendered
|
||||
public WallSection[] Sections
|
||||
@@ -353,10 +362,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls!=null)
|
||||
{
|
||||
convexHulls.ForEach(x => x.Move(amount));
|
||||
}
|
||||
convexHulls?.ForEach(x => x.Move(amount));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -801,7 +807,7 @@ namespace Barotrauma
|
||||
|
||||
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
|
||||
{
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; }
|
||||
if (!Prefab.Body || Prefab.Platform || Indestructible) { return; }
|
||||
|
||||
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }
|
||||
|
||||
@@ -875,13 +881,17 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 SectionPosition(int sectionIndex, bool world = false)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return Vector2.Zero;
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
if (Prefab.BodyRotation == 0.0f)
|
||||
{
|
||||
Vector2 sectionPos = new Vector2(
|
||||
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
|
||||
Sections[sectionIndex].rect.Y - Sections[sectionIndex].rect.Height / 2.0f);
|
||||
|
||||
if (world && Submarine != null)
|
||||
{
|
||||
sectionPos += Submarine.Position;
|
||||
@@ -900,8 +910,11 @@ namespace Barotrauma
|
||||
{
|
||||
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
|
||||
}
|
||||
if (FlippedX) diffFromCenter = -diffFromCenter;
|
||||
|
||||
if (FlippedX)
|
||||
{
|
||||
diffFromCenter = -diffFromCenter;
|
||||
}
|
||||
|
||||
Vector2 sectionPos = Position + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
|
||||
|
||||
@@ -250,17 +250,21 @@ namespace Barotrauma
|
||||
var parentType = element.Parent?.GetAttributeString("prefabtype", "") ?? string.Empty;
|
||||
|
||||
string nameIdentifier = element.GetAttributeString("nameidentifier", "");
|
||||
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
string fallbackNameIdentifier = element.GetAttributeString("fallbacknameidentifier", "");
|
||||
|
||||
string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", "");
|
||||
|
||||
if (string.IsNullOrEmpty(sp.originalName))
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameIdentifier))
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, true) ?? string.Empty;
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + nameIdentifier, true) ?? string.Empty;
|
||||
sp.name = TextManager.Get("EntityName." + nameIdentifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Barotrauma
|
||||
|
||||
private static Vector2 lastPickedPosition;
|
||||
private static float lastPickedFraction;
|
||||
private static Fixture lastPickedFixture;
|
||||
private static Vector2 lastPickedNormal;
|
||||
|
||||
private Vector2 prevPosition;
|
||||
@@ -99,6 +100,11 @@ namespace Barotrauma
|
||||
get { return lastPickedFraction; }
|
||||
}
|
||||
|
||||
public static Fixture LastPickedFixture
|
||||
{
|
||||
get { return lastPickedFixture; }
|
||||
}
|
||||
|
||||
public static Vector2 LastPickedNormal
|
||||
{
|
||||
get { return lastPickedNormal; }
|
||||
@@ -368,7 +374,7 @@ namespace Barotrauma
|
||||
public WreckAI WreckAI { get; private set; }
|
||||
public bool CreateWreckAI()
|
||||
{
|
||||
WreckAI = new WreckAI(this);
|
||||
WreckAI = WreckAI.Create(this);
|
||||
return WreckAI != null;
|
||||
}
|
||||
|
||||
@@ -669,6 +675,7 @@ namespace Barotrauma
|
||||
|
||||
float closestFraction = 1.0f;
|
||||
Vector2 closestNormal = Vector2.Zero;
|
||||
Fixture closestFixture = null;
|
||||
Body closestBody = null;
|
||||
if (allowInsideFixture)
|
||||
{
|
||||
@@ -682,13 +689,15 @@ namespace Barotrauma
|
||||
|
||||
closestFraction = 0.0f;
|
||||
closestNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
closestFixture = fixture;
|
||||
if (fixture.Body != null) { closestBody = fixture.Body; }
|
||||
return false;
|
||||
}, ref aabb);
|
||||
if (closestFraction <= 0.0f)
|
||||
{
|
||||
lastPickedPosition = rayStart;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
return closestBody;
|
||||
}
|
||||
@@ -702,6 +711,7 @@ namespace Barotrauma
|
||||
{
|
||||
closestFraction = fraction;
|
||||
closestNormal = normal;
|
||||
closestFixture = fixture;
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
}
|
||||
return fraction;
|
||||
@@ -709,6 +719,7 @@ namespace Barotrauma
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
|
||||
return closestBody;
|
||||
@@ -752,6 +763,7 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
|
||||
lastPickedFraction = fraction;
|
||||
lastPickedNormal = normal;
|
||||
lastPickedFixture = fixture;
|
||||
}
|
||||
//continue
|
||||
return -1;
|
||||
@@ -772,6 +784,7 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayStart;
|
||||
lastPickedFraction = 0.0f;
|
||||
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||
lastPickedFixture = fixture;
|
||||
bodies.Add(fixture.Body);
|
||||
bodyDist[fixture.Body] = 0.0f;
|
||||
return false;
|
||||
@@ -828,6 +841,7 @@ namespace Barotrauma
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestFraction = 1.0f;
|
||||
Fixture closestFixture = null;
|
||||
Vector2 closestNormal = Vector2.Zero;
|
||||
|
||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
||||
@@ -847,6 +861,8 @@ namespace Barotrauma
|
||||
if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; }
|
||||
if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.Body.UserData as string == "ruinroom") { return -1; }
|
||||
//the hulls have solid fixtures in the submarine's world space collider, ignore them
|
||||
if (fixture.UserData is Hull) { return -1; }
|
||||
if (fixture.Body.UserData is Structure structure)
|
||||
{
|
||||
if (structure.IsPlatform || structure.StairDirection != Direction.None) { return -1; }
|
||||
@@ -861,6 +877,7 @@ namespace Barotrauma
|
||||
{
|
||||
closestBody = fixture.Body;
|
||||
closestFraction = fraction;
|
||||
closestFixture = fixture;
|
||||
closestNormal = normal;
|
||||
}
|
||||
return closestFraction;
|
||||
@@ -870,6 +887,7 @@ namespace Barotrauma
|
||||
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedFixture = closestFixture;
|
||||
lastPickedNormal = closestNormal;
|
||||
return closestBody;
|
||||
}
|
||||
@@ -944,19 +962,23 @@ namespace Barotrauma
|
||||
mapEntity.Move(HiddenSubPosition);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (bodyItems.Contains(item))
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
item.Submarine = this;
|
||||
if (Position == Vector2.Zero) item.Move(-HiddenSubPosition);
|
||||
//two passes: flip docking ports on the 2nd pass because the doors need to be correctly flipped for the port's orientation to be determined correctly
|
||||
if ((item.GetComponent<DockingPort>() != null) == (i == 0)) { continue; }
|
||||
if (bodyItems.Contains(item))
|
||||
{
|
||||
item.Submarine = this;
|
||||
if (Position == Vector2.Zero) { item.Move(-HiddenSubPosition); }
|
||||
}
|
||||
else if (item.Submarine != this)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
item.FlipX(true);
|
||||
}
|
||||
else if (item.Submarine != this)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.FlipX(true);
|
||||
}
|
||||
|
||||
Item.UpdateHulls();
|
||||
@@ -1152,7 +1174,7 @@ namespace Barotrauma
|
||||
prevPosition = position;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position, List<Submarine> checkd = null)
|
||||
public void SetPosition(Vector2 position, List<Submarine> checkd = null, bool forceUndockFromStaticSubmarines = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(position)) { return; }
|
||||
|
||||
@@ -1166,7 +1188,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Submarine dockedSub in DockedTo)
|
||||
{
|
||||
if (dockedSub.PhysicsBody.BodyType == BodyType.Static)
|
||||
if (dockedSub.PhysicsBody.BodyType == BodyType.Static && forceUndockFromStaticSubmarines)
|
||||
{
|
||||
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
|
||||
{
|
||||
@@ -1176,7 +1198,7 @@ namespace Barotrauma
|
||||
}
|
||||
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
|
||||
if (expectedLocation == null) { continue; }
|
||||
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
|
||||
dockedSub.SetPosition(position + expectedLocation.Value, checkd, forceUndockFromStaticSubmarines);
|
||||
dockedSub.UpdateTransform(interpolate: false);
|
||||
}
|
||||
}
|
||||
@@ -1238,6 +1260,26 @@ namespace Barotrauma
|
||||
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
|
||||
}
|
||||
|
||||
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
|
||||
{
|
||||
List<(ItemContainer container, int freeSlots)> containers = new List<(ItemContainer container, int freeSlots)>();
|
||||
var connectedSubs = GetConnectedSubs();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine)) { continue; }
|
||||
if (!item.HasTag("cargocontainer")) { continue; }
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
int emptySlots = 0;
|
||||
for (int i = 0; i < itemContainer.Inventory.Capacity; i++)
|
||||
{
|
||||
if (itemContainer.Inventory.GetItemAt(i) == null) { emptySlots++; }
|
||||
}
|
||||
containers.Add((itemContainer, emptySlots));
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
|
||||
{
|
||||
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
|
||||
@@ -1527,6 +1569,8 @@ namespace Barotrauma
|
||||
|
||||
Rectangle dimensions = CalculateDimensions();
|
||||
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
|
||||
var cargoContainers = GetCargoContainers();
|
||||
element.Add(new XAttribute("cargocapacity", cargoContainers.Sum(c => c.container.Capacity)));
|
||||
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
|
||||
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
|
||||
@@ -1537,6 +1581,41 @@ namespace Barotrauma
|
||||
Info.OutpostModuleInfo?.Save(element);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.PendingItemSwap?.SwappableItem?.ConnectedItemsToSwap == null) { continue; }
|
||||
foreach (var (requiredTag, swapTo) in item.PendingItemSwap.SwappableItem.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)
|
||||
{
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
foreach (var connectedComponent in item.GetConnectedComponentsRecursive<ItemComponent>(c))
|
||||
{
|
||||
if (!itemsToSwap.Contains(connectedComponent.Item) && connectedComponent.Item.HasTag(requiredTag))
|
||||
{
|
||||
itemsToSwap.Add(connectedComponent.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ItemPrefab itemPrefab = ItemPrefab.Find("", swapTo);
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to swap an item connected to \"{item.Name}\" into \"{swapTo}\".");
|
||||
continue;
|
||||
}
|
||||
foreach (Item itemToSwap in itemsToSwap)
|
||||
{
|
||||
itemToSwap.PurchasedNewSwap = item.PurchasedNewSwap;
|
||||
if (itemPrefab != itemToSwap.Prefab) { itemToSwap.PendingItemSwap = itemPrefab; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID))
|
||||
{
|
||||
if (!e.ShouldBeSaved) { continue; }
|
||||
|
||||
@@ -174,6 +174,11 @@ namespace Barotrauma
|
||||
float simWidth = ConvertUnits.ToSimUnits(width);
|
||||
float simHeight = ConvertUnits.ToSimUnits(height);
|
||||
|
||||
if (sub.FlippedX)
|
||||
{
|
||||
simPos.X = -simPos.X;
|
||||
}
|
||||
|
||||
if (width > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
|
||||
@@ -317,19 +322,30 @@ namespace Barotrauma
|
||||
Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
|
||||
}
|
||||
|
||||
if (Position.X < 0)
|
||||
//hard limit for how far outside the level the sub can go
|
||||
float maxDist = 200000.0f;
|
||||
//the force of the current starts to increase exponentially after this point
|
||||
float exponentialForceIncreaseDist = 150000.0f;
|
||||
float distance = Position.X < 0 ? Math.Abs(Position.X) : Position.X - Level.Loaded.Size.X;
|
||||
if (distance > 0)
|
||||
{
|
||||
float force = Math.Abs(Position.X * 0.5f);
|
||||
totalForce += Vector2.UnitX * force;
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
if (distance > maxDist)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
|
||||
if (Position.X < 0)
|
||||
{
|
||||
Body.LinearVelocity = new Vector2(Math.Max(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
Body.LinearVelocity = new Vector2(Math.Min(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float force = (Position.X - Level.Loaded.Size.X) * 0.5f;
|
||||
totalForce -= Vector2.UnitX * force;
|
||||
if (distance > exponentialForceIncreaseDist)
|
||||
{
|
||||
distance += (float)Math.Pow((distance - exponentialForceIncreaseDist) * 0.01f, 2.0f);
|
||||
}
|
||||
float force = distance * 0.5f;
|
||||
totalForce += (Position.X < 0 ? Vector2.UnitX : -Vector2.UnitX) * force;
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
|
||||
@@ -513,7 +529,7 @@ namespace Barotrauma
|
||||
{
|
||||
return CheckCharacterCollision(contact, character);
|
||||
}
|
||||
else if (f2.UserData is Items.Components.DockingPort)
|
||||
else if (f1.UserData is Items.Components.DockingPort || f2.UserData is Items.Components.DockingPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -823,9 +839,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine && Character.Controlled.KnockbackCooldownTimer <= 0.0f)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = impact * 10.0f;
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake);
|
||||
if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player))
|
||||
{
|
||||
float angularVelocity =
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
HideInMenus = 2
|
||||
}
|
||||
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation }
|
||||
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
|
||||
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
|
||||
|
||||
partial class SubmarineInfo : IDisposable
|
||||
@@ -133,6 +133,12 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public int CargoCapacity
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get;
|
||||
@@ -261,6 +267,7 @@ namespace Barotrauma
|
||||
SubmarineClass = original.SubmarineClass;
|
||||
hash = !string.IsNullOrEmpty(original.FilePath) ? original.MD5Hash : null;
|
||||
Dimensions = original.Dimensions;
|
||||
CargoCapacity = original.CargoCapacity;
|
||||
FilePath = original.FilePath;
|
||||
RequiredContentPackages = new HashSet<string>(original.RequiredContentPackages);
|
||||
IsFileCorrupted = original.IsFileCorrupted;
|
||||
@@ -323,6 +330,7 @@ namespace Barotrauma
|
||||
Tags = tags;
|
||||
}
|
||||
Dimensions = SubmarineElement.GetAttributeVector2("dimensions", Vector2.Zero);
|
||||
CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
|
||||
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
|
||||
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
|
||||
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
|
||||
@@ -511,10 +519,14 @@ namespace Barotrauma
|
||||
//saving/loading ----------------------------------------------------
|
||||
public bool SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
|
||||
{
|
||||
var newElement = new XElement(SubmarineElement.Name,
|
||||
SubmarineElement.Attributes().Where(a => !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
!string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)),
|
||||
var newElement = new XElement(
|
||||
SubmarineElement.Name,
|
||||
SubmarineElement.Attributes()
|
||||
.Where(a =>
|
||||
!string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
!string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)),
|
||||
SubmarineElement.Elements());
|
||||
|
||||
if (Type == SubmarineType.OutpostModule)
|
||||
{
|
||||
OutpostModuleInfo.Save(newElement);
|
||||
@@ -523,7 +535,6 @@ namespace Barotrauma
|
||||
XDocument doc = new XDocument(newElement);
|
||||
|
||||
doc.Root.Add(new XAttribute("name", Name));
|
||||
|
||||
if (previewImage != null)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
|
||||
@@ -574,7 +585,7 @@ namespace Barotrauma
|
||||
var contentPackageSubs = ContentPackage.GetFilesOfType(
|
||||
GameMain.Config.AllEnabledPackages,
|
||||
ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule,
|
||||
ContentType.Wreck, ContentType.BeaconStation);
|
||||
ContentType.Wreck, ContentType.BeaconStation, ContentType.EnemySubmarine);
|
||||
|
||||
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -680,8 +691,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string TempFolder = Path.Combine("Submarine", "Temp");
|
||||
|
||||
public static XDocument OpenFile(string file)
|
||||
{
|
||||
return OpenFile(file, out _);
|
||||
@@ -711,7 +720,7 @@ namespace Barotrauma
|
||||
|
||||
if (extension == ".sub")
|
||||
{
|
||||
System.IO.Stream stream = null;
|
||||
System.IO.Stream stream;
|
||||
try
|
||||
{
|
||||
stream = SaveUtil.DecompressFiletoStream(file);
|
||||
|
||||
@@ -192,17 +192,37 @@ namespace Barotrauma
|
||||
float minDist = 100.0f;
|
||||
float heightFromFloor = 110.0f;
|
||||
float hullMinHeight = 100;
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
// Ignore hulls that a human couldn't fit in.
|
||||
// Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually.
|
||||
if (hull.Rect.Height < hullMinHeight) { continue; }
|
||||
// Don't create waypoints if there's no floor.
|
||||
Vector2 floorPos = new Vector2(hull.SimPosition.X, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
|
||||
Body floor = Submarine.PickBody(hull.SimPosition, floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
// Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor.
|
||||
Body floor = null;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
float horizontalOffset = 0;
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
horizontalOffset = hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 2:
|
||||
horizontalOffset = hull.RectWidth * 0.4f;
|
||||
break;
|
||||
case 3:
|
||||
horizontalOffset = -hull.RectWidth * 0.2f;
|
||||
break;
|
||||
case 4:
|
||||
horizontalOffset = -hull.RectWidth * 0.4f;
|
||||
break;
|
||||
}
|
||||
horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset);
|
||||
Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50));
|
||||
floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
if (floor != null) { break; }
|
||||
}
|
||||
if (floor == null) { continue; }
|
||||
// Make sure that the waypoints don't go higher than the halfway of the room.
|
||||
float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2;
|
||||
if (hull.Rect.Width < diffFromHullEdge * 3.0f)
|
||||
{
|
||||
@@ -223,12 +243,42 @@ namespace Barotrauma
|
||||
new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Platforms
|
||||
foreach (Structure platform in Structure.WallList)
|
||||
{
|
||||
if (!platform.IsPlatform) { continue; }
|
||||
float waypointHeight = heightFromFloor;
|
||||
WayPoint prevWaypoint = null;
|
||||
for (float x = platform.Rect.X + diffFromHullEdge; x <= platform.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
WayPoint wayPoint = new WayPoint(new Vector2(x, platform.Rect.Y + waypointHeight), SpawnType.Path, submarine);
|
||||
if (prevWaypoint != null)
|
||||
{
|
||||
wayPoint.ConnectTo(prevWaypoint);
|
||||
}
|
||||
// If the waypoint is close to hull waypoints, remove it.
|
||||
if (wayPoint != null)
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
if (wayPoint.FindClosest(dir, horizontalSearch: true, tolerance: new Vector2(minDist, heightFromFloor), ignored: prevWaypoint.ToEnumerable()) != null)
|
||||
{
|
||||
wayPoint.Remove();
|
||||
wayPoint = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
prevWaypoint = wayPoint;
|
||||
}
|
||||
}
|
||||
|
||||
float outSideWaypointInterval = 100.0f;
|
||||
if (submarine.Info.Type != SubmarineType.OutpostModule)
|
||||
{
|
||||
List<WayPoint> outsideWaypoints = new List<WayPoint>();
|
||||
List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>();
|
||||
|
||||
Rectangle borders = Hull.GetBorders();
|
||||
int originalWidth = borders.Width;
|
||||
@@ -260,7 +310,7 @@ namespace Barotrauma
|
||||
new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
outsideWaypoints.Add(wayPoint);
|
||||
outsideWaypoints.Add((wayPoint, i));
|
||||
|
||||
if (x == borders.X + outSideWaypointInterval)
|
||||
{
|
||||
@@ -284,7 +334,7 @@ namespace Barotrauma
|
||||
new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
outsideWaypoints.Add(wayPoint);
|
||||
outsideWaypoints.Add((wayPoint, i));
|
||||
|
||||
if (y == borders.Y - borders.Height)
|
||||
{
|
||||
@@ -302,8 +352,9 @@ namespace Barotrauma
|
||||
Vector2 center = ConvertUnits.ToSimUnits(submarine.HiddenSubPosition);
|
||||
float halfHeight = ConvertUnits.ToSimUnits(borders.Height / 2);
|
||||
// Try to move the waypoints so that they are near the walls, roughly following the shape of the sub.
|
||||
foreach (WayPoint wp in outsideWaypoints)
|
||||
foreach (var wayPoint in outsideWaypoints)
|
||||
{
|
||||
WayPoint wp = wayPoint.Item1;
|
||||
float xDiff = center.X - wp.SimPosition.X;
|
||||
Vector2 targetPos = new Vector2(center.X - xDiff * 0.5f, center.Y);
|
||||
Body wall = Submarine.PickBody(wp.SimPosition, targetPos, collisionCategory: Physics.CollisionWall, customPredicate: f => !(f.Body.UserData is Submarine));
|
||||
@@ -331,8 +382,9 @@ namespace Barotrauma
|
||||
var removals = new List<WayPoint>();
|
||||
WayPoint previous = null;
|
||||
float tooClose = outSideWaypointInterval / 2;
|
||||
foreach (WayPoint wp in outsideWaypoints)
|
||||
foreach (var wayPoint in outsideWaypoints)
|
||||
{
|
||||
WayPoint wp = wayPoint.Item1;
|
||||
if (wp.CurrentHull != null ||
|
||||
Submarine.PickBody(wp.SimPosition, wp.SimPosition + Vector2.Normalize(center - wp.SimPosition) * 0.1f, collisionCategory: Physics.CollisionWall | Physics.CollisionItem, customPredicate: f => !(f.Body.UserData is Submarine), allowInsideFixture: true) != null)
|
||||
{
|
||||
@@ -341,8 +393,9 @@ namespace Barotrauma
|
||||
previous = wp;
|
||||
continue;
|
||||
}
|
||||
foreach (WayPoint otherWp in outsideWaypoints)
|
||||
foreach (var otherWayPoint in outsideWaypoints)
|
||||
{
|
||||
WayPoint otherWp = otherWayPoint.Item1;
|
||||
if (otherWp == wp) { continue; }
|
||||
if (removals.Contains(otherWp)) { continue; }
|
||||
float sqrDist = Vector2.DistanceSquared(wp.Position, otherWp.Position);
|
||||
@@ -356,13 +409,12 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (WayPoint wp in removals)
|
||||
{
|
||||
outsideWaypoints.Remove(wp);
|
||||
outsideWaypoints.RemoveAll(w => w.Item1 == wp);
|
||||
wp.Remove();
|
||||
}
|
||||
// Connect loose ends (TODO: this sometimes fails, creating the connection to a wrong node)
|
||||
for (int i = 0; i < outsideWaypoints.Count; i++)
|
||||
{
|
||||
WayPoint current = outsideWaypoints[i];
|
||||
WayPoint current = outsideWaypoints[i].Item1;
|
||||
if (current.linkedTo.Count > 1) { continue; }
|
||||
WayPoint next = null;
|
||||
int maxConnections = 2;
|
||||
@@ -371,19 +423,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (current.linkedTo.Count >= maxConnections) { break; }
|
||||
tooFar /= current.linkedTo.Count;
|
||||
// First try to find a loose end
|
||||
next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.linkedTo.Count < 2);
|
||||
// Then accept any connection that not connected to the existing connection
|
||||
next ??= current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp != next && wp.linkedTo.None(e => current.linkedTo.Contains(e)));
|
||||
next = current.FindClosestOutside(outsideWaypoints, tolerance: tooFar, filter: wp => wp.Item1 != next && wp.Item1.linkedTo.None(e => current.linkedTo.Contains(e)) && wp.Item1.linkedTo.Count < 2 && wp.Item2 < i);
|
||||
if (next != null)
|
||||
{
|
||||
current.ConnectTo(next);
|
||||
}
|
||||
}
|
||||
if (current.linkedTo.Count == 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't automatically link waypoint {current.ID}. You should do it manually.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +577,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 70), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
|
||||
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
|
||||
}
|
||||
if (closest == null) { continue; }
|
||||
ladderPoint.ConnectTo(closest);
|
||||
@@ -601,13 +646,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any());
|
||||
|
||||
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && w.linkedTo.None());
|
||||
foreach (WayPoint wp in orphans)
|
||||
{
|
||||
wp.Remove();
|
||||
}
|
||||
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color.");
|
||||
}
|
||||
}
|
||||
|
||||
//re-disable the bodies of the doors that are supposed to be open
|
||||
foreach (Door door in openDoors)
|
||||
{
|
||||
@@ -617,18 +669,20 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
private WayPoint FindClosestOutside(IEnumerable<WayPoint> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<WayPoint, bool> filter = null)
|
||||
private WayPoint FindClosestOutside(IEnumerable<(WayPoint, int)> waypointList, float tolerance, Body ignoredBody = null, IEnumerable<WayPoint> ignored = null, Func<(WayPoint, int), bool> filter = null)
|
||||
{
|
||||
float closestDist = 0;
|
||||
WayPoint closest = null;
|
||||
foreach (WayPoint wp in waypointList)
|
||||
foreach (var wayPoint in waypointList)
|
||||
{
|
||||
WayPoint wp = wayPoint.Item1;
|
||||
if (wp.SpawnType != SpawnType.Path || wp == this) { continue; }
|
||||
// Ignore if already linked
|
||||
if (linkedTo.Contains(wp)) { continue; }
|
||||
if (ignored != null && ignored.Contains(wp)) { continue; }
|
||||
if (filter != null && !filter(wp)) { continue; }
|
||||
if (filter != null && !filter(wayPoint)) { continue; }
|
||||
float sqrDist = Vector2.DistanceSquared(Position, wp.Position);
|
||||
if (sqrDist > tolerance * tolerance) { continue; }
|
||||
if (closest == null || sqrDist < closestDist)
|
||||
{
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
|
||||
@@ -712,14 +766,14 @@ namespace Barotrauma
|
||||
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); }
|
||||
}
|
||||
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
|
||||
{
|
||||
return WayPointList.GetRandom(wp =>
|
||||
wp.Submarine == sub &&
|
||||
wp.ParentRuin == ruin &&
|
||||
wp.spawnType == spawnType &&
|
||||
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab))
|
||||
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
|
||||
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
|
||||
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
|
||||
@@ -775,7 +829,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (assignedWayPoints[i] == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!");
|
||||
DebugConsole.AddWarning("Couldn't find a waypoint for " + crew[i].Name + "!");
|
||||
assignedWayPoints[i] = WayPointList[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user