(bcb06cc5c) Unstable v0.9.9.0
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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)
|
||||
{
|
||||
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), Rand.RandSync.Unsynced);
|
||||
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
|
||||
{
|
||||
InitializeItems(character, itemElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeItems(Character character, XElement itemElement, 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.AddTag("name:" + character.Name);
|
||||
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, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = hull1.Volume * Hull.MaxCompress * 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;
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
|
||||
@@ -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,6 +320,7 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
|
||||
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
|
||||
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
|
||||
minWidth = Math.Min(minWidth, maxWidth);
|
||||
}
|
||||
@@ -668,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
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -691,6 +701,12 @@ namespace Barotrauma
|
||||
|
||||
GenerateSeaFloor(mirror);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create wrecks
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
CreateWrecks();
|
||||
|
||||
levelObjectManager.PlaceObjects(this, generationParams.LevelObjectAmount);
|
||||
|
||||
GenerateItems();
|
||||
@@ -1163,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;
|
||||
@@ -1200,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++;
|
||||
}
|
||||
}
|
||||
@@ -1214,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)
|
||||
@@ -1327,8 +1345,6 @@ namespace Barotrauma
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
|
||||
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
|
||||
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
@@ -1397,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);
|
||||
}
|
||||
}
|
||||
@@ -1483,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);
|
||||
|
||||
@@ -1494,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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1507,11 +1524,310 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (VoronoiCell cell in wall.Cells)
|
||||
{
|
||||
cells.Add(cell);
|
||||
tempCells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
return tempCells;
|
||||
}
|
||||
|
||||
// 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.Load(unloadPrevious: false);
|
||||
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.CreateThalamus())
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to create thalamus inside {wreckName}.", Color.Red);
|
||||
wreck.DisableThalamus();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wreck.DisableThalamus();
|
||||
}
|
||||
}
|
||||
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()
|
||||
@@ -1536,20 +1852,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;
|
||||
@@ -1596,9 +1912,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;
|
||||
@@ -1606,23 +1922,23 @@ 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsModeStartOutpostCompatible()
|
||||
@@ -1634,6 +1950,90 @@ 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);
|
||||
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
|
||||
var spawnPoints = corpsePoints.Union(pathPoints).ToList();
|
||||
spawnPoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
int spawnCounter = 0;
|
||||
for (int j = 0; j < corpseCount; j++)
|
||||
{
|
||||
WayPoint sp = spawnPoints.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)
|
||||
{
|
||||
spawnPoints.Remove(sp);
|
||||
sp = spawnPoints.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 pos;
|
||||
if (sp == null)
|
||||
{
|
||||
if (!TryGetExtraSpawnPoint(out pos))
|
||||
{
|
||||
break;
|
||||
}
|
||||
job = GetJobPrefab();
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = sp.WorldPosition;
|
||||
spawnPoints.Remove(sp);
|
||||
}
|
||||
if (job == null) { continue; }
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job);
|
||||
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, pos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
corpse.TeamID = Character.TeamType.None;
|
||||
corpse.EnableDespawn = false;
|
||||
selectedPrefab.GiveItems(corpse);
|
||||
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();
|
||||
}
|
||||
|
||||
DebugConsole.NewMessage($"{spawnCounter}/{corpseCount} corpses spawned in {wreck.Info.Name}.", spawnCounter == corpseCount ? Color.Green : Color.Yellow);
|
||||
|
||||
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,33 @@ 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; }
|
||||
|
||||
// TODO: default to 0
|
||||
[Serialize(1f, 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 +433,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 +505,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 +515,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", ""),
|
||||
|
||||
@@ -721,8 +721,10 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
//doors create their own gaps, don't create an additional one if there's a door at this
|
||||
bool doorFound = false;
|
||||
foreach (Door door in doors)
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door == null) { continue; }
|
||||
if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 &&
|
||||
Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub != null);
|
||||
|
||||
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2());
|
||||
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.Info.FilePath, rect.Location.ToVector2());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,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);
|
||||
@@ -105,15 +105,20 @@ namespace Barotrauma
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
|
||||
sl.GenerateWallVertices(element);
|
||||
if (sl.wallVertices.Any())
|
||||
{
|
||||
sl.Rect = new Rectangle(
|
||||
(int)sl.wallVertices.Min(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Max(v => v.Y + position.Y),
|
||||
(int)sl.wallVertices.Max(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Min(v => v.Y + position.Y));
|
||||
|
||||
sl.Rect = new Rectangle(
|
||||
(int)sl.wallVertices.Min(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Max(v => v.Y + position.Y),
|
||||
(int)sl.wallVertices.Max(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Min(v => v.Y + position.Y));
|
||||
|
||||
sl.rect = new Rectangle((int)position.X, (int)position.Y, 1, 1);
|
||||
|
||||
sl.Rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
sl.Rect = new Rectangle((int)position.X, (int)position.Y, 10, 10);
|
||||
}
|
||||
return sl;
|
||||
}
|
||||
|
||||
@@ -212,7 +217,8 @@ namespace Barotrauma
|
||||
{
|
||||
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)
|
||||
@@ -325,7 +331,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
|
||||
@@ -43,7 +43,6 @@ namespace Barotrauma
|
||||
|
||||
protected string originalName;
|
||||
protected string identifier;
|
||||
protected ContentPackage contentPackage;
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
@@ -243,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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
@@ -521,9 +521,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 +778,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,564 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user