Release 1.10.5.0 - Autumn Update 2025

This commit is contained in:
Regalis11
2025-09-17 13:44:21 +03:00
parent d13836ce87
commit caa0326cf8
120 changed files with 2584 additions and 635 deletions
@@ -396,7 +396,10 @@ namespace Barotrauma
}
}
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && Attack.Afflictions.None())
if (Attack.Afflictions.None() &&
MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) &&
MathUtils.NearlyEqual(Attack.ItemDamage, 0.0f) &&
MathUtils.NearlyEqual(Attack.StructureDamage, 0.0f))
{
return;
}
@@ -370,18 +370,31 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
Hull hull1 = linkedTo.Count < 1 ? null : linkedTo[0] as Hull;
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
int updateInterval = 4;
float flowMagnitude = flowForce.LengthSquared();
if (flowMagnitude < 1.0f)
//if one hull is at lethal pressure (connected to outside), and the other not yet,
//we need frequent updates to quickly move water into the other hull
if (hull1 != null && hull2 != null &&
hull1.LethalPressure > 0.0f != hull2.LethalPressure > 0.0f)
{
//very sparse updates if there's practically no water moving
updateInterval = 8;
}
else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
{
//frequent updates if water is moving between hulls
updateInterval = 1;
}
else
{
float flowMagnitude = flowForce.LengthSquared();
if (flowMagnitude < 1.0f)
{
//very sparse updates if there's practically no water moving
updateInterval = 8;
}
else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
{
//frequent updates if water is moving between hulls
updateInterval = 1;
}
}
updateCount++;
if (updateCount < updateInterval) { return; }
@@ -409,8 +422,6 @@ namespace Barotrauma
return;
}
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
if (hull1 == hull2) { return; }
UpdateOxygen(hull1, hull2, deltaTime);
@@ -469,6 +480,8 @@ namespace Barotrauma
higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
float delta = 0.0f;
Hull flowSourceHull = null;
//water level is above the lower boundary of the gap
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size)
{
@@ -479,10 +492,9 @@ namespace Barotrauma
{
if (!(hull2.WaterVolume > 0.0f)) { return; }
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
flowTargetHull = hull1;
flowSourceHull = hull2;
//make sure not to move more than what the room contains
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume));
@@ -504,6 +516,7 @@ namespace Barotrauma
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
flowTargetHull = hull2;
flowSourceHull = hull1;
//make sure not to move more than what the room contains
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume));
@@ -547,7 +560,6 @@ namespace Barotrauma
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
{
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
//make sure not to place more water to the target room than it can hold
if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
{
@@ -623,19 +635,30 @@ namespace Barotrauma
}
}
void UpdateRoomToOut(float deltaTime, Hull hull1)
/// <summary>
/// How much water can flow through the gap to the hull if the gap is connected outside.
/// </summary>
private float GetWaterFlowFromOutside(Hull hull, float deltaTime, bool ignoreCurrentWater = false)
{
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = Size * open * open * (1.0f - overlappingGapFlowRateReduction);
float delta = 500.0f * sizeModifier * deltaTime;
if (!ignoreCurrentWater)
{
delta = Math.Min(delta, hull.Volume * Hull.MaxCompress - hull.WaterVolume);
}
return delta;
}
void UpdateRoomToOut(float deltaTime, Hull hull1)
{
float delta = GetWaterFlowFromOutside(hull1, deltaTime);
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
hull1.WaterVolume += delta;
if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; }
if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 100.0f * deltaTime; }
flowTargetHull = hull1;
@@ -698,6 +721,65 @@ namespace Barotrauma
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
}
}
if (hull1.LethalPressure > 0)
{
SimulateWaterFlowFromOutsideToConnectedHulls(hull1, maxFlow: GetWaterFlowFromOutside(hull1, deltaTime, ignoreCurrentWater: true), deltaTime: deltaTime);
}
}
private Hull GetOtherLinkedHull(Hull hull1)
{
if (linkedTo.Count != 2 || hull1 == null) { return null; }
return (linkedTo[0] == hull1 ? linkedTo[1] : linkedTo[0]) as Hull;
}
private static readonly HashSet<Hull> checkedHulls = new HashSet<Hull>();
/// <summary>
/// Simulates water flow from the source to all the hulls it's connected to across the sub, as if the water was coming directly from outside.
/// Used to prevent gaps from slowing down flooding when hulls are directly connected outside and highly pressurized.
/// </summary>
void SimulateWaterFlowFromOutsideToConnectedHulls(Hull hull, float maxFlow, float deltaTime)
{
checkedHulls.Clear();
checkedHulls.Add(hull);
foreach (var connectedGap in hull.ConnectedGaps)
{
if (connectedGap == this || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; }
var otherHull = connectedGap.GetOtherLinkedHull(hull);
if (otherHull == null) { continue; }
SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, hull, maxFlow, deltaTime);
}
}
static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, HashSet<Hull> checkedHulls, Hull originHull, float maxFlow, float deltaTime)
{
const float decay = 0.95f;
maxFlow = Math.Min(maxFlow, gap.GetWaterFlowFromOutside(targetHull, deltaTime, ignoreCurrentWater: true)) * decay;
if (maxFlow <= 0.001f) { return; }
checkedHulls.Add(targetHull);
//don't multiply by deltatime here, we already did that in GetWaterFlowFromOutside
targetHull.WaterVolume += maxFlow;
//lerp lethal pressure up very fast
if (targetHull.WaterVolume > targetHull.Volume)
{
targetHull.LethalPressure = Math.Max(targetHull.LethalPressure, MathHelper.Lerp(targetHull.LethalPressure, originHull.LethalPressure, 0.1f));
}
//stop pushing water to the following hulls once we get to a hull that's not at high pressure yet
if (targetHull.LethalPressure <= 0 || targetHull.WaterVolume < targetHull.Volume) { return; }
foreach (var connectedGap in targetHull.ConnectedGaps)
{
if (connectedGap == gap || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; }
var otherHull = connectedGap.GetOtherLinkedHull(targetHull);
if (otherHull == null || checkedHulls.Contains(otherHull)) { continue; }
SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, originHull, maxFlow, deltaTime);
}
}
public bool RefreshOutsideCollider()
@@ -884,6 +966,8 @@ namespace Barotrauma
base.Remove();
GapList.Remove(this);
checkedHulls.Clear();
foreach (Hull hull in Hull.HullList)
{
hull.ConnectedGaps.Remove(this);
@@ -785,7 +785,7 @@ namespace Barotrauma
#region Shared network write
private void SharedStatusWrite(IWriteMessage msg)
{
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
msg.WriteSingle(waterVolume);
System.Diagnostics.Debug.Assert(FireSources.Count <= MaxFireSources, $"Too many fire sources ({FireSources.Count}) in hull {ID} (max {MaxFireSources}).");
msg.WriteRangedInteger(Math.Min(FireSources.Count, MaxFireSources), 0, MaxFireSources);
@@ -833,7 +833,7 @@ namespace Barotrauma
private void SharedStatusRead(IReadMessage msg, out float newWaterVolume, out NetworkFireSource[] newFireSources)
{
newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
newWaterVolume = msg.ReadSingle();
int fireSourceCount = msg.ReadRangedInteger(0, MaxFireSources);
newFireSources = new NetworkFireSource[fireSourceCount];
@@ -1269,6 +1269,23 @@ namespace Barotrauma
return null;
}
/// <summary>
/// Recursively find all the hulls linked to the specified hull.
/// </summary>
public void GetLinkedHulls(List<Hull> linkedHulls, bool includeHiddenHulls = false)
{
foreach (var linkedEntity in linkedTo)
{
if (linkedEntity is Hull linkedHull)
{
if (linkedHulls.Contains(linkedHull)) { continue; }
if (!includeHiddenHulls && linkedHull.IsHidden) { continue; }
linkedHulls.Add(linkedHull);
linkedHull.GetLinkedHulls(linkedHulls, includeHiddenHulls);
}
}
}
public static void DetectItemVisibility(Character c=null)
{
if (c==null)
@@ -4291,6 +4291,11 @@ namespace Barotrauma
{
placeableWrecks.RemoveAt(i);
}
// Exclude wrecks that have mission tags, those can't show up randomly
else if (wreckInfo.MissionTags.Count != 0)
{
placeableWrecks.RemoveAt(i);
}
}
if (placeableWrecks.None())
{
@@ -4742,6 +4747,11 @@ namespace Barotrauma
{
beaconStationFiles.RemoveAt(i);
}
// Exclude beacons that have mission tags, those can't show up randomly
else if (beaconInfo.MissionTags.Count != 0)
{
beaconStationFiles.RemoveAt(i);
}
}
}
if (beaconStationFiles.None())
@@ -4926,17 +4936,17 @@ namespace Barotrauma
{
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
var humanSpawnPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Human);
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
if (!corpsePoints.Any() && !pathPoints.Any()) { continue; }
pathPoints.Shuffle(Rand.RandSync.ServerAndClient);
if (corpsePoints.None() && humanSpawnPoints.None()) { continue; }
humanSpawnPoints.Shuffle(Rand.RandSync.ServerAndClient);
// Sort by job so that we first spawn those with a predefined job (might have special id cards)
corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList();
var usedJobs = new HashSet<JobPrefab>();
int spawnCounter = 0;
for (int j = 0; j < corpseCount; j++)
{
WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault();
WayPoint sp = corpsePoints.FirstOrDefault() ?? humanSpawnPoints.FirstOrDefault();
JobPrefab job = sp?.AssignedJob;
CorpsePrefab selectedPrefab;
if (job == null)
@@ -4949,8 +4959,8 @@ namespace Barotrauma
if (selectedPrefab == null)
{
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
humanSpawnPoints.Remove(sp);
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? humanSpawnPoints.FirstOrDefault(sp => sp.AssignedJob == null);
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(usedJobs);
if (selectedPrefab != null)
@@ -4972,7 +4982,7 @@ namespace Barotrauma
{
worldPos = sp.WorldPosition;
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
humanSpawnPoints.Remove(sp);
}
job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p));
@@ -473,7 +473,7 @@ namespace Barotrauma
{
get
{
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure);
return availableMissions;
}
}
@@ -1091,6 +1091,12 @@ namespace Barotrauma
mission.TimesAttempted = loadedMission.TimesAttempted;
availableMissions.Add(mission);
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
var levelData = destination == this ? LevelData : Connections.FirstOrDefault(c => c.OtherLocation(this) == destination)?.LevelData;
if (levelData != null)
{
mission.AdjustLevelData(levelData);
}
}
loadedMissions = null;
}
@@ -39,7 +39,7 @@ namespace Barotrauma
{
get
{
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure);
return availableMissions;
}
}
@@ -235,15 +235,17 @@ namespace Barotrauma
//backwards compatibility (or support for loading maps created with mods that modify the end biome setup):
//if there's too few end locations, create more
int missingOutpostCount = endLocations.First().Biome.EndBiomeLocationCount - endLocations.Count;
Location firstEndLocation = EndLocations[0];
Biome endBiome = firstEndLocation.Biome;
int missingOutpostCount = endBiome.EndBiomeLocationCount - endLocations.Count;
for (int i = 0; i < missingOutpostCount; i++)
{
Vector2 mapPos = new Vector2(
MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)),
Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble()));
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, firstEndLocation.Biome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations);
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, endBiome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations);
newEndLocation.Biome = endBiome;
newEndLocation.LevelData = new LevelData(newEndLocation, this, difficulty: 100.0f);
Locations.Add(newEndLocation);
endLocations.Add(newEndLocation);
@@ -11,6 +11,8 @@ namespace Barotrauma
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
public HashSet<Identifier> MissionTags { get; } = [];
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
public float MinLevelDifficulty { get; set; }
@@ -21,6 +23,10 @@ namespace Barotrauma
{
Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
foreach (var missionTag in element.GetAttributeIdentifierArray(nameof(MissionTags), []))
{
MissionTags.Add(missionTag);
}
}
public ExtraSubmarineInfo(SubmarineInfo submarineInfo)
@@ -41,11 +47,18 @@ namespace Barotrauma
kvp.Value.TrySetValue(this, kvp.Value.GetValue(original));
}
}
foreach (var missionTag in original.MissionTags)
{
MissionTags.Add(missionTag);
}
}
public virtual void Save(XElement element)
{
SerializableProperty.SerializeProperties(this, element);
// MissionTags is not automatically serialized because HashSet<Identifier> is not a supported type
// We need to manually serialize it as a comma-separated string
element.SetAttributeValue(nameof(MissionTags), string.Join(',', MissionTags));
}
}
@@ -135,18 +148,10 @@ namespace Barotrauma
[Serialize(50.0f, IsPropertySaveable.Yes), Editable]
public float PreferredDifficulty { get; set; }
private readonly HashSet<Identifier> missionTags = new HashSet<Identifier>();
public HashSet<Identifier> MissionTags => missionTags;
public EnemySubmarineInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element)
{
Name = $"{nameof(EnemySubmarineInfo)} ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
foreach (var missionTag in element.GetAttributeIdentifierArray(nameof(MissionTags), Array.Empty<Identifier>()))
{
missionTags.Add(missionTag);
}
}
public EnemySubmarineInfo(SubmarineInfo submarineInfo) : base(submarineInfo)
@@ -156,16 +161,8 @@ namespace Barotrauma
public EnemySubmarineInfo(EnemySubmarineInfo original) : base(original)
{
foreach (var missionTag in original.missionTags)
{
missionTags.Add(missionTag);
}
}
public override void Save(XElement element)
{
base.Save(element);
element.Add(new XAttribute(nameof(MissionTags), string.Join(',', missionTags)));
}
}
}
@@ -159,11 +159,11 @@ namespace Barotrauma
if (GameMain.NetworkMember?.ServerSettings is { } serverSettings &&
serverSettings.SelectedOutpostName != "Random")
{
var matchingOutpost = outpostInfos.FirstOrDefault(o => o.Name == serverSettings.SelectedOutpostName);
//...but only if the outpost is suitable for the mission (or if the mission has no specific requirements for the outpost)
if (outpostInfosSuitableForMission.None() ||
outpostInfosSuitableForMission.Any(outpostInfo => outpostInfo.OutpostTags.Contains(serverSettings.SelectedOutpostName)))
if (outpostInfosSuitableForMission.Contains(matchingOutpost) ||
outpostInfosSuitableForMission.None())
{
var matchingOutpost = outpostInfos.FirstOrDefault(o => o.Name == serverSettings.SelectedOutpostName);
if (matchingOutpost != null)
{
return matchingOutpost;
@@ -37,14 +37,14 @@ namespace Barotrauma
public bool RequiresUnlock { get; }
/// <summary>
/// Used when neither <see cref="MinAvailableAmount"/> or <see cref="MaxAvailableAmount"/> are defined.
/// Default minimum amount when no MinAvailableAmount is defined.
/// </summary>
private const int DefaultAmount = 5;
private const int DefaultMinAmount = 1;
/// <summary>
/// How much more the maximum stock is relative to the minimum stock if not defined. Stores will gradually stock up towards the maximum.
/// Default maximum amount when no MaxAvailableAmount is defined.
/// </summary>
private const float DefaultMaxAvailabilityRelativeToMin = 1.2f;
private const int DefaultMaxAmount = 5;
/// <summary>
/// If set, the item is only available in outposts with this faction.
@@ -66,11 +66,12 @@ namespace Barotrauma
public PriceInfo(XElement element)
{
Price = element.GetAttributeInt("buyprice", 0);
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
MinLevelDifficulty = GetMinLevelDifficulty(element, 0);
BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
CanBeBought = true;
MinAvailableAmount = Math.Min(GetMinAmount(element, defaultValue: DefaultAmount), CargoManager.MaxQuantity);
MaxAvailableAmount = MathHelper.Clamp(GetMaxAmount(element, defaultValue: (int)(MinAvailableAmount * DefaultMaxAvailabilityRelativeToMin)), MinAvailableAmount, CargoManager.MaxQuantity);
MinAvailableAmount = Math.Min(GetMinAmount(element, defaultValue: DefaultMinAmount), CargoManager.MaxQuantity);
int maxAmount = GetMaxAmount(element, defaultValue: DefaultMaxAmount);
MaxAvailableAmount = MathHelper.Clamp(maxAmount, MinAvailableAmount, CargoManager.MaxQuantity);
RequiresUnlock = element.GetAttributeBool("requiresunlock", false);
RequiredFaction = element.GetAttributeIdentifier(nameof(RequiredFaction), Identifier.Empty);
System.Diagnostics.Debug.Assert(MaxAvailableAmount >= MinAvailableAmount);
@@ -112,27 +113,27 @@ namespace Barotrauma
var priceInfos = new List<PriceInfo>();
defaultPrice = null;
int basePrice = element.GetAttributeInt("baseprice", 0);
int minAmount = GetMinAmount(element, defaultValue: DefaultAmount);
int maxAmount = GetMaxAmount(element, defaultValue: (int)(DefaultAmount * DefaultMaxAvailabilityRelativeToMin));
int minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
int minAmount = GetMinAmount(element, defaultValue: DefaultMinAmount);
int maxAmount = GetMaxAmount(element, defaultValue: DefaultMaxAmount);
int minLevelDifficulty = GetMinLevelDifficulty(element, 0);
bool canBeSpecial = element.GetAttributeBool("canbespecial", true);
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
bool soldByDefault = GetSold(element, element.GetAttributeBool("soldbydefault", true));
bool requiresUnlock = element.GetAttributeBool("requiresunlock", false);
Identifier requiredFactionByDefault = element.GetAttributeIdentifier(nameof(RequiredFaction), Identifier.Empty);
foreach (XElement childElement in element.GetChildElements("price"))
{
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
bool sold = childElement.GetAttributeBool("sold", soldByDefault);
int storeMinLevelDifficulty = childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty);
bool sold = GetSold(childElement, soldByDefault);
int storeMinLevelDifficulty = GetMinLevelDifficulty(childElement, minLevelDifficulty);
float storeBuyingMultiplier = childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier);
string backwardsCompatibleIdentifier = childElement.GetAttributeString("locationtype", "");
if (!string.IsNullOrEmpty(backwardsCompatibleIdentifier))
{
backwardsCompatibleIdentifier = $"merchant{backwardsCompatibleIdentifier}";
}
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
string storeIdentifier = GetStoreIdentifier(childElement, backwardsCompatibleIdentifier);
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice),
canBeBought: sold,
@@ -167,12 +168,40 @@ namespace Barotrauma
return priceInfos;
}
private static int GetMinAmount(XElement element, int defaultValue) => element != null ?
element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) :
defaultValue;
private static int GetMinAmount(XElement element, int defaultValue) =>
element?.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) ?? defaultValue;
private static int GetMaxAmount(XElement element, int defaultValue) => element != null ?
element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) :
defaultValue;
private static int GetMaxAmount(XElement element, int defaultValue) =>
element?.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) ?? defaultValue;
public static bool HasMinAmountDefined(XElement element) => element != null &&
(element.GetAttribute("minamount") != null || element.GetAttribute("minavailable") != null);
public static bool HasMaxAmountDefined(XElement element) => element != null &&
(element.GetAttribute("maxamount") != null || element.GetAttribute("maxavailable") != null);
public static bool HasSoldDefined(XElement element) => element != null &&
element.GetAttribute("sold") != null;
public static string GetMinAmountString(XElement element)
{
if (element == null) { return null; }
return element.GetAttributeString("minamount", null) ?? element.GetAttributeString("minavailable", null);
}
public static string GetMaxAmountString(XElement element)
{
if (element == null) { return null; }
return element.GetAttributeString("maxamount", null) ?? element.GetAttributeString("maxavailable", null);
}
public static bool GetSold(XElement element, bool defaultValue = true) =>
element?.GetAttributeBool("sold", defaultValue) ?? defaultValue;
public static int GetMinLevelDifficulty(XElement element, int defaultValue = 0) =>
element?.GetAttributeInt("minleveldifficulty", defaultValue) ?? defaultValue;
public static string GetStoreIdentifier(XElement element, string defaultValue = "unknown") =>
element?.GetAttributeString("storeidentifier", defaultValue) ?? defaultValue;
}
}
@@ -343,6 +343,9 @@ namespace Barotrauma
}
/// <summary>
/// Is the sub at the depth where it starts to take damage to appear due to the pressure?
/// </summary>
public bool AtDamageDepth
{
get
@@ -352,6 +355,18 @@ namespace Barotrauma
}
}
/// <summary>
/// Is the sub at the depth where cosmetic effects (e.g. camera shake) start to appear due to the pressure?
/// </summary>
public bool AtCosmeticDamageDepth
{
get
{
if (Level.Loaded == null || subBody == null) { return false; }
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth + SubmarineBody.CosmeticDamageEffectThreshold && RealWorldDepth > RealWorldCrushDepth + SubmarineBody.CosmeticDamageEffectThreshold;
}
}
public bool IsRespawnShuttle =>
GameMain.NetworkMember?.RespawnManager is { } respawnManager && respawnManager.RespawnShuttles.Contains(this);
@@ -579,13 +579,16 @@ namespace Barotrauma
Body.SetTransform(ConvertUnits.ToSimUnits(position), 0.0f);
}
/// <summary>
/// Camera shake and sounds start playing 500 meters before crush depth
/// </summary>
public const float CosmeticDamageEffectThreshold = -500.0f;
private void UpdateDepthDamage(float deltaTime)
{
if (GameMain.GameSession?.GameMode is TestGameMode) { return; }
if (Level.Loaded == null) { return; }
//camera shake and sounds start playing 500 meters before crush depth
const float CosmeticEffectThreshold = -500.0f;
//breaches won't get any more severe 500 meters below crush depth
const float MaxEffectThreshold = 500.0f;
const float MinWallDamageProbability = 0.1f;
@@ -598,7 +601,7 @@ namespace Barotrauma
//(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)
if (!Submarine.AtCosmeticDamageDepth)
{
return;
}
@@ -606,9 +609,9 @@ namespace Barotrauma
damageSoundTimer -= deltaTime;
if (damageSoundTimer <= 0.0f)
{
const float PressureSoundRange = -CosmeticEffectThreshold;
const float PressureSoundRange = -CosmeticDamageEffectThreshold;
//Ratio between 0 (where the 'approaching crush depth' indication starts) and 1 (at crush depth or past it)
float closenessToCrushDepthRatio = Math.Clamp((Submarine.RealWorldDepth - (Submarine.RealWorldCrushDepth + CosmeticEffectThreshold)) / PressureSoundRange, 0f, 1f);
float closenessToCrushDepthRatio = Math.Clamp((Submarine.RealWorldDepth - (Submarine.RealWorldCrushDepth + CosmeticDamageEffectThreshold)) / PressureSoundRange, 0f, 1f);
#if CLIENT
SoundPlayer.PlayDamageSound("pressure", MathHelper.Lerp(0f, 100f, closenessToCrushDepthRatio), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f, gain: 1f + closenessToCrushDepthRatio * 2);
#endif
@@ -1066,8 +1069,15 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; }
if (item.body.Mass > impulseMagnitude) { continue; }
if (item.Submarine != submarine) { continue; }
if (item.body is not { BodyType: BodyType.Dynamic })
{
if (!item.Prefab.ReceiveSubmarineImpacts) { continue; }
item.ReceiveImpact(impact, recursive: false);
}
if (!item.body.Enabled || item.CurrentHull == null || item.body.Mass > impulseMagnitude) { continue; }
item.body.ApplyLinearImpulse(impulse, 10.0f);
item.PositionUpdateInterval = 0.0f;