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