(f0d812055) v0.9.9.0

This commit is contained in:
Joonas Rikkonen
2020-04-23 19:19:37 +03:00
parent b647059b93
commit ac37a3b0e4
391 changed files with 15054 additions and 5420 deletions
@@ -0,0 +1,228 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class CorpsePrefab : IPrefab, IDisposable
{
public static readonly PrefabCollection<CorpsePrefab> Prefabs = new PrefabCollection<CorpsePrefab>();
private bool disposed = false;
public void Dispose()
{
if (disposed) { return; }
disposed = true;
Prefabs.Remove(this);
}
public static CorpsePrefab Get(string identifier)
{
if (Prefabs == null)
{
DebugConsole.ThrowError("Issue in the code execution order: job prefabs not loaded.");
return null;
}
if (Prefabs.ContainsKey(identifier))
{
return Prefabs[identifier];
}
else
{
DebugConsole.ThrowError("Couldn't find a job prefab with the given identifier: " + identifier);
return null;
}
}
[Serialize("notfound", false)]
public string Identifier { get; private set; }
[Serialize("any", false)]
public string Job { get; private set; }
[Serialize(1f, false)]
public float Commonness { get; private set; }
[Serialize(Level.PositionType.Wreck, false)]
public Level.PositionType SpawnPosition { get; private set; }
public string OriginalName { get { return Identifier; } }
public ContentPackage ContentPackage { get; private set; }
public string FilePath { get; private set; }
public XElement Element { get; private set; }
public readonly Dictionary<XElement, float> ItemSets = new Dictionary<XElement, float>();
public CorpsePrefab(XElement element, string filePath, bool allowOverriding)
{
FilePath = filePath;
SerializableProperty.DeserializeProperties(this, element);
Identifier = Identifier.ToLowerInvariant();
Job = Job.ToLowerInvariant();
Element = element;
element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1)));
Prefabs.Add(this, allowOverriding);
}
public static CorpsePrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync);
public static void LoadAll(IEnumerable<ContentFile> files)
{
foreach (ContentFile file in files)
{
LoadFromFile(file);
}
}
public static void LoadFromFile(ContentFile file)
{
DebugConsole.Log("*** " + file.Path + " ***");
RemoveByFile(file.Path);
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
if (doc == null) { return; }
var rootElement = doc.Root;
switch (rootElement.Name.ToString().ToLowerInvariant())
{
case "corpse":
new CorpsePrefab(rootElement, file.Path, false)
{
ContentPackage = file.ContentPackage
};
break;
case "corpses":
foreach (var element in rootElement.Elements())
{
if (element.IsOverride())
{
var itemElement = element.GetChildElement("item");
if (itemElement != null)
{
new CorpsePrefab(itemElement, file.Path, true)
{
ContentPackage = file.ContentPackage
};
}
else
{
DebugConsole.ThrowError($"Cannot find an item element from the children of the override element defined in {file.Path}");
}
}
else
{
new CorpsePrefab(element, file.Path, false)
{
ContentPackage = file.ContentPackage
};
}
}
break;
case "override":
var corpses = rootElement.GetChildElement("corpses");
if (corpses != null)
{
foreach (var element in corpses.Elements())
{
new CorpsePrefab(element, file.Path, true)
{
ContentPackage = file.ContentPackage,
};
}
}
foreach (var element in rootElement.GetChildElements("corpse"))
{
new CorpsePrefab(element, file.Path, true)
{
ContentPackage = file.ContentPackage
};
}
break;
default:
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
break;
}
}
public static void RemoveByFile(string filePath)
{
Prefabs.RemoveByFile(filePath);
}
public void GiveItems(Character character, Submarine submarine)
{
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), Rand.RandSync.Unsynced);
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
{
InitializeItems(character, itemElement, submarine);
}
}
private void InitializeItems(Character character, XElement itemElement, Submarine submarine, Item parentItem = null)
{
ItemPrefab itemPrefab;
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to spawn \"" + Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found.");
return;
}
Item item = new Item(itemPrefab, character.Position, null);
#if SERVER
if (GameMain.Server != null && Entity.Spawner != null)
{
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
{
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
}
Entity.Spawner.CreateNetworkEvent(item, false);
}
#endif
if (itemElement.GetAttributeBool("equip", false))
{
List<InvSlotType> allowedSlots = new List<InvSlotType>(item.AllowedSlots);
allowedSlots.Remove(InvSlotType.Any);
character.Inventory.TryPutItem(item, null, allowedSlots);
}
else
{
character.Inventory.TryPutItem(item, null, item.AllowedSlots);
}
if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck")
{
item.AddTag("name:" + character.Name);
item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine));
var job = character.Info?.Job;
if (job != null)
{
item.AddTag("job:" + job.Name);
}
}
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
{
wifiComponent.TeamID = character.TeamID;
}
if (parentItem != null)
{
parentItem.Combine(item, user: null);
}
foreach (XElement childItemElement in itemElement.Elements())
{
InitializeItems(character, childItemElement, submarine, item);
}
}
}
}
@@ -30,9 +30,9 @@ namespace Barotrauma
public float EmpStrength { get; set; }
public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f)
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f)
{
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range)
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
{
SeverLimbsProbability = 1.0f
};
@@ -220,7 +220,7 @@ namespace Barotrauma
Vector2 explosionPos = worldPosition;
if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
Hull hull = Hull.FindHull(explosionPos, null, false);
bool underWater = hull == null || explosionPos.Y < hull.Surface;
explosionPos = ConvertUnits.ToSimUnits(explosionPos);
@@ -14,7 +14,7 @@ namespace Barotrauma
partial class FireSource : ISpatialEntity
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
const float GrowSpeed = 20.0f;
protected Hull hull;
@@ -128,8 +128,9 @@ namespace Barotrauma
outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall;
outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter;
outsideCollisionBlocker.Enabled = false;
#if CLIENT
Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
#endif
DebugConsole.Log("Created gap (" + ID + ")");
}
@@ -226,8 +227,6 @@ namespace Barotrauma
if (hulls[i] == null) hulls[i] = Hull.FindHullOld(searchPos[i], null, false, true);
}
if (hulls[1] == hulls[0]) { hulls[1] = null; }
if (hulls[0] == null && hulls[1] == null) { return; }
if (hulls[0] == null && hulls[1] != null)
@@ -241,7 +240,7 @@ namespace Barotrauma
for (int i = 0; i < 2; i++)
{
if (hulls[i] == null) continue;
if (hulls[i] == null) { continue; }
linkedTo.Add(hulls[i]);
if (!hulls[i].ConnectedGaps.Contains(this)) hulls[i].ConnectedGaps.Add(this);
}
@@ -259,17 +258,21 @@ namespace Barotrauma
return;
}
UpdateOxygen();
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
if (hull1 == hull2) { return; }
UpdateOxygen(hull1, hull2);
if (linkedTo.Count == 1)
{
//gap leading from a room to outside
UpdateRoomToOut(deltaTime);
UpdateRoomToOut(deltaTime, hull1);
}
else
else if (linkedTo.Count == 2)
{
//gap leading from a room to another
UpdateRoomToRoom(deltaTime);
UpdateRoomToRoom(deltaTime, hull1, hull2);
}
flowForce.X = MathHelper.Clamp(flowForce.X, -MaxFlowForce, MaxFlowForce);
@@ -329,12 +332,8 @@ namespace Barotrauma
partial void EmitParticles(float deltaTime);
void UpdateRoomToRoom(float deltaTime)
void UpdateRoomToRoom(float deltaTime, Hull hull1, Hull hull2)
{
if (linkedTo.Count < 2) return;
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
Vector2 subOffset = Vector2.Zero;
if (hull1.Submarine != Submarine)
{
@@ -378,7 +377,7 @@ namespace Barotrauma
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.WaterVolume, hull2.Volume));
//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));
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - (hull1.WaterVolume));
hull1.WaterVolume += delta;
hull2.WaterVolume -= delta;
if (hull1.WaterVolume > hull1.Volume)
@@ -399,7 +398,7 @@ namespace Barotrauma
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 5.0f * sizeModifier, Math.Min(hull1.WaterVolume, hull1.Volume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull2.Volume + Hull.MaxCompress - (hull2.WaterVolume));
delta = Math.Min(delta, hull2.Volume * Hull.MaxCompress - (hull2.WaterVolume));
hull1.WaterVolume -= delta;
hull2.WaterVolume += delta;
if (hull2.WaterVolume > hull2.Volume)
@@ -414,14 +413,14 @@ namespace Barotrauma
{
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
if (hull1.WaterVolume < hull1.Volume - Hull.MaxCompress &&
if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
{
hull1.WaveVel[hull1.WaveY.Length - 1] = (avg - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
}
if (hull2.WaterVolume < hull2.Volume - Hull.MaxCompress &&
if (hull2.WaterVolume < hull2.Volume / Hull.MaxCompress &&
hull2.Surface + hull2.WaveY[0] < rect.Y)
{
hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
@@ -436,12 +435,12 @@ namespace Barotrauma
//lower room is full of water
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
{
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + Hull.MaxCompress, deltaTime * 8000.0f * sizeModifier);
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)
if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
{
delta -= (hull1.WaterVolume + delta) - (hull1.Volume + Hull.MaxCompress);
delta -= (hull1.WaterVolume + delta) - (hull1.Volume * Hull.MaxCompress);
}
delta = Math.Max(delta, 0.0f);
@@ -469,9 +468,9 @@ namespace Barotrauma
float delta = Math.Min(hull1.WaterVolume, deltaTime * 25000f * sizeModifier);
//make sure not to place more water to the target room than it can hold
if (hull2.WaterVolume + delta > hull2.Volume + Hull.MaxCompress)
if (hull2.WaterVolume + delta > hull2.Volume * Hull.MaxCompress)
{
delta -= (hull2.WaterVolume + delta) - (hull2.Volume + Hull.MaxCompress);
delta -= (hull2.WaterVolume + delta) - (hull2.Volume * Hull.MaxCompress);
}
hull1.WaterVolume -= delta;
hull2.WaterVolume += delta;
@@ -489,7 +488,7 @@ namespace Barotrauma
if (open > 0.0f)
{
if (hull1.WaterVolume > hull1.Volume - Hull.MaxCompress && hull2.WaterVolume > hull2.Volume - Hull.MaxCompress)
if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && hull2.WaterVolume > hull2.Volume / Hull.MaxCompress)
{
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
hull1.LethalPressure = avgLethality;
@@ -503,22 +502,18 @@ namespace Barotrauma
}
}
void UpdateRoomToOut(float deltaTime)
void UpdateRoomToOut(float deltaTime, Hull hull1)
{
if (linkedTo.Count != 1) return;
float size = (IsHorizontal) ? rect.Height : rect.Width;
Hull hull1 = (Hull)linkedTo[0];
float size = IsHorizontal ? rect.Height : rect.Width;
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = size * open * open;
float delta = Hull.MaxCompress * sizeModifier * deltaTime;
float delta = 500.0f * sizeModifier * 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);
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
hull1.WaterVolume += delta;
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
@@ -541,7 +536,7 @@ namespace Barotrauma
higherSurface = hull1.Surface;
lowerSurface = rect.Y;
if (hull1.WaterVolume < hull1.Volume - Hull.MaxCompress &&
if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
hull1.Surface < rect.Y)
{
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
@@ -576,7 +571,7 @@ namespace Barotrauma
{
flowForce = new Vector2(0.0f, delta);
}
if (hull1.WaterVolume >= hull1.Volume - Hull.MaxCompress)
if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
}
@@ -600,7 +595,7 @@ namespace Barotrauma
private void UpdateOutsideColliderPos(Hull hull)
{
if (Submarine == null || IsRoomToRoom) { return; }
if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; }
Vector2 rayDir;
if (IsHorizontal)
@@ -615,6 +610,19 @@ namespace Barotrauma
Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition);
Vector2 rayEnd = rayStart + rayDir * 500.0f;
var levelCells = Level.Loaded.GetCells(WorldPosition, searchDepth: 1);
foreach (var cell in levelCells)
{
if (cell.IsPointInside(WorldPosition))
{
outsideCollisionBlocker.Enabled = true;
Vector2 colliderPos = rayStart - Submarine.SimPosition;
float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2;
outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
return;
}
}
var blockingBody = Submarine.CheckVisibility(rayStart, rayEnd);
if (blockingBody != null)
{
@@ -631,11 +639,9 @@ namespace Barotrauma
}
}
private void UpdateOxygen()
private void UpdateOxygen(Hull hull1, Hull hull2)
{
if (linkedTo.Count < 2) { return; }
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
if (hull1 == null || hull2 == null) { return; }
if (IsHorizontal)
{
@@ -26,8 +26,9 @@ namespace Barotrauma
public static float WaveSpread = 0.05f;
public static float WaveDampening = 0.05f;
//how much excess water the room can contain (= more than the volume of the room)
public const float MaxCompress = 10000f;
//how much excess water the room can contain, relative to the volume of the room.
//needed to make it possible for pressure to "push" water up through U-shaped hull configurations
public const float MaxCompress = 1.05f;
public readonly Dictionary<string, SerializableProperty> properties;
public Dictionary<string, SerializableProperty> SerializableProperties
@@ -154,7 +155,7 @@ namespace Barotrauma
set
{
if (!MathUtils.IsValid(value)) return;
waterVolume = MathHelper.Clamp(value, 0.0f, Volume + MaxCompress);
waterVolume = MathHelper.Clamp(value, 0.0f, Volume * MaxCompress);
if (waterVolume < Volume) Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
if (waterVolume > 0.0f) update = true;
}
@@ -228,7 +229,7 @@ namespace Barotrauma
surface = rect.Y - rect.Height;
if (submarine != null)
if (submarine?.Info != null && !submarine.Info.IsWreck)
{
aiTarget = new AITarget(this)
{
@@ -321,6 +322,7 @@ namespace Barotrauma
CeilingHeight = ConvertUnits.ToDisplayUnits(upperPickedPos.Y - lowerPickedPos.Y);
}
}
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
}
public void AddToGrid(Submarine submarine)
@@ -444,15 +446,23 @@ namespace Barotrauma
lethalPressure = 0.0f;
return;
}
float waterDepth = WaterVolume / rect.Width;
if (waterDepth < 1.0f)
{
//if there's only a minuscule amount of water, consider the surface to be at the bottom of the hull
//otherwise unnoticeable amounts of water can for example cause magnesium to explode
waterDepth = 0.0f;
}
surface = Math.Max(MathHelper.Lerp(
surface,
rect.Y - rect.Height + WaterVolume / rect.Width,
rect.Y - rect.Height + waterDepth,
deltaTime * 10.0f), rect.Y - rect.Height);
//interpolate the position of the rendered surface towards the "target surface"
drawSurface = Math.Max(MathHelper.Lerp(
drawSurface,
rect.Y - rect.Height + WaterVolume / rect.Width,
rect.Y - rect.Height + waterDepth,
deltaTime * 10.0f), rect.Y - rect.Height);
for (int i = 0; i < waveY.Length; i++)
@@ -873,7 +883,7 @@ namespace Barotrauma
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine)
{
waterVolume = element.GetAttributeFloat("pressure", 0.0f),
WaterVolume = element.GetAttributeFloat("pressure", 0.0f),
ID = (ushort)int.Parse(element.Attribute("ID").Value)
};
@@ -89,7 +89,7 @@ namespace Barotrauma
CreateInstance(rect.Location.ToVector2(), Submarine.MainSub);
}
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub)
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectPrefabs = false)
{
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath);
if (entities.Count == 0) return entities;
@@ -107,10 +107,10 @@ namespace Barotrauma
MapEntity.MapLoaded(entities, true);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
if (Screen.Selected == GameMain.SubEditorScreen && selectPrefabs)
{
MapEntity.SelectedList.Clear();
entities.ForEach(e => MapEntity.AddSelection(e));
entities.ForEach(MapEntity.AddSelection);
}
#endif
return entities;
@@ -27,18 +27,24 @@ namespace Barotrauma
[Flags]
public enum PositionType
{
MainPath = 1, Cave = 2, Ruin = 4
MainPath = 1, Cave = 2, Ruin = 4, Wreck = 8
}
public struct InterestingPosition
{
public Point Position;
public readonly PositionType PositionType;
public bool IsValid;
public Submarine Submarine;
public Ruin Ruin;
public InterestingPosition(Point position, PositionType positionType)
public InterestingPosition(Point position, PositionType positionType, bool isValid = true, Submarine submarine = null, Ruin ruin = null)
{
Position = position;
PositionType = positionType;
IsValid = isValid;
Submarine = submarine;
Ruin = ruin;
}
}
@@ -68,6 +74,8 @@ namespace Barotrauma
private List<Ruin> ruins;
private List<Submarine> wrecks;
private LevelGenerationParams generationParams;
private List<List<Point>> smallTunnels = new List<List<Point>>();
@@ -133,11 +141,13 @@ namespace Barotrauma
get { return positionsOfInterest; }
}
public readonly List<InterestingPosition> UsedPositions = new List<InterestingPosition>();
public Submarine StartOutpost { get; private set; }
public Submarine EndOutpost { get; private set; }
private Submarine preSelectedStartOutpost;
private Submarine preSelectedEndOutpost;
private SubmarineInfo preSelectedStartOutpost;
private SubmarineInfo preSelectedEndOutpost;
public string Seed
{
@@ -210,7 +220,7 @@ namespace Barotrauma
/// </summary>
/// <param name="difficulty">A scalar between 0-100</param>
/// <param name="sizeFactor">A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used)</param>
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, Submarine startOutpost = null, Submarine endOutPost = null)
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, SubmarineInfo startOutpost = null, SubmarineInfo endOutPost = null)
: base(null)
{
@@ -310,7 +320,7 @@ namespace Barotrauma
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.15f);
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
minWidth = Math.Min(minWidth, maxWidth);
}
@@ -669,7 +679,6 @@ namespace Barotrauma
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cellsWithBody, this), generationParams.WallColor);
#endif
//----------------------------------------------------------------------------------
// create (placeholder) outposts at the start and end of the level
//----------------------------------------------------------------------------------
@@ -692,6 +701,12 @@ namespace Barotrauma
GenerateSeaFloor(mirror);
//----------------------------------------------------------------------------------
// create wrecks
//----------------------------------------------------------------------------------
CreateWrecks();
levelObjectManager.PlaceObjects(this, generationParams.LevelObjectAmount);
GenerateItems();
@@ -1164,7 +1179,7 @@ namespace Barotrauma
return;
}
string errorMsg = "Failed to find a suitable position for ruins. Level seed: " + seed +
", ruin size: " + ruinSize + ", selected sub " + (Submarine.MainSub == null ? "none" : Submarine.MainSub.Name);
", ruin size: " + ruinSize + ", selected sub " + (Submarine.MainSub == null ? "none" : Submarine.MainSub.Info.Name);
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Level.GenerateRuins:PosNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
break;
@@ -1201,13 +1216,15 @@ namespace Barotrauma
ruins.Add(ruin);
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
// TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places.
// We need the waypoints for the AI navigation and we could use them for spawning the creatures too.
int waypointCount = 0;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; }
if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition)))
{
positionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin));
positionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin));
waypointCount++;
}
}
@@ -1215,7 +1232,7 @@ namespace Barotrauma
//not enough waypoints inside ruins -> create some spawn positions manually
for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++)
{
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin));
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin));
}
foreach (RuinShape ruinShape in ruin.RuinShapes)
@@ -1328,8 +1345,6 @@ namespace Barotrauma
Vector2 position = Vector2.Zero;
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
int tries = 0;
do
{
@@ -1398,7 +1413,7 @@ namespace Barotrauma
{
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.IsOutpost) { continue; }
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs);
}
}
@@ -1484,8 +1499,10 @@ namespace Barotrauma
return cells;
}
private readonly List<VoronoiCell> tempCells = new List<VoronoiCell>();
public List<VoronoiCell> GetCells(Vector2 worldPos, int searchDepth = 2)
{
tempCells.Clear();
int gridPosX = (int)Math.Floor(worldPos.X / GridCellSize);
int gridPosY = (int)Math.Floor(worldPos.Y / GridCellSize);
@@ -1495,12 +1512,11 @@ namespace Barotrauma
int startY = Math.Max(gridPosY - searchDepth, 0);
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
List<VoronoiCell> cells = new List<VoronoiCell>();
for (int y = startY; y <= endY; y++)
{
for (int x = startX; x <= endX; x++)
{
cells.AddRange(cellGrid[x, y]);
tempCells.AddRange(cellGrid[x, y]);
}
}
@@ -1508,11 +1524,317 @@ namespace Barotrauma
{
foreach (VoronoiCell cell in wall.Cells)
{
cells.Add(cell);
tempCells.Add(cell);
}
}
return cells;
return tempCells;
}
public string GetWreckIDTag(string originalTag, Submarine wreck)
{
string shortSeed = ToolBox.StringToInt(seed + wreck.Info.Name).ToString();
if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); }
return originalTag + "_" + shortSeed;
}
// For debugging
private readonly Dictionary<Submarine, List<Vector2>> wreckPositions = new Dictionary<Submarine, List<Vector2>>();
private readonly Dictionary<Submarine, List<Rectangle>> blockedRects = new Dictionary<Submarine, List<Rectangle>>();
private void CreateWrecks()
{
var totalSW = new Stopwatch();
var tempSW = new Stopwatch();
totalSW.Start();
var wreckFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Wreck).ToList();
if (wreckFiles.None())
{
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
return;
}
wreckFiles.Shuffle(Rand.RandSync.Server);
int wreckCount = Math.Min(Loaded.GenerationParams.WreckCount, wreckFiles.Count);
// Min distance between a wreck and the start/end/other wreck.
float minDistance = Sonar.DefaultSonarRange;
float squaredMinDistance = minDistance * minDistance;
Vector2 start = startPosition.ToVector2();
Vector2 end = endPosition.ToVector2();
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance &&
Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList();
wrecks = new List<Submarine>(wreckCount);
for (int i = 0; i < wreckCount; i++)
{
ContentFile contentFile = wreckFiles[i];
if (contentFile == null) { continue; }
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
Rectangle borders = Submarine.GetBorders(subDoc.Root);
string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path);
// Add some vertical margin so that the wreck doesn't block the path entirely. It's still possible that some larger subs can't pass by.
Point paddedDimensions = new Point(borders.Width, borders.Height + 3000);
tempSW.Restart();
// For storing the translations. Used only for debugging.
var positions = new List<Vector2>();
var rects = new List<Rectangle>();
int maxAttempts = 50;
int attemptsLeft = maxAttempts;
bool success = false;
Vector2 spawnPoint = Vector2.Zero;
while (attemptsLeft > 0)
{
if (attemptsLeft < maxAttempts)
{
Debug.WriteLine($"Failed to position the wreck {wreckName}. Trying again.");
}
attemptsLeft--;
if (TryGetSpawnPoint(out spawnPoint))
{
success = TryPositionWreck(borders, wreckName, ref spawnPoint);
if (success)
{
break;
}
else
{
positions.Clear();
}
}
else
{
DebugConsole.NewMessage($"Failed to find any spawn point for the wreck: {wreckName} (No valid waypoints left).", Color.Red);
break;
}
}
tempSW.Stop();
if (success)
{
Debug.WriteLine($"Wreck {wreckName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds.ToString()} (ms)");
tempSW.Restart();
SubmarineInfo info = new SubmarineInfo(contentFile.Path)
{
Type = SubmarineInfo.SubmarineType.Wreck
};
Submarine wreck = new Submarine(info);
wreck.MakeWreck();
tempSW.Stop();
Debug.WriteLine($"Wreck {wreck.Info.Name} loaded in { tempSW.ElapsedMilliseconds.ToString()} (ms)");
wrecks.Add(wreck);
wreck.SetPosition(spawnPoint);
wreckPositions.Add(wreck, positions);
blockedRects.Add(wreck, rects);
positionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.Wreck, submarine: wreck));
foreach (Hull hull in wreck.GetHulls(false))
{
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.WreckHullFloodingChance)
{
hull.WaterVolume = hull.Volume * Rand.Range(Loaded.GenerationParams.WreckFloodingHullMinWaterPercentage, Loaded.GenerationParams.WreckFloodingHullMaxWaterPercentage, Rand.RandSync.Server);
}
}
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.ThalamusProbability)
{
if (!wreck.CreateWreckAI())
{
DebugConsole.NewMessage($"Failed to create wreck AI inside {wreckName}.", Color.Red);
wreck.DisableWreckAI();
}
}
else
{
wreck.DisableWreckAI();
}
}
else
{
DebugConsole.NewMessage($"Failed to position wreck {wreckName}. Used {tempSW.ElapsedMilliseconds.ToString()} (ms).", Color.Red);
}
bool TryPositionWreck(Rectangle borders, string wreckName, ref Vector2 spawnPoint)
{
positions.Add(spawnPoint);
bool bottomFound = TryRaycastToBottom(borders, ref spawnPoint);
positions.Add(spawnPoint);
bool leftSideBlocked = IsSideBlocked(borders, false);
bool rightSideBlocked = IsSideBlocked(borders, true);
int step = 5;
if (rightSideBlocked && !leftSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, -step);
}
else if (leftSideBlocked && !rightSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, step);
}
else if (!bottomFound)
{
if (!leftSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, -step);
}
else if (!rightSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, step);
}
else
{
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
return false;
}
}
positions.Add(spawnPoint);
bool isBlocked = IsBlocked(spawnPoint, borders.Size - new Point(step + 50));
if (isBlocked)
{
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint(), borders.Size));
Debug.WriteLine($"Invalid position {spawnPoint}. Blocked by level walls.");
}
else if (!bottomFound)
{
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
}
else
{
var sp = spawnPoint;
if (wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance))
{
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
return false;
}
}
return !isBlocked && bottomFound;
bool TryMove(Rectangle borders, ref Vector2 spawnPoint, float amount)
{
float maxMovement = 5000;
float totalAmount = 0;
bool foundBottom = TryRaycastToBottom(borders, ref spawnPoint);
while (!IsSideBlocked(borders, amount > 0))
{
foundBottom = TryRaycastToBottom(borders, ref spawnPoint);
totalAmount += amount;
spawnPoint = new Vector2(spawnPoint.X + amount, spawnPoint.Y);
if (Math.Abs(totalAmount) > maxMovement)
{
Debug.WriteLine($"Moving the wreck {wreckName} failed.");
break;
}
}
return foundBottom;
}
}
bool TryGetSpawnPoint(out Vector2 spawnPoint)
{
spawnPoint = Vector2.Zero;
while (waypoints.Any())
{
var wp = waypoints.GetRandom(Rand.RandSync.Server);
waypoints.Remove(wp);
if (!IsBlocked(wp.WorldPosition, paddedDimensions))
{
spawnPoint = wp.WorldPosition;
return true;
}
}
return false;
}
static bool TryRaycastToBottom(Rectangle borders, ref Vector2 spawnPoint)
{
// Shoot five rays and pick the highest hit point.
int rayCount = 5;
var positions = new Vector2[rayCount];
bool hit = false;
for (int i = 0; i < rayCount; i++)
{
float quarterWidth = borders.Width * 0.25f;
Vector2 rayStart = spawnPoint;
switch (i)
{
case 1:
rayStart = new Vector2(spawnPoint.X - quarterWidth, spawnPoint.Y);
break;
case 2:
rayStart = new Vector2(spawnPoint.X + quarterWidth, spawnPoint.Y);
break;
case 3:
rayStart = new Vector2(spawnPoint.X - quarterWidth / 2, spawnPoint.Y);
break;
case 4:
rayStart = new Vector2(spawnPoint.X + quarterWidth / 2, spawnPoint.Y);
break;
}
var simPos = ConvertUnits.ToSimUnits(rayStart);
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, -1),
customPredicate: f => f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static,
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
if (body != null)
{
positions[i] = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + new Vector2(0, borders.Height / 2);
hit = true;
}
}
float highestPoint = positions.Max(p => p.Y);
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
return hit;
}
bool IsSideBlocked(Rectangle borders, bool front)
{
// Shoot three rays and check whether any of them hits.
int rayCount = 3;
Vector2 halfSize = borders.Size.ToVector2() / 2;
Vector2 quarterSize = halfSize / 2;
var positions = new Vector2[rayCount];
for (int i = 0; i < rayCount; i++)
{
float dir = front ? 1 : -1;
Vector2 rayStart;
Vector2 to;
switch (i)
{
case 1:
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y + quarterSize.Y);
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
break;
case 2:
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y - quarterSize.Y);
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
break;
case 0:
default:
rayStart = spawnPoint;
to = new Vector2(spawnPoint.X + halfSize.X * dir, rayStart.Y);
break;
}
Vector2 simPos = ConvertUnits.ToSimUnits(rayStart);
if (Submarine.PickBody(simPos, ConvertUnits.ToSimUnits(to),
customPredicate: f => f.Body?.UserData is VoronoiCell cell,
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{
return true;
}
}
return false;
}
bool IsBlocked(Vector2 pos, Point size, float maxDistanceMultiplier = 1)
{
float maxDistance = size.Multiply(maxDistanceMultiplier).ToVector2().LengthSquared();
Rectangle bounds = ToolBox.GetWorldBounds(pos.ToPoint(), size);
if (ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds)))
{
return true;
}
var cells = Loaded.GetAllCells().Where(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance);
return cells.Any(c => c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
}
}
totalSW.Stop();
Debug.WriteLine($"{wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)");
}
private void CreateOutposts()
@@ -1537,20 +1859,20 @@ namespace Barotrauma
continue;
}
Submarine outpost = null;
SubmarineInfo outpostInfo = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server).Path;
outpost = new Submarine(outpostFile, tryLoad: false);
outpostInfo = new SubmarineInfo(outpostFile);
}
else
{
outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
outpostInfo = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
}
outpost.Load(unloadPrevious: false);
outpost.MakeOutpost();
outpostInfo.Type = SubmarineInfo.SubmarineType.Outpost;
var outpost = new Submarine(outpostInfo);
Point? minSize = null;
DockingPort subPort = null;
@@ -1597,9 +1919,9 @@ namespace Barotrauma
if (Math.Abs(subDockingPortOffset) > 5000.0f)
{
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
}
float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
@@ -1607,21 +1929,21 @@ namespace Barotrauma
if (Math.Abs(outpostDockingPortOffset) > 5000.0f)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
}
outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset));
if ((i == 0) == !Mirrored)
{
StartOutpost = outpost;
if (GameMain.GameSession?.StartLocation != null) { outpost.Name = GameMain.GameSession.StartLocation.Name; }
if (GameMain.GameSession?.StartLocation != null) { outpost.Info.Name = GameMain.GameSession.StartLocation.Name; }
}
else
{
EndOutpost = outpost;
if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; }
if (GameMain.GameSession?.EndLocation != null) { outpost.Info.Name = GameMain.GameSession.EndLocation.Name; }
}
}
}
@@ -1635,6 +1957,93 @@ namespace Barotrauma
#endif
}
public void SpawnCorpses()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
foreach (Submarine wreck in wrecks)
{
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount);
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
pathPoints.Shuffle(Rand.RandSync.Unsynced);
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
int spawnCounter = 0;
for (int j = 0; j < corpseCount; j++)
{
WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault();
JobPrefab job = sp?.AssignedJob;
CorpsePrefab selectedPrefab;
if (job == null)
{
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
job = GetJobPrefab();
}
else
{
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier));
if (selectedPrefab == null)
{
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
job = GetJobPrefab();
}
}
if (selectedPrefab == null) { continue; }
Vector2 worldPos;
if (sp == null)
{
if (!TryGetExtraSpawnPoint(out worldPos))
{
break;
}
job = GetJobPrefab();
}
else
{
worldPos = sp.WorldPosition;
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
}
if (job == null) { continue; }
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job);
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
corpse.AnimController.FindHull(worldPos, true);
corpse.TeamID = Character.TeamType.None;
corpse.EnableDespawn = false;
selectedPrefab.GiveItems(corpse, wreck);
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
spawnCounter++;
static CorpsePrefab GetCorpsePrefab(Func<CorpsePrefab, bool> predicate)
{
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate);
return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced);
}
JobPrefab GetJobPrefab() => selectedPrefab.Job != null && selectedPrefab.Job != "any" ? JobPrefab.Get(selectedPrefab.Job) : JobPrefab.Random();
}
#if DEBUG
DebugConsole.NewMessage($"{spawnCounter}/{corpseCount} corpses spawned in {wreck.Info.Name}.", spawnCounter == corpseCount ? Color.Green : Color.Yellow);
#endif
bool TryGetExtraSpawnPoint(out Vector2 point)
{
point = Vector2.Zero;
var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom();
if (hull != null)
{
point = hull.WorldPosition;
}
return hull != null;
}
}
}
public override void Remove()
{
base.Remove();
@@ -9,7 +9,6 @@ namespace Barotrauma
{
class Biome
{
public readonly string Identifier;
public readonly string DisplayName;
public readonly string Description;
@@ -105,8 +104,6 @@ namespace Barotrauma
private int mountainHeightMin, mountainHeightMax;
private int ruinCount;
private float waterParticleScale;
//which biomes can this type of level appear in
@@ -338,12 +335,32 @@ namespace Barotrauma
}
}
[Serialize(1, true, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 50)]
public int RuinCount
{
get { return ruinCount; }
set { ruinCount = MathHelper.Clamp(value, 0, 10); }
}
[Serialize(1, true, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
public int RuinCount { get; set; }
[Serialize(1, true, description: "The maximum number of wrecks in the level. Note that this value cannot be higher than the amount of wreck prefabs (subs)."), Editable(MinValueInt = 0, MaxValueInt = 10)]
public int WreckCount { get; set; }
// TODO: Move the wreck parameters under a separate class?
#region Wreck parameters
[Serialize(1, true, description: "The minimum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
public int MinCorpseCount { get; set; }
[Serialize(5, true, description: "The maximum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
public int MaxCorpseCount { get; set; }
[Serialize(0.0f, true, description: "How likely is it that a Thalamus inhabits a wreck. Percentage from 0 to 1 per wreck."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
public float ThalamusProbability { get; set; }
[Serialize(0.5f, true, description: "How likely the water level of a hull inside a wreck is randomly set."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
public float WreckHullFloodingChance { get; set; }
[Serialize(0.1f, true, description: "The min water percentage of randomly flooding hulls in wrecks."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
public float WreckFloodingHullMinWaterPercentage { get; set; }
[Serialize(1.0f, true, description: "The min water percentage of randomly flooding hulls in wrecks."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
public float WreckFloodingHullMaxWaterPercentage { get; set; }
#endregion
[Serialize(0.4f, true, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
public float BottomHoleProbability
@@ -415,10 +432,10 @@ namespace Barotrauma
string biomeName = biomeNames[i].Trim().ToLowerInvariant();
if (biomeName == "none") { continue; }
Biome matchingBiome = biomes.Find(b => b.Identifier.ToLowerInvariant() == biomeName);
Biome matchingBiome = biomes.Find(b => b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
if (matchingBiome == null)
{
matchingBiome = biomes.Find(b => b.DisplayName.ToLowerInvariant() == biomeName);
matchingBiome = biomes.Find(b => b.DisplayName.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
if (matchingBiome == null)
{
DebugConsole.ThrowError("Error in level generation parameters: biome \"" + biomeName + "\" not found.");
@@ -487,7 +504,7 @@ namespace Barotrauma
mainElement = doc.Root.FirstElement();
biomeElements.Clear();
levelParamElements.Clear();
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
DebugConsole.NewMessage($"Overriding the level generation parameters and biomes with '{file.Path}'", Color.Yellow);
}
else if (biomeElements.Any() || levelParamElements.Any())
{
@@ -497,7 +514,22 @@ namespace Barotrauma
foreach (XElement element in mainElement.Elements())
{
if (element.Name.ToString().ToLowerInvariant() == "biomes")
if (element.IsOverride())
{
if (element.FirstElement().Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
{
biomeElements.Clear();
biomeElements.AddRange(element.FirstElement().Elements());
DebugConsole.NewMessage($"Overriding biomes with '{file.Path}'", Color.Yellow);
}
else
{
levelParamElements.Clear();
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
levelParamElements.AddRange(element.Elements());
}
}
else if (element.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
{
biomeElements.AddRange(element.Elements());
}
@@ -79,11 +79,13 @@ namespace Barotrauma
Vector2[] vertices = new Vector2[4];
vertices[0] = edgePositions[i];
vertices[1] = edgePositions[i + 1];
vertices[2] = vertices[0] + extendAmount;
vertices[3] = vertices[1] + extendAmount;
vertices[2] = vertices[1] + extendAmount;
vertices[3] = vertices[0] + extendAmount;
VoronoiCell wallCell = new VoronoiCell(vertices);
wallCell.CellType = CellType.Edge;
VoronoiCell wallCell = new VoronoiCell(vertices)
{
CellType = CellType.Edge
};
wallCell.Edges[0].Cell1 = wallCell;
wallCell.Edges[1].Cell1 = wallCell;
wallCell.Edges[2].Cell1 = wallCell;
@@ -277,7 +277,7 @@ namespace Barotrauma.RuinGeneration
{
foreach (XElement subElement in element2.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "chooseone")
if (subElement.Name.ToString().Equals("chooseone", StringComparison.OrdinalIgnoreCase))
{
groupIndex++;
LoadEntities(subElement, ref groupIndex);
@@ -390,7 +390,7 @@ namespace Barotrauma.RuinGeneration
SourceEntityIdentifier = element.GetAttributeString("sourceentity", "");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "wire")
if (subElement.Name.ToString().Equals("wire", StringComparison.OrdinalIgnoreCase))
{
WireConnection = new Pair<string, string>(
subElement.GetAttributeString("from", ""),
@@ -19,18 +19,17 @@ namespace Barotrauma
//Prefabs.Remove(this);
}
public readonly Submarine mainSub;
public readonly SubmarineInfo subInfo;
public LinkedSubmarinePrefab(Submarine submarine)
public LinkedSubmarinePrefab(SubmarineInfo subInfo)
{
this.mainSub = submarine;
this.subInfo = subInfo;
}
protected override void CreateInstance(Rectangle rect)
{
System.Diagnostics.Debug.Assert(Submarine.MainSub != null);
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2());
LinkedSubmarine.CreateDummy(Submarine.MainSub, subInfo.FilePath, rect.Location.ToVector2());
}
}
@@ -92,7 +91,7 @@ namespace Barotrauma
public static LinkedSubmarine CreateDummy(Submarine mainSub, string filePath, Vector2 position)
{
XDocument doc = Submarine.OpenFile(filePath);
XDocument doc = SubmarineInfo.OpenFile(filePath);
if (doc == null || doc.Root == null) return null;
LinkedSubmarine sl = CreateDummy(mainSub, doc.Root, position);
@@ -113,7 +112,9 @@ namespace Barotrauma
(int)sl.wallVertices.Max(v => v.X + position.X),
(int)sl.wallVertices.Min(v => v.Y + position.Y));
sl.Rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height);
int width = sl.rect.Width - sl.rect.X;
int height = sl.rect.Y - sl.rect.Height;
sl.Rect = new Rectangle((int)(position.X - width / 2), (int)(position.Y + height / 2), width, height);
}
else
{
@@ -162,8 +163,7 @@ namespace Barotrauma
public static LinkedSubmarine Load(XElement element, Submarine submarine)
{
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
LinkedSubmarine linkedSub = null;
LinkedSubmarine linkedSub;
if (Screen.Selected == GameMain.SubEditorScreen)
{
linkedSub = CreateDummy(submarine, element, pos);
@@ -198,26 +198,32 @@ namespace Barotrauma
for (int i = 0; i < linkedToIds.Length; i++)
{
linkedSub.linkedToID.Add((ushort)linkedToIds[i]);
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked)
{
linkedSub.linkedTo.Add(linked);
}
}
}
linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0);
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
return linkedSub.loadSub ? linkedSub : null;
}
public void LinkDummyToMainSubmarine()
{
if (Screen.Selected != GameMain.SubEditorScreen) { return; }
for (int i = 0; i < linkedToID.Count; i++)
{
if (FindEntityByID(linkedToID[i]) is MapEntity linked)
{
linkedTo.Add(linked);
}
}
}
public override void OnMapLoaded()
{
if (!loadSub) { return; }
sub = Submarine.Load(saveElement, false);
SubmarineInfo info = new SubmarineInfo(Submarine.Info.FilePath, "", saveElement);
sub = Submarine.Load(info, false);
Vector2 worldPos = saveElement.GetAttributeVector2("worldpos", Vector2.Zero);
if (worldPos != Vector2.Zero)
@@ -330,7 +336,7 @@ namespace Barotrauma
{
if (this.saveElement == null)
{
var doc = Submarine.OpenFile(filePath);
var doc = SubmarineInfo.OpenFile(filePath);
saveElement = doc.Root;
saveElement.Name = "LinkedSubmarine";
saveElement.Add(new XAttribute("filepath", filePath));
@@ -284,7 +284,7 @@ namespace Barotrauma
foreach (LocationConnection connection in connections)
{
float centerDist = Vector2.Distance(connection.CenterPos, mapCenter);
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100);
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 0, 100);
}
AssignBiomes();
@@ -463,7 +463,7 @@ namespace Barotrauma
bool disallowedFound = false;
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.ToLowerInvariant() == disallowedLocationName.ToLowerInvariant()))
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(disallowedLocationName, StringComparison.OrdinalIgnoreCase)))
{
disallowedFound = true;
break;
@@ -475,7 +475,7 @@ namespace Barotrauma
bool requiredFound = false;
foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.ToLowerInvariant() == requiredLocationName.ToLowerInvariant()))
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(requiredLocationName, StringComparison.OrdinalIgnoreCase)))
{
requiredFound = true;
break;
@@ -499,7 +499,7 @@ namespace Barotrauma
if (selectedTypeChange != null)
{
string prevName = location.Name;
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.ToLowerInvariant() == selectedTypeChange.ChangeToType.ToLowerInvariant()));
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase)));
ChangeLocationType(location, prevName, selectedTypeChange);
location.TypeChangeTimer = -1;
break;
@@ -553,13 +553,12 @@ namespace Barotrauma
string prevLocationName = location.Name;
LocationType prevLocationType = location.Type;
location.Discovered = true;
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.ToLowerInvariant() == locationType.ToLowerInvariant()));
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)));
location.TypeChangeTimer = typeChangeTimer;
location.MissionsCompleted = missionsCompleted;
if (showNotifications && prevLocationType != location.Type)
{
var change = prevLocationType.CanChangeTo.Find(c =>
c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant());
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
if (change != null)
{
ChangeLocationType(location, prevLocationName, change);
@@ -36,8 +36,6 @@ namespace Barotrauma
//is the mouse inside the rect
private bool isHighlighted;
public event Action<Rectangle> Resized;
public bool IsHighlighted
{
get { return isHighlighted || ExternalHighlight; }
@@ -115,6 +113,40 @@ namespace Barotrauma
}
}
// We could use NaN or nullables, but in this case the first is not preferable, because it needs to be checked every time the value is used.
// Nullable on the other requires boxing that we don't want to do too often, since it generates garbage.
public bool SpriteDepthOverrideIsSet { get; private set; }
public float SpriteOverrideDepth => SpriteDepth;
private float _spriteOverrideDepth = float.NaN;
[Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, true)]
public float SpriteDepth
{
get
{
if (SpriteDepthOverrideIsSet) { return _spriteOverrideDepth; }
return Sprite != null ? Sprite.Depth : 0;
}
set
{
if (!float.IsNaN(value))
{
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999f);
if (this is Item) { _spriteOverrideDepth = Math.Min(_spriteOverrideDepth, 0.9f); }
SpriteDepthOverrideIsSet = true;
}
}
}
[Serialize(1f, true), Editable(0.01f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
public virtual float Scale { get; set; } = 1;
[Editable, Serialize(false, true)]
public bool HiddenInGame
{
get;
set;
}
public override Vector2 Position
{
get
@@ -175,9 +207,6 @@ namespace Barotrauma
get { return ""; }
}
// Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation.
private Memento<Rectangle> rectMemento;
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
{
this.prefab = prefab;
@@ -560,34 +589,5 @@ namespace Barotrauma
}
}
}
#region Serialized properties
// We could use NaN or nullables, but in this case the first is not preferable, because it needs to be checked every time the value is used.
// Nullable on the other requires boxing that we don't want to do too often, since it generates garbage.
public bool SpriteDepthOverrideIsSet { get; private set; }
public float SpriteOverrideDepth => SpriteDepth;
private float _spriteOverrideDepth = float.NaN;
[Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, true)]
public float SpriteDepth
{
get
{
if (SpriteDepthOverrideIsSet) { return _spriteOverrideDepth; }
return Sprite != null ? Sprite.Depth : 0;
}
set
{
if (!float.IsNaN(value))
{
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999f);
if (this is Item) { _spriteOverrideDepth = Math.Min(_spriteOverrideDepth, 0.9f); }
SpriteDepthOverrideIsSet = true;
}
}
}
[Serialize(1f, true), Editable(0.01f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
public virtual float Scale { get; set; } = 1;
#endregion
}
}
@@ -10,7 +10,7 @@ namespace Barotrauma
[Flags]
enum MapEntityCategory
{
Structure = 1, Decorative = 2, Machine = 4, Equipment = 8, Electrical = 16, Material = 32, Misc = 64, Alien = 128, ItemAssembly = 256, Legacy = 512
Structure = 1, Decorative = 2, Machine = 4, Equipment = 8, Electrical = 16, Material = 32, Misc = 64, Alien = 128, Wrecked = 256, Thalamus = 512, ItemAssembly = 1024, Legacy = 2048
}
abstract partial class MapEntityPrefab : IPrefab, IDisposable
@@ -242,23 +242,35 @@ namespace Barotrauma
/// <param name="identifier">The identifier of the item (if null, the identifier is ignored and the search is done only based on the name)</param>
public static MapEntityPrefab Find(string name, string identifier = null, bool showErrorMessages = true)
{
if (name != null) name = name.ToLowerInvariant();
if (name != null)
{
name = name.ToLowerInvariant();
}
foreach (MapEntityPrefab prefab in List)
{
if (identifier != null)
{
if (prefab.identifier != identifier)
{
if (prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(identifier, StringComparison.OrdinalIgnoreCase)))
{
return prefab;
}
continue;
}
else
{
if (string.IsNullOrEmpty(name)) return prefab;
if (string.IsNullOrEmpty(name)) { return prefab; }
}
}
if (!string.IsNullOrEmpty(name))
{
if (prefab.Name.ToLowerInvariant() == name || prefab.originalName.ToLowerInvariant() == name || (prefab.Aliases != null && prefab.Aliases.Any(a => a.ToLowerInvariant() == name))) return prefab;
if (prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
prefab.originalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
(prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))))
{
return prefab;
}
}
}
@@ -281,27 +293,9 @@ namespace Barotrauma
/// <summary>
/// Check if the name or any of the aliases of this prefab match the given name.
/// </summary>
public bool NameMatches(string name, bool caseSensitive = false)
{
if (caseSensitive)
{
return this.originalName == name || (Aliases != null && Aliases.Any(a => a == name));
}
else
{
name = name.ToLowerInvariant();
return this.originalName.ToLowerInvariant() == name || (Aliases != null && Aliases.Any(a => a.ToLowerInvariant() == name));
}
}
public bool NameMatches(string name, StringComparison comparisonType) => originalName.Equals(name, comparisonType) || (Aliases != null && Aliases.Any(a => a.Equals(name, comparisonType)));
public bool NameMatches(IEnumerable<string> allowedNames, bool caseSensitive = false)
{
foreach (string name in allowedNames)
{
if (NameMatches(name, caseSensitive)) return true;
}
return false;
}
public bool NameMatches(IEnumerable<string> allowedNames, StringComparison comparisonType) => allowedNames.Any(n => NameMatches(n, comparisonType));
public bool IsLinkAllowed(MapEntityPrefab target)
{
@@ -56,6 +56,15 @@ namespace Barotrauma
Level.Loaded.TopBarrier.Enabled = false;
foreach (Character character in Character.CharacterList)
{
character.AnimController.Frozen = true;
foreach (Limb limb in character.AnimController.Limbs)
{
limb.body.PhysEnabled = false;
}
}
cam.TargetPos = Vector2.Zero;
float timer = 0.0f;
float initialZoom = cam.Zoom;
@@ -322,7 +322,7 @@ namespace Barotrauma
: base(sp, submarine)
{
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
if (rectangle.Width == 0 || rectangle.Height == 0) return;
if (rectangle.Width == 0 || rectangle.Height == 0) { return; }
defaultRect = rectangle;
rect = rectangle;
@@ -358,27 +358,30 @@ namespace Barotrauma
InitProjSpecific();
if (Prefab.Body)
if (!HiddenInGame)
{
Bodies = new List<Body>();
WallList.Add(this);
CreateSections();
UpdateSections();
}
else
{
Sections = new WallSection[1];
Sections[0] = new WallSection(rect);
if (StairDirection != Direction.None)
if (Prefab.Body)
{
CreateStairBodies();
Bodies = new List<Body>();
WallList.Add(this);
CreateSections();
UpdateSections();
}
else
{
Sections = new WallSection[1];
Sections[0] = new WallSection(rect);
if (StairDirection != Direction.None)
{
CreateStairBodies();
}
}
}
// Only add ai targets automatically to submarine/outpost walls
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !Prefab.NoAITarget)
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !Prefab.NoAITarget)
{
aiTarget = new AITarget(this)
{
@@ -910,7 +913,7 @@ namespace Barotrauma
//the structure doesn't have any other gap, log the structure being fixed
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + GameServer.CharacterLogName(attacker), ServerLog.MessageType.ItemInteraction);
}
#endif
DebugConsole.Log("Removing gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID);
@@ -984,11 +987,11 @@ namespace Barotrauma
//the structure didn't have any other gaps yet, log the breach
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + GameServer.CharacterLogName(attacker), ServerLog.MessageType.ItemInteraction);
}
#endif
}
float gapOpen = (damage / Prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
Sections[sectionIndex].gap.Open = gapOpen;
}
@@ -1216,9 +1219,9 @@ namespace Barotrauma
SerializableProperty.DeserializeProperties(s, element);
if (submarine?.GameVersion != null)
if (submarine?.Info.GameVersion != null)
{
SerializableProperty.UpgradeGameVersion(s, s.Prefab.ConfigElement, submarine.GameVersion);
SerializableProperty.UpgradeGameVersion(s, s.Prefab.ConfigElement, submarine.Info.GameVersion);
}
foreach (XElement subElement in element.Elements())
@@ -1322,6 +1325,8 @@ namespace Barotrauma
public virtual void Reset()
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
Sprite.ReloadXML();
SpriteDepth = Sprite.Depth;
}
public override void Update(float deltaTime, Camera cam)
@@ -4,6 +4,7 @@ using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using System.IO;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
#endif
@@ -231,6 +232,23 @@ namespace Barotrauma
sp.name = sp.originalName;
sp.ConfigElement = element;
sp.identifier = element.GetAttributeString("identifier", "");
var parentType = element.Parent?.GetAttributeString("prefabtype", "") ?? string.Empty;
string nameIdentifier = element.GetAttributeString("nameidentifier", "");
if (string.IsNullOrEmpty(sp.originalName))
{
if (string.IsNullOrEmpty(nameIdentifier))
{
sp.name = TextManager.Get("EntityName." + sp.identifier, true) ?? string.Empty;
}
else
{
sp.name = TextManager.Get("EntityName." + nameIdentifier, true) ?? string.Empty;
}
}
if (string.IsNullOrEmpty(sp.name))
{
sp.name = TextManager.Get("EntityName." + sp.identifier, returnNull: true) ?? $"Not defined ({sp.identifier})";
@@ -258,17 +276,15 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!");
}
#if CLIENT
if (subElement.GetAttributeBool("fliphorizontal", false))
if (subElement.GetAttributeBool("fliphorizontal", false))
sp.sprite.effects = SpriteEffects.FlipHorizontally;
if (subElement.GetAttributeBool("flipvertical", false))
if (subElement.GetAttributeBool("flipvertical", false))
sp.sprite.effects = SpriteEffects.FlipVertically;
#endif
sp.canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
sp.canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
if (subElement.Attribute("name") == null && !string.IsNullOrWhiteSpace(sp.Name))
{
sp.sprite.Name = sp.Name;
@@ -286,19 +302,52 @@ namespace Barotrauma
sp.BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
}
#if CLIENT
if (subElement.GetAttributeBool("fliphorizontal", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally;
if (subElement.GetAttributeBool("flipvertical", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipVertically;
if (subElement.GetAttributeBool("fliphorizontal", false)) { sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally; }
if (subElement.GetAttributeBool("flipvertical", false)) { sp.BackgroundSprite.effects = SpriteEffects.FlipVertically; }
sp.BackgroundSpriteColor = subElement.GetAttributeColor("color", Color.White);
#endif
break;
case "decorativesprite":
#if CLIENT
string decorativeSpriteFolder = "";
if (!subElement.GetAttributeString("texture", "").Contains("/"))
{
decorativeSpriteFolder = Path.GetDirectoryName(file.Path);
}
int groupID = 0;
DecorativeSprite decorativeSprite = null;
if (subElement.Attribute("texture") == null)
{
groupID = subElement.GetAttributeInt("randomgroupid", 0);
}
else
{
decorativeSprite = new DecorativeSprite(subElement, decorativeSpriteFolder, lazyLoad: true);
sp.DecorativeSprites.Add(decorativeSprite);
groupID = decorativeSprite.RandomGroupID;
}
if (!sp.DecorativeSpriteGroups.ContainsKey(groupID))
{
sp.DecorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
}
sp.DecorativeSpriteGroups[groupID].Add(decorativeSprite);
#endif
break;
}
}
if (string.Equals(parentType, "wrecked", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrEmpty(sp.Name))
{
sp.name = TextManager.GetWithVariable("wreckeditemformat", "[name]", sp.name);
}
}
if (!Enum.TryParse(element.GetAttributeString("category", "Structure"), true, out MapEntityCategory category))
{
category = MapEntityCategory.Structure;
category = MapEntityCategory.Structure;
}
sp.Category = category;
@@ -325,7 +374,14 @@ namespace Barotrauma
if (string.IsNullOrEmpty(sp.Description))
{
sp.Description = TextManager.Get("EntityDescription." + sp.identifier, returnNull: true) ?? string.Empty;
if (string.IsNullOrEmpty(nameIdentifier))
{
sp.Description = TextManager.Get("EntityDescription." + sp.identifier, returnNull: true) ?? string.Empty;
}
else
{
sp.Description = TextManager.Get("EntityDescription." + nameIdentifier, true) ?? string.Empty;
}
}
//backwards compatibility
File diff suppressed because it is too large Load Diff
@@ -131,7 +131,7 @@ namespace Barotrauma
farseerBody.UserData = this;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
if (wall.Submarine != submarine || wall.IsPlatform) { continue; }
Rectangle rect = wall.Rect;
@@ -150,7 +150,7 @@ namespace Barotrauma
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != submarine) continue;
if (hull.Submarine != submarine) { continue; }
Rectangle rect = hull.Rect;
farseerBody.CreateRectangle(
@@ -167,7 +167,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
if (item.StaticBodyConfig == null || item.Submarine != submarine) continue;
if (item.StaticBodyConfig == null || item.Submarine != submarine) { continue; }
float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale;
float width = item.StaticBodyConfig.GetAttributeFloat("width", 0.0f) * item.Scale;
@@ -180,7 +180,7 @@ namespace Barotrauma
if (width > 0.0f && height > 0.0f)
{
farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y);
@@ -189,9 +189,9 @@ namespace Barotrauma
}
else if (radius > 0.0f && width > 0.0f)
{
farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2));
minExtents.X = Math.Min(item.Position.X - width / 2 - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + width / 2 + radius, maxExtents.X);
@@ -199,9 +199,9 @@ namespace Barotrauma
}
else if (radius > 0.0f && height > 0.0f)
{
farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2));
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
@@ -209,12 +209,13 @@ namespace Barotrauma
}
else if (radius > 0.0f)
{
farseerBody.CreateCircle(simRadius, 5.0f, simPos).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos));
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
}
item.StaticFixtures.ForEach(f => f.UserData = item);
}
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
@@ -521,9 +522,16 @@ namespace Barotrauma
Vector2 normalizedVel = character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
Vector2.Zero : Vector2.Normalize(character.AnimController.Collider.LinearVelocity);
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
//try to find the hull right next to the contact point
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal * 0.1f);
Hull newHull = Hull.FindHull(targetPos, null);
//not found, try searching a bit further
if (newHull == null)
{
targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
newHull = Hull.FindHull(targetPos, null);
}
//still not found, try searching in the direction the character is heading to
if (newHull == null)
{
targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
@@ -771,7 +779,7 @@ namespace Barotrauma
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
if (!submarine.IsOutpost && !submarine.DockedTo.Any(s => s.IsOutpost))
if (submarine.Info.Type == SubmarineInfo.SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineInfo.SubmarineType.Player))
{
float angularVelocity =
(impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y
@@ -0,0 +1,611 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
[Flags]
public enum SubmarineTag
{
[Description("Shuttle")]
Shuttle = 1,
[Description("Hide in menus")]
HideInMenus = 2
}
partial class SubmarineInfo : IDisposable
{
public const string SavePath = "Submarines";
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
public static IEnumerable<SubmarineInfo> SavedSubmarines
{
get { return savedSubmarines; }
}
private Task hashTask;
private Md5Hash hash;
public readonly DateTime LastModifiedTime;
public SubmarineTag Tags { get; private set; }
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
public string RecommendedCrewExperience;
public HashSet<string> RequiredContentPackages = new HashSet<string>();
public string Name
{
get;
set;
}
public string DisplayName
{
get;
set;
}
public string Description
{
get;
set;
}
public Version GameVersion
{
get;
set;
}
public bool IsOutpost => Type == SubmarineType.Outpost;
public bool IsWreck => Type == SubmarineType.Wreck;
public bool IsPlayer => Type == SubmarineType.Player;
public enum SubmarineType { Player, Outpost, Wreck }
public SubmarineType Type { get; set; }
public Md5Hash MD5Hash
{
get
{
if (hash == null)
{
XDocument doc = OpenFile(FilePath);
StartHashDocTask(doc);
hashTask.Wait();
hashTask = null;
}
return hash;
}
}
public Vector2 Dimensions
{
get;
private set;
}
public string FilePath
{
get;
set;
}
public readonly XElement SubmarineElement;
public override string ToString()
{
return "Barotrauma.SubmarineInfo (" + Name + ")";
}
public bool IsFileCorrupted
{
get;
private set;
}
private bool? requiredContentPackagesInstalled;
public bool RequiredContentPackagesInstalled
{
get
{
if (requiredContentPackagesInstalled.HasValue) { return requiredContentPackagesInstalled.Value; }
return RequiredContentPackages.All(cp => GameMain.SelectedPackages.Any(cp2 => cp2.Name == cp));
}
set
{
requiredContentPackagesInstalled = value;
}
}
private bool? subsLeftBehind;
public bool SubsLeftBehind
{
get
{
if (subsLeftBehind.HasValue) { return subsLeftBehind.Value; }
CheckSubsLeftBehind(SubmarineElement);
return subsLeftBehind.Value;
}
}
public bool LeftBehindSubDockingPortOccupied
{
get; private set;
}
//constructors & generation ----------------------------------------------------
public SubmarineInfo()
{
FilePath = null;
Name = DisplayName = TextManager.Get("UnspecifiedSubFileName");
IsFileCorrupted = false;
RequiredContentPackages = new HashSet<string>();
}
public SubmarineInfo(string filePath, string hash = "", XElement element = null, bool tryLoad = true)
{
FilePath = filePath;
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
LastModifiedTime = File.GetLastWriteTime(filePath);
}
try
{
Name = DisplayName = Path.GetFileNameWithoutExtension(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading submarine " + filePath + "!", e);
}
if (!string.IsNullOrWhiteSpace(hash))
{
this.hash = new Md5Hash(hash);
}
IsFileCorrupted = false;
RequiredContentPackages = new HashSet<string>();
if (element == null && tryLoad)
{
XDocument doc = null;
int maxLoadRetries = 4;
for (int i = 0; i <= maxLoadRetries; i++)
{
doc = OpenFile(filePath, out Exception e);
if (e != null && !(e is IOException)) { break; }
if (doc != null || i == maxLoadRetries || !File.Exists(filePath)) { break; }
DebugConsole.NewMessage("Opening submarine file \"" + filePath + "\" failed, retrying in 250 ms...");
Thread.Sleep(250);
}
if (doc == null || doc.Root == null)
{
IsFileCorrupted = true;
return;
}
if (string.IsNullOrWhiteSpace(hash))
{
StartHashDocTask(doc);
}
SubmarineElement = doc.Root;
}
else
{
SubmarineElement = element;
}
Name = SubmarineElement.GetAttributeString("name", null) ?? Name;
Init();
}
public SubmarineInfo(Submarine sub) : this(sub.Info)
{
SubmarineElement = new XElement("Submarine");
sub.SaveToXElement(SubmarineElement);
Init();
}
public SubmarineInfo(SubmarineInfo original)
{
Name = original.Name;
DisplayName = original.DisplayName;
Description = original.Description;
GameVersion = original.GameVersion;
Type = original.Type;
hash = !string.IsNullOrEmpty(original.FilePath) ? original.MD5Hash : null;
Dimensions = original.Dimensions;
FilePath = original.FilePath;
RequiredContentPackages = new HashSet<string>(original.RequiredContentPackages);
IsFileCorrupted = original.IsFileCorrupted;
SubmarineElement = original.SubmarineElement;
RecommendedCrewExperience = original.RecommendedCrewExperience;
RecommendedCrewSizeMin = original.RecommendedCrewSizeMin;
RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
Tags = original.Tags;
#if CLIENT
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage.Texture, null, null) : null;
#endif
}
private void Init()
{
DisplayName = TextManager.Get("Submarine.Name." + Name, true);
if (string.IsNullOrEmpty(DisplayName)) { DisplayName = Name; }
Description = TextManager.Get("Submarine.Description." + Name, true);
if (string.IsNullOrEmpty(Description)) { Description = SubmarineElement.GetAttributeString("description", ""); }
GameVersion = new Version(SubmarineElement.GetAttributeString("gameversion", "0.0.0.0"));
if (Enum.TryParse(SubmarineElement.GetAttributeString("tags", ""), out SubmarineTag tags))
{
Tags = tags;
}
Dimensions = SubmarineElement.GetAttributeVector2("dimensions", Vector2.Zero);
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
//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", new string[0]);
foreach (string contentPackageName in contentPackageNames)
{
RequiredContentPackages.Add(contentPackageName);
}
InitProjectSpecific();
}
partial void InitProjectSpecific();
public void Dispose()
{
if (savedSubmarines.Contains(this)) { savedSubmarines.Remove(this); }
}
public bool IsVanillaSubmarine()
{
var vanilla = GameMain.VanillaContent;
if (vanilla != null)
{
var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine);
string pathToCompare = FilePath.Replace(@"\", @"/").ToLowerInvariant();
if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare))
{
return true;
}
}
return false;
}
public void StartHashDocTask(XDocument doc)
{
if (hash != null) { return; }
if (hashTask != null) { return; }
hashTask = new Task(() =>
{
hash = new Md5Hash(doc, FilePath);
});
hashTask.Start();
}
public bool HasTag(SubmarineTag tag)
{
return Tags.HasFlag(tag);
}
public void AddTag(SubmarineTag tag)
{
if (Tags.HasFlag(tag)) return;
Tags |= tag;
}
public void RemoveTag(SubmarineTag tag)
{
if (!Tags.HasFlag(tag)) return;
Tags &= ~tag;
}
public void CheckSubsLeftBehind(XElement element = null)
{
if (element == null) { element = SubmarineElement; }
subsLeftBehind = false;
LeftBehindSubDockingPortOccupied = false;
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("linkedsubmarine", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Attribute("location") == null) { continue; }
subsLeftBehind = true;
ushort targetDockingPortID = (ushort)subElement.GetAttributeInt("originallinkedto", 0);
XElement targetPortElement = targetDockingPortID == 0 ? null :
element.Elements().FirstOrDefault(e => e.GetAttributeInt("ID", 0) == targetDockingPortID);
if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", new int[0]).Length > 0)
{
LeftBehindSubDockingPortOccupied = true;
}
}
}
//saving/loading ----------------------------------------------------
public bool SaveAs(string filePath, MemoryStream previewImage=null)
{
var newElement = new XElement(SubmarineElement.Name,
SubmarineElement.Attributes().Where(a => !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase)),
SubmarineElement.Elements());
XDocument doc = new XDocument(newElement);
if (previewImage != null)
{
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
}
try
{
SaveUtil.CompressStringToFile(filePath, doc.ToString());
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving submarine \"" + filePath + "\" failed!", e);
return false;
}
return true;
}
public static void AddToSavedSubs(SubmarineInfo subInfo)
{
savedSubmarines.Add(subInfo);
}
public static void RefreshSavedSub(string filePath)
{
string fullPath = Path.GetFullPath(filePath);
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
{
if (Path.GetFullPath(savedSubmarines[i].FilePath) == fullPath)
{
savedSubmarines[i].Dispose();
}
}
if (File.Exists(filePath))
{
var subInfo = new SubmarineInfo(filePath);
if (!subInfo.IsFileCorrupted)
{
savedSubmarines.Add(subInfo);
}
savedSubmarines = savedSubmarines.OrderBy(s => s.FilePath ?? "").ToList();
}
}
public static void RefreshSavedSubs()
{
var contentPackageSubs = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Submarine);
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
{
if (File.Exists(savedSubmarines[i].FilePath) &&
savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) &&
(Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath) ||
contentPackageSubs.Any(fp => Path.GetFullPath(fp.Path).CleanUpPath() == Path.GetFullPath(savedSubmarines[i].FilePath).CleanUpPath())))
{
continue;
}
savedSubmarines[i].Dispose();
}
if (!Directory.Exists(SavePath))
{
try
{
Directory.CreateDirectory(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Directory \"" + SavePath + "\" not found and creating the directory failed.", e);
return;
}
}
List<string> filePaths;
string[] subDirectories;
try
{
filePaths = Directory.GetFiles(SavePath).ToList();
subDirectories = Directory.GetDirectories(SavePath).Where(s =>
{
DirectoryInfo dir = new DirectoryInfo(s);
return (dir.Attributes & FileAttributes.Hidden) == 0;
}).ToArray();
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open directory \"" + SavePath + "\"!", e);
return;
}
foreach (string subDirectory in subDirectories)
{
try
{
filePaths.AddRange(Directory.GetFiles(subDirectory).ToList());
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open subdirectory \"" + subDirectory + "\"!", e);
return;
}
}
foreach (ContentFile subFile in contentPackageSubs)
{
if (!filePaths.Any(fp => Path.GetFullPath(fp) == Path.GetFullPath(subFile.Path)))
{
filePaths.Add(subFile.Path);
}
}
filePaths.RemoveAll(p => savedSubmarines.Any(sub => sub.FilePath == p));
foreach (string path in filePaths)
{
var subInfo = new SubmarineInfo(path);
if (subInfo.IsFileCorrupted)
{
#if CLIENT
if (DebugConsole.IsOpen) { DebugConsole.Toggle(); }
var deleteSubPrompt = new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariable("SubLoadError", "[subname]", subInfo.Name) + "\n" +
TextManager.GetWithVariable("DeleteFileVerification", "[filename]", subInfo.Name),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
string filePath = path;
deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e);
}
deleteSubPrompt.Close();
return true;
};
deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close;
#endif
}
else
{
savedSubmarines.Add(subInfo);
}
}
}
static readonly string TempFolder = Path.Combine("Submarine", "Temp");
public static XDocument OpenFile(string file)
{
return OpenFile(file, out _);
}
public static XDocument OpenFile(string file, out Exception exception)
{
XDocument doc = null;
string extension = "";
exception = null;
try
{
extension = System.IO.Path.GetExtension(file);
}
catch
{
//no file extension specified: try using the default one
file += ".sub";
}
if (string.IsNullOrWhiteSpace(extension))
{
extension = ".sub";
file += ".sub";
}
if (extension == ".sub")
{
Stream stream = null;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
}
catch (FileNotFoundException e)
{
exception = e;
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (File not found) " + Environment.StackTrace, e);
return null;
}
catch (Exception e)
{
exception = e;
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed!", e);
return null;
}
try
{
stream.Position = 0;
doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file);
stream.Close();
stream.Dispose();
}
catch (Exception e)
{
exception = e;
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
return null;
}
}
else if (extension == ".xml")
{
try
{
ToolBox.IsProperFilenameCase(file);
doc = XDocument.Load(file, LoadOptions.SetBaseUri);
}
catch (Exception e)
{
exception = e;
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
return null;
}
}
else
{
DebugConsole.ThrowError("Couldn't load submarine \"" + file + "! (Unrecognized file extension)");
return null;
}
return doc;
}
}
}
@@ -4,13 +4,14 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.RuinGeneration;
using Barotrauma.Extensions;
namespace Barotrauma
{
public enum SpawnType { Path, Human, Enemy, Cargo };
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 3, Corpse = 4 };
partial class WayPoint : MapEntity
{
public static List<WayPoint> WayPointList = new List<WayPoint>();
@@ -121,9 +122,16 @@ namespace Barotrauma
idCardTags = new string[0];
#if CLIENT
if (iconTexture == null)
if (iconSprites == null)
{
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
iconSprites = new Dictionary<SpawnType, Sprite>()
{
{ SpawnType.Path, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,0,128,128)) },
{ SpawnType.Human, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,0,128,128)) },
{ SpawnType.Enemy, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256,0,128,128)) },
{ SpawnType.Cargo, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) },
{ SpawnType.Corpse, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512,0,128,128)) }
};
}
#endif
@@ -148,21 +156,12 @@ namespace Barotrauma
return clone;
}
public override bool IsMouseOn(Vector2 position)
{
#if CLIENT
if (IsHidden()) return false;
#endif
return base.IsMouseOn(position);
}
public static void GenerateSubWaypoints(Submarine submarine)
public static bool GenerateSubWaypoints(Submarine submarine)
{
if (!Hull.hullList.Any())
{
DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
return;
return false;
}
List<WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path);
@@ -465,6 +464,8 @@ namespace Barotrauma
{
door.Body.Enabled = false;
}
return true;
}
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null)
@@ -520,22 +521,14 @@ namespace Barotrauma
if (!wayPoint2.linkedTo.Contains(this)) wayPoint2.linkedTo.Add(this);
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, bool useSyncedRand = false)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
{
List<WayPoint> wayPoints = new List<WayPoint>();
foreach (WayPoint wp in WayPointList)
{
if (sub != null && wp.Submarine != sub) continue;
if (wp.spawnType != spawnType) continue;
if (assignedJob != null && wp.assignedJob != assignedJob.Prefab) continue;
wayPoints.Add(wp);
}
if (!wayPoints.Any()) return null;
return wayPoints[Rand.Int(wayPoints.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))];
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
wp.ParentRuin == ruin &&
wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.assignedJob == assignedJob.Prefab))
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
@@ -584,7 +577,7 @@ namespace Barotrauma
if (assignedWayPoints[i] != null) continue;
//everything else failed -> just give a random spawnpoint inside the sub
assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, true);
assignedWayPoints[i] = GetRandom(SpawnType.Human, null, submarine, useSyncedRand: true);
}
for (int i = 0; i < assignedWayPoints.Length; i++)
@@ -654,7 +647,7 @@ namespace Barotrauma
{
w.assignedJob =
JobPrefab.Get(jobIdentifier) ??
JobPrefab.Prefabs.Find(jp => jp.Name.ToLowerInvariant() == jobIdentifier);
JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase));
}
w.ladderId = (ushort)element.GetAttributeInt("ladders", 0);