Release 1.10.5.0 - Autumn Update 2025
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user