Merge remote-tracking branch 'upstream/master' into develop
This commit is contained in:
@@ -92,7 +92,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
if (availableBranches.Count == 0) { return availableBranches; }
|
||||
|
||||
//prefer growing from the branches furthest from the root (ones with the largest branch depth)
|
||||
var branch = ToolBox.SelectWeightedRandom(availableBranches, b => (float)b.BranchDepth, Rand.RandSync.Unsynced);
|
||||
var branch = ToolBox.SelectWeightedRandom(availableBranches, b => b.BranchDepth, Rand.RandSync.Unsynced);
|
||||
|
||||
TileSide side = branch.GetRandomFreeSide();
|
||||
if (side == TileSide.None) { return availableBranches; }
|
||||
|
||||
@@ -761,11 +761,12 @@ namespace Barotrauma
|
||||
hull2.Oxygen -= deltaOxygen;
|
||||
}
|
||||
|
||||
public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
|
||||
public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom = false)
|
||||
{
|
||||
foreach (Gap gap in gaps)
|
||||
{
|
||||
if (gap.Open == 0.0f || gap.IsRoomToRoom) { continue; }
|
||||
if (gap.Open == 0.0f) { continue; }
|
||||
if (gap.IsRoomToRoom && !allowRoomToRoom) { continue; }
|
||||
|
||||
if (gap.ConnectedWall != null)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -430,6 +431,11 @@ namespace Barotrauma
|
||||
{
|
||||
get { return ForcedDifficulty ?? LevelData.Difficulty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inclusive (matching the min an max values is accepted).
|
||||
/// </summary>
|
||||
public bool IsAllowedDifficulty(float minDifficulty, float maxDifficulty) => LevelData.IsAllowedDifficulty(minDifficulty, maxDifficulty);
|
||||
|
||||
public LevelData.LevelType Type
|
||||
{
|
||||
@@ -3718,7 +3724,7 @@ namespace Barotrauma
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(endPosition, endExitPosition, position) < minDist * minDist;
|
||||
}
|
||||
|
||||
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
|
||||
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type, bool forceThalamus = false)
|
||||
{
|
||||
var tempSW = new Stopwatch();
|
||||
|
||||
@@ -3801,7 +3807,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
// Only spawn thalamus when the wreck has some thalamus items defined.
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) <= Loaded.GenerationParams.ThalamusProbability && sub.GetItems(false).Any(i => i.Prefab.HasSubCategory("thalamus")))
|
||||
if ((forceThalamus || Rand.Value(Rand.RandSync.ServerAndClient) <= Loaded.GenerationParams.ThalamusProbability) && sub.GetItems(false).Any(i => i.Prefab.HasSubCategory("thalamus")))
|
||||
{
|
||||
if (!sub.CreateWreckAI())
|
||||
{
|
||||
@@ -4039,72 +4045,114 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Submarine, List<Vector2>> wreckPositions = new Dictionary<Submarine, List<Vector2>>();
|
||||
private readonly Dictionary<Submarine, List<Rectangle>> blockedRects = new Dictionary<Submarine, List<Rectangle>>();
|
||||
|
||||
private readonly record struct PlaceableWreck(WreckFile WreckFile, WreckInfo WreckInfo)
|
||||
{
|
||||
public static Option<PlaceableWreck> TryCreate(WreckFile wreckFile)
|
||||
{
|
||||
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(i => i.FilePath == wreckFile.Path.Value);
|
||||
if (matchingSub?.WreckInfo is null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No matching submarine info found for the wreck file {wreckFile.Path.Value}");
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
return Option.Some(new PlaceableWreck(wreckFile, matchingSub.WreckInfo));
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWrecks()
|
||||
{
|
||||
var totalSW = new Stopwatch();
|
||||
totalSW.Start();
|
||||
|
||||
var wreckFiles = ContentPackageManager.EnabledPackages.All
|
||||
var placeableWrecks = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<WreckFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToList();
|
||||
.OrderBy(f => f.UintIdentifier)
|
||||
.Select(PlaceableWreck.TryCreate)
|
||||
.Where(w => w.IsSome())
|
||||
.Select(o => o.TryUnwrap(out var w) ? w : throw new InvalidOperationException())
|
||||
.ToList();
|
||||
|
||||
for (int i = wreckFiles.Count - 1; i >= 0; i--)
|
||||
for (int i = placeableWrecks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wreckFile = wreckFiles[i];
|
||||
var wreckInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsWreck);
|
||||
var matchingInfo = wreckInfos.SingleOrDefault(info => info.FilePath == wreckFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.WreckInfo is WreckInfo wreckInfo)
|
||||
var wreckInfo = placeableWrecks[i].WreckInfo;
|
||||
if (!IsAllowedDifficulty(wreckInfo.MinLevelDifficulty, wreckInfo.MaxLevelDifficulty))
|
||||
{
|
||||
if (Difficulty < wreckInfo.MinLevelDifficulty || Difficulty > wreckInfo.MaxLevelDifficulty)
|
||||
{
|
||||
wreckFiles.RemoveAt(i);
|
||||
}
|
||||
placeableWrecks.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
if (wreckFiles.None())
|
||||
if (placeableWrecks.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"No wreck files found for the level difficulty {LevelData.Difficulty}!");
|
||||
Wrecks = new List<Submarine>();
|
||||
return;
|
||||
}
|
||||
wreckFiles.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
placeableWrecks.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
|
||||
int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, wreckFiles.Count);
|
||||
int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, wreckFiles.Count);
|
||||
int minWreckCount = Math.Min(Loaded.GenerationParams.MinWreckCount, placeableWrecks.Count);
|
||||
int maxWreckCount = Math.Min(Loaded.GenerationParams.MaxWreckCount, placeableWrecks.Count);
|
||||
int wreckCount = Rand.Range(minWreckCount, maxWreckCount + 1, Rand.RandSync.ServerAndClient);
|
||||
bool requireThalamus = false;
|
||||
|
||||
if (GameMain.GameSession?.GameMode?.Missions.Any(m => m.Prefab.RequireWreck) ?? false)
|
||||
{
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode?.Missions.Any(static m => m.Prefab.RequireThalamusWreck) ?? false)
|
||||
{
|
||||
requireThalamus = true;
|
||||
}
|
||||
|
||||
if (LevelData.ForceWreck != null)
|
||||
{
|
||||
//force the desired wreck to be chosen first
|
||||
var matchingFile = wreckFiles.FirstOrDefault(w => w.Path == LevelData.ForceWreck.FilePath);
|
||||
if (matchingFile != null)
|
||||
var matchingFile = placeableWrecks.FirstOrDefault(w => w.WreckFile.Path == LevelData.ForceWreck.FilePath);
|
||||
if (matchingFile.WreckFile != null)
|
||||
{
|
||||
wreckFiles.Remove(matchingFile);
|
||||
wreckFiles.Insert(0, matchingFile);
|
||||
placeableWrecks.Remove(matchingFile);
|
||||
placeableWrecks.Insert(0, matchingFile);
|
||||
}
|
||||
wreckCount = Math.Max(wreckCount, 1);
|
||||
}
|
||||
|
||||
if (requireThalamus)
|
||||
{
|
||||
var thalamusWrecks = placeableWrecks
|
||||
.Where(static w => w.WreckInfo.WreckContainsThalamus == WreckInfo.HasThalamus.Yes)
|
||||
.ToList();
|
||||
|
||||
if (thalamusWrecks.Any())
|
||||
{
|
||||
thalamusWrecks.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
|
||||
foreach (var wreck in thalamusWrecks)
|
||||
{
|
||||
placeableWrecks.Remove(wreck);
|
||||
placeableWrecks.Insert(0, wreck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Wrecks = new List<Submarine>(wreckCount);
|
||||
for (int i = 0; i < wreckCount; i++)
|
||||
{
|
||||
//how many times we'll try placing another sub before giving up
|
||||
const int MaxSubsToTry = 2;
|
||||
int attempts = 0;
|
||||
while (wreckFiles.Any() && attempts < MaxSubsToTry)
|
||||
while (placeableWrecks.Any() && attempts < MaxSubsToTry)
|
||||
{
|
||||
ContentFile contentFile = wreckFiles.First();
|
||||
wreckFiles.RemoveAt(0);
|
||||
if (contentFile == null) { continue; }
|
||||
string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
|
||||
if (SpawnSubOnPath(wreckName, contentFile, SubmarineType.Wreck) != null)
|
||||
var placeableWreck = placeableWrecks.First();
|
||||
var wreckFile = placeableWreck.WreckFile;
|
||||
placeableWrecks.RemoveAt(0);
|
||||
if (wreckFile == null) { continue; }
|
||||
string wreckName = System.IO.Path.GetFileNameWithoutExtension(wreckFile.Path.Value);
|
||||
if (SpawnSubOnPath(wreckName, wreckFile, SubmarineType.Wreck, forceThalamus: requireThalamus) is { } wreck)
|
||||
{
|
||||
if (wreck.WreckAI is not null)
|
||||
{
|
||||
requireThalamus = false;
|
||||
}
|
||||
//placed successfully
|
||||
break;
|
||||
}
|
||||
@@ -4223,7 +4271,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (MapEntity entityToHide in MapEntity.MapEntityList.Where(me => me.Submarine == outpost && (me.Prefab?.HasSubCategory(categoryToHide) ?? false)))
|
||||
{
|
||||
entityToHide.HiddenInGame = true;
|
||||
entityToHide.IsLayerHidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4489,72 +4537,97 @@ namespace Barotrauma
|
||||
else if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedDevices = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation?.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
allowDamagedDevices = info.AllowDamagedDevices;
|
||||
}
|
||||
|
||||
//remove wires
|
||||
float removeWireMinDifficulty = 20.0f;
|
||||
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (removeWireProbability > 0.0f && allowDisconnectedWires)
|
||||
float disconnectWireMinDifficulty = 20.0f;
|
||||
float disconnectWireProbability = MathUtils.InverseLerp(disconnectWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (disconnectWireProbability > 0.0f && allowDisconnectedWires)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
#if SERVER
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DisconnectBeaconStationWires(disconnectWireProbability);
|
||||
}
|
||||
|
||||
if (allowDamagedDevices)
|
||||
{
|
||||
DamageBeaconStationDevices(breakDeviceProbability: 0.5f);
|
||||
}
|
||||
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)))
|
||||
DamageBeaconStationWalls(damageWallProbability: 0.25f);
|
||||
}
|
||||
}
|
||||
SetLinkedSubCrushDepth(BeaconStation);
|
||||
}
|
||||
|
||||
public void DisconnectBeaconStationWires(float disconnectWireProbability)
|
||||
{
|
||||
if (disconnectWireProbability <= 0.0f) { return; }
|
||||
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||
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))
|
||||
{
|
||||
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) < disconnectWireProbability)
|
||||
{
|
||||
foreach (Connection connection in wire.Connections)
|
||||
{
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
if (connection != null)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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));
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
#if SERVER
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetLinkedSubCrushDepth(BeaconStation);
|
||||
}
|
||||
|
||||
public void DamageBeaconStationDevices(float breakDeviceProbability)
|
||||
{
|
||||
if (breakDeviceProbability <= 0.0f) { return; }
|
||||
//break powered items
|
||||
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||
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) < breakDeviceProbability)
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DamageBeaconStationWalls(float damageWallProbability)
|
||||
{
|
||||
if (damageWallProbability <= 0.0f) { return; }
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < damageWallProbability)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckBeaconActive()
|
||||
|
||||
@@ -99,6 +99,11 @@ namespace Barotrauma
|
||||
return Math.Max(Size.Y * Physics.DisplayToRealWorldRatio, Level.DefaultRealWorldCrushDepth);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inclusive (matching the min an max values is accepted).
|
||||
/// </summary>
|
||||
public bool IsAllowedDifficulty(float minDifficulty, float maxDifficulty) => Difficulty >= minDifficulty && Difficulty <= maxDifficulty;
|
||||
|
||||
public LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
|
||||
{
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Header("General")]
|
||||
[Serialize(LevelData.LevelType.LocationConnection, IsPropertySaveable.Yes), Editable]
|
||||
public LevelData.LevelType Type
|
||||
{
|
||||
@@ -88,42 +89,9 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("27,30,36", IsPropertySaveable.Yes), Editable]
|
||||
public Color AmbientLightColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundTextureColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WallColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WaterParticleColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private Vector2 startPosition;
|
||||
|
||||
[Header("Layout")]
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
@@ -169,32 +137,11 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
|
||||
public bool CreateHoleToAbyss
|
||||
[Serialize(0.4f, IsPropertySaveable.Yes, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
|
||||
public float BottomHoleProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, no walls generate in the level. Can be useful for e.g. levels that are just supposed to consist of a pre-built outpost."), Editable]
|
||||
public bool NoLevelGeometry
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(80, IsPropertySaveable.Yes, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int BackgroundCreatureAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return bottomHoleProbability; }
|
||||
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
[Serialize(100000, IsPropertySaveable.Yes), Editable]
|
||||
@@ -232,31 +179,10 @@ namespace Barotrauma
|
||||
set { initialDepthMax = Math.Max(value, initialDepthMin); }
|
||||
}
|
||||
|
||||
[Serialize(6500, IsPropertySaveable.Yes, description: "Minimum width of the main tunnel going through the level, in pixels. Can be automatically increased by the level editor if the submarine is larger than this."), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
public int MinTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Header("Level geometry")]
|
||||
|
||||
|
||||
[Serialize("0,1", IsPropertySaveable.Yes, description: "Amount of side tunnels in the level (min,max)."), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", IsPropertySaveable.Yes, description: "Minimum width of the side tunnels, in pixels. Unlike the main tunnel, does not get adjusted based on the size of the submarine."), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, no walls generate in the level. Can be useful for e.g. levels that are just supposed to consist of a pre-built outpost."), Editable]
|
||||
public bool NoLevelGeometry
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -324,6 +250,37 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Header("Tunnels")]
|
||||
[Serialize(6500, IsPropertySaveable.Yes, description: "Minimum width of the main tunnel going through the level, in pixels. Can be automatically increased by the level editor if the submarine is larger than this."), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
public int MinTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0,1", IsPropertySaveable.Yes, description: "Amount of side tunnels in the level (min,max)."), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", IsPropertySaveable.Yes, description: "Minimum width of the side tunnels, in pixels. Unlike the main tunnel, does not get adjusted based on the size of the submarine."), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
|
||||
Serialize("5000, 10000", IsPropertySaveable.Yes, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
|
||||
public Point MainPathNodeIntervalRange
|
||||
@@ -343,6 +300,22 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Header("Contents")]
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(80, IsPropertySaveable.Yes, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int BackgroundCreatureAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Editable, Serialize(5, IsPropertySaveable.Yes, description: "The number of caves placed along the main path.")]
|
||||
public int CaveCount
|
||||
{
|
||||
@@ -406,6 +379,14 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Header("Abyss")]
|
||||
[Serialize(true, IsPropertySaveable.Yes, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
|
||||
public bool CreateHoleToAbyss
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(5, IsPropertySaveable.Yes, description: "Number of abyss islands in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int AbyssIslandCount
|
||||
{
|
||||
@@ -448,6 +429,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Header("Sea floor")]
|
||||
[Serialize(-300000, IsPropertySaveable.Yes, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
@@ -506,6 +488,7 @@ namespace Barotrauma
|
||||
|
||||
public int GetMaxRuinCount() => UseRandomRuinCount() ? MaxRuinCount : RuinCount;
|
||||
|
||||
[Header("Ruins")]
|
||||
[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; }
|
||||
|
||||
@@ -517,6 +500,7 @@ namespace Barotrauma
|
||||
|
||||
// TODO: Move the wreck parameters under a separate class?
|
||||
#region Wreck parameters
|
||||
[Header("Wrecks")]
|
||||
[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)]
|
||||
public int MinWreckCount { get; set; }
|
||||
|
||||
@@ -545,13 +529,7 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Should a beacon station always spawn in this type of level?")]
|
||||
public string ForceBeaconStation { get; set; }
|
||||
|
||||
[Serialize(0.4f, IsPropertySaveable.Yes, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
|
||||
public float BottomHoleProbability
|
||||
{
|
||||
get { return bottomHoleProbability; }
|
||||
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
[Header("Visuals")]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "Scale of the water particle texture."), Editable]
|
||||
public float WaterParticleScale
|
||||
{
|
||||
@@ -595,6 +573,58 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, IsPropertySaveable.Yes, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeExpandInwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Header("Colors")]
|
||||
[Serialize("27,30,36", IsPropertySaveable.Yes), Editable]
|
||||
public Color AmbientLightColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundTextureColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WallColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WaterParticleColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Header("Sounds")]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the \"ambient noise\" of the biome play in this level if it's an outpost level."), Editable]
|
||||
public bool PlayNoiseLoopInOutpostLevel
|
||||
{
|
||||
@@ -609,19 +639,6 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, IsPropertySaveable.Yes, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeExpandInwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite BackgroundSprite { get; private set; }
|
||||
public Sprite BackgroundTopSprite { get; private set; }
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
if (!structure.HasBody || structure.HiddenInGame) { continue; }
|
||||
if (!structure.HasBody || structure.IsHidden) { continue; }
|
||||
|
||||
LevelObjectPrefab.SpawnPosType spawnPosType = LevelObjectPrefab.SpawnPosType.None;
|
||||
if (level.Ruins.Any(r => r.Submarine == structure.Submarine))
|
||||
|
||||
@@ -188,18 +188,7 @@ namespace Barotrauma
|
||||
|
||||
public static PurchasedItem CreateInitialStockItem(ItemPrefab itemPrefab, PriceInfo priceInfo)
|
||||
{
|
||||
int quantity = PriceInfo.DefaultAmount;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
{
|
||||
quantity =
|
||||
priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount ?
|
||||
Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1) :
|
||||
priceInfo.MaxAvailableAmount;
|
||||
}
|
||||
else if (priceInfo.MinAvailableAmount > 0)
|
||||
{
|
||||
quantity = priceInfo.MinAvailableAmount;
|
||||
}
|
||||
int quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1);
|
||||
return new PurchasedItem(itemPrefab, quantity, buyer: null);
|
||||
}
|
||||
|
||||
@@ -256,7 +245,7 @@ namespace Barotrauma
|
||||
if (stockItem.ItemPrefab.GetPriceInfo(this) is PriceInfo priceInfo)
|
||||
{
|
||||
if (!priceInfo.CanBeSpecial) { continue; }
|
||||
var baseQuantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount : PriceInfo.DefaultAmount;
|
||||
var baseQuantity = priceInfo.MinAvailableAmount;
|
||||
weight += (float)(stockItem.Quantity - baseQuantity) / baseQuantity;
|
||||
if (weight < 0.0f) { continue; }
|
||||
}
|
||||
@@ -906,8 +895,8 @@ namespace Barotrauma
|
||||
}
|
||||
MissionPrefab missionPrefab =
|
||||
random != null ?
|
||||
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, random) :
|
||||
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, Rand.RandSync.Unsynced);
|
||||
ToolBox.SelectWeightedRandom(suitableMissions, m => m.Commonness, random) :
|
||||
ToolBox.SelectWeightedRandom(suitableMissions, m => m.Commonness, Rand.RandSync.Unsynced);
|
||||
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
@@ -1141,6 +1130,12 @@ namespace Barotrauma
|
||||
return HireManager.AvailableCharacters;
|
||||
}
|
||||
|
||||
public void ForceHireableCharacters(IEnumerable<CharacterInfo> hireableCharacters)
|
||||
{
|
||||
HireManager ??= new HireManager();
|
||||
HireManager.AvailableCharacters = hireableCharacters.ToList();
|
||||
}
|
||||
|
||||
private void CreateRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsEmpty)
|
||||
@@ -1402,12 +1397,12 @@ namespace Barotrauma
|
||||
existingStock.Quantity =
|
||||
Math.Min(
|
||||
existingStock.Quantity + 1,
|
||||
priceInfo.MaxAvailableAmount > 0 ? priceInfo.MaxAvailableAmount : CargoManager.MaxQuantity);
|
||||
priceInfo.MaxAvailableAmount);
|
||||
}
|
||||
}
|
||||
else if (existingStock != null)
|
||||
{
|
||||
stockToRemove.Add(existingStock);
|
||||
stockToRemove.Add(existingStock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
private readonly ImmutableArray<Sprite> portraits;
|
||||
|
||||
//<name, commonness>
|
||||
private readonly ImmutableArray<(Identifier Name, float Commonness)> hireableJobs;
|
||||
private readonly ImmutableArray<(Identifier Identifier, float Commonness, bool AlwaysAvailableIfMissingFromCrew)> hireableJobs;
|
||||
private readonly float totalHireableWeight;
|
||||
|
||||
public readonly Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma
|
||||
MinCountPerZone[zoneIndex] = minCount;
|
||||
}
|
||||
var portraits = new List<Sprite>();
|
||||
var hireableJobs = new List<(Identifier, float)>();
|
||||
var hireableJobs = new List<(Identifier, float, bool)>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -234,8 +234,9 @@ namespace Barotrauma
|
||||
case "hireable":
|
||||
Identifier jobIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
|
||||
bool availableIfMissing = subElement.GetAttributeBool("AlwaysAvailableIfMissingFromCrew", false);
|
||||
totalHireableWeight += jobCommonness;
|
||||
hireableJobs.Add((jobIdentifier, jobCommonness));
|
||||
hireableJobs.Add((jobIdentifier, jobCommonness, availableIfMissing));
|
||||
break;
|
||||
case "symbol":
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
@@ -270,16 +271,33 @@ namespace Barotrauma
|
||||
this.hireableJobs = hireableJobs.ToImmutableArray();
|
||||
}
|
||||
|
||||
public IEnumerable<JobPrefab> GetHireablesMissingFromCrew()
|
||||
{
|
||||
if (GameMain.GameSession?.CrewManager != null)
|
||||
{
|
||||
var missingJobs = hireableJobs
|
||||
.Where(j => j.AlwaysAvailableIfMissingFromCrew)
|
||||
.Where(j => GameMain.GameSession.CrewManager.GetCharacterInfos().None(c => c.Job?.Prefab.Identifier == j.Identifier));
|
||||
if (missingJobs.Any())
|
||||
{
|
||||
foreach (var missingJob in missingJobs)
|
||||
{
|
||||
if (JobPrefab.Prefabs.TryGet(missingJob.Identifier, out JobPrefab job))
|
||||
{
|
||||
yield return job;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JobPrefab GetRandomHireable()
|
||||
{
|
||||
float randFloat = Rand.Range(0.0f, totalHireableWeight, Rand.RandSync.ServerAndClient);
|
||||
|
||||
foreach ((Identifier jobIdentifier, float commonness) in hireableJobs)
|
||||
Identifier selectedJobId = hireableJobs.GetRandomByWeight(j => j.Commonness, Rand.RandSync.ServerAndClient).Identifier;
|
||||
if (JobPrefab.Prefabs.TryGet(selectedJobId, out JobPrefab job))
|
||||
{
|
||||
if (randFloat < commonness) { return JobPrefab.Prefabs[jobIdentifier]; }
|
||||
randFloat -= commonness;
|
||||
return job;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -261,8 +261,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var endLocation in EndLocations)
|
||||
{
|
||||
if (endLocation.Type?.ForceLocationName != null &&
|
||||
!endLocation.Type.ForceLocationName.IsEmpty)
|
||||
if (endLocation.Type?.ForceLocationName is { IsEmpty: false })
|
||||
{
|
||||
endLocation.ForceName(endLocation.Type.ForceLocationName);
|
||||
}
|
||||
|
||||
@@ -200,6 +200,16 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the layer this entity is in currently hidden? If it is, the entity is not updated and should do nothing.
|
||||
/// </summary>
|
||||
public bool IsLayerHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the entity hidden due to <see cref="HiddenInGame"/> being enabled or the layer the entity is in being hidden?
|
||||
/// </summary>
|
||||
public bool IsHidden => HiddenInGame || IsLayerHidden;
|
||||
|
||||
public override Vector2 Position
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -52,6 +54,9 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedWalls { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedDevices { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDisconnectedWires { get; set; }
|
||||
|
||||
@@ -73,16 +78,52 @@ namespace Barotrauma
|
||||
|
||||
class WreckInfo : ExtraSubmarineInfo
|
||||
{
|
||||
// Unknown -> older submarines before this property was added
|
||||
public enum HasThalamus { Unknown, Yes, No }
|
||||
|
||||
[Serialize(HasThalamus.Unknown, IsPropertySaveable.Yes)]
|
||||
public HasThalamus WreckContainsThalamus { get; private set; }
|
||||
|
||||
public WreckInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
|
||||
{
|
||||
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
|
||||
TryDetermineThalamusIfUnknown(element);
|
||||
}
|
||||
|
||||
public WreckInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
|
||||
{
|
||||
Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})";
|
||||
TryDetermineThalamusIfUnknown(submarineInfo.SubmarineElement);
|
||||
}
|
||||
|
||||
public WreckInfo(WreckInfo original) : base(original) { }
|
||||
|
||||
// Attempts to determine if the wreck contains a thalamus item
|
||||
private void TryDetermineThalamusIfUnknown(XElement element)
|
||||
{
|
||||
if (WreckContainsThalamus != HasThalamus.Unknown) { return; }
|
||||
|
||||
if (element == null)
|
||||
{
|
||||
// nothing we can do, oh well
|
||||
WreckContainsThalamus = HasThalamus.Unknown;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!string.Equals(subElement.Name.ToString(), nameof(Item), StringComparison.InvariantCultureIgnoreCase)) { continue; }
|
||||
|
||||
var tags = subElement.GetAttributeIdentifierImmutableHashSet(nameof(ItemPrefab.Tags), ImmutableHashSet<Identifier>.Empty);
|
||||
|
||||
if (tags.Contains(Tags.Thalamus))
|
||||
{
|
||||
WreckContainsThalamus = HasThalamus.Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
WreckContainsThalamus = HasThalamus.No;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -8,9 +9,15 @@ namespace Barotrauma
|
||||
{
|
||||
public int Price { get; }
|
||||
public bool CanBeBought { get; }
|
||||
//minimum number of items available at a given store
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number of items available at a given store
|
||||
/// </summary>
|
||||
public int MinAvailableAmount { get; }
|
||||
//maximum number of items available at a given store
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of items available at a given store. Defaults to 20% more than the minimum amount.
|
||||
/// </summary>
|
||||
public int MaxAvailableAmount { get; }
|
||||
/// <summary>
|
||||
/// Can the item be a Daily Special or a Requested Good
|
||||
@@ -30,9 +37,14 @@ namespace Barotrauma
|
||||
public bool RequiresUnlock { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when both <see cref="MinAvailableAmount"/> and <see cref="MaxAvailableAmount"/> are set to 0.
|
||||
/// Used when neither <see cref="MinAvailableAmount"/> or <see cref="MaxAvailableAmount"/> are defined.
|
||||
/// </summary>
|
||||
public const int DefaultAmount = 5;
|
||||
private const int DefaultAmount = 5;
|
||||
|
||||
/// <summary>
|
||||
/// How much more the maximum stock is relative to the minimum stock if not defined. Stores will gradually stock up towards the maximum.
|
||||
/// </summary>
|
||||
private const float DefaultMaxAvailabilityRelativeToMin = 1.2f;
|
||||
|
||||
private readonly Dictionary<Identifier, float> minReputation = new Dictionary<Identifier, float>();
|
||||
|
||||
@@ -52,12 +64,11 @@ namespace Barotrauma
|
||||
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
CanBeBought = true;
|
||||
int minAmount = GetMinAmount(element);
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
MinAvailableAmount = Math.Min(GetMinAmount(element, defaultValue: DefaultAmount), CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = MathHelper.Clamp(GetMaxAmount(element, defaultValue: (int)(MinAvailableAmount * DefaultMaxAvailabilityRelativeToMin)), MinAvailableAmount, CargoManager.MaxQuantity);
|
||||
RequiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
|
||||
System.Diagnostics.Debug.Assert(MaxAvailableAmount >= MinAvailableAmount);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought,
|
||||
@@ -67,14 +78,15 @@ namespace Barotrauma
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(Math.Min(maxAmount, CargoManager.MaxQuantity), minAmount);
|
||||
BuyingPriceMultiplier = buyingPriceMultiplier;
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
|
||||
MinLevelDifficulty = minLevelDifficulty;
|
||||
CanBeSpecial = canBeSpecial;
|
||||
DisplayNonEmpty = displayNonEmpty;
|
||||
StoreIdentifier = new Identifier(storeIdentifier);
|
||||
RequiresUnlock = requiresUnlock;
|
||||
|
||||
System.Diagnostics.Debug.Assert(MaxAvailableAmount >= MinAvailableAmount);
|
||||
}
|
||||
|
||||
private void LoadReputationRestrictions(XElement priceInfoElement)
|
||||
@@ -95,8 +107,8 @@ namespace Barotrauma
|
||||
var priceInfos = new List<PriceInfo>();
|
||||
defaultPrice = null;
|
||||
int basePrice = element.GetAttributeInt("baseprice", 0);
|
||||
int minAmount = GetMinAmount(element);
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
int minAmount = GetMinAmount(element, defaultValue: DefaultAmount);
|
||||
int maxAmount = GetMaxAmount(element, defaultValue: (int)(DefaultAmount * DefaultMaxAvailabilityRelativeToMin));
|
||||
int minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
bool canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
@@ -143,11 +155,11 @@ namespace Barotrauma
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
private static int GetMinAmount(XElement element, int defaultValue = 0) => element != null ?
|
||||
private static int GetMinAmount(XElement element, int defaultValue) => element != null ?
|
||||
element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) :
|
||||
defaultValue;
|
||||
|
||||
private static int GetMaxAmount(XElement element, int defaultValue = 0) => element != null ?
|
||||
private static int GetMaxAmount(XElement element, int defaultValue) => element != null ?
|
||||
element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) :
|
||||
defaultValue;
|
||||
}
|
||||
|
||||
@@ -928,7 +928,7 @@ namespace Barotrauma
|
||||
public bool SectionIsLeakingFromOutside(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
|
||||
return SectionIsLeaking(sectionIndex) && !Sections[sectionIndex].gap.IsRoomToRoom;
|
||||
return SectionIsLeaking(sectionIndex) && Sections[sectionIndex].gap is { IsRoomToRoom: false };
|
||||
}
|
||||
|
||||
public int SectionLength(int sectionIndex)
|
||||
@@ -971,7 +971,7 @@ namespace Barotrauma
|
||||
float prevDamage = section.damage;
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
SetDamage(sectionIndex, section.damage + damage, attacker);
|
||||
SetDamage(sectionIndex, section.damage + damage, attacker, createWallDamageProjectiles: createWallDamageProjectiles);
|
||||
}
|
||||
#if CLIENT
|
||||
if (damage > 0 && emitParticles)
|
||||
@@ -1003,10 +1003,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
SetDamage(sectionIndex, section.damage + damage, attacker, createWallDamageProjectiles: createWallDamageProjectiles);
|
||||
}
|
||||
}
|
||||
|
||||
public int FindSectionIndex(Vector2 displayPos, bool world = false, bool clamp = false)
|
||||
|
||||
@@ -7,6 +7,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -675,7 +676,7 @@ namespace Barotrauma
|
||||
if (item.GetComponent<Turret>() != null) { return false; }
|
||||
if (item.body != null && !item.body.Enabled) { return true; }
|
||||
}
|
||||
if (e.HiddenInGame) { return true; }
|
||||
if (e.IsHidden) { return true; }
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -1101,6 +1102,11 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
item.FlipX(true);
|
||||
|
||||
if (!item.Prefab.CanFlipX && item.Prefab.AllowRotatingInEditor)
|
||||
{
|
||||
item.Rotation = -item.Rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,6 +1125,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LayerExistsInAnySub(Identifier layer)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
{
|
||||
if (me.Layer == layer) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool LayerExists(Identifier layer)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
@@ -1130,20 +1145,45 @@ namespace Barotrauma
|
||||
|
||||
public void SetLayerEnabled(Identifier layer, bool enabled, bool sendNetworkEvent = false)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.MapEntityList)
|
||||
foreach (MapEntity entity in MapEntity.MapEntityList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != this || me.Layer != layer) { continue; }
|
||||
me.HiddenInGame = !enabled;
|
||||
#if CLIENT
|
||||
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
|
||||
if (me.HiddenInGame && me is Item item)
|
||||
if (string.IsNullOrEmpty(entity.Layer) || entity.Submarine != this || entity.Layer != layer) { continue; }
|
||||
entity.IsLayerHidden = !enabled;
|
||||
|
||||
if (entity is WayPoint wp)
|
||||
{
|
||||
foreach (var lightComponent in item.GetComponents<LightComponent>())
|
||||
if (enabled)
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
wp.SpawnType = wp.SpawnType.RemoveFlag(SpawnType.Disabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
wp.SpawnType = wp.SpawnType.AddFlag(SpawnType.Disabled);
|
||||
}
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
foreach (var connectionPanel in item.GetComponents<ConnectionPanel>())
|
||||
{
|
||||
foreach (var connection in connectionPanel.Connections)
|
||||
{
|
||||
foreach (var wire in connection.Wires)
|
||||
{
|
||||
wire.Item.IsLayerHidden = entity.IsLayerHidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (entity.IsLayerHidden)
|
||||
{
|
||||
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
|
||||
foreach (var lightComponent in item.GetComponents<LightComponent>())
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (sendNetworkEvent)
|
||||
@@ -1413,7 +1453,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine)) { continue; }
|
||||
if (!item.HasTag(Tags.CargoContainer)) { continue; }
|
||||
if (item.NonInteractable || item.HiddenInGame) { continue; }
|
||||
if (item.HasTag(Tags.DisallowCargo)) { continue; }
|
||||
if (item.NonInteractable || item.IsHidden) { continue; }
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
int emptySlots = 0;
|
||||
@@ -1703,9 +1744,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Identifier layer in Info.LayersHiddenByDefault)
|
||||
if (Screen.Selected is { IsEditor : false })
|
||||
{
|
||||
SetLayerEnabled(layer, enabled: false);
|
||||
foreach (Identifier layer in Info.LayersHiddenByDefault)
|
||||
{
|
||||
SetLayerEnabled(layer, enabled: false);
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.GameSession?.Campaign?.UpgradeManager?.OnUpgradesChanged.Register(upgradeEventIdentifier, _ => ResetCrushDepth());
|
||||
@@ -1839,10 +1883,37 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("layerhiddenbydefault", string.Join(", ", Info.LayersHiddenByDefault)));
|
||||
}
|
||||
|
||||
if (Info.WreckInfo != null)
|
||||
{
|
||||
bool hasThalamus = false;
|
||||
|
||||
var wreckAiEntities = WreckAIConfig.Prefabs.Select(p => p.Entity).ToImmutableHashSet();
|
||||
var prefabsOnSub = GetItems(true).Select(i => i.Prefab).Distinct().ToImmutableHashSet();
|
||||
|
||||
foreach (ItemPrefab prefab in prefabsOnSub)
|
||||
{
|
||||
foreach (Identifier entity in wreckAiEntities)
|
||||
{
|
||||
if (WreckAI.IsThalamus(prefab, entity))
|
||||
{
|
||||
hasThalamus = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasThalamus) { break; }
|
||||
}
|
||||
|
||||
element.Add(new XAttribute(nameof(WreckInfo.WreckContainsThalamus), hasThalamus ? WreckInfo.HasThalamus.Yes : WreckInfo.HasThalamus.No));
|
||||
}
|
||||
|
||||
if (Info.Type == SubmarineType.OutpostModule)
|
||||
{
|
||||
Info.OutpostModuleInfo?.Save(element);
|
||||
}
|
||||
if (Info.GetExtraSubmarineInfo is { } extraSubInfo)
|
||||
{
|
||||
extraSubInfo.Save(element);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
@@ -2186,7 +2257,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (potentialContainer.Removed) { continue; }
|
||||
if (potentialContainer.NonInteractable) { continue; }
|
||||
if (potentialContainer.HiddenInGame) { continue; }
|
||||
if (potentialContainer.IsHidden) { continue; }
|
||||
if (allowConnectedSubs)
|
||||
{
|
||||
if (!connectedSubs.Contains(potentialContainer.Submarine)) { continue; }
|
||||
|
||||
@@ -600,6 +600,9 @@ namespace Barotrauma
|
||||
const float MaxWallDamage = 500.0f;
|
||||
const float MinCameraShake = 5f;
|
||||
const float MaxCameraShake = 50.0f;
|
||||
//delay at the start of the round during which you take no depth damage
|
||||
//(gives you a bit of time to react and return if you start the round in a level that's too deep)
|
||||
const float MinRoundDuration = 60.0f;
|
||||
|
||||
if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth + CosmeticEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth + CosmeticEffectThreshold)
|
||||
{
|
||||
@@ -616,7 +619,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
if (depthDamageTimer <= 0.0f)
|
||||
if (depthDamageTimer <= 0.0f && (GameMain.GameSession == null || GameMain.GameSession.RoundDuration > MinRoundDuration))
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using Barotrauma.Extensions;
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32 };
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32, Disabled = 64 };
|
||||
|
||||
partial class WayPoint : MapEntity
|
||||
{
|
||||
@@ -932,6 +932,9 @@ namespace Barotrauma
|
||||
{
|
||||
return WayPointList.GetRandom(wp =>
|
||||
(ignoreSubmarine || wp.Submarine == sub) &&
|
||||
//checking for the disabled flag is not strictly necessary because we check for equality of the spawn type,
|
||||
//but lets do that anyway in case we change the handling of the spawn type at some point
|
||||
!wp.spawnType.HasFlag(SpawnType.Disabled) &&
|
||||
wp.spawnType == spawnType &&
|
||||
(spawnPointTag.IsNullOrEmpty() || wp.Tags.Any(t => t == spawnPointTag)) &&
|
||||
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
|
||||
|
||||
Reference in New Issue
Block a user