Build 0.18.4.0

This commit is contained in:
Markus Isberg
2022-05-31 23:13:05 +09:00
parent 077917fa5d
commit 64db1a6a44
175 changed files with 4916 additions and 2393 deletions
@@ -10,12 +10,11 @@ using System.Net;
namespace Barotrauma
{
#warning TODO: MapEntityPrefab should be constrained further to not include item assemblies, as assemblies are effectively not entities at all
partial class ItemAssemblyPrefab : MapEntityPrefab
{
public static readonly PrefabCollection<ItemAssemblyPrefab> Prefabs = new PrefabCollection<ItemAssemblyPrefab>();
public static readonly string VanillaSaveFolder = Path.Combine("Content", "Items", "Assemblies");
private readonly XElement configElement;
public readonly ImmutableArray<(Identifier Identifier, Rectangle Rect)> DisplayEntities;
@@ -49,7 +49,7 @@ namespace Barotrauma
Cave = 0x4,
Ruin = 0x8,
Wreck = 0x10,
BeaconStation = 0x20, // Not used anywhere
BeaconStation = 0x20,
Abyss = 0x40,
AbyssCave = 0x80
}
@@ -395,6 +395,13 @@ namespace Barotrauma
/// </summary>
public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost;
/// <summary>
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1)
/// </summary>
public static bool IsLoadedFriendlyOutpost =>
loaded?.Type == LevelData.LevelType.Outpost &&
(loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.FriendlyNPC || loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.Team1);
public LevelGenerationParams GenerationParams
{
get { return LevelData.GenerationParams; }
@@ -421,7 +428,7 @@ namespace Barotrauma
borders = new Rectangle(Point.Zero, levelData.Size);
}
public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
public static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
{
Debug.Assert(levelData.Biome != null);
if (levelData.Biome == null) { throw new ArgumentException("Biome was null"); }
@@ -433,11 +440,11 @@ namespace Barotrauma
preSelectedStartOutpost = startOutpost,
preSelectedEndOutpost = endOutpost
};
level.Generate(mirror);
level.Generate(mirror, startLocation, endLocation);
return level;
}
private void Generate(bool mirror)
private void Generate(bool mirror, Location startLocation, Location endLocation)
{
Loaded?.Remove();
Loaded = this;
@@ -454,8 +461,8 @@ namespace Barotrauma
if (LevelData.ForceOutpostGenerationParams == null)
{
StartLocation = GameMain.GameSession?.StartLocation;
EndLocation = GameMain.GameSession?.EndLocation;
StartLocation = startLocation;
EndLocation = endLocation;
}
GenerateEqualityCheckValue(LevelGenStage.GenStart);
@@ -509,7 +516,7 @@ namespace Barotrauma
Rectangle pathBorders = borders;
pathBorders.Inflate(
-Math.Min(Math.Min(minMainPathWidth * 2, MaxSubmarineWidth), borders.Width / 5),
-Math.Min(minMainPathWidth, borders.Height / 5));
-Math.Min(minMainPathWidth * 2, borders.Height / 5));
if (pathBorders.Width <= 0) { throw new InvalidOperationException($"The width of the level's path area is invalid ({pathBorders.Width})"); }
if (pathBorders.Height <= 0) { throw new InvalidOperationException($"The height of the level's path area is invalid ({pathBorders.Height})"); }
@@ -1713,7 +1720,7 @@ namespace Barotrauma
#endif
}
}
else
else if (abyssHeight > 30000)
{
//if the bottom of the abyss area is below crush depth, try to move it up to keep (most) of the abyss content above crush depth
//but only if start of the abyss is above crush depth (no point in doing this if all of it is below crush depth)
@@ -3527,6 +3534,8 @@ namespace Barotrauma
}
else if (type == SubmarineType.BeaconStation)
{
PositionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.BeaconStation, submarine: sub));
sub.ShowSonarMarker = false;
sub.DockedTo.ForEach(s => s.ShowSonarMarker = false);
sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static;
@@ -3940,7 +3949,7 @@ namespace Barotrauma
//the submarine port has to be at the top of the sub
if (port.Item.WorldPosition.Y < Submarine.MainSub.WorldPosition.Y) { continue; }
float dist = Math.Abs(port.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X);
if (dist < closestDistance)
if (dist < closestDistance || subPort.MainDockingPort)
{
subPort = port;
closestDistance = dist;
@@ -4023,6 +4032,26 @@ namespace Barotrauma
DebugConsole.ThrowError("No BeaconStation files found in the selected content packages!");
return;
}
var beaconInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsBeacon);
for (int i = beaconStationFiles.Count - 1; i >= 0; i--)
{
var beaconStationFile = beaconStationFiles[i];
var matchingInfo = beaconInfos.SingleOrDefault(info => info.FilePath == beaconStationFile.Path.Value);
Debug.Assert(matchingInfo != null);
if (matchingInfo?.BeaconStationInfo is BeaconStationInfo beaconInfo)
{
if (LevelData.Difficulty < beaconInfo.MinLevelDifficulty || LevelData.Difficulty > beaconInfo.MaxLevelDifficulty)
{
beaconStationFiles.RemoveAt(i);
}
}
}
if (beaconStationFiles.None())
{
DebugConsole.ThrowError($"No BeaconStation files found for the level difficulty {LevelData.Difficulty}!");
return;
}
var contentFile = beaconStationFiles.GetRandom(Rand.RandSync.ServerAndClient);
string beaconStationName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
@@ -4078,24 +4107,22 @@ namespace Barotrauma
{
if (!(GameMain.NetworkMember?.IsClient ?? false))
{
//empty the reactor
if (reactorContainer != null)
bool allowDisconnectedWires = true;
bool allowDamagedWalls = true;
if (BeaconStation.Info?.BeaconStationInfo is BeaconStationInfo info)
{
foreach (Item item in reactorContainer.Inventory.AllItems)
{
if (item.NonInteractable) { continue; }
Spawner.AddItemToRemoveQueue(item);
}
allowDisconnectedWires = info.AllowDisconnectedWires;
allowDamagedWalls = info.AllowDamagedWalls;
}
//remove wires
float removeWireMinDifficulty = 20.0f;
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
if (removeWireProbability > 0.0f)
if (removeWireProbability > 0.0f && allowDisconnectedWires)
{
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
{
if (item.NonInteractable) { continue; }
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))
@@ -4115,8 +4142,8 @@ namespace Barotrauma
connection.ConnectionPanel.DisconnectedWires.Add(wire);
wire.RemoveConnection(connection.Item);
#if SERVER
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
wire.CreateNetworkEvent();
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
wire.CreateNetworkEvent();
#endif
}
}
@@ -4124,23 +4151,25 @@ namespace Barotrauma
}
}
//break powered items
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
if (allowDamagedWalls)
{
if (item.NonInteractable) { continue; }
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
//break powered items
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
{
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
{
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)
//poke holes in the walls
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
{
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));
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));
}
}
}
}
@@ -20,7 +20,7 @@ namespace Barotrauma
public readonly string Seed;
public float Difficulty;
public readonly float Difficulty;
public readonly Biome Biome;
@@ -432,7 +432,7 @@ namespace Barotrauma
if (sub != null)
{
bool leaveBehind = false;
if (!sub.DockedTo.Contains(Submarine.MainSub))
if (sub.Submarine != null && !sub.DockedTo.Contains(sub.Submarine))
{
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndExit || Submarine.MainSub.AtStartExit);
if (Submarine.MainSub.AtEndExit)
@@ -78,7 +78,7 @@ namespace Barotrauma
/// <summary>
/// Load a previously saved campaign map from XML
/// </summary>
private Map(CampaignMode campaign, XElement element, CampaignSettings settings) : this(settings)
private Map(CampaignMode campaign, XElement element) : this(campaign.Settings)
{
Seed = element.GetAttributeString("seed", "a");
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
@@ -104,7 +104,7 @@ namespace Barotrauma
case "radiation":
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
{
Enabled = settings.RadiationEnabled
Enabled = campaign.Settings.RadiationEnabled
};
break;
}
@@ -208,12 +208,12 @@ namespace Barotrauma
/// <summary>
/// Generate a new campaign map from the seed
/// </summary>
public Map(CampaignMode campaign, string seed, CampaignSettings settings) : this(settings)
public Map(CampaignMode campaign, string seed) : this(campaign.Settings)
{
Seed = seed;
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
Generate();
Generate(campaign.Settings);
if (Locations.Count == 0)
{
@@ -228,10 +228,7 @@ namespace Barotrauma
foreach (Location location in Locations)
{
if (location.Type.Identifier != "outpost") { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
SetStartLocation(location);
}
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
if (CurrentLocation == null)
@@ -239,25 +236,36 @@ namespace Barotrauma
foreach (Location location in Locations)
{
if (!location.Type.HasOutpost) { continue; }
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
SetStartLocation(location);
}
}
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
if (StartLocation?.LevelData != null)
void SetStartLocation(Location location)
{
StartLocation.LevelData.Difficulty = 0;
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
{
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
}
}
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
foreach (var locationConnection in StartLocation.Connections)
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
int loops = campaign.CampaignMetadata.GetInt("campaign.endings".ToIdentifier(), 0);
if (loops == 0 && (campaign.Settings.Difficulty == GameDifficulty.Easy || campaign.Settings.Difficulty == GameDifficulty.Medium))
{
if (locationConnection.Difficulty > 0.0f)
if (StartLocation != null)
{
locationConnection.Difficulty = 0.0f;
locationConnection.LevelData = new LevelData(locationConnection);
StartLocation.LevelData = new LevelData(StartLocation, 0);
}
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
foreach (var locationConnection in StartLocation.Connections)
{
if (locationConnection.Difficulty > 0.0f)
{
locationConnection.Difficulty = 0.0f;
locationConnection.LevelData = new LevelData(locationConnection);
}
}
}
@@ -276,7 +284,7 @@ namespace Barotrauma
#region Generation
private void Generate()
private void Generate(CampaignSettings settings)
{
Connections.Clear();
Locations.Clear();
@@ -294,7 +302,6 @@ namespace Barotrauma
Voronoi voronoi = new Voronoi(0.5f);
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height);
float zoneWidth = Width / generationParams.DifficultyZones;
Vector2 margin = new Vector2(
Math.Min(10, Width * 0.1f),
@@ -310,6 +317,7 @@ namespace Barotrauma
voronoiSites.Clear();
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
bool possibleStartOutpostCreated = false;
foreach (GraphEdge edge in edges)
{
if (edge.Point1 == edge.Point2) { continue; }
@@ -344,12 +352,26 @@ namespace Barotrauma
}
LocationType forceLocationType = null;
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
if (!possibleStartOutpostCreated)
{
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
float zoneWidth = Width / generationParams.DifficultyZones;
float threshold = zoneWidth * 0.1f;
if (position.X < threshold)
{
forceLocationType = locationType;
break;
LocationType.Prefabs.TryGet("outpost", out forceLocationType);
possibleStartOutpostCreated = true;
}
}
if (forceLocationType == null)
{
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
{
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
{
forceLocationType = locationType;
break;
}
}
}
@@ -455,9 +477,7 @@ namespace Barotrauma
if (zone1 == zone2) { continue; }
if (zone1 > zone2)
{
int temp = zone2;
zone2 = zone1;
zone1 = temp;
(zone1, zone2) = (zone2, zone1);
}
if (generationParams.GateCount[zone1] == 0) { continue; }
@@ -527,32 +547,43 @@ namespace Barotrauma
foreach (LocationConnection connection in Connections)
{
float difficulty = connection.CenterPos.X / Width * 100;
float minDifficulty = 0;
float maxDifficulty = 100;
var biome = connection.Biome;
if (biome != null)
if (connection.Locations.Any(l => l.IsGateBetweenBiomes))
{
minDifficulty = connection.Biome.MinDifficulty;
maxDifficulty = connection.Biome.MaxDifficulty;
if (connection.Locked)
{
connection.Difficulty = maxDifficulty;
}
connection.Difficulty = connection.Locations.Min(l => l.Biome.MaxDifficulty);
}
else
{
connection.Difficulty = CalculateDifficulty(connection.CenterPos.X, connection.Biome);
}
connection.Difficulty = MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
}
CreateEndLocation();
foreach (Location location in Locations)
{
location.LevelData = new LevelData(location, MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f));
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
}
foreach (LocationConnection connection in Connections)
{
connection.LevelData = new LevelData(connection);
}
float CalculateDifficulty(float mapPosition, Biome biome)
{
float settingsFactor = settings.LevelDifficultyMultiplier;
float minDifficulty = 0;
float maxDifficulty = 100;
float difficulty = mapPosition / Width * 100;
System.Diagnostics.Debug.Assert(biome != null);
if (biome != null)
{
minDifficulty = biome.MinDifficulty;
maxDifficulty = biome.MaxDifficulty;
float diff = 1 - settingsFactor;
difficulty *= 1 - (1f / biome.AllowedZones.Max() * diff);
}
return MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
}
}
partial void GenerateLocationConnectionVisuals();
@@ -633,6 +664,11 @@ namespace Barotrauma
if (EndLocation == null || previousToEndLocation == null) { return; }
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
{
previousToEndLocation.ChangeType(locationType);
}
//remove all locations from the end biome except the end location
for (int i = Locations.Count - 1; i >= 0; i--)
{
@@ -652,7 +688,7 @@ namespace Barotrauma
}
//removed all connections from the second-to-last location, need to reconnect it
if (!previousToEndLocation.Connections.Any())
if (previousToEndLocation.Connections.None())
{
Location connectTo = Locations.First();
foreach (Location location in Locations)
@@ -759,6 +795,7 @@ namespace Barotrauma
CurrentLocation = Locations[index];
CurrentLocation.Discover();
CurrentLocation.CreateStores();
if (prevLocation != CurrentLocation)
{
var connection = CurrentLocation.Connections.Find(c => c.Locations.Contains(prevLocation));
@@ -766,10 +803,8 @@ namespace Barotrauma
{
connection.Passed = true;
}
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
CurrentLocation.CreateStores();
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
public void SelectLocation(int index)
@@ -789,6 +824,7 @@ namespace Barotrauma
return;
}
Location prevSelected = SelectedLocation;
SelectedLocation = Locations[index];
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
SelectedConnection =
@@ -798,7 +834,10 @@ namespace Barotrauma
{
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
}
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
if (prevSelected != SelectedLocation)
{
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
}
public void SelectLocation(Location location)
@@ -811,13 +850,17 @@ namespace Barotrauma
return;
}
Location prevSelected = SelectedLocation;
SelectedLocation = location;
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
if (SelectedConnection?.Locked ?? false)
{
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
}
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
if (prevSelected != SelectedLocation)
{
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
}
public void SelectMission(IEnumerable<int> missionIndices)
@@ -830,23 +873,24 @@ namespace Barotrauma
return;
}
CurrentLocation.SetSelectedMissionIndices(missionIndices);
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
if (!missionIndices.SequenceEqual(GetSelectedMissionIndices()))
{
if (selectedMission.Locations[0] != CurrentLocation ||
selectedMission.Locations[1] != CurrentLocation)
CurrentLocation.SetSelectedMissionIndices(missionIndices);
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
{
if (SelectedConnection == null) { return; }
//the destination must be the same as the destination of the mission
if (selectedMission.Locations[1] != SelectedLocation)
if (selectedMission.Locations[0] != CurrentLocation ||
selectedMission.Locations[1] != CurrentLocation)
{
CurrentLocation.DeselectMission(selectedMission);
if (SelectedConnection == null) { return; }
//the destination must be the same as the destination of the mission
if (selectedMission.Locations[1] != SelectedLocation)
{
CurrentLocation.DeselectMission(selectedMission);
}
}
}
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
}
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
}
public void SelectRandomLocation(bool preferUndiscovered)
@@ -1070,9 +1114,9 @@ namespace Barotrauma
/// <summary>
/// Load a previously saved map from an xml element
/// </summary>
public static Map Load(CampaignMode campaign, XElement element, CampaignSettings settings)
public static Map Load(CampaignMode campaign, XElement element)
{
Map map = new Map(campaign, element, settings);
Map map = new Map(campaign, element);
map.LoadState(element, false);
#if CLIENT
map.DrawOffset = -map.CurrentLocation.MapPosition;
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class BeaconStationInfo : ISerializableEntity
{
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDamagedWalls { get; set; }
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool AllowDisconnectedWires { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
public float MinLevelDifficulty { get; set; }
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
public float MaxLevelDifficulty { get; set; }
public string Name { get; private set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
{
Name = $"BeaconStationInfo ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
public BeaconStationInfo(SubmarineInfo submarineInfo)
{
Name = $"BeaconStationInfo ({submarineInfo.Name})";
SerializableProperties = SerializableProperty.DeserializeProperties(this);
}
public BeaconStationInfo(BeaconStationInfo original)
{
Name = original.Name;
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
foreach (KeyValuePair<Identifier, SerializableProperty> kvp in original.SerializableProperties)
{
SerializableProperties.Add(kvp.Key, kvp.Value);
if (SerializableProperty.GetSupportedTypeName(kvp.Value.PropertyType) != null)
{
kvp.Value.TrySetValue(this, kvp.Value.GetValue(original));
}
}
}
public void Save(XElement element)
{
SerializableProperty.SerializeProperties(this, element);
}
}
}
@@ -830,43 +830,44 @@ namespace Barotrauma
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType, bool allowDifferentLocationType)
{
IEnumerable<SubmarineInfo> availableModules = null;
IEnumerable<SubmarineInfo> modulesWithCorrectFlags = null;
if (moduleFlag.IsEmpty || moduleFlag.Equals("none"))
{
availableModules = modules
modulesWithCorrectFlags = modules
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())));
}
else
{
availableModules = modules
modulesWithCorrectFlags = modules
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
}
modulesWithCorrectFlags = modulesWithCorrectFlags.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
availableModules = availableModules.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
if (prevModule != null)
var suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
if (!suitableModules.Any())
{
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));// && CanAttachTo(prevModule, m.OutpostModuleInfo));
//no suitable module found, see if we can find a "generic" module that's not meant for any specific type of outpost
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
//still not found, see if we can find something that's otherwise suitable but not meant to attach to the previous module
if (!suitableModules.Any())
{
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
}
//still not found! Try if we can find a generic module that's not meant to attach to the previous module
if (!suitableModules.Any())
{
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
}
}
if (availableModules.Count() == 0) { return null; }
//try to search for modules made specifically for this location type first
var modulesSuitableForLocationType =
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
//if not found, search for modules suitable for any location type
if (allowDifferentLocationType && !modulesSuitableForLocationType.Any())
{
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
}
if (!modulesSuitableForLocationType.Any())
if (!suitableModules.Any())
{
if (allowDifferentLocationType)
{
if (modulesWithCorrectFlags.Any())
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
return ToolBox.SelectWeightedRandom(modulesWithCorrectFlags.ToList(), modulesWithCorrectFlags.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
}
else
{
@@ -875,7 +876,28 @@ namespace Barotrauma
}
else
{
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
return ToolBox.SelectWeightedRandom(suitableModules.ToList(), suitableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
}
IEnumerable<SubmarineInfo> GetSuitable(IEnumerable<SubmarineInfo> modules, bool requireAllowAttachToPrevious, bool requireCorrectLocationType, bool disallowNonLocationTypeSpecific)
{
IEnumerable<SubmarineInfo> suitable = modules;
if (requireCorrectLocationType)
{
if (disallowNonLocationTypeSpecific)
{
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
}
else
{
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier) || !m.OutpostModuleInfo.AllowedLocationTypes.Any());
}
}
if (requireAllowAttachToPrevious && prevModule != null)
{
suitable = suitable.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));
}
return suitable;
}
}
@@ -1590,10 +1612,6 @@ namespace Barotrauma
{
npc.CharacterHealth.Unkillable = true;
}
else
{
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
}
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.ServerAndClient);
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
{
@@ -1483,8 +1483,10 @@ namespace Barotrauma
{
if (item.Submarine != this) continue;
if (item.ParentInventory != null || item.body != null) continue;
var lightComponent = item.GetComponent<Items.Components.LightComponent>();
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
foreach (var light in item.GetComponents<LightComponent>())
{
light.LightColor = new Color(light.LightColor, light.LightColor.A / 255.0f * 0.5f);
}
}
}
GenerateOutdoorNodes();
@@ -1555,7 +1557,7 @@ namespace Barotrauma
element.Add(new XAttribute("cargocapacity", cargoCapacity));
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience.ToString()));
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", Info.RequiredContentPackages)));
if (Info.Type == SubmarineType.OutpostModule)
@@ -1632,6 +1634,7 @@ namespace Barotrauma
Type = Info.Type,
FilePath = filePath,
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null,
Name = Path.GetFileNameWithoutExtension(filePath)
};
#if CLIENT
@@ -39,7 +39,15 @@ namespace Barotrauma
public SubmarineTag Tags { get; private set; }
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
public string RecommendedCrewExperience;
public enum CrewExperienceLevel
{
Unknown,
CrewExperienceLow,
CrewExperienceMid,
CrewExperienceHigh
}
public CrewExperienceLevel RecommendedCrewExperience;
/// <summary>
/// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match
@@ -89,6 +97,7 @@ namespace Barotrauma
public SubmarineClass SubmarineClass;
public OutpostModuleInfo OutpostModuleInfo { get; set; }
public BeaconStationInfo BeaconStationInfo { get; set; }
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
@@ -280,6 +289,10 @@ namespace Barotrauma
{
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
}
if (original.BeaconStationInfo != null)
{
BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo);
}
#if CLIENT
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
#endif
@@ -330,7 +343,24 @@ namespace Barotrauma
CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
var recommendedCrewExperience = SubmarineElement.GetAttributeIdentifier("recommendedcrewexperience", CrewExperienceLevel.Unknown.ToIdentifier());
// Backwards compatibility
if (recommendedCrewExperience == "Beginner")
{
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceLow;
}
else if (recommendedCrewExperience == "Intermediate")
{
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceMid;
}
else if (recommendedCrewExperience == "Experienced")
{
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceHigh;
}
else
{
Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
}
if (SubmarineElement?.Attribute("type") != null)
{
@@ -341,6 +371,10 @@ namespace Barotrauma
{
OutpostModuleInfo = new OutpostModuleInfo(this, SubmarineElement);
}
else if (Type == SubmarineType.BeaconStation)
{
BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement);
}
}
}
@@ -359,20 +393,6 @@ namespace Barotrauma
SubmarineClass = SubmarineClass.Undefined;
}
//backwards compatibility (use text tags instead of the actual text)
if (RecommendedCrewExperience == "Beginner")
{
RecommendedCrewExperience = "CrewExperienceLow";
}
else if (RecommendedCrewExperience == "Intermediate")
{
RecommendedCrewExperience = "CrewExperienceMid";
}
else if (RecommendedCrewExperience == "Experienced")
{
RecommendedCrewExperience = "CrewExperienceHigh";
}
RequiredContentPackages.Clear();
string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", Array.Empty<string>());
foreach (string contentPackageName in contentPackageNames)
@@ -528,6 +548,11 @@ namespace Barotrauma
OutpostModuleInfo.Save(newElement);
OutpostModuleInfo = new OutpostModuleInfo(this, newElement);
}
else if (Type == SubmarineType.BeaconStation)
{
BeaconStationInfo.Save(newElement);
BeaconStationInfo = new BeaconStationInfo(this, newElement);
}
XDocument doc = new XDocument(newElement);
doc.Root.Add(new XAttribute("name", Name));
@@ -590,6 +615,7 @@ namespace Barotrauma
List<string> filePaths = new List<string>();
foreach (BaseSubFile subFile in contentPackageSubs)
{
if (!File.Exists(subFile.Path.Value)) { continue; }
if (!filePaths.Any(fp => fp == subFile.Path))
{
filePaths.Add(subFile.Path.Value);