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
@@ -21,6 +21,9 @@
[Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")]
public int State { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the mission is forced to fail without a chance of retrying it.")]
public bool ForceFailure { get; set; }
private bool isFinished;
public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -31,7 +34,7 @@
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.",
contentPackage: element.ContentPackage);
}
if (Operation == OperationType.Add && State == 0)
if (Operation == OperationType.Add && State == 0 && !ForceFailure)
{
DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.",
contentPackage: element.ContentPackage);
@@ -54,6 +57,11 @@
foreach (Mission mission in GameMain.GameSession.Missions)
{
if (mission.Prefab.Identifier != MissionIdentifier) { continue; }
if (ForceFailure)
{
mission.ForceFailure = true;
}
switch (Operation)
{
case OperationType.Set:
@@ -410,16 +410,39 @@ namespace Barotrauma
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false, bool allowInPlayerView = true)
{
bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost;
List<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable);
IEnumerable<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
potentialSpawnPoints = potentialSpawnPoints.Where(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable);
//find spawnpoints with the desired type, or any random spawnpoints if not specified
IEnumerable<WayPoint> spawnPointsWithCorrectType;
if (spawnPointType.HasValue)
{
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp =>
spawnPointType.Value.HasFlag(wp.SpawnType) &&
//need to handle zero (SpawnType.Path) separately, because spawnPointType will always have the flag 0
(wp.SpawnType != 0 || spawnPointType.Value == 0));
}
else
{
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
}
if (spawnPointsWithCorrectType.Any())
{
potentialSpawnPoints = spawnPointsWithCorrectType;
}
//with correct module flags, if there are any
if (moduleFlags != null && moduleFlags.Any())
{
var spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains));
if (spawnPoints.Any())
var spawnPointsWithCorrectFlags = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains));
if (spawnPointsWithCorrectFlags.Any())
{
potentialSpawnPoints = spawnPoints.ToList();
potentialSpawnPoints = spawnPointsWithCorrectFlags.ToList();
}
}
//with correct spawn point tags, if there are any
if (spawnpointTags != null && spawnpointTags.Any())
{
var spawnPointsWithTag = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && wp.IsTraversable));
@@ -461,16 +484,8 @@ namespace Barotrauma
return null;
}
IEnumerable<WayPoint> validSpawnPoints;
if (spawnPointType.HasValue)
{
validSpawnPoints = potentialSpawnPoints.FindAll(wp => spawnPointType.Value.HasFlag(wp.SpawnType));
}
else
{
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path);
if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; }
}
//spawnpoints that match the desired criteria found, choose the best one next
IEnumerable<WayPoint> validSpawnPoints = potentialSpawnPoints;
//don't spawn in an airlock module if there are other options
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false);
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -23,10 +24,12 @@ namespace Barotrauma
private bool swarmSpawned;
private readonly List<MonsterSet> monsterSets = new List<MonsterSet>();
private readonly LocalizedString sonarLabel;
private readonly ImmutableArray<Identifier> beaconTags;
public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
{
swarmSpawned = false;
beaconTags = prefab.ConfigElement.GetAttributeIdentifierArray("beacontags", []).ToImmutableArray();
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
@@ -185,6 +188,29 @@ namespace Barotrauma
{
levelData.HasBeaconStation = true;
levelData.IsBeaconActive = false;
if (beaconTags.Length > 0)
{
var selectedBeacon = GetRandomBeaconByTags(beaconTags, levelData);
if (selectedBeacon != null)
{
levelData.ForceBeaconStation = selectedBeacon;
}
else
{
DebugConsole.ThrowError($"Beacon mission \"{Prefab.Identifier}\" could not find a suitable beacon station with beacontags \"{string.Join(", ", beaconTags)}\" for level difficulty {levelData.Difficulty:F1}.",
contentPackage: Prefab.ContentPackage);
}
}
}
private static SubmarineInfo GetRandomBeaconByTags(ImmutableArray<Identifier> tags, LevelData levelData)
{
return GetRandomSubmarineByTagsAndDifficulty(
tags,
levelData,
s => s.IsBeacon,
"beacon station");
}
}
}
@@ -48,7 +48,7 @@ namespace Barotrauma
protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
private readonly CheckDataAction completeCheckDataAction;
protected readonly CheckDataAction completeCheckDataAction;
public readonly ImmutableArray<LocalizedString> Headers;
public readonly ImmutableArray<LocalizedString> Messages;
@@ -110,9 +110,11 @@ namespace Barotrauma
public bool Failed
{
get { return failed; }
get { return failed || ForceFailure; }
}
public bool ForceFailure;
public virtual bool AllowRespawning
{
get { return true; }
@@ -541,9 +543,10 @@ namespace Barotrauma
{
if (GameMain.NetworkMember is not { IsClient: true })
{
completed =
completed =
!ForceFailure &&
DetermineCompleted() &&
(completeCheckDataAction == null ||completeCheckDataAction.GetSuccess());
(completeCheckDataAction == null || completeCheckDataAction.GetSuccess());
}
if (completed)
{
@@ -569,6 +572,10 @@ namespace Barotrauma
TimesAttempted++;
EndMissionSpecific(completed);
if (ForceFailure)
{
failed = true;
}
}
protected abstract bool DetermineCompleted();
@@ -829,6 +836,51 @@ namespace Barotrauma
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.ServerAndClient),
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
}
/// <summary>
/// Gets a random submarine by tags, filtered by difficulty. Used by missions that force specific submarines (wrecks, beacons, etc.)
/// </summary>
/// <param name="tags">Mission tags to match against</param>
/// <param name="seed">Random seed for selection</param>
/// <param name="submarineSelector">Function to filter submarines by type (e.g., s => s.IsWreck)</param>
/// <param name="submarineTypeName">Name of submarine type for error messages (e.g., "wreck", "beacon station")</param>
/// <returns>Selected submarine, or null if none found</returns>
protected static SubmarineInfo GetRandomSubmarineByTagsAndDifficulty(
IEnumerable<Identifier> tags,
LevelData levelData,
Func<SubmarineInfo, bool> submarineSelector,
string submarineTypeName)
{
var rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
float levelDifficulty = levelData.Difficulty;
var submarinesWithTags = SubmarineInfo.SavedSubmarines
.Where(submarineSelector)
.Where(s =>
{
return s.GetExtraSubmarineInfo is { } extraInfo && (tags.None() || tags.Any(t => extraInfo.MissionTags.Contains(t)));
})
.ToList();
var matchingSubmarines = submarinesWithTags
.Where(s =>
{
return s.GetExtraSubmarineInfo is { } extraInfo &&
levelDifficulty >= extraInfo.MinLevelDifficulty &&
levelDifficulty <= extraInfo.MaxLevelDifficulty;
})
.ToList();
if (matchingSubmarines.Count == 0)
{
if (submarinesWithTags.Count > 0)
{
DebugConsole.ThrowError($"Found {submarinesWithTags.Count} {submarineTypeName}(s) with matching tags \"{string.Join(", ", tags)}\", but none are suitable for level difficulty {levelDifficulty:F1}.");
}
return null;
}
return matchingSubmarines[rand.Next(matchingSubmarines.Count)];
}
}
class AbilityMissionMoneyGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
@@ -4,6 +4,7 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -240,6 +241,8 @@ namespace Barotrauma
/// </summary>
private readonly float requiredDeliveryAmount;
private readonly ImmutableArray<Identifier> wreckTags;
private LocalizedString pickedUpMessage;
/// <summary>
@@ -311,12 +314,13 @@ namespace Barotrauma
: base(prefab, locations, sub)
{
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeFloat(nameof(requiredDeliveryAmount), 0.98f);
//LevelData may not be instantiated at this point, in that case use the name identifier of the location
rng = new MTRandom(ToolBox.StringToInt(
locations[0].LevelData?.Seed ?? locations[0].NameIdentifier.Value +
locations[1].LevelData?.Seed ?? locations[1].NameIdentifier.Value));
wreckTags = prefab.ConfigElement.GetAttributeIdentifierArray("wrecktags", []).ToImmutableArray();
partiallyRetrievedMessage = GetMessage(nameof(partiallyRetrievedMessage));
allRetrievedMessage = GetMessage(nameof(allRetrievedMessage));
pickedUpMessage = GetMessage(nameof(pickedUpMessage));
@@ -756,5 +760,31 @@ namespace Barotrauma
target.Reset();
}
}
public override void AdjustLevelData(LevelData levelData)
{
if (wreckTags.Length > 0)
{
var selectedWreck = GetRandomWreckByTags(wreckTags, levelData);
if (selectedWreck != null)
{
levelData.ForceWreck = selectedWreck;
}
else
{
DebugConsole.ThrowError($"Salvage mission \"{Prefab.Identifier}\" could not find a suitable wreck with wrecktags \"{string.Join(", ", wreckTags)}\" for level difficulty {levelData.Difficulty:F1}.",
contentPackage: Prefab.ContentPackage);
}
}
}
private static SubmarineInfo GetRandomWreckByTags(ImmutableArray<Identifier> tags, LevelData levelData)
{
return GetRandomSubmarineByTagsAndDifficulty(
tags,
levelData,
s => s.IsWreck,
"wreck");
}
}
}