Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -8,7 +13,80 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<CoreEntityPrefab> Prefabs = new PrefabCollection<CoreEntityPrefab>();
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
private CoreEntityPrefab(
|
||||
Identifier identifier,
|
||||
ConstructorInfo constructor,
|
||||
bool resizeHorizontal = false,
|
||||
bool resizeVertical = false,
|
||||
bool linkable = false,
|
||||
IEnumerable<Identifier> allowedLinks = null,
|
||||
IEnumerable<string> aliases = null)
|
||||
: base(identifier)
|
||||
{
|
||||
this.constructor = constructor;
|
||||
this.Name = TextManager.Get($"EntityName.{identifier}");
|
||||
this.Description = TextManager.Get($"EntityDescription.{identifier}");
|
||||
this.ResizeHorizontal = resizeHorizontal;
|
||||
this.ResizeVertical = resizeVertical;
|
||||
this.Linkable = linkable;
|
||||
this.AllowedLinks = (allowedLinks ?? Enumerable.Empty<Identifier>()).ToImmutableHashSet();
|
||||
this.Aliases = (aliases ?? Enumerable.Empty<string>()).Concat(identifier.Value.ToEnumerable()).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static void InitCorePrefabs()
|
||||
{
|
||||
CoreEntityPrefab ep = new CoreEntityPrefab(
|
||||
"hull".ToIdentifier(),
|
||||
typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
resizeHorizontal: true,
|
||||
resizeVertical: true,
|
||||
linkable: true,
|
||||
allowedLinks: new Identifier[] { "hull".ToIdentifier() });
|
||||
Prefabs.Add(ep, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
"gap".ToIdentifier(),
|
||||
typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
resizeHorizontal: true,
|
||||
resizeVertical: true);
|
||||
Prefabs.Add(ep, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
"waypoint".ToIdentifier(),
|
||||
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
|
||||
Prefabs.Add(ep, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
"spawnpoint".ToIdentifier(),
|
||||
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
|
||||
Prefabs.Add(ep, false);
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
if (constructor == null) return;
|
||||
object[] lobject = new object[] { this, rect };
|
||||
constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
public override Sprite Sprite => null;
|
||||
|
||||
public override string OriginalName => Name.Value;
|
||||
|
||||
public override LocalizedString Name { get; }
|
||||
|
||||
public override ImmutableHashSet<Identifier> Tags { get; } = Enumerable.Empty<Identifier>().ToImmutableHashSet();
|
||||
|
||||
public override ImmutableHashSet<Identifier> AllowedLinks { get; }
|
||||
|
||||
public override MapEntityCategory Category => MapEntityCategory.Structure;
|
||||
|
||||
public override ImmutableHashSet<string> Aliases { get; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -18,45 +19,74 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public readonly BallastFloraBehavior? ParentBallastFlora;
|
||||
public int ID = -1;
|
||||
|
||||
public Item ClaimedItem;
|
||||
public Item? ClaimedItem;
|
||||
public int ClaimedItemId = -1;
|
||||
|
||||
public float MaxHealth = 100f;
|
||||
public float Health = 100f;
|
||||
|
||||
private float health = 100;
|
||||
public float Health
|
||||
{
|
||||
get { return health; }
|
||||
set { health = MathHelper.Clamp(value, 0.0f, MaxHealth); }
|
||||
}
|
||||
|
||||
public float RemoveTimer = 60.0f;
|
||||
|
||||
public bool SpawningItem;
|
||||
public Item? AttackItem;
|
||||
|
||||
public bool IsRoot;
|
||||
/// <summary>
|
||||
/// Decorative branches that grow around the root
|
||||
/// </summary>
|
||||
public bool IsRootGrowth;
|
||||
public bool Removed;
|
||||
|
||||
public bool DisconnectedFromRoot;
|
||||
|
||||
public Hull? CurrentHull;
|
||||
|
||||
public float Pulse = 1.0f;
|
||||
private bool inflate;
|
||||
private float pulseDelay = Rand.Range(0f, 3f);
|
||||
|
||||
public readonly BallastFloraBranch? ParentBranch;
|
||||
/// <summary>
|
||||
/// How far from the root this branch is
|
||||
/// </summary>
|
||||
public readonly int BranchDepth;
|
||||
|
||||
public float AccumulatedDamage;
|
||||
public float DamageVisualizationTimer;
|
||||
#if CLIENT
|
||||
public Vector2 ShakeAmount;
|
||||
#endif
|
||||
|
||||
// Adjacent tiles, used to free up sides when this branch gets removed
|
||||
public readonly Dictionary<TileSide, BallastFloraBranch> Connections = new Dictionary<TileSide, BallastFloraBranch>();
|
||||
|
||||
public BallastFloraBranch(BallastFloraBehavior? parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
|
||||
public BallastFloraBranch(BallastFloraBehavior? parent, BallastFloraBranch? parentBranch, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
|
||||
: base(null, position, type, flowerConfig, leafConfig, rect)
|
||||
{
|
||||
ParentBranch = parentBranch;
|
||||
ParentBallastFlora = parent;
|
||||
if (parentBranch != null)
|
||||
{
|
||||
BranchDepth = parentBranch.BranchDepth + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHealth()
|
||||
{
|
||||
if (MaxHealth <= Health) { return; }
|
||||
Color healthColor = Color.White * (1.0f - Health / MaxHealth);
|
||||
HealthColor = healthColor;
|
||||
HealthColor = Color.Lerp(HealthColor, healthColor, 0.05f);
|
||||
}
|
||||
|
||||
public void UpdatePulse(float deltaTime, float inflateSpeed, float deflateSpeed, float delay)
|
||||
{
|
||||
if (ParentBallastFlora == null) { return; }
|
||||
if (ParentBallastFlora == null || DisconnectedFromRoot) { return; }
|
||||
|
||||
if (pulseDelay > 0)
|
||||
{
|
||||
@@ -91,7 +121,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
||||
#endif
|
||||
|
||||
private static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
|
||||
private readonly static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
|
||||
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
|
||||
|
||||
public enum NetworkHeader
|
||||
@@ -101,119 +131,126 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
BranchCreate,
|
||||
BranchRemove,
|
||||
BranchDamage,
|
||||
Infect
|
||||
Infect,
|
||||
Remove
|
||||
}
|
||||
|
||||
public enum AttackType
|
||||
{
|
||||
Fire,
|
||||
Explosives,
|
||||
Other
|
||||
Other,
|
||||
CutFromRoot
|
||||
}
|
||||
|
||||
public struct AITarget
|
||||
{
|
||||
public string[] Tags;
|
||||
public Identifier[] Tags;
|
||||
public int Priority;
|
||||
|
||||
public AITarget(XElement element)
|
||||
public AITarget(ContentXElement element)
|
||||
{
|
||||
Tags = element.GetAttributeStringArray("tags", new string[0]);
|
||||
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>())!;
|
||||
Priority = element.GetAttributeInt("priority", 0);
|
||||
}
|
||||
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
foreach (string tag in item.GetTags())
|
||||
{
|
||||
foreach (Identifier targetTag in Tags)
|
||||
{
|
||||
foreach (string targetTag in Tags)
|
||||
{
|
||||
if (tag == targetTag) { return true; }
|
||||
}
|
||||
if (item.HasTag(targetTag)) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.25f, true, "Scale of the branches")]
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes, "Scale of the branches.")]
|
||||
public float BaseBranchScale { get; set; }
|
||||
|
||||
[Serialize(0.25f, true, "Scale of the flowers")]
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes, "Scale of the flowers.")]
|
||||
public float BaseFlowerScale { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, "Scale of the leaves")]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, "Scale of the leaves.")]
|
||||
public float BaseLeafScale { get; set; }
|
||||
|
||||
[Serialize(0.33f, true, "Chance for a flower to appear on the branch")]
|
||||
[Serialize(0.33f, IsPropertySaveable.Yes, "Chance for a flower to appear on a branch.")]
|
||||
public float FlowerProbability { get; set; }
|
||||
|
||||
[Serialize(0.7f, true, "Change for leaves to appear for the branch")]
|
||||
[Serialize(0.7f, IsPropertySaveable.Yes, "Chance for leaves to appear on a branch.")]
|
||||
public float LeafProbability { get; set; }
|
||||
|
||||
[Serialize(3f, true, "Delay between pulses")]
|
||||
[Serialize(3f, IsPropertySaveable.Yes, "Delay between pulses.")]
|
||||
public float PulseDelay { get; set; }
|
||||
|
||||
[Serialize(3f, true, "How fast the flower inflates during a pulse")]
|
||||
[Serialize(3f, IsPropertySaveable.Yes, "How fast the flower inflates during a pulse.")]
|
||||
public float PulseInflateSpeed { get; set; }
|
||||
|
||||
[Serialize(1f, true, "How fast the flower deflates")]
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How fast the flower deflates.")]
|
||||
public float PulseDeflateSpeed { get; set; }
|
||||
|
||||
[Serialize(32, true, "How many vines must grow before the plant breaks thru the wall")]
|
||||
[Serialize(32, IsPropertySaveable.Yes, "How many vines must grow before the plant breaks through the wall.")]
|
||||
public int BreakthroughPoint { get; set; }
|
||||
|
||||
[Serialize(false, true, "Has the plant grown large enough to expose itself")]
|
||||
[Serialize(false, IsPropertySaveable.Yes, "Has the plant grown large enough to expose itself.")]
|
||||
public bool HasBrokenThrough { get; set; }
|
||||
|
||||
[Serialize(300, true, "How far the ballast flora can detect items")]
|
||||
[Serialize(300, IsPropertySaveable.Yes, "How far the ballast flora can detect items from.")]
|
||||
public int Sight { get; set; }
|
||||
|
||||
[Serialize(100, true, "How much health the branches have")]
|
||||
[Serialize(100, IsPropertySaveable.Yes, "How much health the branches have.")]
|
||||
public int BranchHealth { get; set; }
|
||||
|
||||
[Serialize(400, true, "How much health the stem has")]
|
||||
public int StemHealth { get; set; }
|
||||
[Serialize(400, IsPropertySaveable.Yes, "How much health the root has.")]
|
||||
public int RootHealth { get; set; }
|
||||
|
||||
[Serialize(300f, true, "How much power the ballast flora takes from junction boxes")]
|
||||
[Serialize(0.0005f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")]
|
||||
public float HealthRegenPerBranch { get; set; }
|
||||
|
||||
[Serialize(30, IsPropertySaveable.Yes, "How far away from the root branches can regenerate health (in number of branches). The amount of regen decreases lineary further from the root.")]
|
||||
public int MaxBranchHealthRegenDistance { get; set; }
|
||||
|
||||
[Serialize("255,255,255,255", IsPropertySaveable.Yes)]
|
||||
public Color RootColor { get; set; }
|
||||
|
||||
[Serialize(300f, IsPropertySaveable.Yes, "How much power the ballast flora takes from junction boxes.")]
|
||||
public float PowerConsumptionMin { get; set; }
|
||||
|
||||
[Serialize(3000f, true, "How much the power drain spikes")]
|
||||
[Serialize(3000f, IsPropertySaveable.Yes, "How much the power drain spikes.")]
|
||||
public float PowerConsumptionMax { get; set; }
|
||||
|
||||
[Serialize(10f, true, "How long it takes for power drain to wind down")]
|
||||
[Serialize(10f, IsPropertySaveable.Yes, "How long it takes for power drain to wind down.")]
|
||||
public float PowerConsumptionDuration { get; set; }
|
||||
|
||||
[Serialize(250f, true, "How much power does it take to accelerate growth")]
|
||||
[Serialize(250f, IsPropertySaveable.Yes, "How much power does it take to accelerate growth.")]
|
||||
public float PowerRequirement { get; set; }
|
||||
|
||||
[Serialize(5f, true, "Maximum anger, anger increases when the plant gets damaged and increases growth speed")]
|
||||
[Serialize(5f, IsPropertySaveable.Yes, "Maximum anger, anger increases when the plant gets damaged and increases growth speed.")]
|
||||
public float MaxAnger { get; set; }
|
||||
|
||||
[Serialize(10000f, true, "Maximum power buffer")]
|
||||
[Serialize(10000f, IsPropertySaveable.Yes, "Maximum power buffer.")]
|
||||
public float MaxPowerCapacity { get; set; }
|
||||
|
||||
[Serialize("", true, "Item prefab that is spawned when threatened")]
|
||||
public string AttackItemPrefab { get; set; } = "";
|
||||
[Serialize("", IsPropertySaveable.Yes, "Item prefab that is spawned when threatened.")]
|
||||
public Identifier AttackItemPrefab { get; set; } = Identifier.Empty;
|
||||
|
||||
[Serialize(0.8f, true, "How resistant the ballast flora is to exlposives before it blooms")]
|
||||
[Serialize(0.8f, IsPropertySaveable.Yes, "How resistant the ballast flora is to explosives before it blooms.")]
|
||||
public float ExplosionResistance { get; set; }
|
||||
|
||||
[Serialize(5f, true, "How much damage is taken from open fires")]
|
||||
[Serialize(5f, IsPropertySaveable.Yes, "How much damage is taken from open fires.")]
|
||||
public float FireVulnerability { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, "How much resistance against fire is gained while submerged.")]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, "How much resistance against fire is gained while submerged.")]
|
||||
public float SubmergedWaterResistance { get; set; }
|
||||
|
||||
[Serialize(0.8f, true, "What depth the branches will be drawn on")]
|
||||
[Serialize(0.8f, IsPropertySaveable.Yes, "What depth the branches will be drawn on.")]
|
||||
public float BranchDepth { get; set; }
|
||||
|
||||
[Serialize("", true, "What sound to play when the ballast flora bursts thru walls")]
|
||||
[Serialize("", IsPropertySaveable.Yes, "What sound to play when the ballast flora bursts through walls.")]
|
||||
public string BurstSound { get; set; } = "";
|
||||
|
||||
private float availablePower;
|
||||
|
||||
[Serialize(0f, true, "How much power the ballast flora has stored.")]
|
||||
[Serialize(0f, IsPropertySaveable.Yes, "How much power the ballast flora has stored.")]
|
||||
public float AvailablePower
|
||||
{
|
||||
get => availablePower;
|
||||
@@ -222,7 +259,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
private float anger;
|
||||
|
||||
[Serialize(1f, true, "How enraged the flora is, affects how fast it grows.")]
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How enraged the flora is, affects how fast it grows.")]
|
||||
public float Anger
|
||||
{
|
||||
get => anger;
|
||||
@@ -235,13 +272,13 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
public BallastFloraPrefab Prefab { get; private set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public Vector2 Offset;
|
||||
|
||||
public readonly List<Item> ClaimedTargets = new List<Item>();
|
||||
public readonly List<PowerTransfer> ClaimedJunctionBoxes = new List<PowerTransfer>();
|
||||
public readonly List<PowerContainer> ClaimedBatteries = new List<PowerContainer>();
|
||||
public readonly HashSet<Item> ClaimedTargets = new HashSet<Item>();
|
||||
public readonly HashSet<PowerTransfer> ClaimedJunctionBoxes = new HashSet<PowerTransfer>();
|
||||
public readonly HashSet<PowerContainer> ClaimedBatteries = new HashSet<PowerContainer>();
|
||||
public readonly Dictionary<Item, int> IgnoredTargets = new Dictionary<Item, int>();
|
||||
|
||||
private readonly List<Tuple<UInt16, int>> tempClaimedTargets = new List<Tuple<ushort, int>>();
|
||||
@@ -252,11 +289,12 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public float PowerConsumptionTimer;
|
||||
|
||||
private float defenseCooldown, toxinsCooldown, fireCheckCooldown;
|
||||
private float damageIndicatorTimer, selfDamageTimer, toxinsTimer;
|
||||
private float selfDamageTimer, toxinsTimer, toxinsSpawnTimer;
|
||||
|
||||
private readonly List<BallastFloraBranch> branchesVulnerableToFire = new List<BallastFloraBranch>();
|
||||
|
||||
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
|
||||
private BallastFloraBranch? root;
|
||||
private readonly List<Body> bodies = new List<Body>();
|
||||
|
||||
public readonly BallastFloraStateMachine StateMachine;
|
||||
@@ -319,15 +357,15 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, prefab.Element);
|
||||
LoadPrefab(prefab.Element);
|
||||
StateMachine = new BallastFloraStateMachine(this);
|
||||
if (firstGrowth) { GenerateStem(); }
|
||||
if (firstGrowth) { GenerateRoot(); }
|
||||
_entityList.Add(this);
|
||||
}
|
||||
|
||||
partial void LoadPrefab(XElement element);
|
||||
partial void LoadPrefab(ContentXElement element);
|
||||
|
||||
public void LoadTargets(XElement element)
|
||||
public void LoadTargets(ContentXElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
Targets.Add(new AITarget(subElement));
|
||||
}
|
||||
@@ -358,6 +396,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
be.Add(new XAttribute("claimed", (int)(branch.ClaimedItem?.ID ?? -1)));
|
||||
}
|
||||
if (branch.ParentBranch != null)
|
||||
{
|
||||
be.Add(new XAttribute("parentbranch", (int)(branch.ParentBranch?.ID ?? -1)));
|
||||
}
|
||||
|
||||
saveElement.Add(be);
|
||||
}
|
||||
@@ -382,7 +424,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -412,8 +454,15 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
int sides = getInt("sides");
|
||||
int blockedSides = getInt("blockedsides");
|
||||
int claimedId = branchElement.GetAttributeInt("claimed", -1);
|
||||
int parentBranchId = branchElement.GetAttributeInt("parentbranch", -1);
|
||||
|
||||
BallastFloraBranch newBranch = new BallastFloraBranch(this, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig))
|
||||
BallastFloraBranch? parentBranch = null;
|
||||
if (parentBranchId > -1)
|
||||
{
|
||||
parentBranch = Branches[parentBranchId];
|
||||
}
|
||||
|
||||
BallastFloraBranch newBranch = new BallastFloraBranch(this, parentBranch, pos, VineTileType.CrossJunction, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafconfig))
|
||||
{
|
||||
ID = id,
|
||||
Health = health,
|
||||
@@ -422,6 +471,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
BlockedSides = (TileSide) blockedSides,
|
||||
IsRoot = isRoot
|
||||
};
|
||||
if (newBranch.IsRoot) { root = newBranch; }
|
||||
|
||||
if (claimedId > -1)
|
||||
{
|
||||
@@ -437,6 +487,15 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
if (Branches.Count == 0)
|
||||
{
|
||||
Remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
branch.UpdateScale(deltaTime);
|
||||
@@ -446,38 +505,26 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#endif
|
||||
}
|
||||
|
||||
if (damageIndicatorTimer <= 0)
|
||||
{
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.AccumulatedDamage > 0)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
CreateDamageParticle(branch, branch.AccumulatedDamage);
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
var pos = (Parent?.Position ?? Vector2.Zero) + Offset + branch.Position;
|
||||
GUI.AddMessage($"{(int)branch.AccumulatedDamage}", GUI.Style.Red, pos, Vector2.UnitY * 10.0f, 3f, playSound: false, subId: Parent?.Submarine?.ID ?? -1);
|
||||
}
|
||||
#elif SERVER
|
||||
SendNetworkMessage(this, NetworkHeader.BranchDamage, branch, branch.AccumulatedDamage);
|
||||
#endif
|
||||
}
|
||||
|
||||
branch.AccumulatedDamage = 0f;
|
||||
}
|
||||
|
||||
damageIndicatorTimer = 1f;
|
||||
}
|
||||
|
||||
damageIndicatorTimer -= deltaTime;
|
||||
UpdateDamage(deltaTime);
|
||||
|
||||
UpdatePowerDrain(deltaTime);
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
if (root != null && HealthRegenPerBranch > 0.0f)
|
||||
{
|
||||
float healAmount = Branches.Count(b => !b.IsRoot && !b.IsRootGrowth && !b.DisconnectedFromRoot) * HealthRegenPerBranch;
|
||||
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.Health > branch.MaxHealth * 0.9f || branch.DisconnectedFromRoot) { continue; }
|
||||
float branchHealAmount = (float)(MaxBranchHealthRegenDistance - branch.BranchDepth) / MaxBranchHealthRegenDistance * healAmount;
|
||||
if (branchHealAmount <= 0.0f) { continue; }
|
||||
float prevHealth = branch.Health;
|
||||
branch.Health += branchHealAmount;
|
||||
branch.AccumulatedDamage += (prevHealth - branch.Health);
|
||||
}
|
||||
}
|
||||
StateMachine.Update(deltaTime);
|
||||
|
||||
if (HasBrokenThrough)
|
||||
@@ -509,15 +556,16 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
Anger -= deltaTime;
|
||||
}
|
||||
|
||||
// This entire scope is probably very heavy for GC, need to experiment
|
||||
if (toxinsTimer > 0.1f)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(AttackItemPrefab))
|
||||
toxinsSpawnTimer -= deltaTime;
|
||||
if (!AttackItemPrefab.IsEmpty && toxinsSpawnTimer <= 0.0f)
|
||||
{
|
||||
toxinsSpawnTimer = 1.0f;
|
||||
Dictionary<Hull, List<BallastFloraBranch>> branches = new Dictionary<Hull, List<BallastFloraBranch>>();
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.CurrentHull == null || branch.FlowerConfig.Variant < 0) { continue; }
|
||||
if (branch.CurrentHull == null || branch.FlowerConfig.Variant < 0 || branch.DisconnectedFromRoot) { continue; }
|
||||
|
||||
if (branches.TryGetValue(branch.CurrentHull, out List<BallastFloraBranch>? list))
|
||||
{
|
||||
@@ -534,11 +582,12 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
List<BallastFloraBranch> list = branches[hull];
|
||||
if (!list.Any(HasAcidEmitter))
|
||||
{
|
||||
BallastFloraBranch randomBranch = branches[hull].GetRandom();
|
||||
BallastFloraBranch randomBranch = branches[hull].GetRandomUnsynced();
|
||||
randomBranch.SpawningItem = true;
|
||||
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, AttackItemPrefab);
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Parent.Position + Offset + randomBranch.Position, Parent.Submarine, onSpawned: item =>
|
||||
#warning TODO: Parent needs a nullability sanity check
|
||||
Entity.Spawner?.AddItemToSpawnQueue(prefab, Parent!.Position + Offset + randomBranch.Position, Parent.Submarine, onSpawned: item =>
|
||||
{
|
||||
randomBranch.AttackItem = item;
|
||||
randomBranch.SpawningItem = false;
|
||||
@@ -563,38 +612,50 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateDamage(float deltaTime);
|
||||
|
||||
private readonly List<BallastFloraBranch> toBeRemoved = new List<BallastFloraBranch>();
|
||||
private void UpdateSelfDamage(float deltaTime)
|
||||
{
|
||||
if (selfDamageTimer <= 0)
|
||||
{
|
||||
bool hasRoot = false;
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.IsRoot)
|
||||
{
|
||||
hasRoot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRoot)
|
||||
{
|
||||
Kill();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasBrokenThrough && !CanGrowMore())
|
||||
{
|
||||
Branches.ForEachMod(branch =>
|
||||
{
|
||||
float maxHealth = branch.IsRoot ? StemHealth : BranchHealth;
|
||||
float maxHealth = branch.IsRoot ? RootHealth : BranchHealth;
|
||||
DamageBranch(branch, Rand.Range(1f, maxHealth), AttackType.Other);
|
||||
});
|
||||
}
|
||||
|
||||
selfDamageTimer = 1f;
|
||||
}
|
||||
toBeRemoved.Clear();
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.ParentBranch != null && (branch.ParentBranch.DisconnectedFromRoot || branch.ParentBranch.Health <= 0.0f))
|
||||
{
|
||||
float speed = MathHelper.Lerp(5.0f, 0.1f, branch.ParentBranch.Health / branch.ParentBranch.MaxHealth);
|
||||
DamageBranch(branch, speed * speed * deltaTime, AttackType.CutFromRoot);
|
||||
}
|
||||
if (branch.Health <= 0.0f)
|
||||
{
|
||||
if (branch.ClaimedItem != null)
|
||||
{
|
||||
RemoveClaim(branch.ClaimedItem);
|
||||
branch.ClaimedItem = null;
|
||||
}
|
||||
|
||||
branch.RemoveTimer -= deltaTime;
|
||||
if (branch.RemoveTimer <= 0.0f)
|
||||
{
|
||||
toBeRemoved.Add(branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (BallastFloraBranch branch in toBeRemoved)
|
||||
{
|
||||
RemoveBranch(branch);
|
||||
}
|
||||
selfDamageTimer -= deltaTime;
|
||||
}
|
||||
|
||||
@@ -675,20 +736,25 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
branch.CurrentHull = Hull.FindHull(GetWorldPosition() + branch.Position, Parent, true);
|
||||
}
|
||||
|
||||
private void GenerateStem()
|
||||
private void GenerateRoot()
|
||||
{
|
||||
BallastFloraBranch stem = new BallastFloraBranch(this, Vector2.Zero, VineTileType.Stem, FoliageConfig.EmptyConfig, FoliageConfig.EmptyConfig)
|
||||
if (root != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ballast flora: tried to grow a root even though root has already been created.\n" + Environment.StackTrace);
|
||||
}
|
||||
|
||||
root = new BallastFloraBranch(this, null, Vector2.Zero, VineTileType.Stem, FoliageConfig.EmptyConfig, FoliageConfig.EmptyConfig)
|
||||
{
|
||||
BlockedSides = TileSide.Bottom | TileSide.Left | TileSide.Right,
|
||||
GrowthStep = 1f,
|
||||
Health = StemHealth,
|
||||
MaxHealth = StemHealth,
|
||||
MaxHealth = RootHealth,
|
||||
Health = RootHealth,
|
||||
IsRoot = true,
|
||||
CurrentHull = Parent
|
||||
};
|
||||
|
||||
Branches.Add(stem);
|
||||
CreateBody(stem);
|
||||
|
||||
Branches.Add(root);
|
||||
CreateBody(root);
|
||||
}
|
||||
|
||||
public float GetGrowthSpeed(float deltaTime)
|
||||
@@ -704,19 +770,19 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
return deltaTime;
|
||||
}
|
||||
|
||||
public bool TryGrowBranch(BallastFloraBranch parent, TileSide side, out List<BallastFloraBranch> result)
|
||||
public bool TryGrowBranch(BallastFloraBranch parent, TileSide side, out List<BallastFloraBranch> result, bool isRootGrowth = false, Vector2? forcePosition = null)
|
||||
{
|
||||
result = new List<BallastFloraBranch>();
|
||||
if (parent.IsSideBlocked(side)) { return false; }
|
||||
if (!isRootGrowth && parent.IsSideBlocked(side)) { return false; }
|
||||
|
||||
Vector2 pos = parent.AdjacentPositions[side];
|
||||
Vector2 pos = forcePosition ?? parent.AdjacentPositions[side];
|
||||
Rectangle rect = VineTile.CreatePlantRect(pos);
|
||||
|
||||
if (CollidesWithWorld(rect))
|
||||
if (CollidesWithWorld(rect, checkOtherBranches: !isRootGrowth))
|
||||
{
|
||||
parent.BlockedSides |= side;
|
||||
parent.FailedGrowthAttempts++;
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
FoliageConfig flowerConfig = FoliageConfig.EmptyConfig;
|
||||
@@ -732,21 +798,22 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
leafConfig = FoliageConfig.CreateRandomConfig(leafVariants, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
BallastFloraBranch newBranch = new BallastFloraBranch(this, pos, VineTileType.CrossJunction, flowerConfig, leafConfig, rect)
|
||||
BallastFloraBranch newBranch = new BallastFloraBranch(this, parent, pos, VineTileType.CrossJunction, flowerConfig, leafConfig, rect)
|
||||
{
|
||||
ID = CreateID(),
|
||||
MaxHealth = BranchHealth,
|
||||
Health = BranchHealth,
|
||||
MaxHealth = BranchHealth
|
||||
IsRootGrowth = isRootGrowth
|
||||
};
|
||||
|
||||
SetHull(newBranch);
|
||||
|
||||
if (newBranch.CurrentHull == null || newBranch.CurrentHull.Submarine != Parent.Submarine)
|
||||
{
|
||||
parent.BlockedSides |= side;
|
||||
if (!isRootGrowth) { parent.BlockedSides |= side; }
|
||||
parent.FailedGrowthAttempts++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateConnections(newBranch, parent);
|
||||
|
||||
@@ -760,12 +827,28 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
GrowthWarps--;
|
||||
}
|
||||
|
||||
int rootGrowthCount = Branches.Count(b => b.IsRootGrowth);
|
||||
if (rootGrowthCount < GetDesiredRootGrowthAmount())
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
Vector2 rootGrowthPos = Rand.Vector(Math.Max(rootGrowthCount, 1) * Rand.Range(3.0f, 5.0f));
|
||||
TryGrowBranch(root, TileSide.None, out List<BallastFloraBranch> newRootGrowth, isRootGrowth: true, forcePosition: rootGrowthPos);
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
SendNetworkMessage(this, NetworkHeader.BranchCreate, newBranch, parent.ID);
|
||||
CreateNetworkMessage(new BranchCreateEventData(newBranch, parent));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetDesiredRootGrowthAmount()
|
||||
{
|
||||
if (root == null) { return 0; }
|
||||
return MathHelper.Clamp(Branches.Count(b => !b.IsRootGrowth && b.Health > 0) / 20, 3, 30);
|
||||
}
|
||||
|
||||
public bool BranchContainsTarget(BallastFloraBranch branch, Item target)
|
||||
{
|
||||
Rectangle worldRect = branch.Rect;
|
||||
@@ -797,7 +880,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#if SERVER
|
||||
if (!load)
|
||||
{
|
||||
SendNetworkMessage(this, NetworkHeader.Infect, target.ID, true, branch);
|
||||
CreateNetworkMessage(new InfectEventData(target, InfectEventData.InfectState.Yes, branch));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -874,8 +957,6 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
/// <param name="branch"></param>
|
||||
private void CreateBody(BallastFloraBranch branch)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
Rectangle rect = branch.Rect;
|
||||
Vector2 pos = Parent.Position + Offset + branch.Position;
|
||||
|
||||
@@ -894,8 +975,44 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public void DamageBranch(BallastFloraBranch branch, float amount, AttackType type, Character? attacker = null)
|
||||
{
|
||||
float damage = amount;
|
||||
// damage is handled server side currently
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (damage > 0)
|
||||
{
|
||||
damage = Math.Min(damage, branch.Health);
|
||||
}
|
||||
else
|
||||
{
|
||||
damage = Math.Max(damage, branch.Health - branch.MaxHealth);
|
||||
}
|
||||
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
branch.DamageVisualizationTimer = 1.0f;
|
||||
}
|
||||
|
||||
if (branch.IsRootGrowth && root != null && root.Health > 0.0f) { return; }
|
||||
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
branch.AccumulatedDamage += damage;
|
||||
Anger += damage * 0.001f;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
// damage is handled server side
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//accumulate damage on the server's side to ensure clients get notified
|
||||
if (type == AttackType.Other || type == AttackType.CutFromRoot)
|
||||
{
|
||||
branch.AccumulatedDamage += damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attacker != null && toxinsCooldown <= 0)
|
||||
{
|
||||
@@ -917,48 +1034,26 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
StateMachine.EnterState(new DefendWithPumpState(branch, ClaimedTargets, attacker));
|
||||
defenseCooldown = 180f;
|
||||
}
|
||||
|
||||
defenseCooldown = 10f;
|
||||
else
|
||||
{
|
||||
defenseCooldown = 10f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
branch.AccumulatedDamage += damage;
|
||||
|
||||
branch.Health -= damage;
|
||||
|
||||
if (type != AttackType.Other)
|
||||
{
|
||||
Anger += damage * 0.001f;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnBallastFloraDamaged(attacker, damage);
|
||||
#endif
|
||||
|
||||
if (branch.Health < 0)
|
||||
if (branch.Health <= 0 && type != AttackType.CutFromRoot)
|
||||
{
|
||||
RemoveBranch(branch);
|
||||
if (branch.IsRoot) { Kill(); }
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
GameMain.World.Remove(body);
|
||||
}
|
||||
|
||||
Parent.BallastFlora = null;
|
||||
Branches.Clear();
|
||||
|
||||
foreach (Item target in ClaimedTargets)
|
||||
{
|
||||
target.Infector = null;
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
}
|
||||
|
||||
public void RemoveBranch(BallastFloraBranch branch)
|
||||
{
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
@@ -969,6 +1064,21 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
Branches.Remove(branch);
|
||||
branch.Removed = true;
|
||||
|
||||
bool foundDisconnected = false;
|
||||
do
|
||||
{
|
||||
foundDisconnected = false;
|
||||
foreach (BallastFloraBranch otherBranch in Branches)
|
||||
{
|
||||
if (otherBranch.ParentBranch == null || otherBranch.DisconnectedFromRoot) { continue; }
|
||||
if (otherBranch.ParentBranch.Removed || otherBranch.ParentBranch.DisconnectedFromRoot)
|
||||
{
|
||||
otherBranch.DisconnectedFromRoot = true;
|
||||
foundDisconnected = true;
|
||||
}
|
||||
}
|
||||
} while (foundDisconnected);
|
||||
|
||||
bodies.ForEachMod(body =>
|
||||
{
|
||||
if (body.UserData == branch)
|
||||
@@ -995,11 +1105,21 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
});
|
||||
|
||||
#if CLIENT
|
||||
CreateDeathParticle(branch);
|
||||
CreateDeathParticle(branch, 1.0f);
|
||||
#endif
|
||||
|
||||
if (isClient) { return; }
|
||||
|
||||
int rootGrowthCount = Branches.Count(b => b.IsRootGrowth);
|
||||
if (rootGrowthCount > GetDesiredRootGrowthAmount())
|
||||
{
|
||||
var rootGrowth = Branches.LastOrDefault(b => b.IsRootGrowth);
|
||||
if (rootGrowth != null)
|
||||
{
|
||||
RemoveBranch(rootGrowth);
|
||||
}
|
||||
}
|
||||
|
||||
if (branch.ClaimedItem != null)
|
||||
{
|
||||
RemoveClaim(branch.ClaimedItem);
|
||||
@@ -1013,7 +1133,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
#if SERVER
|
||||
if (!wasRemoved)
|
||||
{
|
||||
SendNetworkMessage(this, NetworkHeader.BranchRemove, branch);
|
||||
CreateNetworkMessage(new BranchRemoveEventData(branch));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1044,14 +1164,16 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
});
|
||||
#if SERVER
|
||||
SendNetworkMessage(this, NetworkHeader.Infect, item.ID, false);
|
||||
CreateNetworkMessage(new InfectEventData(item, InfectEventData.InfectState.No, null));
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
Branches.ForEachMod(RemoveBranch);
|
||||
Parent.BallastFlora = null;
|
||||
foreach (var branch in Branches)
|
||||
{
|
||||
branch.DisconnectedFromRoot = true;
|
||||
}
|
||||
|
||||
foreach (Item target in ClaimedTargets)
|
||||
{
|
||||
@@ -1059,6 +1181,19 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
StateMachine?.State?.Exit();
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Kill();
|
||||
|
||||
Branches.ForEachMod(RemoveBranch);
|
||||
Branches.Clear();
|
||||
toBeRemoved.Clear();
|
||||
Parent.BallastFlora = null;
|
||||
|
||||
// clean up leftover (can probably be removed)
|
||||
foreach (Body body in bodies)
|
||||
@@ -1067,8 +1202,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
GameMain.World.Remove(body);
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
#if SERVER
|
||||
SendNetworkMessage(this, NetworkHeader.Kill);
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1093,9 +1229,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
private bool CanGrowMore() => Branches.Any(b => b.CanGrowMore());
|
||||
|
||||
private bool CollidesWithWorld(Rectangle rect)
|
||||
private bool CollidesWithWorld(Rectangle rect, bool checkOtherBranches = true)
|
||||
{
|
||||
if (Branches.Any(g => g.Rect.Contains(rect))) { return true; }
|
||||
if (checkOtherBranches && Branches.Any(g => g.Rect.Contains(rect))) { return true; }
|
||||
|
||||
Rectangle worldRect = rect;
|
||||
worldRect.Location = (Parent.Position + Offset).ToPoint() + worldRect.Location;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
internal partial class BallastFloraBehavior
|
||||
{
|
||||
public interface IEventData : NetEntityEvent.IData
|
||||
{
|
||||
public NetworkHeader NetworkHeader { get; }
|
||||
}
|
||||
|
||||
public readonly struct SpawnEventData : IEventData
|
||||
{
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.Spawn;
|
||||
}
|
||||
|
||||
private readonly struct KillEventData : IEventData
|
||||
{
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.Kill;
|
||||
}
|
||||
|
||||
private readonly struct BranchCreateEventData : IEventData
|
||||
{
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.BranchCreate;
|
||||
public readonly BallastFloraBranch NewBranch;
|
||||
public readonly BallastFloraBranch Parent;
|
||||
|
||||
public BranchCreateEventData(BallastFloraBranch newBranch, BallastFloraBranch parent)
|
||||
{
|
||||
NewBranch = newBranch;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BranchRemoveEventData : IEventData
|
||||
{
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.BranchRemove;
|
||||
public readonly BallastFloraBranch Branch;
|
||||
|
||||
public BranchRemoveEventData(BallastFloraBranch branch)
|
||||
{
|
||||
Branch = branch;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BranchDamageEventData : IEventData
|
||||
{
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.BranchDamage;
|
||||
public readonly BallastFloraBranch Branch;
|
||||
|
||||
public BranchDamageEventData(BallastFloraBranch branch)
|
||||
{
|
||||
Branch = branch;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct InfectEventData : IEventData
|
||||
{
|
||||
public enum InfectState { Yes, No }
|
||||
|
||||
public NetworkHeader NetworkHeader => NetworkHeader.Infect;
|
||||
public readonly Item Item;
|
||||
public readonly InfectState Infect;
|
||||
public readonly BallastFloraBranch Infector;
|
||||
|
||||
public InfectEventData(Item item, InfectState infect, BallastFloraBranch infector)
|
||||
{
|
||||
Item = item;
|
||||
Infect = infect;
|
||||
Infector = infector;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,125 +4,28 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BallastFloraPrefab : IPrefab, IDisposable
|
||||
class BallastFloraPrefab : Prefab
|
||||
{
|
||||
public string OriginalName { get; }
|
||||
public string Identifier { get; }
|
||||
public string FilePath { get; }
|
||||
public XElement Element { get; }
|
||||
|
||||
public ContentPackage ContentPackage { get; private set; }
|
||||
public LocalizedString DisplayName { get; }
|
||||
public ContentXElement Element { get; }
|
||||
|
||||
public bool Disposed;
|
||||
|
||||
public static readonly PrefabCollection<BallastFloraPrefab> Prefabs = new PrefabCollection<BallastFloraPrefab>();
|
||||
|
||||
private BallastFloraPrefab(XElement element, string filePath, bool isOverride)
|
||||
public BallastFloraPrefab(ContentXElement element, BallastFloraFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
OriginalName = element.GetAttributeString("name", "");
|
||||
DisplayName = TextManager.Get(Identifier).Fallback(OriginalName);
|
||||
Element = element;
|
||||
FilePath = filePath;
|
||||
Prefabs.Add(this, isOverride);
|
||||
}
|
||||
|
||||
public static BallastFloraPrefab Find(string idenfitier)
|
||||
public static BallastFloraPrefab Find(Identifier identifier)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(idenfitier) ? Prefabs.Find(prefab => prefab.Identifier == idenfitier) : null;
|
||||
return Prefabs.ContainsKey(identifier) ? Prefabs[identifier] : null;
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable files)
|
||||
{
|
||||
DebugConsole.Log("Loading map creature prefabs: ");
|
||||
|
||||
foreach (ContentFile file in files) { LoadFromFile(file); }
|
||||
}
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
|
||||
var rootElement = doc?.Root;
|
||||
if (rootElement == null) { return; }
|
||||
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "ballastflorabehavior":
|
||||
{
|
||||
new BallastFloraPrefab(rootElement, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
break;
|
||||
}
|
||||
case "ballastflorabehaviors":
|
||||
{
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
XElement upgradeElement = element.GetChildElement("mapcreature");
|
||||
if (upgradeElement != null)
|
||||
{
|
||||
new BallastFloraPrefab(upgradeElement, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a map creature element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element.Name.ToString().Equals("mapcreature", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "override":
|
||||
{
|
||||
XElement mapCreatures = rootElement.GetChildElement("ballastflorabehaviors");
|
||||
if (mapCreatures != null)
|
||||
{
|
||||
foreach (XElement element in mapCreatures.Elements())
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement element in rootElement.GetChildElements("ballastflorabehavior"))
|
||||
{
|
||||
new BallastFloraPrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}\n " +
|
||||
"Valid elements are: \"MapCreature\", \"MapCreatures\" and \"Override\".");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -17,6 +17,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
lastState = State;
|
||||
State?.Exit();
|
||||
State = null;
|
||||
|
||||
newState.Enter();
|
||||
State = newState;
|
||||
}
|
||||
@@ -35,11 +37,9 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
case ExitState.Running:
|
||||
break;
|
||||
|
||||
case ExitState.ReturnLast when lastState != null && lastState.GetState() == ExitState.Running:
|
||||
EnterState(lastState);
|
||||
break;
|
||||
|
||||
default:
|
||||
EnterState(new GrowIdleState(parent));
|
||||
break;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
private bool tryDrown;
|
||||
private readonly Character attacker;
|
||||
|
||||
public DefendWithPumpState(BallastFloraBranch branch, List<Item> items, Character attacker)
|
||||
public DefendWithPumpState(BallastFloraBranch branch, IEnumerable<Item> items, Character attacker)
|
||||
{
|
||||
targetBranch = branch;
|
||||
this.attacker = attacker;
|
||||
|
||||
@@ -59,11 +59,14 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
protected virtual void Grow()
|
||||
{
|
||||
List<BallastFloraBranch> newTiles = GrowRandomly();
|
||||
List<BallastFloraBranch> newBranches = GrowRandomly();
|
||||
#if DEBUG
|
||||
Behavior.debugSearchLines.Clear();
|
||||
#endif
|
||||
if (newTiles.Any(TryScanTargets)) { return; }
|
||||
foreach (var branch in newBranches)
|
||||
{
|
||||
TryScanTargets(branch);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateIgnoredTargets()
|
||||
@@ -85,23 +88,20 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
private List<BallastFloraBranch> GrowRandomly()
|
||||
{
|
||||
List<BallastFloraBranch> newBranches = new List<BallastFloraBranch>();
|
||||
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(Behavior.Branches);
|
||||
foreach (BallastFloraBranch branch in newList)
|
||||
{
|
||||
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
|
||||
List<BallastFloraBranch> availableBranches = Behavior.Branches.Where(b => !b.DisconnectedFromRoot && b.FailedGrowthAttempts <= 8 && b.CanGrowMore()).ToList();
|
||||
if (availableBranches.Count == 0) { return availableBranches; }
|
||||
|
||||
if (Rand.Range(0, Behavior.Branches.Count(tile => tile.CanGrowMore())) != 0) { continue; }
|
||||
//prefer growing from the branches furthest from the root (ones with the largest branch depth)
|
||||
var branch = ToolBox.SelectWeightedRandom(availableBranches, b => (float)b.BranchDepth, Rand.RandSync.Unsynced);
|
||||
|
||||
TileSide side = branch.GetRandomFreeSide();
|
||||
TileSide side = branch.GetRandomFreeSide();
|
||||
if (side == TileSide.None) { return availableBranches; }
|
||||
|
||||
if (side == TileSide.None) { continue; }
|
||||
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
|
||||
availableBranches.Clear();
|
||||
availableBranches.Add(branch);
|
||||
|
||||
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
|
||||
newBranches.AddRange(result);
|
||||
}
|
||||
|
||||
return newBranches;
|
||||
return availableBranches;
|
||||
}
|
||||
|
||||
private Item? ScanForTargets(VineTile branch)
|
||||
@@ -117,22 +117,22 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
int highestPriority = 0;
|
||||
Item? currentItem = null;
|
||||
|
||||
foreach (Item item in Item.ItemList.Where(it => !Behavior.ClaimedTargets.Contains(it)))
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != parent.Submarine || Vector2.DistanceSquared(worldPos, item.WorldPosition) > Behavior.Sight * Behavior.Sight) { continue; }
|
||||
if (Behavior.ClaimedTargets.Contains(item)) { continue; }
|
||||
if (Behavior.IgnoredTargets.ContainsKey(item)) { continue; }
|
||||
|
||||
int priority = 0;
|
||||
foreach (BallastFloraBehavior.AITarget target in Behavior.Targets)
|
||||
{
|
||||
if (!target.Matches(item) || target.Priority <= highestPriority) { continue; }
|
||||
if (target.Priority <= highestPriority || !target.Matches(item)) { continue; }
|
||||
priority = target.Priority;
|
||||
break;
|
||||
}
|
||||
|
||||
if (priority == 0) { continue; }
|
||||
|
||||
if (item.Submarine != parent.Submarine || Vector2.Distance(worldPos, item.WorldPosition) > Behavior.Sight) { continue; }
|
||||
|
||||
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
|
||||
#if DEBUG
|
||||
@@ -156,6 +156,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
{
|
||||
foreach (BallastFloraBranch existingBranch in Behavior.Branches)
|
||||
{
|
||||
if (existingBranch.Health <= 0 || existingBranch.IsRootGrowth) { continue; }
|
||||
if (Behavior.BranchContainsTarget(existingBranch, currentItem))
|
||||
{
|
||||
Behavior.ClaimTarget(currentItem, existingBranch);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(TargetBranches);
|
||||
foreach (BallastFloraBranch branch in newList)
|
||||
{
|
||||
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
|
||||
if (branch.FailedGrowthAttempts > 8 || branch.DisconnectedFromRoot || !branch.CanGrowMore()) { continue; }
|
||||
|
||||
// Get what side gets us closest to the target
|
||||
TileSide side = GetClosestSide(branch, Target.WorldPosition);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -70,6 +72,14 @@ namespace Barotrauma
|
||||
public double SpawnTime => spawnTime;
|
||||
private readonly double spawnTime;
|
||||
|
||||
private static UInt64 creationCounter = 0;
|
||||
private readonly static object creationCounterMutex = new object();
|
||||
|
||||
public readonly string CreationStackTrace;
|
||||
public readonly UInt64 CreationIndex;
|
||||
public string ErrorLine
|
||||
=> $"- {ID}: {this} ({Submarine?.Info?.Name ?? "[null]"} {Submarine?.ID ?? 0}) {CreationStackTrace}";
|
||||
|
||||
public Entity(Submarine submarine, ushort id)
|
||||
{
|
||||
this.Submarine = submarine;
|
||||
@@ -84,6 +94,31 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
dictionary.Add(ID, this);
|
||||
|
||||
CreationStackTrace = "";
|
||||
#if DEBUG
|
||||
var st = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
|
||||
var frames = st.GetFrames();
|
||||
int frameCount = 0;
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
string fileName = frame.GetFileName();
|
||||
if ((fileName?.Contains("BarotraumaClient") ?? false) || (fileName?.Contains("BarotraumaServer") ?? false)) { break; }
|
||||
|
||||
fileName = Path.GetFileNameWithoutExtension(fileName);
|
||||
int fileLineNumber = frame.GetFileLineNumber();
|
||||
|
||||
if (fileName.IsNullOrEmpty() || fileLineNumber <= 0) { continue; }
|
||||
|
||||
CreationStackTrace += $"{fileName}@{fileLineNumber}; ";
|
||||
}
|
||||
#endif
|
||||
#warning TODO: consider removing this mutex, entity creation probably shouldn't be multithreaded
|
||||
lock (creationCounterMutex)
|
||||
{
|
||||
CreationIndex = creationCounter;
|
||||
creationCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ushort DetermineID(ushort id, Submarine submarine)
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace Barotrauma
|
||||
smoke = true;
|
||||
flames = true;
|
||||
underwaterBubble = true;
|
||||
ignoreFireEffectsForTags = new string[0];
|
||||
ignoreFireEffectsForTags = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public Explosion(XElement element, string parentDebugName)
|
||||
public Explosion(ContentXElement element, string parentDebugName)
|
||||
{
|
||||
Attack = new Attack(element, parentDebugName + ", Explosion");
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", showEffects);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames && showEffects);
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true);
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
onlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
|
||||
flash = element.GetAttributeBool("flash", showEffects);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
if (element.GetAttribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); }
|
||||
flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow);
|
||||
|
||||
EmpStrength = element.GetAttributeFloat("empstrength", 0.0f);
|
||||
@@ -133,6 +133,7 @@ namespace Barotrauma
|
||||
{
|
||||
displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius);
|
||||
Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage);
|
||||
Attack.SourceItem ??= sourceItem;
|
||||
}
|
||||
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
@@ -237,9 +238,9 @@ namespace Barotrauma
|
||||
if (!fireProof)
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
|
||||
if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (item.Condition <= 0.0f && GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFire));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,11 +338,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
|
||||
if (attackData.Afflictions != null)
|
||||
{
|
||||
modifiedAfflictions.AddRange(attackData.Afflictions);
|
||||
}
|
||||
|
||||
//use a position slightly from the limb's position towards the explosion
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier);
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
|
||||
if (attack.StatusEffects != null && attack.StatusEffects.Any())
|
||||
@@ -482,7 +489,7 @@ namespace Barotrauma
|
||||
{
|
||||
List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.BallastFlora != null) { ballastFlorae.Add(hull.BallastFlora); }
|
||||
}
|
||||
|
||||
@@ -363,9 +363,9 @@ namespace Barotrauma
|
||||
if (item.Position.Y < position.Y - size.Y || item.Position.Y > hull.Rect.Y) { continue; }
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFire, deltaTime);
|
||||
if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (item.Condition <= 0.0f && GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFire));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Barotrauma
|
||||
{ }
|
||||
|
||||
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base(MapEntityPrefab.Find(null, "gap"), submarine, id)
|
||||
: base(MapEntityPrefab.FindByIdentifier("gap".ToIdentifier()), submarine, id)
|
||||
{
|
||||
this.rect = rect;
|
||||
flowForce = Vector2.Zero;
|
||||
@@ -249,16 +249,17 @@ namespace Barotrauma
|
||||
}
|
||||
linkedTo.Clear();
|
||||
|
||||
int tolerance = 1;
|
||||
Vector2[] searchPos = new Vector2[2];
|
||||
if (IsHorizontal)
|
||||
{
|
||||
searchPos[0] = new Vector2(rect.X, rect.Y - rect.Height / 2);
|
||||
searchPos[1] = new Vector2(rect.Right, rect.Y - rect.Height / 2);
|
||||
searchPos[0] = new Vector2(rect.X - tolerance, rect.Y - rect.Height / 2);
|
||||
searchPos[1] = new Vector2(rect.Right + tolerance, rect.Y - rect.Height / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchPos[0] = new Vector2(rect.Center.X, rect.Y);
|
||||
searchPos[1] = new Vector2(rect.Center.X, rect.Y - rect.Height);
|
||||
searchPos[0] = new Vector2(rect.Center.X, rect.Y + tolerance);
|
||||
searchPos[1] = new Vector2(rect.Center.X, rect.Y - rect.Height - tolerance);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
@@ -712,7 +713,7 @@ namespace Barotrauma
|
||||
base.ShallowRemove();
|
||||
GapList.Remove(this);
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
}
|
||||
@@ -723,7 +724,7 @@ namespace Barotrauma
|
||||
base.Remove();
|
||||
GapList.Remove(this);
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
}
|
||||
@@ -740,11 +741,11 @@ namespace Barotrauma
|
||||
if (!DisableHullRechecks) FindHulls();
|
||||
}
|
||||
|
||||
public static Gap Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
public static Gap Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
|
||||
if (element.Attribute("rect") != null)
|
||||
if (element.GetAttribute("rect") != null)
|
||||
{
|
||||
rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
||||
}
|
||||
@@ -752,15 +753,15 @@ namespace Barotrauma
|
||||
{
|
||||
//backwards compatibility
|
||||
rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
int.Parse(element.GetAttribute("x").Value),
|
||||
int.Parse(element.GetAttribute("y").Value),
|
||||
int.Parse(element.GetAttribute("width").Value),
|
||||
int.Parse(element.GetAttribute("height").Value));
|
||||
}
|
||||
|
||||
bool isHorizontal = rect.Height > rect.Width;
|
||||
|
||||
var horizontalAttribute = element.Attribute("horizontal");
|
||||
var horizontalAttribute = element.GetAttribute("horizontal");
|
||||
if (horizontalAttribute != null)
|
||||
{
|
||||
isHorizontal = horizontalAttribute.Value.ToString() == "true";
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace Barotrauma
|
||||
public readonly Vector2 Noise;
|
||||
public readonly Color DirtColor;
|
||||
|
||||
#if CLIENT
|
||||
public Sprite GrimeSprite;
|
||||
#endif
|
||||
|
||||
public float ColorStrength
|
||||
{
|
||||
get;
|
||||
@@ -51,6 +55,9 @@ namespace Barotrauma
|
||||
PerlinNoise.GetPerlin(Rect.Y / 1000.0f + 0.5f, Rect.X / 1000.0f + 0.5f));
|
||||
|
||||
Color = DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X);
|
||||
#if CLIENT
|
||||
GrimeSprite = DecalManager.GrimeSprites[$"{nameof(GrimeSprite)}{index % DecalManager.GrimeSpriteCount}"].Sprite;
|
||||
#endif
|
||||
}
|
||||
|
||||
public BackgroundSection(Rectangle rect, ushort index, float colorStrength, Color color, ushort rowIndex)
|
||||
@@ -68,6 +75,9 @@ namespace Barotrauma
|
||||
PerlinNoise.GetPerlin(Rect.Y / 1000.0f + 0.5f, Rect.X / 1000.0f + 0.5f));
|
||||
|
||||
DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X);
|
||||
#if CLIENT
|
||||
GrimeSprite = DecalManager.GrimeSprites[$"{nameof(GrimeSprite)}{index % DecalManager.GrimeSpriteCount}"].Sprite;
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool SetColor(Color color)
|
||||
@@ -101,8 +111,8 @@ namespace Barotrauma
|
||||
|
||||
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable
|
||||
{
|
||||
public static List<Hull> hullList = new List<Hull>();
|
||||
public static List<EntityGrid> EntityGrids { get; } = new List<EntityGrid>();
|
||||
public readonly static List<Hull> HullList = new List<Hull>();
|
||||
public readonly static List<EntityGrid> EntityGrids = new List<EntityGrid>();
|
||||
|
||||
public static bool ShowHulls = true;
|
||||
|
||||
@@ -124,8 +134,8 @@ namespace Barotrauma
|
||||
|
||||
public const int BackgroundSectionsPerNetworkEvent = 16;
|
||||
|
||||
public readonly Dictionary<string, SerializableProperty> properties;
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public readonly Dictionary<Identifier, SerializableProperty> properties;
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get { return properties; }
|
||||
}
|
||||
@@ -155,32 +165,23 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<Gap> ConnectedGaps = new List<Gap>();
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Hull";
|
||||
}
|
||||
}
|
||||
public override string Name => "Hull";
|
||||
|
||||
public string DisplayName
|
||||
public LocalizedString DisplayName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly HashSet<string> moduleTags = new HashSet<string>();
|
||||
private readonly HashSet<Identifier> moduleTags = new HashSet<Identifier>();
|
||||
|
||||
/// <summary>
|
||||
/// Inherited flags from outpost generation.
|
||||
/// </summary>
|
||||
public IEnumerable<string> OutpostModuleTags
|
||||
{
|
||||
get { return moduleTags; }
|
||||
}
|
||||
public IEnumerable<Identifier> OutpostModuleTags => moduleTags;
|
||||
|
||||
private string roomName;
|
||||
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, translationTextTag: "RoomName.")]
|
||||
public string RoomName
|
||||
{
|
||||
get { return roomName; }
|
||||
@@ -188,7 +189,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (roomName == value) { return; }
|
||||
roomName = value;
|
||||
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
|
||||
DisplayName = TextManager.Get(roomName).Fallback(roomName);
|
||||
if (!IsWetRoom && ForceAsWetRoom)
|
||||
{
|
||||
IsWetRoom = true;
|
||||
@@ -200,7 +201,7 @@ namespace Barotrauma
|
||||
|
||||
private Color ambientLight;
|
||||
|
||||
[Editable, Serialize("0,0,0,0", true)]
|
||||
[Editable, Serialize("0,0,0,0", IsPropertySaveable.Yes)]
|
||||
public Color AmbientLight
|
||||
{
|
||||
get { return ambientLight; }
|
||||
@@ -283,38 +284,22 @@ namespace Barotrauma
|
||||
get { return Submarine == null ? surface : surface + Submarine.Position.Y; }
|
||||
}
|
||||
|
||||
private float dirtiedVolume = 0.0f;
|
||||
|
||||
public float WaterVolume
|
||||
{
|
||||
get { return waterVolume; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
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;
|
||||
if (BackgroundSections != null)
|
||||
{
|
||||
float volumeMultiplier = Math.Clamp(waterVolume / Volume, 0f, 1f);
|
||||
if (Math.Abs(volumeMultiplier - dirtiedVolume) > 0.075f)
|
||||
{
|
||||
RefreshSubmergedSections(new Rectangle(new Point(0, -rect.Height), new Point(rect.Width, (int)(rect.Height * volumeMultiplier))));
|
||||
dirtiedVolume = volumeMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
submergedSections.Clear();
|
||||
dirtiedVolume = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(100000.0f, true)]
|
||||
[Serialize(100000.0f, IsPropertySaveable.Yes)]
|
||||
public float Oxygen
|
||||
{
|
||||
get { return oxygen; }
|
||||
@@ -329,10 +314,11 @@ namespace Barotrauma
|
||||
roomName != null && (
|
||||
roomName.Contains("ballast", StringComparison.OrdinalIgnoreCase) ||
|
||||
roomName.Contains("bilge", StringComparison.OrdinalIgnoreCase) ||
|
||||
roomName.Contains("airlock", StringComparison.OrdinalIgnoreCase));
|
||||
roomName.Contains("airlock", StringComparison.OrdinalIgnoreCase) ||
|
||||
roomName.Contains("dockingport", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private bool isWetRoom;
|
||||
[Editable, Serialize(false, true, description: "It's normal for this hull to be filled with water. If the room name contains 'ballast', 'bilge', or 'airlock', you can't disable this setting.")]
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "It's normal for this hull to be filled with water. If the room name contains 'ballast', 'bilge', or 'airlock', you can't disable this setting.")]
|
||||
public bool IsWetRoom
|
||||
{
|
||||
get { return isWetRoom; }
|
||||
@@ -347,7 +333,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private bool avoidStaying;
|
||||
[Editable, Serialize(false, true, description: "Bots avoid staying here, but they are still allowed to access the room when needed and go through it. Forced true for wet rooms.")]
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Bots avoid staying here, but they are still allowed to access the room when needed and go through it. Forced true for wet rooms.")]
|
||||
public bool AvoidStaying
|
||||
{
|
||||
get { return avoidStaying || IsWetRoom; }
|
||||
@@ -399,8 +385,6 @@ namespace Barotrauma
|
||||
|
||||
private readonly HashSet<int> pendingSectionUpdates = new HashSet<int>();
|
||||
|
||||
private readonly List<BackgroundSection> submergedSections = new List<BackgroundSection>();
|
||||
|
||||
public int xBackgroundMax, yBackgroundMax;
|
||||
|
||||
public bool SupportsPaintedColors
|
||||
@@ -469,7 +453,7 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
hullList.Add(this);
|
||||
HullList.Add(this);
|
||||
|
||||
if (submarine == null || !submarine.Loading)
|
||||
{
|
||||
@@ -488,11 +472,11 @@ namespace Barotrauma
|
||||
|
||||
public static Rectangle GetBorders()
|
||||
{
|
||||
if (!hullList.Any()) return Rectangle.Empty;
|
||||
if (!HullList.Any()) return Rectangle.Empty;
|
||||
|
||||
Rectangle rect = hullList[0].rect;
|
||||
Rectangle rect = HullList[0].rect;
|
||||
|
||||
foreach (Hull hull in hullList)
|
||||
foreach (Hull hull in HullList)
|
||||
{
|
||||
if (hull.Rect.X < rect.X)
|
||||
{
|
||||
@@ -516,12 +500,15 @@ namespace Barotrauma
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
var clone = new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine);
|
||||
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties)
|
||||
var clone = new Hull(MapEntityPrefab.FindByIdentifier("hull".ToIdentifier()), rect, Submarine);
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> property in SerializableProperties)
|
||||
{
|
||||
if (!property.Value.Attributes.OfType<Editable>().Any()) { continue; }
|
||||
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
|
||||
}
|
||||
#if CLIENT
|
||||
clone.lastAmbientLightEditTime = 0.0;
|
||||
#endif
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -536,17 +523,17 @@ namespace Barotrauma
|
||||
{
|
||||
var newGrid = new EntityGrid(submarine, 200.0f);
|
||||
EntityGrids.Add(newGrid);
|
||||
foreach (Hull hull in hullList)
|
||||
foreach (Hull hull in HullList)
|
||||
{
|
||||
if (hull.Submarine == submarine && !hull.IdFreed) { newGrid.InsertEntity(hull); }
|
||||
}
|
||||
return newGrid;
|
||||
}
|
||||
|
||||
public void SetModuleTags(IEnumerable<string> tags)
|
||||
public void SetModuleTags(IEnumerable<Identifier> tags)
|
||||
{
|
||||
moduleTags.Clear();
|
||||
foreach (string tag in tags)
|
||||
foreach (Identifier tag in tags)
|
||||
{
|
||||
moduleTags.Add(tag);
|
||||
}
|
||||
@@ -630,7 +617,7 @@ namespace Barotrauma
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.Remove();
|
||||
hullList.Remove(this);
|
||||
HullList.Remove(this);
|
||||
|
||||
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
|
||||
{
|
||||
@@ -659,7 +646,7 @@ namespace Barotrauma
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
hullList.Remove(this);
|
||||
HullList.Remove(this);
|
||||
BallastFlora?.Remove();
|
||||
|
||||
if (Submarine != null && !Submarine.Loading && !Submarine.Unloading)
|
||||
@@ -669,7 +656,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
BackgroundSections?.Clear();
|
||||
submergedSections?.Clear();
|
||||
|
||||
List<FireSource> fireSourcesToRemove = new List<FireSource>(FireSources);
|
||||
foreach (FireSource fireSource in fireSourcesToRemove)
|
||||
@@ -712,7 +698,7 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
var decal = GameMain.DecalManager.Prefabs.Find(p => p.UIntIdentifier == decalId);
|
||||
var decal = DecalManager.Prefabs.Find(p => p.UintIdentifier == decalId);
|
||||
if (decal == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a decal prefab with the UInt identifier {decalId}!");
|
||||
@@ -732,12 +718,12 @@ namespace Barotrauma
|
||||
|
||||
if (decals.Count >= MaxDecalsPerHull) { return null; }
|
||||
|
||||
var decal = GameMain.DecalManager.CreateDecal(decalName, scale, worldPosition, this, spriteIndex);
|
||||
var decal = DecalManager.CreateDecal(decalName, scale, worldPosition, this, spriteIndex);
|
||||
if (decal != null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { false });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new DecalEventData());
|
||||
}
|
||||
decals.Add(decal);
|
||||
}
|
||||
@@ -745,6 +731,96 @@ namespace Barotrauma
|
||||
return decal;
|
||||
}
|
||||
|
||||
#region Shared network write
|
||||
private void SharedStatusWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
|
||||
|
||||
msg.WriteRangedInteger(Math.Min(FireSources.Count, 16), 0, 16);
|
||||
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
|
||||
{
|
||||
var fireSource = FireSources[i];
|
||||
Vector2 normalizedPos = new Vector2(
|
||||
(fireSource.Position.X - rect.X) / rect.Width,
|
||||
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
|
||||
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
|
||||
private void SharedBackgroundSectionsWrite(IWriteMessage msg, in BackgroundSectionsEventData backgroundSectionsEventData)
|
||||
{
|
||||
int sectorToUpdate = backgroundSectionsEventData.SectorStartIndex;
|
||||
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
|
||||
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
|
||||
msg.WriteRangedInteger(sectorToUpdate, 0, BackgroundSections.Count - 1);
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
|
||||
msg.Write(BackgroundSections[i].Color.PackedValue);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Shared network read
|
||||
public readonly struct NetworkFireSource
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly float Size;
|
||||
|
||||
public NetworkFireSource(Hull hull, Vector2 normalizedPosition, float normalizedSize)
|
||||
{
|
||||
Position = hull.Rect.Location.ToVector2()
|
||||
+ new Vector2(0, -hull.Rect.Height)
|
||||
+ normalizedPosition * hull.Rect.Size.ToVector2();
|
||||
Size = normalizedSize * hull.Rect.Width;
|
||||
}
|
||||
}
|
||||
|
||||
private void SharedStatusRead(IReadMessage msg, out float newWaterVolume, out NetworkFireSource[] newFireSources)
|
||||
{
|
||||
newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
|
||||
|
||||
int fireSourceCount = msg.ReadRangedInteger(0, 16);
|
||||
newFireSources = new NetworkFireSource[fireSourceCount];
|
||||
for (int i = 0; i < fireSourceCount; i++)
|
||||
{
|
||||
float x = MathHelper.Clamp(msg.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
|
||||
float y = MathHelper.Clamp(msg.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
|
||||
float size = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
newFireSources[i] = new NetworkFireSource(this, new Vector2(x, y), size);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BackgroundSectionNetworkUpdate
|
||||
{
|
||||
public readonly int SectionIndex;
|
||||
public readonly Color Color;
|
||||
public readonly float ColorStrength;
|
||||
public BackgroundSectionNetworkUpdate(int sectionIndex, Color color, float colorStrength)
|
||||
{
|
||||
SectionIndex = sectionIndex;
|
||||
Color = color;
|
||||
ColorStrength = colorStrength;
|
||||
}
|
||||
}
|
||||
|
||||
private void SharedBackgroundSectionRead(IReadMessage msg, Action<BackgroundSectionNetworkUpdate> action, out int sectorToUpdate)
|
||||
{
|
||||
sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1);
|
||||
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
|
||||
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
Color color = new Color(msg.ReadUInt32());
|
||||
|
||||
action(new BackgroundSectionNetworkUpdate(i, color, colorStrength));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
@@ -888,12 +964,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//0.016 increase every ~2000 frames = reaches full dirtiness in ~35 minutes
|
||||
if (submergedSections.Count > 0 && Submarine != null && Submarine.Info.Type == SubmarineType.Player && Rand.Int(2000) == 1)
|
||||
{
|
||||
DirtySections(submergedSections, deltaTime);
|
||||
}
|
||||
|
||||
if (waterVolume < Volume)
|
||||
{
|
||||
LethalPressure -= 10.0f * deltaTime;
|
||||
@@ -1110,12 +1180,12 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static Hull FindHullUnoptimized(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
{
|
||||
if (guess != null && hullList.Contains(guess))
|
||||
if (guess != null && HullList.Contains(guess))
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive)) return guess;
|
||||
}
|
||||
|
||||
foreach (Hull hull in hullList)
|
||||
foreach (Hull hull in HullList)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position, inclusive)) return hull;
|
||||
}
|
||||
@@ -1135,15 +1205,15 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
Hull h = c.CurrentHull;
|
||||
hullList.ForEach(j => j.Visible = false);
|
||||
HullList.ForEach(j => j.Visible = false);
|
||||
List<Hull> visibleHulls;
|
||||
if (h == null || c.Submarine == null)
|
||||
{
|
||||
visibleHulls = hullList.FindAll(j => j.CanSeeOther(null, false));
|
||||
visibleHulls = HullList.FindAll(j => j.CanSeeOther(null, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
visibleHulls = hullList.FindAll(j => h.CanSeeOther(j, true));
|
||||
visibleHulls = HullList.FindAll(j => h.CanSeeOther(j, true));
|
||||
}
|
||||
visibleHulls.ForEach(j => j.Visible = true);
|
||||
foreach (Item it in Item.ItemList)
|
||||
@@ -1164,7 +1234,7 @@ namespace Barotrauma
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
if (g.ConnectedWall != null && g.ConnectedWall.CastShadow) continue;
|
||||
List<Hull> otherHulls = hullList.FindAll(h => h.ConnectedGaps.Contains(g) && h != this);
|
||||
List<Hull> otherHulls = HullList.FindAll(h => h.ConnectedGaps.Contains(g) && h != this);
|
||||
retVal = otherHulls.Any(h => h == other);
|
||||
if (!retVal && allowIndirect) retVal = otherHulls.Any(h => h.CanSeeOther(other, false));
|
||||
if (retVal) return true;
|
||||
@@ -1174,7 +1244,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
if (g.ConnectedDoor != null && !hullList.Any(h => h.ConnectedGaps.Contains(g) && h != this)) return true;
|
||||
if (g.ConnectedDoor != null && !HullList.Any(h => h.ConnectedGaps.Contains(g) && h != this)) return true;
|
||||
}
|
||||
List<MapEntity> structures = mapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
|
||||
return structures.Any(st => !(st as Structure).CastShadow);
|
||||
@@ -1209,7 +1279,7 @@ namespace Barotrauma
|
||||
if (moduleFlags != null && moduleFlags.Any() &&
|
||||
(Submarine.Info.Type == SubmarineType.OutpostModule || Submarine.Info.Type == SubmarineType.Outpost))
|
||||
{
|
||||
if (moduleFlags.Contains("airlock") &&
|
||||
if (moduleFlags.Contains("airlock".ToIdentifier()) &&
|
||||
ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null))
|
||||
{
|
||||
return "RoomName.Airlock";
|
||||
@@ -1313,7 +1383,7 @@ namespace Barotrauma
|
||||
|
||||
public static Hull GetCleanTarget(Vector2 worldPosition)
|
||||
{
|
||||
foreach (Hull hull in hullList)
|
||||
foreach (Hull hull in HullList)
|
||||
{
|
||||
Rectangle worldRect = hull.WorldRect;
|
||||
if (worldPosition.X < worldRect.X || worldPosition.X > worldRect.Right) { continue; }
|
||||
@@ -1366,17 +1436,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshSubmergedSections(Rectangle waterArea)
|
||||
{
|
||||
if (BackgroundSections == null) { return; }
|
||||
|
||||
submergedSections.Clear();
|
||||
foreach (var section in GetBackgroundSectionsViaContaining(waterArea))
|
||||
{
|
||||
submergedSections.Add(section);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoesSectionMatch(int index, int row)
|
||||
{
|
||||
return index >= 0 && row >= 0 && BackgroundSections.Count > index && BackgroundSections[index] != null && BackgroundSections[index].RowIndex == row;
|
||||
@@ -1443,15 +1502,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void DirtySections(List<BackgroundSection> sections, float dirtyVal)
|
||||
{
|
||||
if (sections == null) { return; }
|
||||
for (int i = 0; i < sections.Count; i++)
|
||||
{
|
||||
IncreaseSectionColorOrStrength(sections[i], sections[i].DirtColor, dirtyVal, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanSection(BackgroundSection section, float cleanVal, bool updateRequired)
|
||||
{
|
||||
bool decalsCleaned = false;
|
||||
@@ -1476,10 +1526,10 @@ namespace Barotrauma
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static Hull Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
public static Hull Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect;
|
||||
if (element.Attribute("rect") != null)
|
||||
if (element.GetAttribute("rect") != null)
|
||||
{
|
||||
rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
||||
}
|
||||
@@ -1487,10 +1537,10 @@ namespace Barotrauma
|
||||
{
|
||||
//backwards compatibility
|
||||
rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
int.Parse(element.GetAttribute("x").Value),
|
||||
int.Parse(element.GetAttribute("y").Value),
|
||||
int.Parse(element.GetAttribute("width").Value),
|
||||
int.Parse(element.GetAttribute("height").Value));
|
||||
}
|
||||
|
||||
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine, idRemap.GetOffsetId(element))
|
||||
@@ -1507,7 +1557,7 @@ namespace Barotrauma
|
||||
hull.OriginalAmbientLight = XMLExtensions.ParseColor(originalAmbientLight, false);
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -1525,7 +1575,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "ballastflorabehavior":
|
||||
string identifier = subElement.GetAttributeString("identifier", string.Empty);
|
||||
Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
BallastFloraPrefab prefab = BallastFloraPrefab.Find(identifier);
|
||||
if (prefab != null)
|
||||
{
|
||||
@@ -1554,7 +1604,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SerializableProperty.DeserializeProperties(hull, element);
|
||||
if (element.Attribute("oxygen") == null) { hull.Oxygen = hull.Volume; }
|
||||
if (element.GetAttribute("oxygen") == null) { hull.Oxygen = hull.Volume; }
|
||||
|
||||
return hull;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Hull
|
||||
{
|
||||
[Flags]
|
||||
public enum EventType
|
||||
{
|
||||
Status = 0,
|
||||
Decal = 1,
|
||||
BackgroundSections = 2,
|
||||
BallastFlora = 3,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 3
|
||||
}
|
||||
|
||||
public interface IEventData : NetEntityEvent.IData
|
||||
{
|
||||
public EventType EventType { get; }
|
||||
}
|
||||
|
||||
private readonly struct StatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
private readonly struct DecalEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Decal;
|
||||
public readonly Decal Decal;
|
||||
|
||||
public DecalEventData(Decal decal)
|
||||
{
|
||||
Decal = decal;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BackgroundSectionsEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.BackgroundSections;
|
||||
public readonly int SectorStartIndex;
|
||||
|
||||
public BackgroundSectionsEventData(int sectorStartIndex)
|
||||
{
|
||||
SectorStartIndex = sectorStartIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BallastFloraEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.BallastFlora;
|
||||
public readonly BallastFloraBehavior Behavior;
|
||||
public readonly BallastFloraBehavior.IEventData SubEventData;
|
||||
|
||||
public BallastFloraEventData(BallastFloraBehavior behavior, BallastFloraBehavior.IEventData subEventData)
|
||||
{
|
||||
Behavior = behavior;
|
||||
SubEventData = subEventData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,31 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
interface IDamageable
|
||||
{
|
||||
Vector2 SimPosition
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Vector2 WorldPosition
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
float Health
|
||||
{
|
||||
get;
|
||||
}
|
||||
Vector2 SimPosition { get; }
|
||||
Vector2 WorldPosition { get; }
|
||||
float Health { get; }
|
||||
|
||||
AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
|
||||
|
||||
|
||||
public readonly struct AttackEventData
|
||||
{
|
||||
public readonly ISpatialEntity Attacker;
|
||||
public readonly IDamageable TargetEntity;
|
||||
public readonly Limb TargetLimb;
|
||||
public readonly Vector2 AttackSimPosition;
|
||||
|
||||
public AttackEventData(ISpatialEntity attacker, IDamageable targetEntity, Limb targetLimb, Vector2 attackSimPosition)
|
||||
{
|
||||
Attacker = attacker;
|
||||
TargetEntity = targetEntity;
|
||||
TargetLimb = targetLimb;
|
||||
AttackSimPosition = attackSimPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,64 +5,56 @@ using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class ItemAssemblyPrefab : MapEntityPrefab
|
||||
{
|
||||
private readonly string name;
|
||||
public override string Name { get { return name; } }
|
||||
|
||||
public static readonly PrefabCollection<ItemAssemblyPrefab> Prefabs = new PrefabCollection<ItemAssemblyPrefab>();
|
||||
|
||||
public static readonly string VanillaSaveFolder = Path.Combine("Content", "Items", "Assemblies");
|
||||
public static readonly string SaveFolder = "ItemAssemblies";
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
|
||||
private readonly XElement configElement;
|
||||
|
||||
public List<Pair<MapEntityPrefab, Rectangle>> DisplayEntities
|
||||
|
||||
public readonly ImmutableArray<(Identifier Identifier, Rectangle Rect)> DisplayEntities;
|
||||
|
||||
public readonly Rectangle Bounds;
|
||||
|
||||
public override LocalizedString Name { get; }
|
||||
|
||||
public override Sprite Sprite => null;
|
||||
|
||||
public override string OriginalName => Name.Value;
|
||||
|
||||
public override ImmutableHashSet<Identifier> Tags { get; }
|
||||
|
||||
public override ImmutableHashSet<Identifier> AllowedLinks => null;
|
||||
|
||||
public override MapEntityCategory Category => MapEntityCategory.ItemAssembly;
|
||||
|
||||
public override ImmutableHashSet<string> Aliases => null;
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
return element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("name", ""));
|
||||
}
|
||||
|
||||
public Rectangle Bounds;
|
||||
|
||||
public ItemAssemblyPrefab(string filePath, bool allowOverwrite = false)
|
||||
public ItemAssemblyPrefab(ContentXElement element, ItemAssemblyFile file) : base(element, file)
|
||||
{
|
||||
FilePath = filePath;
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null) { return; }
|
||||
|
||||
XElement element = doc.Root;
|
||||
if (element.IsOverride())
|
||||
{
|
||||
element = element.Elements().First();
|
||||
}
|
||||
|
||||
originalName = element.GetAttributeString("name", "");
|
||||
identifier = element.GetAttributeString("identifier", null) ?? originalName.ToLowerInvariant().Replace(" ", "");
|
||||
configElement = element;
|
||||
|
||||
Category = MapEntityCategory.ItemAssembly;
|
||||
|
||||
SerializableProperty.DeserializeProperties(this, configElement);
|
||||
|
||||
name = TextManager.Get("EntityName." + identifier, returnNull: true) ?? originalName;
|
||||
Description = TextManager.Get("EntityDescription." + identifier, returnNull: true) ?? Description;
|
||||
Name = TextManager.Get($"EntityName.{Identifier}").Fallback(element.GetAttributeString("name", ""));
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}");
|
||||
Tags = Enumerable.Empty<Identifier>().ToImmutableHashSet();
|
||||
|
||||
List<ushort> containedItemIDs = new List<ushort>();
|
||||
foreach (XElement entityElement in element.Elements())
|
||||
{
|
||||
var containerElement = entityElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("itemcontainer", StringComparison.OrdinalIgnoreCase));
|
||||
var containerElement = entityElement.GetChildElement("itemcontainer");
|
||||
if (containerElement == null) { continue; }
|
||||
|
||||
string containedString = containerElement.GetAttributeString("contained", "");
|
||||
@@ -84,52 +76,36 @@ namespace Barotrauma
|
||||
|
||||
int minX = int.MaxValue, minY = int.MaxValue;
|
||||
int maxX = int.MinValue, maxY = int.MinValue;
|
||||
DisplayEntities = new List<Pair<MapEntityPrefab, Rectangle>>();
|
||||
var displayEntities = new List<(Identifier, Rectangle)>();
|
||||
foreach (XElement entityElement in element.Elements())
|
||||
{
|
||||
ushort id = (ushort)entityElement.GetAttributeInt("ID", 0);
|
||||
if (id > 0 && containedItemIDs.Contains(id)) { continue; }
|
||||
|
||||
string identifier = entityElement.GetAttributeString("identifier", entityElement.Name.ToString().ToLowerInvariant());
|
||||
MapEntityPrefab mapEntity = List.FirstOrDefault(p => p.Identifier == identifier);
|
||||
if (mapEntity == null)
|
||||
{
|
||||
string entityName = entityElement.GetAttributeString("name", "");
|
||||
mapEntity = List.FirstOrDefault(p => p.Name == entityName);
|
||||
}
|
||||
Identifier identifier = entityElement.GetAttributeIdentifier("identifier", entityElement.Name.ToString().ToLowerInvariant());
|
||||
|
||||
Rectangle rect = entityElement.GetAttributeRect("rect", Rectangle.Empty);
|
||||
if (mapEntity != null && !entityElement.Elements().Any(e => e.Name.LocalName.Equals("wire", StringComparison.OrdinalIgnoreCase)))
|
||||
if (!entityElement.Elements().Any(e => e.Name.LocalName.Equals("wire", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!entityElement.GetAttributeBool("hideinassemblypreview", false)) { DisplayEntities.Add(new Pair<MapEntityPrefab, Rectangle>(mapEntity, rect)); }
|
||||
if (!entityElement.GetAttributeBool("hideinassemblypreview", false)) { displayEntities.Add((identifier, rect)); }
|
||||
minX = Math.Min(minX, rect.X);
|
||||
minY = Math.Min(minY, rect.Y - rect.Height);
|
||||
maxX = Math.Max(maxX, rect.Right);
|
||||
maxY = Math.Max(maxY, rect.Y);
|
||||
}
|
||||
}
|
||||
DisplayEntities = displayEntities.ToImmutableArray();
|
||||
|
||||
Bounds = minX == int.MaxValue ?
|
||||
new Rectangle(0, 0, 1, 1) :
|
||||
new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
|
||||
if (allowOverwrite && Prefabs.ContainsKey(identifier))
|
||||
{
|
||||
Prefabs.Remove(Prefabs[identifier]);
|
||||
}
|
||||
Prefabs.Add(this, doc.Root.IsOverride());
|
||||
}
|
||||
|
||||
public static void Remove(string filePath)
|
||||
{
|
||||
Prefabs.RemoveByFile(filePath);
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
#if CLIENT
|
||||
var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub, selectInstance: Screen.Selected == GameMain.SubEditorScreen);
|
||||
if (Screen.Selected is SubEditorScreen)
|
||||
if (Screen.Selected is SubEditorScreen && loaded.Any())
|
||||
{
|
||||
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(loaded, false, handleInventoryBehavior: false));
|
||||
}
|
||||
@@ -140,7 +116,7 @@ namespace Barotrauma
|
||||
|
||||
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectInstance = false)
|
||||
{
|
||||
return PasteEntities(position, sub, configElement, FilePath, selectInstance);
|
||||
return PasteEntities(position, sub, configElement, ContentFile.Path.Value, selectInstance);
|
||||
}
|
||||
|
||||
public static List<MapEntity> PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false)
|
||||
@@ -183,53 +159,22 @@ namespace Barotrauma
|
||||
public void Delete()
|
||||
{
|
||||
Dispose();
|
||||
if (File.Exists(FilePath))
|
||||
try
|
||||
{
|
||||
try
|
||||
if (ContentPackage is { Files: { Length: 1 } }
|
||||
&& ContentPackageManager.LocalPackages.Contains(ContentPackage))
|
||||
{
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Deleting item assembly \"" + name + "\" failed.", e);
|
||||
Directory.Delete(ContentPackage.Dir, recursive: true);
|
||||
ContentPackageManager.LocalPackages.Refresh();
|
||||
ContentPackageManager.EnabledPackages.DisableRemovedMods();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Deleting item assembly \"" + Name + "\" failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Loading item assembly prefabs: ");
|
||||
}
|
||||
|
||||
List<string> itemAssemblyFiles = new List<string>();
|
||||
|
||||
//find assembly files in the item assembly folders
|
||||
if (Directory.Exists(VanillaSaveFolder))
|
||||
{
|
||||
itemAssemblyFiles.AddRange(Directory.GetFiles(VanillaSaveFolder));
|
||||
}
|
||||
if (Directory.Exists(SaveFolder))
|
||||
{
|
||||
itemAssemblyFiles.AddRange(Directory.GetFiles(SaveFolder));
|
||||
}
|
||||
|
||||
//find assembly files in selected content packages
|
||||
foreach (ContentPackage cp in GameMain.Config.AllEnabledPackages)
|
||||
{
|
||||
foreach (string filePath in cp.GetFilesOfType(ContentType.ItemAssembly))
|
||||
{
|
||||
//ignore files that have already been added (= file saved to item assembly folder)
|
||||
if (itemAssemblyFiles.Any(f => Path.GetFullPath(f) == Path.GetFullPath(filePath))) { continue; }
|
||||
itemAssemblyFiles.Add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string file in itemAssemblyFiles)
|
||||
{
|
||||
new ItemAssemblyPrefab(file);
|
||||
}
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Biome : PrefabWithUintIdentifier
|
||||
{
|
||||
public readonly static PrefabCollection<Biome> Prefabs = new PrefabCollection<Biome>();
|
||||
|
||||
public readonly Identifier OldIdentifier;
|
||||
public readonly LocalizedString DisplayName;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly bool IsEndBiome;
|
||||
|
||||
public readonly ImmutableHashSet<int> AllowedZones;
|
||||
|
||||
public Biome(ContentXElement element, LevelGenerationParametersFile file) : base(file, ParseIdentifier(element))
|
||||
{
|
||||
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
|
||||
|
||||
DisplayName =
|
||||
TextManager.Get("biomename." + Identifier).Fallback(
|
||||
element.GetAttributeString("name", "Biome"));
|
||||
|
||||
Description =
|
||||
TextManager.Get("biomedescription." + Identifier).Fallback(
|
||||
element.GetAttributeString("description", ""));
|
||||
|
||||
IsEndBiome = element.GetAttributeBool("endbiome", false);
|
||||
|
||||
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static Identifier ParseIdentifier(ContentXElement element)
|
||||
{
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
identifier = element.GetAttributeIdentifier("name", "");
|
||||
DebugConsole.ThrowError("Error in biome \"" + identifier + "\": identifier missing, using name as the identifier.");
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,18 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CaveGenerationParams : ISerializableEntity
|
||||
class CaveGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public static List<CaveGenerationParams> CaveParams { get; private set; }
|
||||
public readonly static PrefabCollection<CaveGenerationParams> CaveParams = new PrefabCollection<CaveGenerationParams>();
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public readonly string Identifier;
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
private int minWidth, maxWidth;
|
||||
private int minHeight, maxHeight;
|
||||
|
||||
private int minBranchCount, maxBranchCount;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -33,100 +28,100 @@ namespace Barotrauma
|
||||
/// Overrides the commonness of the object in a specific level type.
|
||||
/// Key = name of the level type, value = commonness in that level type.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, float> OverrideCommonness = new Dictionary<string, float>();
|
||||
public readonly Dictionary<Identifier, float> OverrideCommonness = new Dictionary<Identifier, float>();
|
||||
|
||||
[Editable, Serialize(1.0f, true)]
|
||||
[Editable, Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
[Serialize(8000, IsPropertySaveable.Yes), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
[Serialize(10000, IsPropertySaveable.Yes), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = Math.Max(value, minWidth); }
|
||||
}
|
||||
|
||||
[Serialize(8000, true), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
[Serialize(8000, IsPropertySaveable.Yes), Editable(MinValueInt = 1000, MaxValueInt = 100000)]
|
||||
public int MinHeight
|
||||
{
|
||||
get { return minHeight; }
|
||||
set { minHeight = Math.Max(value, 1000); }
|
||||
}
|
||||
|
||||
[Serialize(10000, true), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
[Serialize(10000, IsPropertySaveable.Yes), Editable(MinValueInt = 1000, MaxValueInt = 1000000)]
|
||||
public int MaxHeight
|
||||
{
|
||||
get { return maxHeight; }
|
||||
set { maxHeight = Math.Max(value, minHeight); }
|
||||
}
|
||||
|
||||
[Serialize(2, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
[Serialize(2, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinBranchCount
|
||||
{
|
||||
get { return minBranchCount; }
|
||||
set { minBranchCount = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(4, true), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
[Serialize(4, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxBranchCount
|
||||
{
|
||||
get { return maxBranchCount; }
|
||||
set { maxBranchCount = Math.Max(value, minBranchCount); }
|
||||
}
|
||||
|
||||
[Serialize(50, true), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
[Serialize(50, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.1f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
public float DestructibleWallRatio
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
public readonly Sprite WallSprite;
|
||||
public readonly Sprite WallEdgeSprite;
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, bool abyss, Rand.RandSync rand)
|
||||
{
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
|
||||
var caveParams = CaveParams.OrderBy(p => p.UintIdentifier).ToList();
|
||||
if (caveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
|
||||
{
|
||||
return CaveParams.First();
|
||||
return caveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
|
||||
return ToolBox.SelectWeightedRandom(caveParams.ToList(), caveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams, bool abyss)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss" : generationParams.Identifier, out float commonness))
|
||||
if (generationParams != null &&
|
||||
generationParams.Identifier != Identifier.Empty &&
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss".ToIdentifier() : generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
private CaveGenerationParams(XElement element)
|
||||
public CaveGenerationParams(ContentXElement element, CaveGenerationParametersFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Identifier = element == null ? "default" : element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -137,7 +132,7 @@ namespace Barotrauma
|
||||
WallEdgeSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
Identifier levelType = subElement.GetAttributeIdentifier("leveltype", "");
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
@@ -147,71 +142,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
CaveParams = new List<CaveGenerationParams>();
|
||||
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.CaveGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files = new List<ContentFile>() { new ContentFile("Content/Map/CaveGenerationParameters.xml", ContentType.CaveGenerationParameters) };
|
||||
}
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
CaveParams.Clear();
|
||||
DebugConsole.NewMessage($"Overriding cave generation parameters with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
if (isOverride)
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the cave generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
CaveParams.Remove(existingParams);
|
||||
}
|
||||
CaveParams.Add(new CaveGenerationParams(element.FirstElement()));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", "");
|
||||
var existingParams = CaveParams.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingParams != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate cave generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
CaveParams.Add(new CaveGenerationParams(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
foreach (KeyValuePair<string, float> overrideCommonness in OverrideCommonness)
|
||||
foreach (KeyValuePair<Identifier, float> overrideCommonness in OverrideCommonness)
|
||||
{
|
||||
bool elementFound = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("overridecommonness", StringComparison.OrdinalIgnoreCase)
|
||||
&& subElement.GetAttributeString("leveltype", "").Equals(overrideCommonness.Key, StringComparison.OrdinalIgnoreCase))
|
||||
if (subElement.NameAsIdentifier() == "overridecommonness"
|
||||
&& subElement.GetAttributeIdentifier("leveltype", "") == overrideCommonness.Key)
|
||||
{
|
||||
subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture);
|
||||
elementFound = true;
|
||||
@@ -226,5 +166,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
WallSprite?.Remove(); WallEdgeSprite?.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace Barotrauma
|
||||
{
|
||||
|
||||
float centerF = 0.5f - Math.Abs(0.5f - (i / (float)pointCount));
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server);
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.ServerAndClient);
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
@@ -469,7 +469,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
|
||||
verts.Add(new Vector2(dir.X * width / 2, dir.Y * height / 2) + dir * Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server));
|
||||
verts.Add(new Vector2(dir.X * width / 2, dir.Y * height / 2) + dir * Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.ServerAndClient));
|
||||
angle += angleStep;
|
||||
}
|
||||
return verts;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,8 +31,20 @@ namespace Barotrauma
|
||||
|
||||
public bool HasHuntingGrounds, OriginallyHadHuntingGrounds;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum difficulty of the level before hunting grounds can appear.
|
||||
/// </summary>
|
||||
public const float HuntingGroundsDifficultyThreshold = 25;
|
||||
|
||||
/// <summary>
|
||||
/// Probability of hunting grounds appearing in 100% difficulty levels.
|
||||
/// </summary>
|
||||
public const float MaxHuntingGroundsProbability = 0.3f;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public bool AllowInvalidOutpost;
|
||||
|
||||
public readonly Point Size;
|
||||
|
||||
public readonly int InitialDepth;
|
||||
@@ -92,7 +104,7 @@ namespace Barotrauma
|
||||
OriginallyHadHuntingGrounds = element.GetAttributeBool("originallyhadhuntinggrounds", HasHuntingGrounds);
|
||||
|
||||
string generationParamsId = element.GetAttributeString("generationparams", "");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || l.OldIdentifier == generationParamsId);
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || (!l.OldIdentifier.IsEmpty && l.OldIdentifier == generationParamsId));
|
||||
if (GenerationParams == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\".");
|
||||
@@ -106,18 +118,18 @@ namespace Barotrauma
|
||||
InitialDepth = element.GetAttributeInt("initialdepth", GenerationParams.InitialDepthMin);
|
||||
|
||||
string biomeIdentifier = element.GetAttributeString("biome", "");
|
||||
Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier || b.OldIdentifier == biomeIdentifier);
|
||||
Biome = Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeIdentifier || (!b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeIdentifier));
|
||||
if (Biome == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\".");
|
||||
Biome = LevelGenerationParams.GetBiomes().First();
|
||||
Biome = Biome.Prefabs.First();
|
||||
}
|
||||
|
||||
string[] prefabNames = element.GetAttributeStringArray("eventhistory", new string[] { });
|
||||
EventHistory.AddRange(EventSet.PrefabList.Where(p => prefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase))));
|
||||
EventHistory.AddRange(EventPrefab.Prefabs.Where(p => prefabNames.Any(n => p.Identifier == n)));
|
||||
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
|
||||
NonRepeatableEvents.AddRange(EventSet.PrefabList.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase))));
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +141,7 @@ namespace Barotrauma
|
||||
Seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName;
|
||||
Biome = locationConnection.Biome;
|
||||
Type = LevelType.LocationConnection;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome);
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome.Identifier);
|
||||
Difficulty = locationConnection.Difficulty;
|
||||
|
||||
float sizeFactor = MathUtils.InverseLerp(
|
||||
@@ -150,11 +162,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
float huntingGroundsDifficultyThreshold = 25;
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(HuntingGroundsDifficultyThreshold, 100.0f, Difficulty) * MaxHuntingGroundsProbability;
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
}
|
||||
IsBeaconActive = false;
|
||||
@@ -168,7 +176,7 @@ namespace Barotrauma
|
||||
Seed = location.BaseName;
|
||||
Biome = location.Biome;
|
||||
Type = LevelType.Outpost;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome);
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome.Identifier);
|
||||
Difficulty = 0.0f;
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
@@ -183,7 +191,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
|
||||
seed = Rand.Range(0, int.MaxValue, Rand.RandSync.ServerAndClient).ToString();
|
||||
}
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
@@ -194,18 +202,18 @@ namespace Barotrauma
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
var biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
|
||||
LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server);
|
||||
Biome.Prefabs.FirstOrDefault(b => generationParams?.AllowedBiomeIdentifiers.Contains(b.Identifier) ?? false) ??
|
||||
Biome.Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
|
||||
var levelData = new LevelData(
|
||||
seed,
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient),
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient),
|
||||
generationParams,
|
||||
biome);
|
||||
if (type == LevelType.LocationConnection)
|
||||
{
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
float beaconRng = Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient);
|
||||
levelData.HasBeaconStation = beaconRng < 0.5f;
|
||||
levelData.IsBeaconActive = beaconRng > 0.25f;
|
||||
}
|
||||
|
||||
@@ -1,68 +1,20 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Biome
|
||||
class LevelGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public readonly string Identifier;
|
||||
public readonly string OldIdentifier;
|
||||
public readonly string DisplayName;
|
||||
public readonly string Description;
|
||||
public readonly static PrefabCollection<LevelGenerationParams> LevelParams = new PrefabCollection<LevelGenerationParams>();
|
||||
|
||||
public readonly bool IsEndBiome;
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
public readonly List<int> AllowedZones = new List<int>();
|
||||
|
||||
public Biome(string name, string description)
|
||||
{
|
||||
Identifier = name;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public Biome(XElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
OldIdentifier = element.GetAttributeString("oldidentifier", null);
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
Identifier = element.GetAttributeString("name", "");
|
||||
DebugConsole.ThrowError("Error in biome \"" + Identifier + "\": identifier missing, using name as the identifier.");
|
||||
}
|
||||
|
||||
DisplayName =
|
||||
TextManager.Get("biomename." + Identifier, returnNull: true) ??
|
||||
element.GetAttributeString("name", "Biome") ??
|
||||
TextManager.Get("biomename." + Identifier);
|
||||
|
||||
Description =
|
||||
TextManager.Get("biomedescription." + Identifier, returnNull: true) ??
|
||||
element.GetAttributeString("description", "") ??
|
||||
TextManager.Get("biomedescription." + Identifier);
|
||||
|
||||
IsEndBiome = element.GetAttributeBool("endbiome", false);
|
||||
|
||||
AllowedZones.AddRange(element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
|
||||
}
|
||||
}
|
||||
|
||||
class LevelGenerationParams : ISerializableEntity
|
||||
{
|
||||
public static List<LevelGenerationParams> LevelParams { get; private set; }
|
||||
|
||||
private static List<Biome> biomes;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string OldIdentifier;
|
||||
public Identifier OldIdentifier { get; }
|
||||
|
||||
private int minWidth, maxWidth, height;
|
||||
|
||||
@@ -100,48 +52,44 @@ namespace Barotrauma
|
||||
private int initialDepthMin, initialDepthMax;
|
||||
|
||||
//which biomes can this type of level appear in
|
||||
private readonly List<Biome> allowedBiomes = new List<Biome>();
|
||||
public readonly ImmutableHashSet<Identifier> AllowedBiomeIdentifiers;
|
||||
public readonly bool AnyBiomeAllowed;
|
||||
|
||||
public IEnumerable<Biome> AllowedBiomes
|
||||
{
|
||||
get { return allowedBiomes; }
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(LevelData.LevelType.LocationConnection, true), Editable]
|
||||
[Serialize(LevelData.LevelType.LocationConnection, IsPropertySaveable.Yes), Editable]
|
||||
public LevelData.LevelType Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("27,30,36", true), Editable]
|
||||
[Serialize("27,30,36", IsPropertySaveable.Yes), Editable]
|
||||
public Color AmbientLightColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", true), Editable()]
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable()]
|
||||
public Color BackgroundTextureColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", true), Editable]
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("255,255,255", true), Editable]
|
||||
[Serialize("255,255,255", IsPropertySaveable.Yes), Editable]
|
||||
public Color WallColor
|
||||
{
|
||||
get;
|
||||
@@ -149,7 +97,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 startPosition;
|
||||
[Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
@@ -162,7 +110,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 endPosition;
|
||||
[Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
[Serialize("1,0", IsPropertySaveable.Yes, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable(DecimalCount = 2)]
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
@@ -174,70 +122,70 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true, "Should there be a hole in the wall next to the end outpost (can be used to prevent players from having to backtrack if they approach the outpost from the wrong side of the main path's walls)."), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes, "Should there be a hole in the wall next to the end outpost (can be used to prevent players from having to backtrack if they approach the outpost from the wrong side of the main path's walls)."), Editable]
|
||||
public bool CreateHoleNextToEnd
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
|
||||
public bool CreateHoleToAbyss
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000, true, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(80, true, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
[Serialize(80, IsPropertySaveable.Yes, description: "The total number of decorative background creatures."), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int BackgroundCreatureAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable]
|
||||
[Serialize(100000, IsPropertySaveable.Yes), Editable]
|
||||
public int MinWidth
|
||||
{
|
||||
get { return minWidth; }
|
||||
set { minWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(100000, true), Editable]
|
||||
[Serialize(100000, IsPropertySaveable.Yes), Editable]
|
||||
public int MaxWidth
|
||||
{
|
||||
get { return maxWidth; }
|
||||
set { maxWidth = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(50000, true), Editable]
|
||||
[Serialize(50000, IsPropertySaveable.Yes), Editable]
|
||||
public int Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(80000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMin
|
||||
{
|
||||
get { return initialDepthMin; }
|
||||
set { initialDepthMin = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(80000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(80000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMax
|
||||
{
|
||||
get { return initialDepthMax; }
|
||||
set { initialDepthMax = Math.Max(value, initialDepthMin); }
|
||||
}
|
||||
|
||||
[Serialize(6500, true), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
[Serialize(6500, IsPropertySaveable.Yes), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
public int MinTunnelRadius
|
||||
{
|
||||
get;
|
||||
@@ -245,7 +193,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0,1", true), Editable]
|
||||
[Serialize("0,1", IsPropertySaveable.Yes), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
@@ -253,21 +201,21 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", true), Editable]
|
||||
[Serialize("2000,6000", IsPropertySaveable.Yes), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("3000, 3000", true, description: "How far from each other voronoi sites are placed. " +
|
||||
[Editable, Serialize("3000, 3000", IsPropertySaveable.Yes, description: "How far from each other voronoi sites are placed. " +
|
||||
"Sites determine shape of the voronoi graph which the level walls are generated from. " +
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")]
|
||||
public Point VoronoiSiteInterval
|
||||
@@ -280,7 +228,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("700,700", true, description: "How much random variation to apply to the positions of the voronoi sites on each axis. " +
|
||||
[Editable, Serialize("700,700", IsPropertySaveable.Yes, description: "How much random variation to apply to the positions of the voronoi sites on each axis. " +
|
||||
"Small values produce roughly rectangular level walls. The larger the values are, the less uniform the shapes get.")]
|
||||
public Point VoronoiSiteVariance
|
||||
{
|
||||
@@ -293,7 +241,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueInt = 500, MaxValueInt = 10000), Serialize(5000, true, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
[Editable(MinValueInt = 500, MaxValueInt = 10000), Serialize(5000, IsPropertySaveable.Yes, description: "The edges of the individual wall cells are subdivided into edges of this size. "
|
||||
+ "Can be used in conjunction with the rounding values to make the cells rounder. Smaller values will make the cells look smoother, " +
|
||||
"but make the level more performance-intensive as the number of polygons used in rendering and physics calculations increases.")]
|
||||
public int CellSubdivisionLength
|
||||
@@ -306,7 +254,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.5f, true, description: "How much the individual wall cells are rounded. "
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the individual wall cells are rounded. "
|
||||
+ "Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
|
||||
public float CellRoundingAmount
|
||||
{
|
||||
@@ -317,7 +265,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.1f, true, description: "How much random variance is applied to the edges of the cells. "
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f), Serialize(0.1f, IsPropertySaveable.Yes, description: "How much random variance is applied to the edges of the cells. "
|
||||
+ "Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
|
||||
public float CellIrregularity
|
||||
{
|
||||
@@ -330,7 +278,7 @@ namespace Barotrauma
|
||||
|
||||
|
||||
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
|
||||
Serialize("5000, 10000", true, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
|
||||
Serialize("5000, 10000", IsPropertySaveable.Yes, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
|
||||
public Point MainPathNodeIntervalRange
|
||||
{
|
||||
get { return mainPathNodeIntervalRange; }
|
||||
@@ -341,42 +289,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float MainPathVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(5, true, description: "The number of caves placed along the main path.")]
|
||||
[Editable, Serialize(5, IsPropertySaveable.Yes, description: "The number of caves placed along the main path.")]
|
||||
public int CaveCount
|
||||
{
|
||||
get { return caveCount; }
|
||||
set { caveCount = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
[Serialize(100, true), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
[Serialize(100, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
public int ItemCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("19200,38400", true, description: "The minimum and maximum distance between two resource spawn points on a path."), Editable(100, 100000)]
|
||||
[Serialize("19200,38400", IsPropertySaveable.Yes, description: "The minimum and maximum distance between two resource spawn points on a path."), Editable(100, 100000)]
|
||||
public Point ResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("9600,19200", true, description: "The minimum and maximum distance between two resource spawn points on a cave path."), Editable(100, 100000)]
|
||||
[Serialize("9600,19200", IsPropertySaveable.Yes, description: "The minimum and maximum distance between two resource spawn points on a cave path."), Editable(100, 100000)]
|
||||
public Point CaveResourceIntervalRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2,8", true, description: "The minimum and maximum amount of resources in a single cluster. " +
|
||||
[Serialize("2,8", IsPropertySaveable.Yes, description: "The minimum and maximum amount of resources in a single cluster. " +
|
||||
"In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."), Editable(1, 20)]
|
||||
public Point ResourceClusterSizeRange
|
||||
{
|
||||
@@ -384,76 +332,90 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.3f, true, description: "How likely a resource spawn point on a path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
[Serialize(0.3f, IsPropertySaveable.Yes, description: "How likely a resource spawn point on a path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float CaveResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int FloatingIceChunkCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
public int IslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int IceSpireCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(5, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(5, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int AbyssIslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("4000,7000", true), Editable]
|
||||
[Serialize("4000,7000", IsPropertySaveable.Yes), Editable]
|
||||
public Point AbyssIslandSizeMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("8000,10000", true), Editable]
|
||||
[Serialize("8000,10000", IsPropertySaveable.Yes), Editable]
|
||||
public Point AbyssIslandSizeMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.5f, true), Editable()]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable()]
|
||||
public float AbyssIslandCaveProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
[Serialize(10, IsPropertySaveable.Yes, description: "Minimum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int AbyssResourceClustersMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(50, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
|
||||
public int AbyssResourceClustersMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-300000, IsPropertySaveable.Yes, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
get { return seaFloorBaseDepth; }
|
||||
set { seaFloorBaseDepth = MathHelper.Clamp(value, Level.MaxEntityDepth, 0); }
|
||||
}
|
||||
|
||||
[Serialize(1000, true, description: "Variance of the depth of the sea floor. Smaller values produce a smoother sea floor."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100000.0f)]
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "Variance of the depth of the sea floor. Smaller values produce a smoother sea floor."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100000.0f)]
|
||||
public int SeaFloorVariance
|
||||
{
|
||||
get { return seaFloorVariance; }
|
||||
set { seaFloorVariance = value; }
|
||||
}
|
||||
|
||||
[Serialize(0, true, description: "The minimum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "The minimum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int MountainCountMin
|
||||
{
|
||||
get { return mountainCountMin; }
|
||||
@@ -463,7 +425,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0, true, description: "The maximum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "The maximum number of mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int MountainCountMax
|
||||
{
|
||||
get { return mountainCountMax; }
|
||||
@@ -473,7 +435,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1000, true, description: "The minimum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(1000, IsPropertySaveable.Yes, description: "The minimum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int MountainHeightMin
|
||||
{
|
||||
get { return mountainHeightMin; }
|
||||
@@ -483,7 +445,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(5000, true, description: "The maximum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(5000, IsPropertySaveable.Yes, description: "The maximum height of the mountains on the sea floor."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int MountainHeightMax
|
||||
{
|
||||
get { return mountainHeightMax; }
|
||||
@@ -493,72 +455,72 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1, true, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The number of alien ruins in the level."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int RuinCount { get; set; }
|
||||
|
||||
[Serialize(1, true, description: "The minimum 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)]
|
||||
[Serialize(1, IsPropertySaveable.Yes, description: "The minimum 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 MinWreckCount { 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)]
|
||||
[Serialize(1, IsPropertySaveable.Yes, 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 MaxWreckCount { 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)]
|
||||
[Serialize(1, IsPropertySaveable.Yes, 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)]
|
||||
[Serialize(5, IsPropertySaveable.Yes, 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)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, 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)]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, 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)]
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes, 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)]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, 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()]
|
||||
[Serialize(0.4f, IsPropertySaveable.Yes, 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
|
||||
{
|
||||
get { return bottomHoleProbability; }
|
||||
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
[Serialize(1.0f, true, description: "Scale of the water particle texture."), Editable]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "Scale of the water particle texture."), Editable]
|
||||
public float WaterParticleScale
|
||||
{
|
||||
get { return waterParticleScale; }
|
||||
private set { waterParticleScale = Math.Max(value, 0.01f); }
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
[Serialize(2048.0f, IsPropertySaveable.Yes, description: "Size of the level wall texture."), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallTextureSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(2048.0f, true), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
[Serialize(2048.0f, IsPropertySaveable.Yes), Editable(minValue: 10.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeTextureWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes, description: "How far the level walls' edge texture portrudes outside the actual, \"physical\" edge of the cell."), Editable(minValue: 0.0f, maxValue: 1000.0f)]
|
||||
public float WallEdgeExpandOutwardsAmount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, true, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
[Serialize(1000.0f, IsPropertySaveable.Yes, description: "How far inside the level walls the edge texture continues."), Editable(minValue: 0.0f, maxValue: 10000.0f)]
|
||||
public float WallEdgeExpandInwardsAmount
|
||||
{
|
||||
get;
|
||||
@@ -574,38 +536,31 @@ namespace Barotrauma
|
||||
public Sprite WallSpriteDestroyed { get; private set; }
|
||||
public Sprite WaterParticles { get; private set; }
|
||||
|
||||
public static IEnumerable<Biome> GetBiomes()
|
||||
{
|
||||
return biomes;
|
||||
}
|
||||
|
||||
public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Biome biome = null)
|
||||
public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Identifier biome = default)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
if (LevelParams == null || !LevelParams.Any())
|
||||
if (!LevelParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Level generation presets not found - using default presets");
|
||||
return new LevelGenerationParams(null);
|
||||
throw new InvalidOperationException("Level generation presets not found - using default presets");
|
||||
}
|
||||
|
||||
var matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type && lp.allowedBiomes.Any());
|
||||
if (biome == null)
|
||||
var matchingLevelParams = LevelParams.Where(lp =>
|
||||
lp.Type == type &&
|
||||
(lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Any()) &&
|
||||
!lp.AllowedBiomeIdentifiers.Contains("None".ToIdentifier()));
|
||||
matchingLevelParams = biome.IsEmpty
|
||||
? matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || !lp.AllowedBiomeIdentifiers.All(b => Biome.Prefabs[b].IsEndBiome))
|
||||
: matchingLevelParams.Where(lp => lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Contains(biome));
|
||||
|
||||
if (!matchingLevelParams.Any())
|
||||
{
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.All(b => b.IsEndBiome));
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => lp.allowedBiomes.Contains(biome));
|
||||
}
|
||||
if (matchingLevelParams.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{(biome?.Identifier ?? "null")}\", type: \"{type}\"!");
|
||||
if (biome != null)
|
||||
DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{biome.IfEmpty("null".ToIdentifier())}\", type: \"{type}\")");
|
||||
if (!biome.IsEmpty)
|
||||
{
|
||||
//try to find params that at least have a suitable type
|
||||
matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type);
|
||||
if (matchingLevelParams.Count == 0)
|
||||
matchingLevelParams = LevelParams.Where(lp => lp.Type == type);
|
||||
if (!matchingLevelParams.Any())
|
||||
{
|
||||
//still not found, give up and choose some params randomly
|
||||
matchingLevelParams = LevelParams;
|
||||
@@ -613,53 +568,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
return matchingLevelParams[Rand.Range(0, matchingLevelParams.Count, Rand.RandSync.Server)];
|
||||
return matchingLevelParams.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
private LevelGenerationParams(XElement element)
|
||||
public LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Identifier = element == null ? "default" :
|
||||
element.GetAttributeString("identifier", null) ?? element.Name.ToString();
|
||||
OldIdentifier = element?.GetAttributeString("oldidentifier", null)?.ToLowerInvariant();
|
||||
Identifier = Identifier.ToLowerInvariant();
|
||||
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
if (element == null) { return; }
|
||||
if (element is null) { throw new ArgumentNullException($"{nameof(element)} is null"); }
|
||||
|
||||
string biomeStr = element.GetAttributeString("biomes", "");
|
||||
if (string.IsNullOrWhiteSpace(biomeStr) || biomeStr.Equals("any", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
allowedBiomes = new List<Biome>(biomes);
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] biomeNames = biomeStr.Split(',');
|
||||
for (int i = 0; i < biomeNames.Length; i++)
|
||||
{
|
||||
string biomeName = biomeNames[i].Trim().ToLowerInvariant();
|
||||
if (biomeName == "none") { continue; }
|
||||
var allowedBiomeIdentifiers = element.GetAttributeIdentifierArray("biomes", Array.Empty<Identifier>()).ToHashSet();
|
||||
AnyBiomeAllowed = allowedBiomeIdentifiers.Contains("any".ToIdentifier());
|
||||
allowedBiomeIdentifiers.Remove("any".ToIdentifier());
|
||||
AllowedBiomeIdentifiers = allowedBiomeIdentifiers.ToImmutableHashSet();
|
||||
|
||||
Biome matchingBiome = biomes.Find(b =>
|
||||
b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase) || (b.OldIdentifier?.Equals(biomeName, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
if (matchingBiome == null)
|
||||
{
|
||||
matchingBiome = biomes.Find(b => b.DisplayName.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchingBiome == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in level generation parameters: biome \"" + biomeName + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage("Please use biome identifiers instead of names in level generation parameter \"" + Identifier + "\".", Color.Orange);
|
||||
}
|
||||
}
|
||||
|
||||
allowedBiomes.Add(matchingBiome);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -691,87 +615,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
LevelParams = new List<LevelGenerationParams>();
|
||||
biomes = new List<Biome>();
|
||||
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files = new List<ContentFile>() { new ContentFile("Content/Map/LevelGenerationParameters.xml", ContentType.LevelGenerationParameters) };
|
||||
}
|
||||
|
||||
List<XElement> biomeElements = new List<XElement>();
|
||||
Dictionary<string, XElement> levelParamElements = new Dictionary<string, XElement>();
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
biomeElements.Clear();
|
||||
DebugConsole.NewMessage($"Overriding biomes with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
else if (biomeElements.Any() && mainElement.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another level generation parameter file already loaded! Use <override></override> tags to override the biomes.");
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
if (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
|
||||
{
|
||||
string identifier = element.FirstElement().GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the level generation parameters '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
levelParamElements.Remove(identifier);
|
||||
}
|
||||
levelParamElements.Add(identifier, element.FirstElement());
|
||||
}
|
||||
}
|
||||
else if (element.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
biomeElements.AddRange(element.Elements());
|
||||
}
|
||||
else
|
||||
{
|
||||
string identifier = element.GetAttributeString("identifier", null) ?? element.GetAttributeString("name", "");
|
||||
if (levelParamElements.ContainsKey(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate level generation parameters: '{identifier}' defined in {element.Name} of '{file.Path}'. Use <override></override> tags to override the generation parameters.");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
levelParamElements.Add(identifier, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement biomeElement in biomeElements)
|
||||
{
|
||||
biomes.Add(new Biome(biomeElement));
|
||||
}
|
||||
|
||||
foreach (XElement levelParamElement in levelParamElements.Values)
|
||||
{
|
||||
LevelParams.Add(new LevelGenerationParams(levelParamElement));
|
||||
}
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Barotrauma
|
||||
|
||||
public string Name => Prefab?.Name ?? "LevelObject (null)";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; } = new Dictionary<string, SerializableProperty>();
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; } = new Dictionary<Identifier, SerializableProperty>();
|
||||
|
||||
public Level.Cave ParentCave;
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Barotrauma
|
||||
Rotation = rotation;
|
||||
Health = prefab.Health;
|
||||
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
|
||||
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.ServerAndClient) : -1;
|
||||
|
||||
if (Sprite != null && prefab.SpriteSpecificPhysicsBodyElements.ContainsKey(Sprite))
|
||||
{
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
Physics.CollisionWall | Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
foreach (XElement triggerElement in prefab.LevelTriggerElements)
|
||||
foreach (var triggerElement in prefab.LevelTriggerElements)
|
||||
{
|
||||
Triggers ??= new List<LevelTrigger>();
|
||||
Vector2 triggerPosition = triggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
|
||||
@@ -147,7 +147,7 @@ namespace Barotrauma
|
||||
if (overrideProperties == null) { continue; }
|
||||
if (overrideProperties.Sprites.Count > 0)
|
||||
{
|
||||
spriteIndex = Rand.Int(overrideProperties.Sprites.Count, Rand.RandSync.Server);
|
||||
spriteIndex = Rand.Int(overrideProperties.Sprites.Count, Rand.RandSync.ServerAndClient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+40
-28
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -31,6 +30,16 @@ namespace Barotrauma
|
||||
{
|
||||
}
|
||||
|
||||
private readonly struct EventData : NetEntityEvent.IData
|
||||
{
|
||||
public readonly LevelObject LevelObject;
|
||||
|
||||
public EventData(LevelObject levelObject)
|
||||
{
|
||||
LevelObject = levelObject;
|
||||
}
|
||||
}
|
||||
|
||||
class SpawnPosition
|
||||
{
|
||||
public readonly GraphEdge GraphEdge;
|
||||
@@ -140,7 +149,7 @@ namespace Barotrauma
|
||||
new GraphEdge(level.EndPosition - Vector2.UnitX, level.EndPosition + Vector2.UnitX),
|
||||
-Vector2.UnitY, LevelObjectPrefab.SpawnPosType.LevelEnd, Alignment.Top));
|
||||
|
||||
var availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List);
|
||||
var availablePrefabs =LevelObjectPrefab.Prefabs.OrderBy(p => p.UintIdentifier).ToList();
|
||||
objects = new List<LevelObject>();
|
||||
updateableObjects = new List<LevelObject>();
|
||||
|
||||
@@ -167,7 +176,7 @@ namespace Barotrauma
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (prefab.MaxCount < amount)
|
||||
@@ -181,7 +190,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (Level.Cave cave in level.Caves)
|
||||
{
|
||||
availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall)));
|
||||
availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall))
|
||||
.OrderBy(p => p.UintIdentifier).ToList();
|
||||
availableSpawnPositions.Clear();
|
||||
suitableSpawnPositions.Clear();
|
||||
spawnPositionWeights.Clear();
|
||||
@@ -210,7 +220,7 @@ namespace Barotrauma
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level, cave);
|
||||
if (prefab.MaxCount < amount)
|
||||
@@ -228,7 +238,8 @@ namespace Barotrauma
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
var availablePrefabs = new List<LevelObjectPrefab>(LevelObjectPrefab.List.FindAll(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall)));
|
||||
var availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall))
|
||||
.OrderBy(p => p.UintIdentifier).ToList();
|
||||
Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
|
||||
Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
|
||||
|
||||
@@ -258,7 +269,7 @@ namespace Barotrauma
|
||||
spawnPositionWeights.Add(prefab,
|
||||
suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
|
||||
}
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server);
|
||||
SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
|
||||
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
|
||||
PlaceObject(prefab, spawnPosition, level);
|
||||
if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
|
||||
@@ -271,53 +282,53 @@ namespace Barotrauma
|
||||
private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level, Level.Cave parentCave = null)
|
||||
{
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface && spawnPosition.Normal.LengthSquared() > 0.001f && spawnPosition != null)
|
||||
if (prefab.AlignWithSurface && spawnPosition != null && spawnPosition.Normal.LengthSquared() > 0.001f)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
|
||||
}
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
|
||||
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient);
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
Vector2 edgeDir = Vector2.UnitX;
|
||||
if (spawnPosition == null)
|
||||
{
|
||||
position = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
|
||||
Rand.Range(0.0f, level.Size.X, Rand.RandSync.ServerAndClient),
|
||||
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
|
||||
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
if (!MathUtils.NearlyEqual(prefab.RandomOffset.X, 0.0f) || !MathUtils.NearlyEqual(prefab.RandomOffset.Y, 0.0f))
|
||||
{
|
||||
Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.Server);
|
||||
position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.Server);
|
||||
Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.ServerAndClient);
|
||||
position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
var newObject = new LevelObject(prefab,
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
|
||||
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.ServerAndClient)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.ServerAndClient), rotation);
|
||||
AddObject(newObject, level);
|
||||
newObject.ParentCave = parentCave;
|
||||
|
||||
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
|
||||
{
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.Server);
|
||||
int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.ServerAndClient);
|
||||
for (int j = 0; j < childCount; j++)
|
||||
{
|
||||
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
var matchingPrefabs = LevelObjectPrefab.Prefabs.Where(p => child.AllowedNames.Contains(p.Name));
|
||||
int prefabCount = matchingPrefabs.Count();
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
|
||||
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.ServerAndClient));
|
||||
if (childPrefab == null) { continue; }
|
||||
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
|
||||
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient) * prefab.MinSurfaceWidth;
|
||||
|
||||
var childObject = new LevelObject(childPrefab,
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
|
||||
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.ServerAndClient)),
|
||||
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.ServerAndClient),
|
||||
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient));
|
||||
|
||||
AddObject(childObject, level);
|
||||
childObject.ParentCave = parentCave;
|
||||
@@ -521,12 +532,12 @@ namespace Barotrauma
|
||||
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj });
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
@@ -577,7 +588,7 @@ namespace Barotrauma
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(generationParams)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.Server);
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
|
||||
@@ -585,7 +596,7 @@ namespace Barotrauma
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.Server);
|
||||
availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -606,9 +617,10 @@ namespace Barotrauma
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
LevelObject obj = extraData[0] as LevelObject;
|
||||
if (!(extraData is EventData eventData)) { throw new Exception($"Malformed LevelObjectManager event: expected {nameof(LevelObjectManager)}.{nameof(EventData)}"); }
|
||||
LevelObject obj = eventData.LevelObject;
|
||||
msg.WriteRangedInteger(objects.IndexOf(obj), 0, objects.Count);
|
||||
obj.ServerWrite(msg, c);
|
||||
}
|
||||
|
||||
+67
-127
@@ -1,14 +1,15 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObjectPrefab : ISerializableEntity
|
||||
partial class LevelObjectPrefab : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public static List<LevelObjectPrefab> List { get; } = new List<LevelObjectPrefab>();
|
||||
public readonly static PrefabCollection<LevelObjectPrefab> Prefabs = new PrefabCollection<LevelObjectPrefab>();
|
||||
|
||||
public class ChildObject
|
||||
{
|
||||
@@ -24,7 +25,7 @@ namespace Barotrauma
|
||||
|
||||
public ChildObject(XElement element)
|
||||
{
|
||||
AllowedNames = element.GetAttributeStringArray("names", new string[0]).ToList();
|
||||
AllowedNames = element.GetAttributeStringArray("names", Array.Empty<string>()).ToList();
|
||||
MinCount = element.GetAttributeInt("mincount", 1);
|
||||
MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount);
|
||||
}
|
||||
@@ -58,13 +59,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
public float MinSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
public float MaxSize
|
||||
{
|
||||
get;
|
||||
@@ -74,14 +75,14 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Which sides of a wall the object can appear on.
|
||||
/// </summary>
|
||||
[Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), true, description: "Which sides of a wall the object can spawn on."), Editable]
|
||||
[Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), IsPropertySaveable.Yes, description: "Which sides of a wall the object can spawn on."), Editable]
|
||||
public Alignment Alignment
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(SpawnPosType.Wall, false), Editable()]
|
||||
[Serialize(SpawnPosType.Wall, IsPropertySaveable.No), Editable()]
|
||||
public SpawnPosType SpawnPos
|
||||
{
|
||||
get;
|
||||
@@ -94,13 +95,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly List<XElement> LevelTriggerElements;
|
||||
public readonly List<ContentXElement> LevelTriggerElements;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the commonness of the object in a specific level type.
|
||||
/// Key = name of the level type, value = commonness in that level type.
|
||||
/// </summary>
|
||||
public Dictionary<string, float> OverrideCommonness;
|
||||
public readonly Dictionary<Identifier, float> OverrideCommonness;
|
||||
|
||||
public XElement PhysicsBodyElement
|
||||
{
|
||||
@@ -120,14 +121,14 @@ namespace Barotrauma
|
||||
} = new Dictionary<Sprite, XElement>();
|
||||
|
||||
|
||||
[Serialize(10000, false, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(10000, IsPropertySaveable.No, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
||||
public int MaxCount
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,1.0", true), Editable]
|
||||
[Serialize("0.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
public Vector2 DepthRange
|
||||
{
|
||||
get;
|
||||
@@ -135,7 +136,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f),
|
||||
Serialize(0.0f, true, description: "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")]
|
||||
Serialize(0.0f, IsPropertySaveable.Yes, description: "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")]
|
||||
/// <summary>
|
||||
/// The tendency for the prefab to form clusters. Used as an exponent for perlin noise values
|
||||
/// that are used to determine the probability for an object to spawn at a specific position.
|
||||
@@ -147,7 +148,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f),
|
||||
Serialize(0.0f, true, description: "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " +
|
||||
Serialize(0.0f, IsPropertySaveable.Yes, description: "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " +
|
||||
" for an object to spawn at a specific position. Using the same (or close) value for different objects means the objects tend " +
|
||||
"to form clusters in the same areas.")]
|
||||
/// <summary>
|
||||
@@ -162,35 +163,35 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", true, description: "Random offset from the surface the object spawns on.")]
|
||||
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "Random offset from the surface the object spawns on.")]
|
||||
public Vector2 RandomOffset
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
|
||||
public bool AlignWithSurface
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, true, description: "Can the object be placed near the start of the level.")]
|
||||
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the start of the level.")]
|
||||
public bool AllowAtStart
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, true, description: "Can the object be placed near the end of the level.")]
|
||||
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the end of the level.")]
|
||||
public bool AllowAtEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
/// <summary>
|
||||
/// Minimum length of a graph edge the object can spawn on.
|
||||
/// </summary>
|
||||
@@ -201,7 +202,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private Vector2 randomRotation;
|
||||
[Editable, Serialize("0.0,0.0", true, description: "How much the rotation of the object can vary (min and max values in degrees).")]
|
||||
[Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "How much the rotation of the object can vary (min and max values in degrees).")]
|
||||
public Vector2 RandomRotation
|
||||
{
|
||||
get { return new Vector2(MathHelper.ToDegrees(randomRotation.X), MathHelper.ToDegrees(randomRotation.Y)); }
|
||||
@@ -214,7 +215,7 @@ namespace Barotrauma
|
||||
public Vector2 RandomRotationRad => randomRotation;
|
||||
|
||||
private float swingAmount;
|
||||
[Serialize(0.0f, true, description: "How much the object swings (in degrees)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How much the object swings (in degrees)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f)]
|
||||
public float SwingAmount
|
||||
{
|
||||
get { return MathHelper.ToDegrees(swingAmount); }
|
||||
@@ -226,28 +227,28 @@ namespace Barotrauma
|
||||
|
||||
public float SwingAmountRad => swingAmount;
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the object swings."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the object swings."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float SwingFrequency
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true, description: "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")]
|
||||
[Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")]
|
||||
public Vector2 ScaleOscillation
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "How fast the object's scale oscillates."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the object's scale oscillates."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float ScaleOscillationFrequency
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, true, description: "How likely it is for the object to spawn in a level. " +
|
||||
[Editable, Serialize(1.0f, IsPropertySaveable.Yes, description: "How likely it is for the object to spawn in a level. " +
|
||||
"This is relative to the commonness of the other objects - for example, having an object with " +
|
||||
"a commonness of 1 and another with a commonness of 10 would mean the latter appears in levels 10 times as frequently as the former. " +
|
||||
"The commonness value can be overridden on specific level types.")]
|
||||
@@ -257,45 +258,35 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true, description: "How much the object disrupts submarine's sonar."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How much the object disrupts submarine's sonar."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
||||
public float SonarDisruption
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
|
||||
public bool TakeLevelWallDamage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool HideWhenBroken
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, true), Editable]
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Identifier; }
|
||||
}
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
{
|
||||
@@ -303,7 +294,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
@@ -323,91 +314,20 @@ namespace Barotrauma
|
||||
return "LevelObjectPrefab (" + Identifier + ")";
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
List.Clear();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs);
|
||||
if (files.Count() > 0)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
LoadConfig(file.Path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadConfig("Content/LevelObjects/LevelObject/Prefabs.xml");
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadConfig(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configPath);
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
DebugConsole.NewMessage($"Overriding all level object prefabs with '{configPath}'", Color.Yellow);
|
||||
List.Clear();
|
||||
}
|
||||
else if (List.Any())
|
||||
{
|
||||
DebugConsole.Log($"Loading additional level object prefabs from file '{configPath}'");
|
||||
}
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
var element = subElement.IsOverride() ? subElement.FirstElement() : subElement;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
var existingPrefab = List.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingPrefab != null)
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing level object prefab '{identifier}' using the file '{configPath}'", Color.Yellow);
|
||||
List.Remove(existingPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{configPath}': Duplicate level object prefab '{identifier}' found in '{configPath}'! Each level object prefab must have a unique identifier. " +
|
||||
"Use <override></override> tags to override prefabs.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List.Add(new LevelObjectPrefab(element));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(string.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
public LevelObjectPrefab(XElement element, string identifier = null)
|
||||
public LevelObjectPrefab(ContentXElement element, LevelObjectPrefabsFile file, Identifier identifierOverride = default) : base(file, ParseIdentifier(identifierOverride, element))
|
||||
{
|
||||
ChildObjects = new List<ChildObject>();
|
||||
LevelTriggerElements = new List<XElement>();
|
||||
LevelTriggerElements = new List<ContentXElement>();
|
||||
OverrideProperties = new List<LevelObjectPrefab>();
|
||||
OverrideCommonness = new Dictionary<string, float>();
|
||||
OverrideCommonness = new Dictionary<Identifier, float>();
|
||||
|
||||
Identifier = null;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
if (element != null)
|
||||
{
|
||||
Config = element;
|
||||
Identifier = element.GetAttributeString("identifier", null) ?? identifier;
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#else
|
||||
DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#endif
|
||||
Identifier = element.Name.ToString();
|
||||
}
|
||||
LoadElements(element, -1);
|
||||
|
||||
LoadElements(file, element, -1);
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -419,10 +339,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadElements(XElement element, int parentTriggerIndex)
|
||||
public static Identifier ParseIdentifier(Identifier identifierOverride, XElement element)
|
||||
{
|
||||
if (!identifierOverride.IsEmpty) { return identifierOverride; }
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#else
|
||||
DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
||||
#endif
|
||||
identifier = element.NameAsIdentifier();
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private void LoadElements(LevelObjectPrefabsFile file, ContentXElement element, int parentTriggerIndex)
|
||||
{
|
||||
int propertyOverrideCount = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -430,8 +366,8 @@ namespace Barotrauma
|
||||
var newSprite = new Sprite(subElement, lazyLoad: true);
|
||||
Sprites.Add(newSprite);
|
||||
var spriteSpecificPhysicsBodyElement =
|
||||
subElement.Element("PhysicsBody") ?? subElement.Element("Body") ??
|
||||
subElement.Element("physicsbody") ?? subElement.Element("body");
|
||||
subElement.GetChildElement("PhysicsBody") ?? subElement.GetChildElement("Body") ??
|
||||
subElement.GetChildElement("physicsbody") ?? subElement.GetChildElement("body");
|
||||
if (spriteSpecificPhysicsBodyElement != null)
|
||||
{
|
||||
SpriteSpecificPhysicsBodyElements.Add(newSprite, spriteSpecificPhysicsBodyElement);
|
||||
@@ -441,7 +377,7 @@ namespace Barotrauma
|
||||
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
Identifier levelType = subElement.GetAttributeIdentifier("leveltype", Identifier.Empty);
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
||||
@@ -451,13 +387,13 @@ namespace Barotrauma
|
||||
case "trigger":
|
||||
OverrideProperties.Add(null);
|
||||
LevelTriggerElements.Add(subElement);
|
||||
LoadElements(subElement, LevelTriggerElements.Count - 1);
|
||||
LoadElements(file, subElement, LevelTriggerElements.Count - 1);
|
||||
break;
|
||||
case "childobject":
|
||||
ChildObjects.Add(new ChildObject(subElement));
|
||||
break;
|
||||
case "overrideproperties":
|
||||
var propertyOverride = new LevelObjectPrefab(subElement, identifier: Identifier + "-" + propertyOverrideCount);
|
||||
var propertyOverride = new LevelObjectPrefab(subElement, file, identifierOverride: $"{Identifier}-{propertyOverrideCount}".ToIdentifier());
|
||||
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
|
||||
if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
|
||||
{
|
||||
@@ -475,12 +411,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
|
||||
public float GetCommonness(CaveGenerationParams generationParams, bool requireCaveSpecificOverride = true)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
if (generationParams != null &&
|
||||
generationParams.Identifier != Identifier.Empty &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
@@ -490,13 +427,16 @@ namespace Barotrauma
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
if (generationParams != null &&
|
||||
generationParams.Identifier != Identifier.Empty &&
|
||||
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
|
||||
(generationParams.OldIdentifier != null && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
|
||||
(!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
public string InfectIdentifier
|
||||
public Identifier InfectIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -199,7 +199,7 @@ namespace Barotrauma
|
||||
private bool triggeredOnce;
|
||||
private readonly bool triggerOnce;
|
||||
|
||||
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
public LevelTrigger(ContentXElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
|
||||
{
|
||||
TriggererPosition = new Dictionary<Entity, Vector2>();
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace Barotrauma
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
|
||||
|
||||
InfectIdentifier = element.GetAttributeString("infectidentifier", null);
|
||||
InfectIdentifier = element.GetAttributeIdentifier("infectidentifier", Identifier.Empty);
|
||||
InfectionChance = element.GetAttributeFloat("infectionchance", 0.05f);
|
||||
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
@@ -235,7 +235,7 @@ namespace Barotrauma
|
||||
UseNetworkSyncing = element.GetAttributeBool("networksyncing", false);
|
||||
|
||||
unrotatedForce =
|
||||
element.Attribute("force") != null && element.Attribute("force").Value.Contains(',') ?
|
||||
element.GetAttribute("force") != null && element.GetAttribute("force").Value.Contains(',') ?
|
||||
element.GetAttributeVector2("force", Vector2.Zero) :
|
||||
new Vector2(element.GetAttributeFloat("force", 0.0f), 0.0f);
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace Barotrauma
|
||||
|
||||
TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
|
||||
|
||||
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
|
||||
var tagsArray = element.GetAttributeStringArray("tags", Array.Empty<string>());
|
||||
foreach (string tag in tagsArray)
|
||||
{
|
||||
tags.Add(tag.ToLowerInvariant());
|
||||
@@ -272,7 +272,7 @@ namespace Barotrauma
|
||||
|
||||
if (triggeredBy.HasFlag(TriggererType.OtherTrigger))
|
||||
{
|
||||
var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]);
|
||||
var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", Array.Empty<string>());
|
||||
foreach (string tag in otherTagsArray)
|
||||
{
|
||||
allowedOtherTriggerTags.Add(tag.ToLowerInvariant());
|
||||
@@ -280,7 +280,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
string debugName = string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : $"LevelTrigger in {parentDebugName}";
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -317,12 +317,12 @@ namespace Barotrauma
|
||||
-sa * unrotatedForce.X + ca * unrotatedForce.Y);
|
||||
}
|
||||
|
||||
public static void LoadStatusEffect(List<StatusEffect> statusEffects, XElement element, string parentDebugName)
|
||||
public static void LoadStatusEffect(List<StatusEffect> statusEffects, ContentXElement element, string parentDebugName)
|
||||
{
|
||||
statusEffects.Add(StatusEffect.Load(element, parentDebugName));
|
||||
}
|
||||
|
||||
public static void LoadAttack(XElement element, string parentDebugName, bool triggerOnce, List<Attack> attacks)
|
||||
public static void LoadAttack(ContentXElement element, string parentDebugName, bool triggerOnce, List<Attack> attacks)
|
||||
{
|
||||
var attack = new Attack(element, parentDebugName);
|
||||
if (!triggerOnce)
|
||||
@@ -574,7 +574,7 @@ namespace Barotrauma
|
||||
else if (triggerer is Submarine submarine)
|
||||
{
|
||||
ApplyAttacks(attacks, worldPosition, deltaTime);
|
||||
if (!string.IsNullOrWhiteSpace(InfectIdentifier))
|
||||
if (!InfectIdentifier.IsEmpty)
|
||||
{
|
||||
submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
#if DEBUG
|
||||
using System.Xml;
|
||||
#else
|
||||
@@ -20,93 +22,38 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
class RuinGenerationParams : OutpostGenerationParams
|
||||
{
|
||||
public static List<RuinGenerationParams> RuinParams
|
||||
{
|
||||
get
|
||||
{
|
||||
if (paramsList == null)
|
||||
{
|
||||
LoadAll();
|
||||
}
|
||||
return paramsList;
|
||||
}
|
||||
}
|
||||
public readonly static PrefabCollection<RuinGenerationParams> RuinParams =
|
||||
new PrefabCollection<RuinGenerationParams>();
|
||||
|
||||
private static List<RuinGenerationParams> paramsList;
|
||||
public override string Name => "RuinGenerationParams";
|
||||
|
||||
private readonly string filePath;
|
||||
|
||||
private RuinGenerationParams(XElement element, string filePath) : base(element, filePath)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public static RuinGenerationParams GetRandom(Rand.RandSync randSync = Rand.RandSync.Server)
|
||||
{
|
||||
if (paramsList == null) { LoadAll(); }
|
||||
|
||||
if (paramsList.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("No ruin configuration files found in any content package.");
|
||||
return new RuinGenerationParams(null, null);
|
||||
}
|
||||
|
||||
return paramsList[Rand.Int(paramsList.Count, randSync)];
|
||||
}
|
||||
|
||||
private static void LoadAll()
|
||||
{
|
||||
paramsList = new List<RuinGenerationParams>();
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc?.Root == null) { continue; }
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
var mainElement = subElement;
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
mainElement = subElement.FirstElement();
|
||||
paramsList.Clear();
|
||||
DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow);
|
||||
}
|
||||
else if (paramsList.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'");
|
||||
}
|
||||
var newParams = new RuinGenerationParams(mainElement, configFile.Path);
|
||||
paramsList.Add(newParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
{
|
||||
paramsList?.Clear();
|
||||
paramsList = null;
|
||||
}
|
||||
public RuinGenerationParams(ContentXElement element, RuinConfigFile file) : base(element, file) { }
|
||||
|
||||
public static void SaveAll()
|
||||
{
|
||||
#warning TODO: revise
|
||||
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
IEnumerable<ContentPackage> packages = ContentPackageManager.LocalPackages;
|
||||
#if DEBUG
|
||||
packages = packages.Union(ContentPackageManager.VanillaCorePackage.ToEnumerable());
|
||||
#endif
|
||||
foreach (RuinGenerationParams generationParams in RuinParams)
|
||||
{
|
||||
foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
|
||||
foreach (RuinConfigFile configFile in packages.SelectMany(p => p.GetFiles<RuinConfigFile>()))
|
||||
{
|
||||
if (configFile.Path != generationParams.filePath) { continue; }
|
||||
if (configFile.Path != generationParams.ContentFile.Path) { continue; }
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { continue; }
|
||||
|
||||
SerializableProperty.SerializeProperties(generationParams, doc.Root);
|
||||
|
||||
using (var writer = XmlWriter.Create(configFile.Path, settings))
|
||||
using (var writer = XmlWriter.Create(configFile.Path.Value, settings))
|
||||
{
|
||||
doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
@@ -114,5 +61,7 @@ namespace Barotrauma.RuinGeneration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Barotrauma.RuinGeneration
|
||||
if (interestingPosCount == 0)
|
||||
{
|
||||
//make sure there's at least one PositionsOfInterest in the ruins
|
||||
level.PositionsOfInterest.Add(new Level.InterestingPosition(waypoints.GetRandom(Rand.RandSync.Server).WorldPosition.ToPoint(), Level.PositionType.Ruin, this));
|
||||
level.PositionsOfInterest.Add(new Level.InterestingPosition(waypoints.GetRandom(Rand.RandSync.ServerAndClient).WorldPosition.ToPoint(), Level.PositionType.Ruin, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -21,10 +23,26 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public readonly SubmarineInfo subInfo;
|
||||
|
||||
public LinkedSubmarinePrefab(SubmarineInfo subInfo)
|
||||
|
||||
public override Sprite Sprite => null;
|
||||
|
||||
public override string OriginalName => Name.Value;
|
||||
|
||||
public override LocalizedString Name => subInfo.Name;
|
||||
|
||||
public override ImmutableHashSet<Identifier> Tags => null;
|
||||
|
||||
public override ImmutableHashSet<Identifier> AllowedLinks => null;
|
||||
|
||||
public override MapEntityCategory Category => MapEntityCategory.Misc;
|
||||
|
||||
public override ImmutableHashSet<string> Aliases { get; }
|
||||
|
||||
public LinkedSubmarinePrefab(SubmarineInfo subInfo) : base(subInfo.Name.ToIdentifier())
|
||||
{
|
||||
this.subInfo = subInfo;
|
||||
|
||||
Aliases = Name.Value.ToEnumerable().ToImmutableHashSet();
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
@@ -71,6 +89,8 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int CargoCapacity { get; private set; }
|
||||
|
||||
public LinkedSubmarine(Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base(null, submarine, id)
|
||||
@@ -110,6 +130,7 @@ namespace Barotrauma
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub, id);
|
||||
sl.GenerateWallVertices(element);
|
||||
sl.CargoCapacity = element.GetAttributeInt("cargocapacity", 0);
|
||||
if (sl.wallVertices.Any())
|
||||
{
|
||||
sl.Rect = new Rectangle(
|
||||
@@ -152,7 +173,7 @@ namespace Barotrauma
|
||||
if (element.Name != "Structure") { continue; }
|
||||
|
||||
string name = element.GetAttributeString("name", "");
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
|
||||
StructurePrefab prefab = Structure.FindPrefab(name, identifier);
|
||||
if (prefab == null) { continue; }
|
||||
@@ -173,7 +194,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// LinkedSubmarine.Load() is called from MapEntity.LoadAll()
|
||||
public static LinkedSubmarine Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
public static LinkedSubmarine Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
|
||||
LinkedSubmarine linkedSub;
|
||||
@@ -206,14 +227,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
linkedSub.filePath = element.GetAttributeString("filepath", "");
|
||||
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
|
||||
#warning TODO: revise
|
||||
linkedSub.filePath = element.GetAttributeContentPath("filepath")?.Value ?? string.Empty;
|
||||
int[] linkedToIds = element.GetAttributeIntArray("linkedto", Array.Empty<int>());
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
linkedSub.linkedToID.Add(idRemap.GetOffsetId(linkedToIds[i]));
|
||||
}
|
||||
linkedSub.originalLinkedToID = idRemap.GetOffsetId(element.GetAttributeInt("originallinkedto", 0));
|
||||
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
|
||||
linkedSub.CargoCapacity = element.GetAttributeInt("cargocapacity", 0);
|
||||
|
||||
return linkedSub.loadSub ? linkedSub : null;
|
||||
}
|
||||
@@ -269,7 +292,7 @@ namespace Barotrauma
|
||||
DockingPort linkedPort = null;
|
||||
DockingPort myPort = null;
|
||||
|
||||
MapEntity linkedItem = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent<DockingPort>() != null);
|
||||
MapEntity linkedItem = linkedTo.FirstOrDefault(lt => (lt as Item)?.GetComponent<DockingPort>() != null);
|
||||
if (linkedItem == null)
|
||||
{
|
||||
linkedPort = DockingPort.List.FirstOrDefault(dp => dp.DockingTarget != null && dp.DockingTarget.Item.Submarine == sub);
|
||||
@@ -349,7 +372,7 @@ namespace Barotrauma
|
||||
wall.SetDamage(i, 0, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != sub) { continue; }
|
||||
hull.WaterVolume = 0.0f;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,29 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LocationType
|
||||
class LocationType : PrefabWithUintIdentifier
|
||||
{
|
||||
public static readonly List<LocationType> List = new List<LocationType>();
|
||||
public static readonly PrefabCollection<LocationType> Prefabs = new PrefabCollection<LocationType>();
|
||||
|
||||
private readonly List<string> names;
|
||||
private readonly List<Sprite> portraits = new List<Sprite>();
|
||||
|
||||
//<name, commonness>
|
||||
private readonly List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private readonly ImmutableArray<(Identifier Name, float Commonness)> hireableJobs;
|
||||
private readonly float totalHireableWeight;
|
||||
|
||||
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
|
||||
|
||||
public readonly string Identifier;
|
||||
public readonly string Name;
|
||||
public readonly Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
|
||||
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -31,8 +31,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
|
||||
|
||||
public readonly List<string> MissionIdentifiers = new List<string>();
|
||||
public readonly List<string> MissionTags = new List<string>();
|
||||
public readonly ImmutableArray<Identifier> MissionIdentifiers;
|
||||
public readonly ImmutableArray<Identifier> MissionTags;
|
||||
|
||||
public readonly List<string> HideEntitySubcategories = new List<string>();
|
||||
|
||||
@@ -44,7 +44,15 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<string> NameFormats { get; private set; }
|
||||
private ImmutableArray<string>? nameFormats = null;
|
||||
public IReadOnlyList<string> NameFormats
|
||||
{
|
||||
get
|
||||
{
|
||||
nameFormats ??= TextManager.GetAll($"LocationNameFormat.{Identifier}").ToImmutableArray();
|
||||
return nameFormats;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasHireableCharacters
|
||||
{
|
||||
@@ -56,7 +64,7 @@ namespace Barotrauma
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
public string ReplaceInRadiation { get; }
|
||||
|
||||
public Sprite Sprite { get; private set; }
|
||||
@@ -77,6 +85,8 @@ namespace Barotrauma
|
||||
/// In percentages
|
||||
/// </summary>
|
||||
public int StorePriceModifierRange { get; } = 5;
|
||||
public int DailySpecialsCount { get; } = 1;
|
||||
public int RequestedGoodsCount { get; } = 1;
|
||||
|
||||
public List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>()
|
||||
{
|
||||
@@ -104,32 +114,30 @@ namespace Barotrauma
|
||||
return $"LocationType (" + Identifier + ")";
|
||||
}
|
||||
|
||||
private LocationType(XElement element)
|
||||
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", element.Name.ToString());
|
||||
Name = TextManager.Get("LocationName." + Identifier, fallBackTag: "unknown");
|
||||
Name = TextManager.Get("LocationName." + Identifier, "unknown");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
NameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
|
||||
MissionIdentifiers = element.GetAttributeStringArray("missionidentifiers", new string[0]).ToList();
|
||||
MissionTags = element.GetAttributeStringArray("missiontags", new string[0]).ToList();
|
||||
MissionIdentifiers = element.GetAttributeIdentifierArray("missionidentifiers", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
MissionTags = element.GetAttributeIdentifierArray("missiontags", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
|
||||
HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", new string[0]).ToList();
|
||||
HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", Array.Empty<string>()).ToList();
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeString(nameof(ReplaceInRadiation).ToLower(), "");
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
|
||||
ContentPath nameFile = element.GetAttributeContentPath("namefile") ?? ContentPath.FromRaw(null, "Content/Map/locationNames.txt");
|
||||
try
|
||||
{
|
||||
names = File.ReadAllLines(nameFile).ToList();
|
||||
names = File.ReadAllLines(nameFile.Value).ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -137,7 +145,7 @@ namespace Barotrauma
|
||||
names = new List<string>() { "Name file not found" };
|
||||
}
|
||||
|
||||
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" });
|
||||
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
|
||||
foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
|
||||
{
|
||||
string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
|
||||
@@ -145,37 +153,36 @@ namespace Barotrauma
|
||||
!int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
|
||||
!float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\"");
|
||||
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone1index: zone1commonness, zone2index: zone2commonness\"");
|
||||
break;
|
||||
}
|
||||
CommonnessPerZone[zoneIndex] = zoneCommonness;
|
||||
}
|
||||
|
||||
hireableJobs = new List<Tuple<JobPrefab, float>>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
string[] minCountPerZoneStrs = element.GetAttributeStringArray("mincountperzone", Array.Empty<string>());
|
||||
foreach (string minCountPerZoneStr in minCountPerZoneStrs)
|
||||
{
|
||||
string[] splitMinCountPerZone = minCountPerZoneStr.Split(':');
|
||||
if (splitMinCountPerZone.Length != 2 ||
|
||||
!int.TryParse(splitMinCountPerZone[0].Trim(), out int zoneIndex) ||
|
||||
!int.TryParse(splitMinCountPerZone[1].Trim(), out int minCount))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to read minimum count values for location type \"" + Identifier + "\" - minimum counts should be given in the format \"zone1index: zone1mincount, zone2index: zone2mincount\"");
|
||||
break;
|
||||
}
|
||||
MinCountPerZone[zoneIndex] = minCount;
|
||||
}
|
||||
|
||||
var hireableJobs = new List<(Identifier, float)>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "hireable":
|
||||
string jobIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
JobPrefab jobPrefab = null;
|
||||
if (jobIdentifier == "")
|
||||
{
|
||||
DebugConsole.ThrowError("Error in location type \""+ Identifier + "\" - hireable jobs should be configured using identifiers instead of names.");
|
||||
}
|
||||
else
|
||||
{
|
||||
jobPrefab = JobPrefab.Get(jobIdentifier.ToLowerInvariant());
|
||||
}
|
||||
if (jobPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in in location type " + Identifier + " - could not find a job with the identifier \"" + jobIdentifier + "\".");
|
||||
continue;
|
||||
}
|
||||
Identifier jobIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
|
||||
totalHireableWeight += jobCommonness;
|
||||
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
|
||||
hireableJobs.Add(hireableJob);
|
||||
hireableJobs.Add((jobIdentifier, jobCommonness));
|
||||
break;
|
||||
case "symbol":
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
@@ -213,19 +220,22 @@ namespace Barotrauma
|
||||
StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color));
|
||||
}
|
||||
}
|
||||
DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount);
|
||||
RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.hireableJobs = hireableJobs.ToImmutableArray();
|
||||
}
|
||||
|
||||
public JobPrefab GetRandomHireable()
|
||||
{
|
||||
float randFloat = Rand.Range(0.0f, totalHireableWeight, Rand.RandSync.Server);
|
||||
float randFloat = Rand.Range(0.0f, totalHireableWeight, Rand.RandSync.ServerAndClient);
|
||||
|
||||
foreach (Tuple<JobPrefab, float> hireable in hireableJobs)
|
||||
foreach ((Identifier jobIdentifier, float commonness) in hireableJobs)
|
||||
{
|
||||
if (randFloat < hireable.Item2) return hireable.Item1;
|
||||
randFloat -= hireable.Item2;
|
||||
if (randFloat < commonness) { return JobPrefab.Prefabs[jobIdentifier]; }
|
||||
randFloat -= commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -252,12 +262,13 @@ namespace Barotrauma
|
||||
|
||||
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false)
|
||||
{
|
||||
Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
Debug.Assert(Prefabs.Any(), "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
|
||||
List<LocationType> allowedLocationTypes =
|
||||
List.FindAll(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost));
|
||||
LocationType[] allowedLocationTypes =
|
||||
Prefabs.Where(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost))
|
||||
.OrderBy(p => p.UintIdentifier).ToArray();
|
||||
|
||||
if (allowedLocationTypes.Count == 0)
|
||||
if (allowedLocationTypes.Length == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
|
||||
}
|
||||
@@ -266,82 +277,15 @@ namespace Barotrauma
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
allowedLocationTypes,
|
||||
allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(),
|
||||
allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToArray(),
|
||||
rand);
|
||||
}
|
||||
else
|
||||
{
|
||||
return allowedLocationTypes[rand.Next() % allowedLocationTypes.Count];
|
||||
return allowedLocationTypes[rand.Next() % allowedLocationTypes.Length];
|
||||
}
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
List.Clear();
|
||||
var locationTypeFiles = GameMain.Instance.GetFilesOfType(ContentType.LocationTypes);
|
||||
if (!locationTypeFiles.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("No location types configured in any of the selected content packages. Attempting to load from the vanilla content package...");
|
||||
locationTypeFiles = ContentPackage.GetFilesOfType(GameMain.VanillaContent.ToEnumerable(), ContentType.LocationTypes);
|
||||
if (!locationTypeFiles.Any())
|
||||
{
|
||||
throw new Exception("No location types configured in any of the selected content packages. Please try uninstalling mods or reinstalling the game.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ContentFile file in locationTypeFiles)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
DebugConsole.NewMessage($"Overriding all location types with '{file.Path}'", Color.Yellow);
|
||||
List.Clear();
|
||||
}
|
||||
else if (List.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Loading additional location types from file '{file.Path}'");
|
||||
}
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var element = sourceElement;
|
||||
bool allowOverriding = false;
|
||||
if (sourceElement.IsOverride())
|
||||
{
|
||||
element = sourceElement.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
string identifier = element.GetAttributeString("identifier", null);
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': No identifier defined for {element.Name.ToString()}");
|
||||
continue;
|
||||
}
|
||||
var duplicate = List.FirstOrDefault(l => l.Identifier == identifier);
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (allowOverriding)
|
||||
{
|
||||
List.Remove(duplicate);
|
||||
DebugConsole.NewMessage($"Overriding the location type with the identifier '{identifier}' with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Duplicate identifier defined with the identifier '{identifier}'");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
LocationType locationType = new LocationType(element);
|
||||
List.Add(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (EventSet eventSet in EventSet.List)
|
||||
{
|
||||
eventSet.CheckLocationTypeErrors();
|
||||
}
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -21,7 +23,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// The change can only happen if there's at least one of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> RequiredLocations;
|
||||
public readonly ImmutableArray<Identifier> RequiredLocations;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the change to occur
|
||||
@@ -55,7 +57,7 @@ namespace Barotrauma
|
||||
|
||||
public Requirement(XElement element, LocationTypeChange change)
|
||||
{
|
||||
RequiredLocations = element.GetAttributeStringArray("requiredlocations", element.GetAttributeStringArray("requiredadjacentlocations", new string[0])).ToList();
|
||||
RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty<Identifier>())).ToImmutableArray();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
@@ -124,9 +126,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string CurrentType;
|
||||
public readonly Identifier CurrentType;
|
||||
|
||||
public readonly string ChangeToType;
|
||||
public readonly Identifier ChangeToType;
|
||||
|
||||
/// <summary>
|
||||
/// Base probability per turn for the location to change if near one of the RequiredLocations
|
||||
@@ -137,12 +139,33 @@ namespace Barotrauma
|
||||
|
||||
public List<Requirement> Requirements = new List<Requirement>();
|
||||
|
||||
public List<string> Messages = new List<string>();
|
||||
private readonly bool requireChangeMessages;
|
||||
private readonly string messageTag;
|
||||
private ImmutableArray<string>? messages = null;
|
||||
public IReadOnlyList<string> Messages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!messages.HasValue)
|
||||
{
|
||||
messages = TextManager.GetAll(messageTag).ToImmutableArray();
|
||||
if (messages.Value.None())
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError($"No messages defined for the location type change {CurrentType} -> {ChangeToType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The change can't happen if there's one or more of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> DisallowedAdjacentLocations;
|
||||
public readonly ImmutableArray<Identifier> DisallowedAdjacentLocations;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the DisallowedAdjacentLocations for the change to be disabled
|
||||
@@ -156,14 +179,14 @@ namespace Barotrauma
|
||||
|
||||
public readonly Point RequiredDurationRange;
|
||||
|
||||
public LocationTypeChange(string currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
public LocationTypeChange(Identifier currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
{
|
||||
CurrentType = currentType;
|
||||
ChangeToType = element.GetAttributeString("type", element.GetAttributeString("to", ""));
|
||||
ChangeToType = element.GetAttributeIdentifier("type", element.GetAttributeIdentifier("to", ""));
|
||||
|
||||
RequireDiscovered = element.GetAttributeBool("requirediscovered", false);
|
||||
|
||||
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
|
||||
DisallowedAdjacentLocations = element.GetAttributeIdentifierArray("disallowedadjacentlocations", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
DisallowedProximity = Math.Max(element.GetAttributeInt("disallowedproximity", 1), 1);
|
||||
|
||||
RequiredDurationRange = element.GetAttributePoint("requireddurationrange", Point.Zero);
|
||||
@@ -184,19 +207,10 @@ namespace Barotrauma
|
||||
RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0));
|
||||
}
|
||||
|
||||
string messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
|
||||
this.requireChangeMessages = requireChangeMessages;
|
||||
messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
|
||||
|
||||
Messages = TextManager.GetAll(messageTag);
|
||||
if (Messages == null)
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
}
|
||||
Messages = new List<string>();
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("requirement", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
|
||||
bool lairsFound = false;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -112,11 +112,11 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}", -100, 100, Rand.Range(-10, 11, Rand.RandSync.Server));
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
List<XElement> connectionElements = new List<XElement>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -133,10 +133,10 @@ namespace Barotrauma
|
||||
Locations[locationIndices.Y].Connections.Add(connection);
|
||||
connection.LevelData = new LevelData(subElement.Element("Level"));
|
||||
string biomeId = subElement.GetAttributeString("biome", "");
|
||||
connection.Biome =
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ??
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.OldIdentifier == biomeId) ??
|
||||
LevelGenerationParams.GetBiomes().First();
|
||||
connection.Biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeId) ??
|
||||
Biome.Prefabs.FirstOrDefault(b => !b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeId) ??
|
||||
Biome.Prefabs.First();
|
||||
Connections.Add(connection);
|
||||
connectionElements.Add(subElement);
|
||||
break;
|
||||
@@ -182,8 +182,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
Connections[i].LevelData.HasHuntingGrounds = Rand.Range(0.0f, 1.0f) < Connections[i].Difficulty / 100.0f * maxHuntingGroundsProbability;
|
||||
Connections[i].LevelData.HasHuntingGrounds = Rand.Range(0.0f, 1.0f) < Connections[i].Difficulty / 100.0f * LevelData.MaxHuntingGroundsProbability;
|
||||
connectionElements[i].SetAttributeValue("hashuntinggrounds", true);
|
||||
}
|
||||
}
|
||||
@@ -214,25 +213,33 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}", -100, 100, Rand.Range(-10, 11, Rand.RandSync.Server));
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.Identifier.Equals("city", StringComparison.OrdinalIgnoreCase) &&
|
||||
!location.Type.Identifier.Equals("outpost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.HasOutpost) { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
@@ -252,8 +259,8 @@ namespace Barotrauma
|
||||
for (float y = 10.0f; y < Height - 10.0f; y += generationParams.VoronoiSiteInterval.Y)
|
||||
{
|
||||
voronoiSites.Add(new Vector2(
|
||||
x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server),
|
||||
y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server)));
|
||||
x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient),
|
||||
y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +281,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
voronoiSites.Clear();
|
||||
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.Point1 == edge.Point2) { continue; }
|
||||
@@ -297,12 +305,29 @@ namespace Barotrauma
|
||||
|
||||
Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 };
|
||||
|
||||
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
|
||||
int positionIndex = Rand.Int(1, Rand.RandSync.ServerAndClient);
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; }
|
||||
int zone = GetZoneIndex(position.X);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, existingLocations: Locations);
|
||||
if (!locationsPerZone.ContainsKey(zone))
|
||||
{
|
||||
locationsPerZone[zone] = new List<Location>();
|
||||
}
|
||||
|
||||
LocationType forceLocationType = null;
|
||||
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
|
||||
{
|
||||
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
|
||||
{
|
||||
forceLocationType = locationType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.ServerAndClient),
|
||||
requireOutpost: false, forceLocationType: forceLocationType, existingLocations: Locations);
|
||||
locationsPerZone[zone].Add(newLocations[i]);
|
||||
Locations.Add(newLocations[i]);
|
||||
}
|
||||
|
||||
@@ -394,7 +419,7 @@ namespace Barotrauma
|
||||
connectionsBetweenZones[i] = new List<LocationConnection>();
|
||||
}
|
||||
var shuffledConnections = Connections.ToList();
|
||||
shuffledConnections.Shuffle(Rand.RandSync.Server);
|
||||
shuffledConnections.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
foreach (var connection in shuffledConnections)
|
||||
{
|
||||
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
|
||||
@@ -447,9 +472,9 @@ namespace Barotrauma
|
||||
Connections[i].Locations[0].MapPosition.X < Connections[i].Locations[1].MapPosition.X ?
|
||||
Connections[i].Locations[0] :
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase))
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.List.First(lt => lt.HasOutpost && !lt.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase)));
|
||||
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
@@ -473,10 +498,10 @@ namespace Barotrauma
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
//float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
|
||||
//connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
|
||||
//connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.ServerAndClient), 1.2f, 100.0f);
|
||||
float difficulty = connection.CenterPos.X / Width * 100;
|
||||
float random = difficulty > 10 ? 5 : 0;
|
||||
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-random, random, Rand.RandSync.Server), 1.0f, 100.0f);
|
||||
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-random, random, Rand.RandSync.ServerAndClient), 1.0f, 100.0f);
|
||||
}
|
||||
|
||||
AssignBiomes();
|
||||
@@ -522,21 +547,13 @@ namespace Barotrauma
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
int zoneIndex = (int)Math.Floor(xPos / zoneWidth) + 1;
|
||||
if (zoneIndex < 1)
|
||||
{
|
||||
return LevelGenerationParams.GetBiomes().First();
|
||||
|
||||
}
|
||||
else if (zoneIndex >= generationParams.DifficultyZones)
|
||||
{
|
||||
return LevelGenerationParams.GetBiomes().Last();
|
||||
}
|
||||
return LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex));
|
||||
zoneIndex = Math.Clamp(zoneIndex, 1, generationParams.DifficultyZones - 1);
|
||||
return Biome.Prefabs.FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex));
|
||||
}
|
||||
|
||||
private void AssignBiomes()
|
||||
{
|
||||
var biomes = LevelGenerationParams.GetBiomes();
|
||||
var biomes = Biome.Prefabs;
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
|
||||
List<Biome> allowedBiomes = new List<Biome>(10);
|
||||
@@ -550,7 +567,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (location.MapPosition.X < zoneX)
|
||||
{
|
||||
location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)];
|
||||
location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.ServerAndClient)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -687,15 +704,15 @@ namespace Barotrauma
|
||||
CurrentLocation.Discover();
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.CreateStores();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
|
||||
if (GameMain.GameSession is { Campaign: { CampaignMetadata: { } metadata } })
|
||||
{
|
||||
metadata.SetValue("campaign.location.id", CurrentLocationIndex);
|
||||
metadata.SetValue("campaign.location.name", CurrentLocation.Name);
|
||||
metadata.SetValue("campaign.location.biome", CurrentLocation.Biome?.Identifier ?? "null");
|
||||
metadata.SetValue("campaign.location.type", CurrentLocation.Type?.Identifier ?? "null");
|
||||
metadata.SetValue("campaign.location.id".ToIdentifier(), CurrentLocationIndex);
|
||||
metadata.SetValue("campaign.location.name".ToIdentifier(), CurrentLocation.Name);
|
||||
metadata.SetValue("campaign.location.biome".ToIdentifier(), CurrentLocation.Biome?.Identifier ?? "null".ToIdentifier());
|
||||
metadata.SetValue("campaign.location.type".ToIdentifier(), CurrentLocation.Type?.Identifier ?? "null".ToIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +743,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.CreateStores();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
@@ -864,16 +881,14 @@ namespace Barotrauma
|
||||
|
||||
if (location == CurrentLocation || location == SelectedLocation || location.IsGateBetweenBiomes) { continue; }
|
||||
|
||||
ProgressLocationTypeChanges(location);
|
||||
|
||||
if (location.Discovered)
|
||||
if (!ProgressLocationTypeChanges(location) && location.Discovered)
|
||||
{
|
||||
location.UpdateStore();
|
||||
location.UpdateStores();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProgressLocationTypeChanges(Location location)
|
||||
private bool ProgressLocationTypeChanges(Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
location.LocationTypeChangeCooldown--;
|
||||
@@ -893,9 +908,8 @@ namespace Barotrauma
|
||||
location.PendingLocationTypeChange.Value.parentMission);
|
||||
if (location.PendingLocationTypeChange.Value.delay <= 0)
|
||||
{
|
||||
ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
return ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,9 +941,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLocationType(location, selectedTypeChange);
|
||||
return ChangeLocationType(location, selectedTypeChange);
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,6 +962,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation)
|
||||
@@ -995,15 +1011,15 @@ namespace Barotrauma
|
||||
return distance;
|
||||
}
|
||||
|
||||
private void ChangeLocationType(Location location, LocationTypeChange change)
|
||||
private bool ChangeLocationType(Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
|
||||
var newType = LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase));
|
||||
var newType = LocationType.Prefabs[change.ChangeToType];
|
||||
if (newType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newType.OutpostTeam != location.Type.OutpostTeam ||
|
||||
@@ -1020,6 +1036,7 @@ namespace Barotrauma
|
||||
location.TimeSinceLastTypeChange = 0;
|
||||
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
|
||||
location.PendingLocationTypeChange = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
|
||||
@@ -1053,7 +1070,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -1083,14 +1100,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
string locationType = subElement.GetAttributeString("type", "");
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
LocationType newLocationType = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)) ?? LocationType.List.First();
|
||||
LocationType newLocationType = LocationType.Prefabs.Find(lt => lt.Identifier == locationType) ?? LocationType.Prefabs.First();
|
||||
location.ChangeType(newLocationType);
|
||||
if (showNotifications && prevLocationType != location.Type)
|
||||
{
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType == location.Type.Identifier);
|
||||
if (change != null)
|
||||
{
|
||||
ChangeLocationTypeProjSpecific(location, prevLocationName, change);
|
||||
@@ -1098,7 +1115,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
location.LoadStore(subElement);
|
||||
location.LoadStores(subElement);
|
||||
location.LoadMissions(subElement);
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MapGenerationParams : ISerializableEntity
|
||||
class MapGenerationParams : Prefab, ISerializableEntity
|
||||
{
|
||||
private static MapGenerationParams instance;
|
||||
private static string loadedFile;
|
||||
public static readonly PrefabSelector<MapGenerationParams> Params = new PrefabSelector<MapGenerationParams>();
|
||||
public static MapGenerationParams Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
return Params.ActivePrefab;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Serialize(true, true), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool ShowLocations { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool ShowLevelTypeNames { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool ShowOverlay { get; set; }
|
||||
#else
|
||||
public readonly bool ShowLocations = true;
|
||||
@@ -32,70 +33,70 @@ namespace Barotrauma
|
||||
public readonly bool ShowOverlay = true;
|
||||
#endif
|
||||
|
||||
[Serialize(6, true)]
|
||||
[Serialize(6, IsPropertySaveable.Yes)]
|
||||
public int DifficultyZones { get; set; } //Number of difficulty zones
|
||||
|
||||
[Serialize(8000, true), Editable]
|
||||
[Serialize(8000, IsPropertySaveable.Yes), Editable]
|
||||
public int Width { get; set; }
|
||||
|
||||
[Serialize(500, true), Editable]
|
||||
[Serialize(500, IsPropertySaveable.Yes), Editable]
|
||||
public int Height { get; set; }
|
||||
|
||||
[Serialize(20.0f, true, description: "Connections with a length smaller or equal to this generate the smallest possible levels (using the MinWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
[Serialize(20.0f, IsPropertySaveable.Yes, description: "Connections with a length smaller or equal to this generate the smallest possible levels (using the MinWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
public float SmallLevelConnectionLength { get; set; }
|
||||
|
||||
[Serialize(200.0f, true, description: "Connections with a length larger or equal to this generate the largest possible levels (using the MaxWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
[Serialize(200.0f, IsPropertySaveable.Yes, description: "Connections with a length larger or equal to this generate the largest possible levels (using the MaxWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
public float LargeLevelConnectionLength { get; set; }
|
||||
|
||||
[Serialize("20,20", true, description: "How far from each other voronoi sites are placed. " +
|
||||
[Serialize("20,20", IsPropertySaveable.Yes, description: "How far from each other voronoi sites are placed. " +
|
||||
"Sites determine shape of the voronoi graph. Locations are placed at the vertices of the voronoi cells. " +
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)"), Editable]
|
||||
public Point VoronoiSiteInterval { get; set; }
|
||||
|
||||
[Serialize("5,5", true), Editable]
|
||||
[Serialize("5,5", IsPropertySaveable.Yes), Editable]
|
||||
public Point VoronoiSiteVariance { get; set; }
|
||||
|
||||
[Serialize(10.0f, true, description: "Connections smaller than this are removed."), Editable(0.0f, 500.0f)]
|
||||
[Serialize(10.0f, IsPropertySaveable.Yes, description: "Connections smaller than this are removed."), Editable(0.0f, 500.0f)]
|
||||
public float MinConnectionDistance { get; set; }
|
||||
|
||||
[Serialize(5.0f, true, description: "Locations that are closer than this to another location are removed."), Editable(0.0f, 100.0f)]
|
||||
[Serialize(5.0f, IsPropertySaveable.Yes, description: "Locations that are closer than this to another location are removed."), Editable(0.0f, 100.0f)]
|
||||
public float MinLocationDistance { get; set; }
|
||||
|
||||
[Serialize(0.1f, true, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorIterationMultiplier { get; set; }
|
||||
|
||||
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
|
||||
|
||||
public int[] GateCount { get; private set; }
|
||||
public readonly ImmutableArray<int> GateCount;
|
||||
|
||||
#if CLIENT
|
||||
|
||||
[Serialize(0.75f, true), Editable(DecimalCount = 2)]
|
||||
[Serialize(0.75f, IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
|
||||
public float MinZoom { get; set; }
|
||||
|
||||
[Serialize(1.5f, true), Editable(DecimalCount = 2)]
|
||||
[Serialize(1.5f, IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
|
||||
public float MaxZoom { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(DecimalCount = 2)]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2)]
|
||||
public float MapTileScale { get; set; }
|
||||
|
||||
[Serialize(15.0f, true, description: "Size of the location icons in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
[Serialize(15.0f, IsPropertySaveable.Yes, description: "Size of the location icons in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
public float LocationIconSize { get; set; }
|
||||
|
||||
[Serialize(5.0f, true, description: "Width of the connections between locations, in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
[Serialize(5.0f, IsPropertySaveable.Yes, description: "Width of the connections between locations, in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
public float LocationConnectionWidth { get; set; }
|
||||
|
||||
[Serialize("220,220,100,255", true, description: "The color used to display the indicators (current location, selected location, etc)."), Editable()]
|
||||
[Serialize("220,220,100,255", IsPropertySaveable.Yes, description: "The color used to display the indicators (current location, selected location, etc)."), Editable()]
|
||||
public Color IndicatorColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections between locations."), Editable()]
|
||||
[Serialize("150,150,150,255", IsPropertySaveable.Yes, description: "The color used to display the connections between locations."), Editable()]
|
||||
public Color ConnectionColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections between locations when they're highlighted."), Editable()]
|
||||
[Serialize("150,150,150,255", IsPropertySaveable.Yes, description: "The color used to display the connections between locations when they're highlighted."), Editable()]
|
||||
public Color HighlightedConnectionColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections the player hasn't travelled through."), Editable()]
|
||||
[Serialize("150,150,150,255", IsPropertySaveable.Yes, description: "The color used to display the connections the player hasn't travelled through."), Editable()]
|
||||
public Color UnvisitedConnectionColor { get; set; }
|
||||
|
||||
public Sprite ConnectionSprite { get; private set; }
|
||||
@@ -110,110 +111,36 @@ namespace Barotrauma
|
||||
public Sprite CurrentLocationIndicator { get; private set; }
|
||||
public Sprite SelectedLocationIndicator { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, List<Sprite>> mapTiles = new Dictionary<string, List<Sprite>>();
|
||||
public Dictionary<string, List<Sprite>> MapTiles
|
||||
{
|
||||
get { return mapTiles; }
|
||||
}
|
||||
public readonly ImmutableDictionary<Identifier, ImmutableArray<Sprite>> MapTiles;
|
||||
#endif
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return GetType().ToString(); }
|
||||
}
|
||||
public string Name => GetType().ToString();
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public RadiationParams RadiationParams;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
|
||||
var files = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.MapGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("No map generation parameters found in the selected content packages!");
|
||||
return;
|
||||
}
|
||||
// Let's not actually load the parameters until we have solved which file is the last, because loading the parameters takes some resources that would also need to be released.
|
||||
XElement selectedElement = null;
|
||||
string selectedFile = null;
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
if (selectedElement != null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the map generation parameters with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
}
|
||||
else if (selectedElement != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in {file.Path}: Another map generation parameter file already loaded! Use <override></override> tags to override it.");
|
||||
break;
|
||||
}
|
||||
selectedElement = mainElement;
|
||||
selectedFile = file.Path;
|
||||
}
|
||||
|
||||
if (selectedFile == loadedFile) { return; }
|
||||
|
||||
#if CLIENT
|
||||
if (instance != null)
|
||||
{
|
||||
instance?.ConnectionSprite?.Remove();
|
||||
instance?.PassedConnectionSprite?.Remove();
|
||||
instance?.SelectedLocationIndicator?.Remove();
|
||||
instance?.CurrentLocationIndicator?.Remove();
|
||||
instance?.DecorativeGraphSprite?.Remove();
|
||||
instance?.MissionIcon?.Remove();
|
||||
instance?.TypeChangeIcon?.Remove();
|
||||
instance?.FogOfWarSprite?.Remove();
|
||||
foreach (List<Sprite> spriteList in instance.mapTiles.Values)
|
||||
{
|
||||
foreach (Sprite sprite in spriteList)
|
||||
{
|
||||
sprite.Remove();
|
||||
}
|
||||
}
|
||||
instance.mapTiles.Clear();
|
||||
}
|
||||
#endif
|
||||
instance = null;
|
||||
|
||||
if (selectedElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find a valid element in the map generation parameter files!");
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = new MapGenerationParams(selectedElement);
|
||||
loadedFile = selectedFile;
|
||||
}
|
||||
}
|
||||
|
||||
private MapGenerationParams(XElement element)
|
||||
public MapGenerationParams(ContentXElement element, MapGenerationParametersFile file) : base(file, file.Path.Value.ToIdentifier())
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
GateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null);
|
||||
if (GateCount == null)
|
||||
var gateCount = element.GetAttributeIntArray("gatecount", null) ?? element.GetAttributeIntArray("GateCount", null);
|
||||
if (gateCount == null)
|
||||
{
|
||||
GateCount = new int[DifficultyZones];
|
||||
gateCount = new int[DifficultyZones];
|
||||
for (int i = 0; i < DifficultyZones; i++)
|
||||
{
|
||||
GateCount[i] = 1;
|
||||
gateCount[i] = 1;
|
||||
}
|
||||
}
|
||||
GateCount = gateCount.ToImmutableArray();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
Dictionary<Identifier, List<Sprite>> mapTiles = new Dictionary<Identifier, List<Sprite>>();
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -225,7 +152,7 @@ namespace Barotrauma
|
||||
PassedConnectionSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "maptile":
|
||||
string biome = subElement.GetAttributeString("biome", "");
|
||||
Identifier biome = subElement.GetAttributeIdentifier("biome", "");
|
||||
if (!mapTiles.ContainsKey(biome))
|
||||
{
|
||||
mapTiles[biome] = new List<Sprite>();
|
||||
@@ -257,6 +184,30 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
MapTiles = mapTiles.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
#if CLIENT
|
||||
ConnectionSprite?.Remove();
|
||||
PassedConnectionSprite?.Remove();
|
||||
SelectedLocationIndicator?.Remove();
|
||||
CurrentLocationIndicator?.Remove();
|
||||
DecorativeGraphSprite?.Remove();
|
||||
MissionIcon?.Remove();
|
||||
TypeChangeIcon?.Remove();
|
||||
FogOfWarSprite?.Remove();
|
||||
foreach (ImmutableArray<Sprite> spriteList in MapTiles.Values)
|
||||
{
|
||||
foreach (Sprite sprite in spriteList)
|
||||
{
|
||||
sprite.Remove();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@ namespace Barotrauma
|
||||
{
|
||||
public string Name => nameof(Radiation);
|
||||
|
||||
[Serialize(defaultValue: 0f, isSaveable: true)]
|
||||
[Serialize(defaultValue: 0f, isSaveable: IsPropertySaveable.Yes)]
|
||||
public float Amount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: true, isSaveable: true)]
|
||||
[Serialize(defaultValue: true, isSaveable: IsPropertySaveable.Yes)]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
public readonly Map Map;
|
||||
public readonly RadiationParams Params;
|
||||
|
||||
private Affliction radiationAffliction;
|
||||
private Affliction? radiationAffliction;
|
||||
|
||||
private float radiationTimer;
|
||||
|
||||
|
||||
@@ -7,39 +7,39 @@ namespace Barotrauma
|
||||
internal class RadiationParams: ISerializableEntity
|
||||
{
|
||||
public string Name => nameof(RadiationParams);
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
[Serialize(defaultValue: -100f, isSaveable: false, "How much radiation the world starts with.")]
|
||||
[Serialize(defaultValue: -100f, isSaveable: IsPropertySaveable.No, "How much radiation the world starts with.")]
|
||||
public float StartingRadiation { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 100f, isSaveable: false, "How much radiation is added on each step.")]
|
||||
[Serialize(defaultValue: 100f, isSaveable: IsPropertySaveable.No, "How much radiation is added on each step.")]
|
||||
public float RadiationStep { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10, isSaveable: false, "How many turns in radiation does it take for an outpost to be removed from the map.")]
|
||||
[Serialize(defaultValue: 10, isSaveable: IsPropertySaveable.No, "How many turns in radiation does it take for an outpost to be removed from the map.")]
|
||||
public int CriticalRadiationThreshold { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3, isSaveable: false, "Minimum amount of outposts in the level that cannot be removed due to radiation.")]
|
||||
[Serialize(defaultValue: 3, isSaveable: IsPropertySaveable.No, "Minimum amount of outposts in the level that cannot be removed due to radiation.")]
|
||||
public int MinimumOutpostAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3f, isSaveable: false, "How fast the radiation increase animation goes.")]
|
||||
[Serialize(defaultValue: 3f, isSaveable: IsPropertySaveable.No, "How fast the radiation increase animation goes.")]
|
||||
public float AnimationSpeed { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10f, isSaveable: false, "How long it takes to apply more radiation damage while in a radiated zone.")]
|
||||
[Serialize(defaultValue: 10f, isSaveable: IsPropertySaveable.No, "How long it takes to apply more radiation damage while in a radiated zone.")]
|
||||
public float RadiationDamageDelay { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 1f, isSaveable: false, "How much is the radiation affliction increased by while in a radiated zone.")]
|
||||
[Serialize(defaultValue: 1f, isSaveable: IsPropertySaveable.No, "How much is the radiation affliction increased by while in a radiated zone.")]
|
||||
public float RadiationDamageAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: -1.0f, isSaveable: false, "Maximum amount of radiation.")]
|
||||
[Serialize(defaultValue: -1.0f, isSaveable: IsPropertySaveable.No, "Maximum amount of radiation.")]
|
||||
public float MaxRadiation { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "139,0,0,85", isSaveable: false, "The color of the radiated area.")]
|
||||
[Serialize(defaultValue: "139,0,0,85", isSaveable: IsPropertySaveable.No, "The color of the radiated area.")]
|
||||
public Color RadiationAreaColor { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "255,0,0,255", isSaveable: false, "The tint of the radiation border sprites.")]
|
||||
[Serialize(defaultValue: "255,0,0,255", isSaveable: IsPropertySaveable.No, "The tint of the radiation border sprites.")]
|
||||
public Color RadiationBorderTint { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 16.66f, isSaveable: false, "Speed of the border spritesheet animation.")]
|
||||
[Serialize(defaultValue: 16.66f, isSaveable: IsPropertySaveable.No, "Speed of the border spritesheet animation.")]
|
||||
public float BorderAnimationSpeed { get; set; }
|
||||
|
||||
public RadiationParams(XElement element)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static List<MapEntity> mapEntityList = new List<MapEntity>();
|
||||
|
||||
public readonly MapEntityPrefab prefab;
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
protected List<ushort> linkedToID;
|
||||
public List<ushort> unresolvedLinkedToID;
|
||||
@@ -28,23 +28,22 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
protected readonly List<Upgrade> Upgrades = new List<Upgrade>();
|
||||
|
||||
public HashSet<string> disallowedUpgrades = new HashSet<string>();
|
||||
|
||||
[Editable, Serialize("", true)]
|
||||
public readonly HashSet<Identifier> DisallowedUpgradeSet = new HashSet<Identifier>();
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes)]
|
||||
public string DisallowedUpgrades
|
||||
{
|
||||
get { return string.Join(",", disallowedUpgrades); }
|
||||
get { return string.Join(",", DisallowedUpgradeSet); }
|
||||
set
|
||||
{
|
||||
disallowedUpgrades.Clear();
|
||||
DisallowedUpgradeSet.Clear();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
string[] splitTags = value.Split(',');
|
||||
foreach (string tag in splitTags)
|
||||
{
|
||||
string[] splitTag = tag.Trim().Split(':');
|
||||
splitTag[0] = splitTag[0].ToLowerInvariant();
|
||||
disallowedUpgrades.Add(string.Join(":", splitTag));
|
||||
DisallowedUpgradeSet.Add(string.Join(":", splitTag).ToIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,19 +108,19 @@ namespace Barotrauma
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public List<string> AllowedLinks => prefab == null ? new List<string>() : prefab.AllowedLinks;
|
||||
public IEnumerable<Identifier> AllowedLinks => Prefab == null ? Enumerable.Empty<Identifier>() : Prefab.AllowedLinks;
|
||||
|
||||
public bool ResizeHorizontal
|
||||
{
|
||||
get { return prefab != null && prefab.ResizeHorizontal; }
|
||||
get { return Prefab != null && Prefab.ResizeHorizontal; }
|
||||
}
|
||||
public bool ResizeVertical
|
||||
{
|
||||
get { return prefab != null && prefab.ResizeVertical; }
|
||||
get { return Prefab != null && Prefab.ResizeVertical; }
|
||||
}
|
||||
|
||||
//for upgrading the dimensions of the entity from xml
|
||||
[Serialize(0, false)]
|
||||
[Serialize(0, IsPropertySaveable.No)]
|
||||
public int RectWidth
|
||||
{
|
||||
get { return rect.Width; }
|
||||
@@ -132,7 +131,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
//for upgrading the dimensions of the entity from xml
|
||||
[Serialize(0, false)]
|
||||
[Serialize(0, IsPropertySaveable.No)]
|
||||
public int RectHeight
|
||||
{
|
||||
get { return rect.Height; }
|
||||
@@ -148,7 +147,7 @@ namespace Barotrauma
|
||||
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)]
|
||||
[Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, IsPropertySaveable.Yes)]
|
||||
public float SpriteDepth
|
||||
{
|
||||
get
|
||||
@@ -167,10 +166,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1f, true), Editable(0.01f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
|
||||
[Serialize(1f, IsPropertySaveable.Yes), Editable(0.01f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
|
||||
public virtual float Scale { get; set; } = 1;
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool HiddenInGame
|
||||
{
|
||||
get;
|
||||
@@ -226,14 +225,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool RemoveIfLinkedOutpostDoorInUse
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
} = true;
|
||||
|
||||
[Serialize("", true, "Submarine editor layer")]
|
||||
[Serialize("", IsPropertySaveable.Yes, "Submarine editor layer")]
|
||||
public string Layer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -250,7 +249,7 @@ namespace Barotrauma
|
||||
|
||||
public MapEntity(MapEntityPrefab prefab, Submarine submarine, ushort id) : base(submarine, id)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
this.Prefab = prefab;
|
||||
Scale = prefab != null ? prefab.Scale : 1;
|
||||
}
|
||||
|
||||
@@ -304,12 +303,12 @@ namespace Barotrauma
|
||||
return (Submarine.RectContains(WorldRect, position));
|
||||
}
|
||||
|
||||
public bool HasUpgrade(string identifier)
|
||||
public bool HasUpgrade(Identifier identifier)
|
||||
{
|
||||
return GetUpgrade(identifier) != null;
|
||||
}
|
||||
|
||||
public Upgrade GetUpgrade(string identifier)
|
||||
public Upgrade GetUpgrade(Identifier identifier)
|
||||
{
|
||||
return Upgrades.Find(upgrade => upgrade.Identifier == identifier);
|
||||
}
|
||||
@@ -332,7 +331,7 @@ namespace Barotrauma
|
||||
{
|
||||
AddUpgrade(upgrade, createNetworkEvent);
|
||||
}
|
||||
DebugConsole.Log($"Set (ID: {ID} {prefab.Name})'s \"{upgrade.Prefab.Name}\" upgrade to level {upgrade.Level}");
|
||||
DebugConsole.Log($"Set (ID: {ID} {Prefab.Name})'s \"{upgrade.Prefab.Name}\" upgrade to level {upgrade.Level}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -345,7 +344,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disallowedUpgrades.Contains(upgrade.Identifier)) { return false; }
|
||||
if (DisallowedUpgradeSet.Contains(upgrade.Identifier)) { return false; }
|
||||
|
||||
Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier);
|
||||
|
||||
@@ -655,7 +654,7 @@ namespace Barotrauma
|
||||
IdRemap idRemap = new IdRemap(parentElement, idOffset);
|
||||
|
||||
List<MapEntity> entities = new List<MapEntity>();
|
||||
foreach (XElement element in parentElement.Elements())
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
string typeName = element.Name.ToString();
|
||||
|
||||
@@ -678,7 +677,7 @@ namespace Barotrauma
|
||||
if (t == typeof(Structure))
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
StructurePrefab structurePrefab = Structure.FindPrefab(name, identifier);
|
||||
if (structurePrefab == null)
|
||||
{
|
||||
@@ -692,7 +691,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(XElement), typeof(Submarine), typeof(IdRemap) });
|
||||
MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(ContentXElement), typeof(Submarine), typeof(IdRemap) });
|
||||
if (loadMethod == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
|
||||
@@ -703,7 +702,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine, idRemap });
|
||||
object newEntity = loadMethod.Invoke(t, new object[] { element.FromPackage(null), submarine, idRemap });
|
||||
if (newEntity != null)
|
||||
{
|
||||
entities.Add((MapEntity)newEntity);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -11,19 +11,23 @@ namespace Barotrauma
|
||||
enum MapEntityCategory
|
||||
{
|
||||
Structure = 1,
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
Equipment = 8,
|
||||
Electrical = 16,
|
||||
Material = 32,
|
||||
Misc = 64,
|
||||
Alien = 128,
|
||||
Wrecked = 256,
|
||||
ItemAssembly = 512,
|
||||
Legacy = 1024
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
Medical = 8,
|
||||
Weapon = 16,
|
||||
Diving = 32,
|
||||
Equipment = 64,
|
||||
Fuel = 128,
|
||||
Electrical = 256,
|
||||
Material = 1024,
|
||||
Alien = 2048,
|
||||
Wrecked = 4096,
|
||||
ItemAssembly = 8192,
|
||||
Legacy = 16384,
|
||||
Misc = 32768
|
||||
}
|
||||
|
||||
abstract partial class MapEntityPrefab : IPrefab, IDisposable
|
||||
abstract partial class MapEntityPrefab : PrefabWithUintIdentifier
|
||||
{
|
||||
public static IEnumerable<MapEntityPrefab> List
|
||||
{
|
||||
@@ -51,207 +55,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected string originalName;
|
||||
protected string identifier;
|
||||
|
||||
public Sprite sprite;
|
||||
//which prefab has been selected for placing
|
||||
public static MapEntityPrefab Selected { get; set; }
|
||||
|
||||
//the position where the structure is being placed (needed when stretching the structure)
|
||||
protected static Vector2 placePosition;
|
||||
|
||||
protected ConstructorInfo constructor;
|
||||
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeHorizontal { get; protected set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeVertical { get; protected set; }
|
||||
|
||||
//which prefab has been selected for placing
|
||||
protected static MapEntityPrefab selected;
|
||||
|
||||
public string OriginalName
|
||||
{
|
||||
get { return originalName; }
|
||||
}
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get { return originalName; }
|
||||
}
|
||||
|
||||
public string GetItemNameTextId()
|
||||
{
|
||||
var textId = $"entityname.{Identifier}";
|
||||
return TextManager.ContainsTag(textId) ? textId : null;
|
||||
}
|
||||
|
||||
public string GetHullNameTextId()
|
||||
{
|
||||
var textId = $"roomname.{Identifier}";
|
||||
return TextManager.ContainsTag(textId) ? textId : null;
|
||||
}
|
||||
|
||||
//Used to differentiate between items when saving/loading
|
||||
//Allows changing the name of an item without breaking existing subs or having multiple items with the same name
|
||||
public string Identifier
|
||||
{
|
||||
get { return identifier; }
|
||||
}
|
||||
|
||||
public string FilePath { get; protected set; }
|
||||
|
||||
public ContentPackage ContentPackage { get; protected set; }
|
||||
|
||||
public HashSet<string> Tags
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
} = new HashSet<string>();
|
||||
|
||||
public static MapEntityPrefab Selected
|
||||
{
|
||||
get { return selected; }
|
||||
set { selected = value; }
|
||||
}
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize("", false)]
|
||||
public string AllowedUpgrades { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool HideInMenus { get; set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Subcategory { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool Linkable
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links defined to identifiers.
|
||||
/// </summary>
|
||||
public List<string> AllowedLinks { get; protected set; } = new List<string>();
|
||||
|
||||
public MapEntityCategory Category
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", false)]
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Serialize(1f, true), Editable(0.1f, 10f, DecimalCount = 3)]
|
||||
public float Scale { get; protected set; }
|
||||
|
||||
//If a matching prefab is not found when loading a sub, the game will attempt to find a prefab with a matching alias.
|
||||
//(allows changing names while keeping backwards compatibility with older sub files)
|
||||
public HashSet<string> Aliases
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
CoreEntityPrefab ep = new CoreEntityPrefab
|
||||
{
|
||||
identifier = "hull",
|
||||
originalName = TextManager.Get("EntityName.hull"),
|
||||
Description = TextManager.Get("EntityDescription.hull"),
|
||||
constructor = typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
ResizeHorizontal = true,
|
||||
ResizeVertical = true,
|
||||
Linkable = true
|
||||
};
|
||||
ep.AllowedLinks.Add("hull");
|
||||
ep.Aliases = new HashSet<string> { "hull" };
|
||||
CoreEntityPrefab.Prefabs.Add(ep, false);
|
||||
|
||||
ep = new CoreEntityPrefab
|
||||
{
|
||||
identifier = "gap",
|
||||
originalName = TextManager.Get("EntityName.gap"),
|
||||
Description = TextManager.Get("EntityDescription.gap"),
|
||||
constructor = typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
ResizeHorizontal = true,
|
||||
ResizeVertical = true
|
||||
};
|
||||
CoreEntityPrefab.Prefabs.Add(ep, false);
|
||||
ep.Aliases = new HashSet<string> { "gap" };
|
||||
|
||||
ep = new CoreEntityPrefab
|
||||
{
|
||||
identifier = "waypoint",
|
||||
originalName = TextManager.Get("EntityName.waypoint"),
|
||||
Description = TextManager.Get("EntityDescription.waypoint"),
|
||||
constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) })
|
||||
};
|
||||
CoreEntityPrefab.Prefabs.Add(ep, false);
|
||||
ep.Aliases = new HashSet<string> { "waypoint" };
|
||||
|
||||
ep = new CoreEntityPrefab
|
||||
{
|
||||
identifier = "spawnpoint",
|
||||
originalName = TextManager.Get("EntityName.spawnpoint"),
|
||||
Description = TextManager.Get("EntityDescription.spawnpoint"),
|
||||
constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) })
|
||||
};
|
||||
CoreEntityPrefab.Prefabs.Add(ep, false);
|
||||
ep.Aliases = new HashSet<string> { "spawnpoint" };
|
||||
}
|
||||
|
||||
public abstract void Dispose();
|
||||
|
||||
public MapEntityPrefab()
|
||||
{
|
||||
Category = MapEntityCategory.Structure;
|
||||
}
|
||||
|
||||
public string[] GetAllowedUpgrades()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(AllowedUpgrades) ? new string[0] : AllowedUpgrades.Split(",");
|
||||
}
|
||||
|
||||
public bool HasSubCategory(string subcategory)
|
||||
{
|
||||
return subcategory?.Equals(this.Subcategory, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
|
||||
protected virtual void CreateInstance(Rectangle rect)
|
||||
{
|
||||
if (constructor == null) return;
|
||||
object[] lobject = new object[] { this, rect };
|
||||
constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public void DebugCreateInstance()
|
||||
{
|
||||
Rectangle rect = new Rectangle(new Point((int)Screen.Selected.Cam.WorldViewCenter.X, (int)Screen.Selected.Cam.WorldViewCenter.Y), new Point((int)Submarine.GridSize.X, (int)Submarine.GridSize.Y));
|
||||
CreateInstance(rect);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static bool SelectPrefab(object selection)
|
||||
{
|
||||
if ((selected = selection as MapEntityPrefab) != null)
|
||||
if ((Selected = selection as MapEntityPrefab) != null)
|
||||
{
|
||||
placePosition = Vector2.Zero;
|
||||
return true;
|
||||
@@ -262,37 +74,45 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
|
||||
public static object GetSelected()
|
||||
{
|
||||
return (object)Selected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a matching map entity prefab
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the item (can be omitted when searching based on identifier)</param>
|
||||
/// <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>
|
||||
[Obsolete("Prefer MapEntityPrefab.FindByIdentifier or MapEntityPrefab.FindByName")]
|
||||
public static MapEntityPrefab Find(string name, string identifier = null, bool showErrorMessages = true)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
}
|
||||
return Find(name, (identifier ?? "").ToIdentifier(), showErrorMessages);
|
||||
}
|
||||
|
||||
[Obsolete("Prefer MapEntityPrefab.FindByIdentifier or MapEntityPrefab.FindByName")]
|
||||
public static MapEntityPrefab Find(string name, Identifier identifier, bool showErrorMessages = true)
|
||||
{
|
||||
//try to search based on identifier first
|
||||
if (string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(identifier))
|
||||
if (string.IsNullOrEmpty(name) && !identifier.IsEmpty)
|
||||
{
|
||||
foreach (MapEntityPrefab prefab in List)
|
||||
{
|
||||
if (prefab.identifier == identifier) { return prefab; }
|
||||
}
|
||||
if (CoreEntityPrefab.Prefabs.ContainsKey(identifier)) { return CoreEntityPrefab.Prefabs[identifier]; }
|
||||
if (StructurePrefab.Prefabs.ContainsKey(identifier)) { return StructurePrefab.Prefabs[identifier]; }
|
||||
if (ItemPrefab.Prefabs.ContainsKey(identifier)) { return ItemPrefab.Prefabs[identifier]; }
|
||||
if (ItemAssemblyPrefab.Prefabs.ContainsKey(identifier)) { return ItemAssemblyPrefab.Prefabs[identifier]; }
|
||||
}
|
||||
|
||||
foreach (MapEntityPrefab prefab in List)
|
||||
{
|
||||
if (identifier != null)
|
||||
if (!identifier.IsEmpty)
|
||||
{
|
||||
if (prefab.identifier != identifier)
|
||||
if (prefab.Identifier != identifier)
|
||||
{
|
||||
if (prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(identifier, StringComparison.OrdinalIgnoreCase)))
|
||||
if (prefab.Aliases != null && prefab.Aliases.Any(a => a == identifier))
|
||||
{
|
||||
return prefab;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -302,8 +122,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
prefab.originalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
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;
|
||||
@@ -332,28 +152,133 @@ namespace Barotrauma
|
||||
return List.FirstOrDefault(p => predicate(p));
|
||||
}
|
||||
|
||||
|
||||
public static MapEntityPrefab FindByName(string name)
|
||||
{
|
||||
if (name.IsNullOrEmpty()) { throw new ArgumentException($"{nameof(name)} must not be null or empty"); }
|
||||
|
||||
return Find(prefab =>
|
||||
prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
prefab.OriginalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
(prefab.Aliases != null &&
|
||||
prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))));
|
||||
}
|
||||
|
||||
public static MapEntityPrefab FindByIdentifier(Identifier identifier)
|
||||
=> CoreEntityPrefab.Prefabs.TryGet(identifier, out var corePrefab) ? corePrefab
|
||||
: ItemPrefab.Prefabs.TryGet(identifier, out var itemPrefab) ? itemPrefab
|
||||
: StructurePrefab.Prefabs.TryGet(identifier, out var structurePrefab) ? structurePrefab
|
||||
: ItemAssemblyPrefab.Prefabs.TryGet(identifier, out var itemAssemblyPrefab) ? itemAssemblyPrefab
|
||||
: (MapEntityPrefab)null;
|
||||
|
||||
public abstract Sprite Sprite { get; }
|
||||
|
||||
public abstract string OriginalName { get; }
|
||||
|
||||
public abstract LocalizedString Name { get; }
|
||||
|
||||
public abstract ImmutableHashSet<Identifier> Tags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Links defined to identifiers.
|
||||
/// </summary>
|
||||
public abstract ImmutableHashSet<Identifier> AllowedLinks { get; }
|
||||
|
||||
public abstract MapEntityCategory Category { get; }
|
||||
|
||||
//If a matching prefab is not found when loading a sub, the game will attempt to find a prefab with a matching alias.
|
||||
//(allows changing names while keeping backwards compatibility with older sub files)
|
||||
public abstract ImmutableHashSet<string> Aliases { get; }
|
||||
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool ResizeHorizontal { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool ResizeVertical { get; protected set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public LocalizedString Description { get; protected set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public string AllowedUpgrades { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool HideInMenus { get; protected set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public string Subcategory { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Linkable { get; protected set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)]
|
||||
public Color SpriteColor { get; protected set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes), Editable(0.1f, 10f, DecimalCount = 3)]
|
||||
public float Scale { get; protected set; }
|
||||
|
||||
protected MapEntityPrefab(Identifier identifier) : base(null, identifier) { }
|
||||
|
||||
public MapEntityPrefab(ContentXElement element, ContentFile file) : base(file, element) { }
|
||||
|
||||
public string GetItemNameTextId()
|
||||
{
|
||||
var textId = $"entityname.{Identifier}";
|
||||
return TextManager.ContainsTag(textId) ? textId : null;
|
||||
}
|
||||
|
||||
public string GetHullNameTextId()
|
||||
{
|
||||
var textId = $"roomname.{Identifier}";
|
||||
return TextManager.ContainsTag(textId) ? textId : null;
|
||||
}
|
||||
|
||||
private string cachedAllowedUpgrades = "";
|
||||
private ImmutableHashSet<Identifier> allowedUpgradeSet;
|
||||
public IEnumerable<Identifier> GetAllowedUpgrades()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(AllowedUpgrades)) { return Enumerable.Empty<Identifier>(); }
|
||||
if (allowedUpgradeSet is null || cachedAllowedUpgrades != AllowedUpgrades)
|
||||
{
|
||||
allowedUpgradeSet = AllowedUpgrades.Split(",").ToIdentifiers().ToImmutableHashSet();
|
||||
cachedAllowedUpgrades = AllowedUpgrades;
|
||||
}
|
||||
|
||||
return allowedUpgradeSet;
|
||||
}
|
||||
|
||||
public bool HasSubCategory(string subcategory)
|
||||
{
|
||||
return subcategory?.Equals(this.Subcategory, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
|
||||
protected abstract void CreateInstance(Rectangle rect);
|
||||
|
||||
#if DEBUG
|
||||
public void DebugCreateInstance()
|
||||
{
|
||||
Rectangle rect = new Rectangle(new Point((int)Screen.Selected.Cam.WorldViewCenter.X, (int)Screen.Selected.Cam.WorldViewCenter.Y), new Point((int)Submarine.GridSize.X, (int)Submarine.GridSize.Y));
|
||||
CreateInstance(rect);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Check if the name or any of the aliases of this prefab match the given name.
|
||||
/// </summary>
|
||||
public bool NameMatches(string name, StringComparison comparisonType) => originalName.Equals(name, comparisonType) || (Aliases != null && Aliases.Any(a => a.Equals(name, comparisonType)));
|
||||
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, StringComparison comparisonType) => allowedNames.Any(n => NameMatches(n, comparisonType));
|
||||
|
||||
public bool IsLinkAllowed(MapEntityPrefab target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
if (target is StructurePrefab && AllowedLinks.Contains("structure")) { return true; }
|
||||
if (target is ItemPrefab && AllowedLinks.Contains("item")) { return true; }
|
||||
if (target is LinkedSubmarinePrefab && Tags.Contains("dock")) { return true; }
|
||||
if (this is LinkedSubmarinePrefab && target.Tags.Contains("dock")) { return true; }
|
||||
return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(identifier)
|
||||
|| target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t));
|
||||
}
|
||||
|
||||
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
|
||||
public static object GetSelected()
|
||||
{
|
||||
return (object)selected;
|
||||
if (target is StructurePrefab && AllowedLinks.Contains("structure".ToIdentifier())) { return true; }
|
||||
if (target is ItemPrefab && AllowedLinks.Contains("item".ToIdentifier())) { return true; }
|
||||
if (target is LinkedSubmarinePrefab && Tags.Contains("dock".ToIdentifier())) { return true; }
|
||||
if (this is LinkedSubmarinePrefab && target.Tags.Contains("dock".ToIdentifier())) { return true; }
|
||||
return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(Identifier)
|
||||
|| target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Md5Hash
|
||||
{
|
||||
private static readonly Regex removeWhitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private const string cachePath = "Data/hashcache.txt";
|
||||
private static readonly Dictionary<string, Tuple<Md5Hash, long>> cache = new Dictionary<string, Tuple<Md5Hash, long>>();
|
||||
|
||||
public static void LoadCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(cachePath)) { return; }
|
||||
string[] lines = File.ReadAllLines(cachePath, Encoding.UTF8);
|
||||
if (lines.Length <= 0 || lines[0] != GameMain.Version.ToString()) { return; }
|
||||
foreach (string line in lines.Skip(1))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) { continue; }
|
||||
string[] parts = line.Split('|');
|
||||
if (parts.Length < 3) { continue; }
|
||||
|
||||
string path = parts[0].CleanUpPath();
|
||||
string hashStr = parts[1];
|
||||
long timeLong = long.Parse(parts[2]);
|
||||
|
||||
Md5Hash hash = new Md5Hash(hashStr);
|
||||
DateTime time = DateTime.FromBinary(timeLong);
|
||||
|
||||
if (File.GetLastWriteTime(path) == time && !cache.ContainsKey(path))
|
||||
{
|
||||
cache.Add(path, new Tuple<Md5Hash, long>(hash, timeLong));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to load hash cache: {e.Message}\n{e.StackTrace.CleanupStackTrace()}", Microsoft.Xna.Framework.Color.Orange);
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveCache()
|
||||
{
|
||||
#if SERVER
|
||||
//don't save to the cache if the server is owned by a client,
|
||||
//since this suggests that they're running concurrently and
|
||||
//will interfere with each other here
|
||||
if (GameMain.Server?.OwnerConnection != null) { return; }
|
||||
#endif
|
||||
|
||||
string[] lines = new string[cache.Count + 1];
|
||||
lines[0] = GameMain.Version.ToString();
|
||||
int i = 1;
|
||||
foreach (KeyValuePair<string, Tuple<Md5Hash, long>> kpv in cache)
|
||||
{
|
||||
lines[i] = kpv.Key + "|" + kpv.Value.Item1 + "|" + kpv.Value.Item2;
|
||||
i++;
|
||||
}
|
||||
File.WriteAllLines(cachePath, lines, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private bool LoadFromCache(string filename)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
filename = filename.CleanUpPath();
|
||||
lock (cache)
|
||||
{
|
||||
if (cache.ContainsKey(filename))
|
||||
{
|
||||
Hash = cache[filename].Item1.Hash;
|
||||
ShortHash = cache[filename].Item1.ShortHash;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SaveToCache(string filename, long? time = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filename)) { return; }
|
||||
|
||||
lock (cache)
|
||||
{
|
||||
filename = filename.CleanUpPath();
|
||||
Tuple<Md5Hash, long> cacheVal = new Tuple<Md5Hash, long>(this, time ?? File.GetLastWriteTime(filename).ToBinary());
|
||||
if (cache.ContainsKey(filename))
|
||||
{
|
||||
cache[filename] = cacheVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.Add(filename, cacheVal);
|
||||
}
|
||||
SaveCache();
|
||||
}
|
||||
}
|
||||
|
||||
public static Md5Hash FetchFromCache(string filename)
|
||||
{
|
||||
Md5Hash newHash = new Md5Hash();
|
||||
if (newHash.LoadFromCache(filename)) { return newHash; }
|
||||
return null;
|
||||
}
|
||||
|
||||
public string Hash { get; private set; }
|
||||
|
||||
public string ShortHash { get; private set; }
|
||||
|
||||
private Md5Hash()
|
||||
{
|
||||
this.Hash = null;
|
||||
ShortHash = null;
|
||||
}
|
||||
|
||||
public Md5Hash(string md5Hash)
|
||||
{
|
||||
this.Hash = md5Hash;
|
||||
ShortHash = GetShortHash(md5Hash);
|
||||
}
|
||||
|
||||
public Md5Hash(byte[] bytes)
|
||||
{
|
||||
Hash = CalculateHash(bytes);
|
||||
|
||||
ShortHash = GetShortHash(Hash);
|
||||
}
|
||||
|
||||
public Md5Hash(FileStream fileStream, string filename = null, bool tryLoadFromCache = true)
|
||||
{
|
||||
if (tryLoadFromCache)
|
||||
{
|
||||
if (LoadFromCache(filename)) { return; }
|
||||
}
|
||||
|
||||
Hash = CalculateHash(fileStream);
|
||||
|
||||
ShortHash = GetShortHash(Hash);
|
||||
|
||||
SaveToCache(filename);
|
||||
}
|
||||
|
||||
public Md5Hash(XDocument doc, string filename = null, bool tryLoadFromCache = true)
|
||||
{
|
||||
if (tryLoadFromCache)
|
||||
{
|
||||
if (LoadFromCache(filename)) { return; }
|
||||
}
|
||||
|
||||
if (doc == null) { return; }
|
||||
|
||||
string docString = removeWhitespaceRegex.Replace(doc.ToString(), "");
|
||||
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(docString);
|
||||
|
||||
Hash = CalculateHash(inputBytes);
|
||||
ShortHash = GetShortHash(Hash);
|
||||
|
||||
SaveToCache(filename);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Hash;
|
||||
}
|
||||
|
||||
private string CalculateHash(FileStream stream)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] byteHash = md5.ComputeHash(stream);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private string CalculateHash(byte[] bytes)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] byteHash = md5.ComputeHash(bytes);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShortHash(string fullHash)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullHash)) { return ""; }
|
||||
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
|
||||
}
|
||||
|
||||
public static bool RemoveFromCache(string filename)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
filename = filename.CleanUpPath();
|
||||
lock (cache)
|
||||
{
|
||||
if (cache.ContainsKey(filename))
|
||||
{
|
||||
cache.Remove(filename);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +1,37 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class NPCSet : IDisposable
|
||||
internal class NPCSet : Prefab
|
||||
{
|
||||
private static List<NPCSet>? Sets { get; set; }
|
||||
public readonly static PrefabCollection<NPCSet> Sets = new PrefabCollection<NPCSet>();
|
||||
|
||||
private string Identifier { get; }
|
||||
|
||||
private readonly List<HumanPrefab> Humans = new List<HumanPrefab>();
|
||||
private readonly ImmutableArray<HumanPrefab> Humans;
|
||||
|
||||
private bool Disposed { get; set; }
|
||||
|
||||
private NPCSet(XElement element, string filePath)
|
||||
public NPCSet(ContentXElement element, NPCSetsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", string.Empty);
|
||||
|
||||
foreach (XElement npcElement in element.Elements())
|
||||
{
|
||||
Humans.Add(new HumanPrefab(npcElement, filePath));
|
||||
}
|
||||
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file)).ToImmutableArray();
|
||||
}
|
||||
|
||||
public static HumanPrefab? Get(string identifier, string npcidentifier)
|
||||
public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier)
|
||||
{
|
||||
HumanPrefab prefab = Sets.Where(set => set.Identifier == identifier).SelectMany(npcSet => npcSet.Humans.Where(npcSetHuman => npcSetHuman.Identifier == npcidentifier)).FirstOrDefault();
|
||||
HumanPrefab? prefab = Sets.Where(set => set.Identifier == setIdentifier).SelectMany(npcSet => npcSet.Humans.Where(npcSetHuman => npcSetHuman.Identifier == npcidentifier)).FirstOrDefault();
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find human prefab \"{npcidentifier}\" from \"{identifier}\".");
|
||||
DebugConsole.ThrowError($"Could not find human prefab \"{npcidentifier}\" from \"{setIdentifier}\".");
|
||||
return null;
|
||||
}
|
||||
return new HumanPrefab(prefab.Element, prefab.FilePath);
|
||||
}
|
||||
|
||||
public static void LoadSets()
|
||||
{
|
||||
Sets?.ForEach(set => set.Dispose());
|
||||
Sets = new List<NPCSet>();
|
||||
IEnumerable<ContentFile> files = GameMain.Instance.GetFilesOfType(ContentType.NPCSets);
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
XElement? rootElement = doc?.Root;
|
||||
|
||||
if (doc == null || rootElement == null) { continue; }
|
||||
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
Sets.Clear();
|
||||
DebugConsole.NewMessage($"Overriding all NPC sets with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
|
||||
foreach (XElement element in rootElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
XElement sourceElement = isOverride ? element.FirstElement() : element;
|
||||
string elementName = sourceElement.Name.ToString().ToLowerInvariant();
|
||||
string identifier = sourceElement.GetAttributeString("identifier", null);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"No identifier defined for the NPC set config '{elementName}' in file '{file.Path}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingParams = Sets.Find(set => set.Identifier == identifier);
|
||||
if (existingParams != null)
|
||||
{
|
||||
if (isOverride)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding NPC set config '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
Sets.Remove(existingParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate NPC set config: '{identifier}' defined in {elementName} of '{file.Path}'");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Sets.Add(new NPCSet(element, file.Path));
|
||||
}
|
||||
}
|
||||
return prefab;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
@@ -103,7 +47,7 @@ namespace Barotrauma
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public override void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -1,164 +1,211 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class OutpostGenerationParams : ISerializableEntity
|
||||
class OutpostGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public static List<OutpostGenerationParams> Params { get; private set; }
|
||||
|
||||
public readonly static PrefabCollection<OutpostGenerationParams> OutpostParams = new PrefabCollection<OutpostGenerationParams>();
|
||||
|
||||
public virtual string Name { get; private set; }
|
||||
|
||||
public string Identifier { get; private set; }
|
||||
|
||||
private readonly List<string> allowedLocationTypes = new List<string>();
|
||||
|
||||
private readonly HashSet<Identifier> allowedLocationTypes = new HashSet<Identifier>();
|
||||
|
||||
/// <summary>
|
||||
/// Identifiers of the location types this outpost can appear in. If empty, can appear in all types of locations.
|
||||
/// </summary>
|
||||
public IEnumerable<string> AllowedLocationTypes
|
||||
public IEnumerable<Identifier> AllowedLocationTypes
|
||||
{
|
||||
get { return allowedLocationTypes; }
|
||||
}
|
||||
|
||||
[Serialize(10, isSaveable: true), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
[Serialize(10, IsPropertySaveable.Yes), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
public int TotalModuleCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(200.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
[Serialize(200.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float MinHallwayLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool AlwaysDestructible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool AlwaysRewireable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool SpawnCrewInsideOutpost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool LockUnusedDoors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool RemoveUnusedGaps
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MinWaterPercentage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MaxWaterPercentage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", isSaveable: true), Editable]
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
private readonly Dictionary<string, int> moduleCounts = new Dictionary<string, int>();
|
||||
private readonly Dictionary<Identifier, int> moduleCounts = new Dictionary<Identifier, int>();
|
||||
|
||||
public IEnumerable<KeyValuePair<string, int>> ModuleCounts
|
||||
public IReadOnlyDictionary<Identifier, int> ModuleCounts
|
||||
{
|
||||
get { return moduleCounts; }
|
||||
}
|
||||
|
||||
private readonly List<List<HumanPrefab>> humanPrefabLists = new List<List<HumanPrefab>>();
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
protected OutpostGenerationParams(XElement element, string filePath)
|
||||
private class NpcCollection : IReadOnlyList<HumanPrefab>
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
Name = element.GetAttributeString("name", Identifier);
|
||||
allowedLocationTypes = element.GetAttributeStringArray("allowedlocationtypes", Array.Empty<string>()).ToList();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
private class Entry
|
||||
{
|
||||
private readonly HumanPrefab humanPrefab = null;
|
||||
private readonly Identifier setIdentifier = Identifier.Empty;
|
||||
private readonly Identifier npcIdentifier = Identifier.Empty;
|
||||
|
||||
public Entry(HumanPrefab humanPrefab)
|
||||
{
|
||||
this.humanPrefab = humanPrefab;
|
||||
}
|
||||
|
||||
if (element == null) { return; }
|
||||
foreach (XElement subElement in element.Elements())
|
||||
public Entry(Identifier setIdentifier, Identifier npcIdentifier)
|
||||
{
|
||||
this.setIdentifier = setIdentifier;
|
||||
this.npcIdentifier = npcIdentifier;
|
||||
}
|
||||
|
||||
public HumanPrefab HumanPrefab
|
||||
=> humanPrefab ?? NPCSet.Get(setIdentifier, npcIdentifier);
|
||||
}
|
||||
|
||||
private readonly List<Entry> entries = new List<Entry>();
|
||||
|
||||
public void Add(HumanPrefab humanPrefab)
|
||||
=> entries.Add(new Entry(humanPrefab));
|
||||
|
||||
|
||||
public void Add(Identifier setIdentifier, Identifier npcIdentifier)
|
||||
=> entries.Add(new Entry(setIdentifier, npcIdentifier));
|
||||
|
||||
public IEnumerator<HumanPrefab> GetEnumerator()
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
yield return entry.HumanPrefab;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public int Count => entries.Count;
|
||||
|
||||
public HumanPrefab this[int index] => entries[index].HumanPrefab;
|
||||
}
|
||||
|
||||
private readonly ImmutableArray<IReadOnlyList<HumanPrefab>> humanPrefabCollections;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
private ImmutableHashSet<Identifier> StoreIdentifiers { get; set; }
|
||||
|
||||
#warning TODO: this shouldn't really accept any ContentFile, issue is that RuinConfigFile and OutpostConfigFile are separate derived classes
|
||||
public OutpostGenerationParams(ContentXElement element, ContentFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Name = element.GetAttributeString("name", Identifier.Value);
|
||||
allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()).ToHashSet();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
var humanPrefabCollections = new List<IReadOnlyList<HumanPrefab>>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "modulecount":
|
||||
string moduleFlag = (subElement.GetAttributeString("flag", null) ?? subElement.GetAttributeString("moduletype", "")).ToLowerInvariant();
|
||||
Identifier moduleFlag = subElement.GetAttributeIdentifier("flag", subElement.GetAttributeIdentifier("moduletype", ""));
|
||||
moduleCounts[moduleFlag] = subElement.GetAttributeInt("count", 0);
|
||||
break;
|
||||
case "npcs":
|
||||
humanPrefabLists.Add(new List<HumanPrefab>());
|
||||
foreach (XElement npcElement in subElement.Elements())
|
||||
var newCollection = new NpcCollection();
|
||||
foreach (var npcElement in subElement.Elements())
|
||||
{
|
||||
string from = npcElement.GetAttributeString("from", string.Empty);
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (!string.IsNullOrWhiteSpace(from))
|
||||
Identifier from = npcElement.GetAttributeIdentifier("from", Identifier.Empty);
|
||||
|
||||
if (from != Identifier.Empty)
|
||||
{
|
||||
HumanPrefab prefab = NPCSet.Get(from, npcElement.GetAttributeString("identifier", string.Empty));
|
||||
if (prefab != null)
|
||||
{
|
||||
humanPrefabLists.Last().Add(prefab);
|
||||
}
|
||||
newCollection.Add(from, npcElement.GetAttributeIdentifier("identifier", Identifier.Empty));
|
||||
}
|
||||
else
|
||||
{
|
||||
humanPrefabLists.Last().Add(new HumanPrefab(npcElement, filePath));
|
||||
newCollection.Add(new HumanPrefab(npcElement, file));
|
||||
}
|
||||
}
|
||||
humanPrefabCollections.Add(newCollection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.humanPrefabCollections = humanPrefabCollections.ToImmutableArray();
|
||||
}
|
||||
|
||||
public int GetModuleCount(string moduleFlag)
|
||||
public int GetModuleCount(Identifier moduleFlag)
|
||||
{
|
||||
if (string.IsNullOrEmpty(moduleFlag) || moduleFlag == "none") { return int.MaxValue; }
|
||||
if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return int.MaxValue; }
|
||||
return moduleCounts.ContainsKey(moduleFlag) ? moduleCounts[moduleFlag] : 0;
|
||||
}
|
||||
|
||||
public void SetModuleCount(string moduleFlag, int count)
|
||||
public void SetModuleCount(Identifier moduleFlag, int count)
|
||||
{
|
||||
if (string.IsNullOrEmpty(moduleFlag) || moduleFlag == "none") { return; }
|
||||
if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return; }
|
||||
if (count <= 0)
|
||||
{
|
||||
moduleCounts.Remove(moduleFlag);
|
||||
@@ -169,66 +216,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAllowedLocationTypes(IEnumerable<string> allowedLocationTypes)
|
||||
public void SetAllowedLocationTypes(IEnumerable<Identifier> allowedLocationTypes)
|
||||
{
|
||||
this.allowedLocationTypes.Clear();
|
||||
foreach (string locationType in allowedLocationTypes)
|
||||
foreach (Identifier locationType in allowedLocationTypes)
|
||||
{
|
||||
if (locationType.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (locationType == "any") { continue; }
|
||||
this.allowedLocationTypes.Add(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<HumanPrefab> GetHumanPrefabs(Rand.RandSync randSync)
|
||||
public IReadOnlyList<HumanPrefab> GetHumanPrefabs(Rand.RandSync randSync)
|
||||
{
|
||||
if (humanPrefabLists == null || !humanPrefabLists.Any()) { return Enumerable.Empty<HumanPrefab>(); }
|
||||
return humanPrefabLists.GetRandom(randSync);
|
||||
if (!humanPrefabCollections.Any()) { return Array.Empty<HumanPrefab>(); }
|
||||
return humanPrefabCollections.GetRandom(randSync);
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
public ImmutableHashSet<Identifier> GetStoreIdentifiers()
|
||||
{
|
||||
Params = new List<OutpostGenerationParams>();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.OutpostConfig);
|
||||
foreach (ContentFile file in files)
|
||||
if (StoreIdentifiers == null)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc?.Root == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
var storeIdentifiers = new HashSet<Identifier>();
|
||||
foreach (var collection in humanPrefabCollections)
|
||||
{
|
||||
Params.Clear();
|
||||
DebugConsole.NewMessage($"Overriding all outpost generation parameters with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
XElement sourceElement = isOverride ? element.FirstElement() : element;
|
||||
string elementName = sourceElement.Name.ToString().ToLowerInvariant();
|
||||
string identifier = sourceElement.GetAttributeString("identifier", null);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
foreach (var prefab in collection)
|
||||
{
|
||||
DebugConsole.ThrowError($"No identifier defined for the outpost config '{elementName}' in file '{file.Path}'");
|
||||
continue;
|
||||
}
|
||||
var existingParams = Params.Find(p => p.Identifier == identifier);
|
||||
if (existingParams != null)
|
||||
{
|
||||
if (isOverride)
|
||||
if (prefab?.CampaignInteractionType == CampaignMode.InteractionType.Store)
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding outpost config '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
Params.Remove(existingParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate outpost config: '{identifier}' defined in {elementName} of '{file.Path}'");
|
||||
continue;
|
||||
storeIdentifiers.Add(prefab.Identifier);
|
||||
}
|
||||
}
|
||||
Params.Add(new OutpostGenerationParams(element, file.Path));
|
||||
}
|
||||
StoreIdentifiers = storeIdentifiers.ToImmutableHashSet();
|
||||
}
|
||||
return StoreIdentifiers;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -26,7 +28,7 @@ namespace Barotrauma
|
||||
|
||||
public OutpostModuleInfo.GapPosition UsedGapPositions = 0;
|
||||
|
||||
public readonly HashSet<string> FulfilledModuleTypes = new HashSet<string>();
|
||||
public readonly HashSet<Identifier> FulfilledModuleTypes = new HashSet<Identifier>();
|
||||
|
||||
public Vector2 Offset;
|
||||
|
||||
@@ -55,22 +57,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, bool onlyEntrance = false)
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
return Generate(generationParams, locationType, location: null, onlyEntrance);
|
||||
return Generate(generationParams, locationType, location: null, onlyEntrance, allowInvalidOutpost);
|
||||
}
|
||||
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, Location location, bool onlyEntrance = false)
|
||||
public static Submarine Generate(OutpostGenerationParams generationParams, Location location, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
return Generate(generationParams, location.Type, location, onlyEntrance);
|
||||
return Generate(generationParams, location.Type, location, onlyEntrance, allowInvalidOutpost);
|
||||
}
|
||||
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false)
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false, bool allowInvalidOutpost = false)
|
||||
{
|
||||
var outpostModuleFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.OutpostModule);
|
||||
var outpostModuleFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostModuleFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToArray();
|
||||
var uintIdDupes = outpostModuleFiles.Where(f1 =>
|
||||
outpostModuleFiles.Any(f2 => f1 != f2 && f1.UintIdentifier == f2.UintIdentifier)).ToArray();
|
||||
if (uintIdDupes.Any())
|
||||
{
|
||||
throw new Exception($"OutpostModuleFile UintIdentifier duplicates found: {uintIdDupes.Select(f => f.Path)}");
|
||||
}
|
||||
if (location != null)
|
||||
{
|
||||
if (location.IsCriticallyRadiated() && OutpostGenerationParams.Params.FirstOrDefault(p => p.Identifier.Equals(generationParams.ReplaceInRadiation, StringComparison.OrdinalIgnoreCase)) is { } newParams)
|
||||
if (location.IsCriticallyRadiated() && OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.Identifier == generationParams.ReplaceInRadiation) is { } newParams)
|
||||
{
|
||||
generationParams = newParams;
|
||||
}
|
||||
@@ -80,21 +90,21 @@ namespace Barotrauma
|
||||
|
||||
//load the infos of the outpost module files
|
||||
List<SubmarineInfo> outpostModules = new List<SubmarineInfo>();
|
||||
foreach (ContentFile outpostModuleFile in outpostModuleFiles)
|
||||
foreach (var outpostModuleFile in outpostModuleFiles)
|
||||
{
|
||||
var subInfo = new SubmarineInfo(outpostModuleFile.Path);
|
||||
var subInfo = new SubmarineInfo(outpostModuleFile.Path.Value);
|
||||
if (subInfo.OutpostModuleInfo != null)
|
||||
{
|
||||
if (generationParams is RuinGeneration.RuinGenerationParams)
|
||||
{
|
||||
//if the module doesn't have the ruin flag or any other flag used in the generation params, don't use it in ruins
|
||||
if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") &&
|
||||
if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin".ToIdentifier()) &&
|
||||
!generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Key)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin"))
|
||||
else if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin".ToIdentifier()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -131,14 +141,23 @@ namespace Barotrauma
|
||||
|
||||
selectedModules.Clear();
|
||||
//select which module types the outpost should consist of
|
||||
var pendingModuleFlags = onlyEntrance ? new List<string>() { generationParams.ModuleCounts.First().Key } : SelectModules(outpostModules, generationParams);
|
||||
foreach (string flag in pendingModuleFlags.Distinct().ToList())
|
||||
List<Identifier> pendingModuleFlags;
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
if (flag.Equals("none", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
#warning TODO: cursed
|
||||
pendingModuleFlags = onlyEntrance
|
||||
? generationParams.ModuleCounts
|
||||
.Keys.OrderBy(k => ToolBox.IdentifierToUint32Hash(k, md5))
|
||||
.First().ToEnumerable().ToList()
|
||||
: SelectModules(outpostModules, generationParams);
|
||||
}
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
{
|
||||
if (flag == "none") { continue; }
|
||||
int pendingCount = pendingModuleFlags.Count(f => f == flag);
|
||||
int availableModuleCount =
|
||||
outpostModules
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Any(f => f.Equals(flag, StringComparison.OrdinalIgnoreCase)))
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Any(f => f == flag))
|
||||
.Select(m => m.OutpostModuleInfo.MaxCount)
|
||||
.DefaultIfEmpty(0)
|
||||
.Sum();
|
||||
@@ -154,7 +173,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//the first module is spawned separately, remove it from the list of pending modules
|
||||
string initialModuleFlag = pendingModuleFlags.FirstOrDefault() ?? "airlock";
|
||||
Identifier initialModuleFlag = pendingModuleFlags.FirstOrDefault().IfEmpty("airlock".ToIdentifier());
|
||||
pendingModuleFlags.Remove(initialModuleFlag);
|
||||
|
||||
var initialModule = GetRandomModule(outpostModules, initialModuleFlag, locationType);
|
||||
@@ -162,9 +181,9 @@ namespace Barotrauma
|
||||
{
|
||||
throw new Exception("Failed to generate an outpost (no airlock modules found).");
|
||||
}
|
||||
foreach (string initialFlag in initialModule.OutpostModuleInfo.ModuleFlags)
|
||||
foreach (Identifier initialFlag in initialModule.OutpostModuleInfo.ModuleFlags)
|
||||
{
|
||||
if (pendingModuleFlags.Contains("initialFlag")) { pendingModuleFlags.Remove(initialFlag); }
|
||||
if (pendingModuleFlags.Contains("initialFlag".ToIdentifier())) { pendingModuleFlags.Remove(initialFlag); }
|
||||
}
|
||||
|
||||
if (remainingTries == 1)
|
||||
@@ -176,14 +195,21 @@ namespace Barotrauma
|
||||
selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None));
|
||||
selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag);
|
||||
AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams);
|
||||
if (pendingModuleFlags.Any(flag => !flag.Equals("none", StringComparison.OrdinalIgnoreCase)))
|
||||
if (pendingModuleFlags.Any(flag => flag != "none"))
|
||||
{
|
||||
remainingTries--;
|
||||
if (remainingTries <= 0)
|
||||
if (!allowInvalidOutpost)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough doors at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags));
|
||||
remainingTries--;
|
||||
if (remainingTries <= 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags) + ". Won't retry because invalid outposts are allowed.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var outpostInfo = new SubmarineInfo()
|
||||
@@ -196,7 +222,7 @@ namespace Barotrauma
|
||||
sub.Info.OutpostGenerationParams = generationParams;
|
||||
if (!generationFailed)
|
||||
{
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != sub) { continue; }
|
||||
if (string.IsNullOrEmpty(hull.RoomName) ||
|
||||
@@ -223,12 +249,14 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.NewMessage("Failed to generate an outpost without overlapping modules. Trying to use a pre-built outpost instead...");
|
||||
|
||||
var outpostFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.Outpost);
|
||||
var outpostFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostFile>())
|
||||
.OrderBy(f => f.UintIdentifier).ToArray();
|
||||
if (!outpostFiles.Any())
|
||||
{
|
||||
throw new Exception("Failed to generate an outpost. Could not generate an outpost from the available outpost modules and there are no pre-built outposts available.");
|
||||
}
|
||||
var prebuiltOutpostInfo = new SubmarineInfo(outpostFiles.GetRandom(Rand.RandSync.Server).Path)
|
||||
var prebuiltOutpostInfo = new SubmarineInfo(outpostFiles.GetRandom(Rand.RandSync.ServerAndClient).Path.Value)
|
||||
{
|
||||
Type = SubmarineType.Outpost
|
||||
};
|
||||
@@ -307,7 +335,10 @@ namespace Barotrauma
|
||||
selectedModule.Offset =
|
||||
(selectedModule.PreviousGap.WorldPosition + selectedModule.PreviousModule.Offset) -
|
||||
selectedModule.ThisGap.WorldPosition;
|
||||
selectedModule.Offset += moveDir * generationParams.MinHallwayLength;
|
||||
if (selectedModule.PreviousGap.ConnectedDoor != null || selectedModule.ThisGap.ConnectedDoor != null)
|
||||
{
|
||||
selectedModule.Offset += moveDir * generationParams.MinHallwayLength;
|
||||
}
|
||||
}
|
||||
entities[selectedModule] = moduleEntities;
|
||||
}
|
||||
@@ -386,7 +417,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.Server) * 0.01f;
|
||||
hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.ServerAndClient) * 0.01f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,13 +431,13 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Select the number and types of the modules to use in the outpost
|
||||
/// </summary>
|
||||
private static List<string> SelectModules(IEnumerable<SubmarineInfo> modules, OutpostGenerationParams generationParams)
|
||||
private static List<Identifier> SelectModules(IEnumerable<SubmarineInfo> modules, OutpostGenerationParams generationParams)
|
||||
{
|
||||
int totalModuleCount = generationParams.TotalModuleCount;
|
||||
var pendingModuleFlags = new List<string>();
|
||||
var pendingModuleFlags = new List<Identifier>();
|
||||
bool availableModulesFound = true;
|
||||
|
||||
string initialModuleFlag = generationParams.ModuleCounts.FirstOrDefault().Key;
|
||||
Identifier initialModuleFlag = generationParams.ModuleCounts.FirstOrDefault().Key;
|
||||
pendingModuleFlags.Add(initialModuleFlag);
|
||||
while (pendingModuleFlags.Count < totalModuleCount && availableModulesFound)
|
||||
{
|
||||
@@ -426,19 +457,33 @@ namespace Barotrauma
|
||||
pendingModuleFlags.Add(moduleFlag.Key);
|
||||
}
|
||||
}
|
||||
pendingModuleFlags.Shuffle(Rand.RandSync.Server);
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
pendingModuleFlags.Sort((i1, i2) => (int)ToolBox.StringToUInt32Hash(i1.Value.ToLowerInvariant(), md5) - (int)ToolBox.StringToUInt32Hash(i2.Value.ToLowerInvariant(), md5));
|
||||
}
|
||||
pendingModuleFlags.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
while (pendingModuleFlags.Count < totalModuleCount)
|
||||
{
|
||||
//don't place "none" modules at the end because
|
||||
// a. "filler rooms" at the end of a hallway are pointless
|
||||
// b. placing the unnecessary filler rooms first give more options for the placement of the more important modules
|
||||
pendingModuleFlags.Insert(Rand.Int(pendingModuleFlags.Count - 1, Rand.RandSync.Server), "none");
|
||||
pendingModuleFlags.Insert(Rand.Int(pendingModuleFlags.Count - 1, Rand.RandSync.ServerAndClient), "none".ToIdentifier());
|
||||
}
|
||||
|
||||
//make sure the initial module is inserted first
|
||||
pendingModuleFlags.Remove(initialModuleFlag);
|
||||
pendingModuleFlags.Insert(0, initialModuleFlag);
|
||||
|
||||
if (pendingModuleFlags.Count > totalModuleCount)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error during outpost generation. {pendingModuleFlags.Count} modules set to be used the outpost, but total module count is only {totalModuleCount}. Leaving out some of the modules...");
|
||||
int removeCount = pendingModuleFlags.Count - totalModuleCount;
|
||||
for (int i = 0; i < removeCount; i++)
|
||||
{
|
||||
pendingModuleFlags.Remove(pendingModuleFlags.Last());
|
||||
}
|
||||
}
|
||||
|
||||
return pendingModuleFlags;
|
||||
}
|
||||
|
||||
@@ -452,7 +497,7 @@ namespace Barotrauma
|
||||
/// <param name="selectedModules">The modules we've already selected to be used in the outpost.</param>
|
||||
private static bool AppendToModule(PlacedModule currentModule,
|
||||
List<SubmarineInfo> availableModules,
|
||||
List<string> pendingModuleFlags,
|
||||
List<Identifier> pendingModuleFlags,
|
||||
List<PlacedModule> selectedModules,
|
||||
LocationType locationType,
|
||||
bool retry = true,
|
||||
@@ -461,46 +506,71 @@ namespace Barotrauma
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
|
||||
List<PlacedModule> placedModules = new List<PlacedModule>();
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions().Randomize(Rand.RandSync.Server))
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (!allowExtendBelowInitialModule)
|
||||
//try placing a module meant for this location type first, and if that fails, try choosing whatever fits
|
||||
bool allowDifferentLocationType = i > 0;
|
||||
foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
|
||||
{
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
}
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
if (newModule != null) { placedModules.Add(newModule); }
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; }
|
||||
if (!allowExtendBelowInitialModule)
|
||||
{
|
||||
//don't continue downwards if it'd extend below the airlock
|
||||
if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; }
|
||||
}
|
||||
|
||||
PlacedModule newModule = null;
|
||||
//try appending to the current module if possible
|
||||
if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
|
||||
{
|
||||
newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
}
|
||||
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't append to current module, try one of the other placed modules
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (otherModule == currentModule) { continue; }
|
||||
foreach (OutpostModuleInfo.GapPosition otherGapPosition in
|
||||
GapPositions.Where(g => !otherModule.UsedGapPositions.HasFlag(g) && otherModule.Info.OutpostModuleInfo.GapPositions.HasFlag(g)))
|
||||
{
|
||||
newModule = AppendModule(otherModule, GetOpposingGapPosition(otherGapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
|
||||
if (newModule != null)
|
||||
{
|
||||
placedModules.Add(newModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newModule != null) { break; }
|
||||
}
|
||||
}
|
||||
if (pendingModuleFlags.Count == 0) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
//couldn't place anything, retry
|
||||
if (placedModules.Count == 0 && retry && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule.PreviousModule))
|
||||
//couldn't place a module anywhere, we're probably fucked!
|
||||
if (placedModules.Count == 0 && retry && currentModule.PreviousModule != null && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule))
|
||||
{
|
||||
//try to append to some other module first
|
||||
foreach (PlacedModule otherModule in selectedModules)
|
||||
{
|
||||
if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//try to replace the previously placed module with something else that we can append to
|
||||
var failedModule = currentModule;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
selectedModules.Remove(currentModule);
|
||||
assertAllPreviousModulesPresent();
|
||||
//readd the module types that the previous module was supposed to fulfill to the pending module types
|
||||
pendingModuleFlags.AddRange(currentModule.FulfilledModuleTypes);
|
||||
if (!availableModules.Contains(currentModule.Info)) { availableModules.Add(currentModule.Info); }
|
||||
//retry
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType: true);
|
||||
assertAllPreviousModulesPresent();
|
||||
if (currentModule == null) { break; }
|
||||
if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule))
|
||||
{
|
||||
assertAllPreviousModulesPresent();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -509,9 +579,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (PlacedModule placedModule in placedModules)
|
||||
{
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType);
|
||||
AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: allowExtendBelowInitialModule);
|
||||
}
|
||||
return placedModules.Count > 0;
|
||||
|
||||
void assertAllPreviousModulesPresent()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(selectedModules.All(m => m.PreviousModule == null || selectedModules.Contains(m.PreviousModule)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -526,18 +601,19 @@ namespace Barotrauma
|
||||
PlacedModule currentModule,
|
||||
OutpostModuleInfo.GapPosition gapPosition,
|
||||
List<SubmarineInfo> availableModules,
|
||||
List<string> pendingModuleFlags,
|
||||
List<Identifier> pendingModuleFlags,
|
||||
List<PlacedModule> selectedModules,
|
||||
LocationType locationType)
|
||||
LocationType locationType,
|
||||
bool allowDifferentLocationType)
|
||||
{
|
||||
if (pendingModuleFlags.Count == 0) { return null; }
|
||||
|
||||
string flagToPlace = "none";
|
||||
Identifier flagToPlace = "none".ToIdentifier();
|
||||
SubmarineInfo nextModule = null;
|
||||
foreach (string moduleFlag in pendingModuleFlags)
|
||||
foreach (Identifier moduleFlag in pendingModuleFlags)
|
||||
{
|
||||
flagToPlace = moduleFlag;
|
||||
nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType);
|
||||
nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType, allowDifferentLocationType);
|
||||
if (nextModule != null) { break; }
|
||||
}
|
||||
|
||||
@@ -547,7 +623,7 @@ namespace Barotrauma
|
||||
{
|
||||
Offset = currentModule.Offset + GetMoveDir(gapPosition),
|
||||
};
|
||||
foreach (string moduleFlag in nextModule.OutpostModuleInfo.ModuleFlags)
|
||||
foreach (Identifier moduleFlag in nextModule.OutpostModuleInfo.ModuleFlags)
|
||||
{
|
||||
if (!pendingModuleFlags.Contains(moduleFlag)) { continue; }
|
||||
if (moduleFlag != "none" || flagToPlace == "none")
|
||||
@@ -578,6 +654,7 @@ namespace Barotrauma
|
||||
foreach (PlacedModule otherModule in modules2)
|
||||
{
|
||||
if (module == otherModule) { continue; }
|
||||
if (module.PreviousModule == otherModule && module.PreviousGap.ConnectedDoor == null && module.ThisGap.ConnectedDoor == null) { continue; }
|
||||
if (ModulesOverlap(module, otherModule))
|
||||
{
|
||||
module1 = module;
|
||||
@@ -675,6 +752,7 @@ namespace Barotrauma
|
||||
bool solutionFound = false;
|
||||
foreach (PlacedModule module in movableModules)
|
||||
{
|
||||
if (module.ThisGap.ConnectedDoor == null && module.PreviousGap.ConnectedDoor == null) { continue; }
|
||||
Vector2 moveDir = GetMoveDir(module.ThisGapPosition);
|
||||
Vector2 moveStep = moveDir * 50.0f;
|
||||
Vector2 currentMove = Vector2.Zero;
|
||||
@@ -711,19 +789,19 @@ namespace Barotrauma
|
||||
return solutionFound;
|
||||
}
|
||||
|
||||
private static SubmarineInfo GetRandomModule(IEnumerable<SubmarineInfo> modules, string moduleFlag, LocationType locationType)
|
||||
private static SubmarineInfo GetRandomModule(IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, LocationType locationType)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> availableModules = null;
|
||||
if (string.IsNullOrEmpty(moduleFlag) || moduleFlag.Equals("none"))
|
||||
if (moduleFlag.IsEmpty || moduleFlag == "none")
|
||||
{
|
||||
availableModules = modules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || m.OutpostModuleInfo.ModuleFlags.Contains("none"));
|
||||
availableModules = modules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier()));
|
||||
}
|
||||
else
|
||||
{
|
||||
availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
if (moduleFlag != "hallwayhorizontal" && moduleFlag != "hallwayvertical")
|
||||
{
|
||||
availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayhorizontal") && !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayvertical"));
|
||||
availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayhorizontal".ToIdentifier()) && !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayvertical".ToIdentifier()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,7 +809,7 @@ namespace Barotrauma
|
||||
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
@@ -742,52 +820,62 @@ namespace Barotrauma
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server);
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, string moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType)
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType, bool allowDifferentLocationType)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> availableModules = null;
|
||||
if (string.IsNullOrEmpty(moduleFlag) || moduleFlag.Equals("none"))
|
||||
if (moduleFlag.IsEmpty || moduleFlag.Equals("none"))
|
||||
{
|
||||
availableModules = modules
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none")) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition));
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())));
|
||||
}
|
||||
else
|
||||
{
|
||||
availableModules = modules
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition));
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
}
|
||||
|
||||
availableModules = availableModules.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
if (prevModule != null)
|
||||
{
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule) && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));// && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
}
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
if (allowDifferentLocationType && !modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server);
|
||||
if (allowDifferentLocationType)
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server);
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,13 +895,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<OutpostModuleInfo.GapPosition> GapPositions()
|
||||
private readonly static OutpostModuleInfo.GapPosition[] GapPositions = new[]
|
||||
{
|
||||
yield return OutpostModuleInfo.GapPosition.Right;
|
||||
yield return OutpostModuleInfo.GapPosition.Left;
|
||||
yield return OutpostModuleInfo.GapPosition.Top;
|
||||
yield return OutpostModuleInfo.GapPosition.Bottom;
|
||||
}
|
||||
OutpostModuleInfo.GapPosition.Right,
|
||||
OutpostModuleInfo.GapPosition.Left,
|
||||
OutpostModuleInfo.GapPosition.Top,
|
||||
OutpostModuleInfo.GapPosition.Bottom
|
||||
};
|
||||
|
||||
private static OutpostModuleInfo.GapPosition GetOpposingGapPosition(OutpostModuleInfo.GapPosition thisGapPosition)
|
||||
{
|
||||
@@ -824,7 +912,7 @@ namespace Barotrauma
|
||||
OutpostModuleInfo.GapPosition.Bottom => OutpostModuleInfo.GapPosition.Top,
|
||||
OutpostModuleInfo.GapPosition.Top => OutpostModuleInfo.GapPosition.Bottom,
|
||||
OutpostModuleInfo.GapPosition.None => OutpostModuleInfo.GapPosition.None,
|
||||
_ => throw new InvalidOperationException()
|
||||
_ => throw new ArgumentException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -837,7 +925,7 @@ namespace Barotrauma
|
||||
OutpostModuleInfo.GapPosition.Bottom => Vector2.UnitY,
|
||||
OutpostModuleInfo.GapPosition.Top => -Vector2.UnitY,
|
||||
OutpostModuleInfo.GapPosition.None => Vector2.Zero,
|
||||
_ => throw new InvalidOperationException()
|
||||
_ => throw new ArgumentException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -885,7 +973,7 @@ namespace Barotrauma
|
||||
|
||||
private static bool CanAttachTo(OutpostModuleInfo from, OutpostModuleInfo to)
|
||||
{
|
||||
if (!from.AllowAttachToModules.Any() || from.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase))) { return true; }
|
||||
if (!from.AllowAttachToModules.Any() || from.AllowAttachToModules.All(s => s == "any")) { return true; }
|
||||
return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s));
|
||||
}
|
||||
|
||||
@@ -915,7 +1003,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < thisJunctionBox.Connections.Count && i < previousJunctionBox.Connections.Count; i++)
|
||||
{
|
||||
var wirePrefab = MapEntityPrefab.Find(name: null, identifier: thisJunctionBox.Connections[i].IsPower ? "redwire" : "bluewire") as ItemPrefab;
|
||||
var wirePrefab = MapEntityPrefab.FindByIdentifier((thisJunctionBox.Connections[i].IsPower ? "redwire" : "bluewire").ToIdentifier()) as ItemPrefab;
|
||||
var wire = new Item(wirePrefab, thisJunctionBox.Item.Position, sub).GetComponent<Wire>();
|
||||
|
||||
if (!thisJunctionBox.Connections[i].TryAddLink(wire))
|
||||
@@ -1006,6 +1094,10 @@ namespace Barotrauma
|
||||
}
|
||||
thisWayPoint.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
|
||||
}
|
||||
|
||||
gapToRemove.ConnectedDoor?.Item.Remove();
|
||||
if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); }
|
||||
@@ -1014,16 +1106,20 @@ namespace Barotrauma
|
||||
|
||||
if (hallwayLength <= 1.0f) { continue; }
|
||||
|
||||
var suitableModules = availableModules.Where(m =>
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) &&
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s)));
|
||||
if (suitableModules.Count() == 0)
|
||||
Identifier moduleFlag = (isHorizontal ? "hallwayhorizontal" : "hallwayvertical").ToIdentifier();
|
||||
var hallwayModules = availableModules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
|
||||
var suitableHallwayModules = hallwayModules.Where(m =>
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) &&
|
||||
m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s)));
|
||||
if (suitableHallwayModules.Count() == 0)
|
||||
{
|
||||
suitableModules = availableModules.Where(m =>
|
||||
suitableHallwayModules = hallwayModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowAttachToModules.Any() ||
|
||||
m.OutpostModuleInfo.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase)));
|
||||
m.OutpostModuleInfo.AllowAttachToModules.All(s => s == "any"));
|
||||
}
|
||||
var hallwayInfo = GetRandomModule(suitableModules, isHorizontal ? "hallwayhorizontal" : "hallwayvertical", locationType);
|
||||
|
||||
var hallwayInfo = GetRandomModule(suitableHallwayModules, moduleFlag, locationType);
|
||||
if (hallwayInfo == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Generating hallways between outpost modules failed. No {(isHorizontal ? "horizontal" : "vertical")} hallway modules suitable for use between the modules \"{module.Info.DisplayName}\" and \"{module.PreviousModule.Info.DisplayName}\".");
|
||||
@@ -1145,7 +1241,7 @@ namespace Barotrauma
|
||||
var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.ThisGap);
|
||||
if (startWaypoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {module.ThisGapPosition.ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
|
||||
continue;
|
||||
}
|
||||
var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.PreviousGap);
|
||||
@@ -1442,50 +1538,49 @@ namespace Barotrauma
|
||||
if (outpost?.Info?.OutpostGenerationParams == null) { return; }
|
||||
|
||||
List<HumanPrefab> killedCharacters = new List<HumanPrefab>();
|
||||
Dictionary<HumanPrefab, CharacterInfo> selectedCharacters = new Dictionary<HumanPrefab, CharacterInfo>();
|
||||
foreach (HumanPrefab humanPrefab in outpost.Info.OutpostGenerationParams.GetHumanPrefabs(Rand.RandSync.Server))
|
||||
List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)> selectedCharacters
|
||||
= new List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)>();
|
||||
var humanPrefabs = outpost.Info.OutpostGenerationParams.GetHumanPrefabs(Rand.RandSync.ServerAndClient);
|
||||
foreach (HumanPrefab humanPrefab in humanPrefabs)
|
||||
{
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
|
||||
if (humanPrefab is null) { continue; }
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
|
||||
if (location != null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
|
||||
{
|
||||
killedCharacters.Add(humanPrefab);
|
||||
continue;
|
||||
}
|
||||
selectedCharacters.Add(humanPrefab, characterInfo);
|
||||
selectedCharacters.Add((humanPrefab, characterInfo));
|
||||
}
|
||||
|
||||
//replace killed characters with new ones
|
||||
foreach (HumanPrefab killedCharacter in killedCharacters)
|
||||
{
|
||||
int tries = 0;
|
||||
while (tries < 100)
|
||||
for (int tries = 0; tries < 100; tries++)
|
||||
{
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
|
||||
if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
|
||||
{
|
||||
selectedCharacters.Add(killedCharacter, characterInfo);
|
||||
selectedCharacters.Add((killedCharacter, characterInfo));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var selectedCharacter in selectedCharacters)
|
||||
foreach ((var humanPrefab, var characterInfo) in selectedCharacters)
|
||||
{
|
||||
HumanPrefab humanPrefab = selectedCharacter.Key;
|
||||
CharacterInfo characterInfo = selectedCharacter.Value;
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(characterInfo.Name));
|
||||
|
||||
ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags());
|
||||
if (gotoTarget == null)
|
||||
{
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom(Rand.RandSync.Server);
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
var npc = Character.Create(CharacterPrefab.HumanSpeciesName, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, setSubmarine: true);
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
npc.Prefab = humanPrefab;
|
||||
npc.HumanPrefab = humanPrefab;
|
||||
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
|
||||
{
|
||||
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
|
||||
@@ -1499,7 +1594,7 @@ namespace Barotrauma
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.ServerAndClient);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
{
|
||||
item.AllowStealing = outpost.Info.OutpostGenerationParams.AllowStealing;
|
||||
|
||||
@@ -18,46 +18,49 @@ namespace Barotrauma
|
||||
Bottom = 8
|
||||
}
|
||||
|
||||
private readonly HashSet<string> moduleFlags = new HashSet<string>();
|
||||
public IEnumerable<string> ModuleFlags
|
||||
private readonly HashSet<Identifier> moduleFlags = new HashSet<Identifier>();
|
||||
public IEnumerable<Identifier> ModuleFlags
|
||||
{
|
||||
get { return moduleFlags; }
|
||||
}
|
||||
|
||||
private readonly HashSet<string> allowAttachToModules = new HashSet<string>();
|
||||
public IEnumerable<string> AllowAttachToModules
|
||||
private readonly HashSet<Identifier> allowAttachToModules = new HashSet<Identifier>();
|
||||
public IEnumerable<Identifier> AllowAttachToModules
|
||||
{
|
||||
get { return allowAttachToModules; }
|
||||
}
|
||||
|
||||
private readonly HashSet<string> allowedLocationTypes = new HashSet<string>();
|
||||
public IEnumerable<string> AllowedLocationTypes
|
||||
private readonly HashSet<Identifier> allowedLocationTypes = new HashSet<Identifier>();
|
||||
public IEnumerable<Identifier> AllowedLocationTypes
|
||||
{
|
||||
get { return allowedLocationTypes; }
|
||||
}
|
||||
|
||||
[Serialize(100, isSaveable: true, description: "How many instances of this module can be used in one outpost."), Editable]
|
||||
[Serialize(100, IsPropertySaveable.Yes, description: "How many instances of this module can be used in one outpost."), Editable]
|
||||
public int MaxCount { get; set; }
|
||||
|
||||
[Serialize(10.0f, isSaveable: true, description: "How likely it is for the module to get picked when selecting from a set of modules during the outpost generation."), Editable]
|
||||
[Serialize(10.0f, IsPropertySaveable.Yes, description: "How likely it is for the module to get picked when selecting from a set of modules during the outpost generation."), Editable]
|
||||
public float Commonness { get; set; }
|
||||
|
||||
[Serialize(GapPosition.None, isSaveable: true, description: "Which sides of the module have gaps on them (i.e. from which sides the module can be attached to other modules). Center = no gaps available.")]
|
||||
[Serialize(GapPosition.None, IsPropertySaveable.Yes, description: "Which sides of the module have gaps on them (i.e. from which sides the module can be attached to other modules). Center = no gaps available.")]
|
||||
public GapPosition GapPositions { get; set; }
|
||||
|
||||
[Serialize(GapPosition.Right | GapPosition.Left | GapPosition.Bottom | GapPosition.Top, IsPropertySaveable.Yes, description: "Which sides of this module are allowed to attach to the previously placed module. E.g. if you want a module to always attach to the left side of the docking module, you could set this to Right.")]
|
||||
public GapPosition CanAttachToPrevious { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public OutpostModuleInfo(SubmarineInfo submarineInfo, XElement element)
|
||||
{
|
||||
Name = $"OutpostModuleInfo ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
SetFlags(
|
||||
element.GetAttributeStringArray("flags", null, convertToLowerInvariant: true) ??
|
||||
element.GetAttributeStringArray("moduletypes", new string[0], convertToLowerInvariant: true));
|
||||
SetAllowAttachTo(element.GetAttributeStringArray("allowattachto", new string[0], convertToLowerInvariant: true));
|
||||
allowedLocationTypes = new HashSet<string>(element.GetAttributeStringArray("allowedlocationtypes", new string[0], convertToLowerInvariant: true));
|
||||
element.GetAttributeIdentifierArray("flags", null) ??
|
||||
element.GetAttributeIdentifierArray("moduletypes", Array.Empty<Identifier>()));
|
||||
SetAllowAttachTo(element.GetAttributeIdentifierArray("allowattachto", Array.Empty<Identifier>()));
|
||||
allowedLocationTypes = new HashSet<Identifier>(element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()));
|
||||
}
|
||||
|
||||
public OutpostModuleInfo(SubmarineInfo submarineInfo)
|
||||
@@ -68,12 +71,12 @@ namespace Barotrauma
|
||||
public OutpostModuleInfo(OutpostModuleInfo original)
|
||||
{
|
||||
Name = original.Name;
|
||||
moduleFlags = new HashSet<string>(original.moduleFlags);
|
||||
allowAttachToModules = new HashSet<string>(original.allowAttachToModules);
|
||||
allowedLocationTypes = new HashSet<string>(original.allowedLocationTypes);
|
||||
SerializableProperties = new Dictionary<string, SerializableProperty>();
|
||||
moduleFlags = new HashSet<Identifier>(original.moduleFlags);
|
||||
allowAttachToModules = new HashSet<Identifier>(original.allowAttachToModules);
|
||||
allowedLocationTypes = new HashSet<Identifier>(original.allowedLocationTypes);
|
||||
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
|
||||
GapPositions = original.GapPositions;
|
||||
foreach (KeyValuePair<string, SerializableProperty> kvp in original.SerializableProperties)
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> kvp in original.SerializableProperties)
|
||||
{
|
||||
SerializableProperties.Add(kvp.Key, kvp.Value);
|
||||
if (SerializableProperty.GetSupportedTypeName(kvp.Value.PropertyType) != null)
|
||||
@@ -83,51 +86,51 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFlags(IEnumerable<string> newFlags)
|
||||
public void SetFlags(IEnumerable<Identifier> newFlags)
|
||||
{
|
||||
moduleFlags.Clear();
|
||||
if (newFlags.Contains("hallwayhorizontal"))
|
||||
if (newFlags.Contains("hallwayhorizontal".ToIdentifier()))
|
||||
{
|
||||
moduleFlags.Add("hallwayhorizontal");
|
||||
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
|
||||
moduleFlags.Add("hallwayhorizontal".ToIdentifier());
|
||||
if (newFlags.Contains("ruin".ToIdentifier())) { moduleFlags.Add("ruin".ToIdentifier()); }
|
||||
return;
|
||||
}
|
||||
if (newFlags.Contains("hallwayvertical"))
|
||||
if (newFlags.Contains("hallwayvertical".ToIdentifier()))
|
||||
{
|
||||
moduleFlags.Add("hallwayvertical");
|
||||
if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); }
|
||||
moduleFlags.Add("hallwayvertical".ToIdentifier());
|
||||
if (newFlags.Contains("ruin".ToIdentifier())) { moduleFlags.Add("ruin".ToIdentifier()); }
|
||||
return;
|
||||
}
|
||||
if (!newFlags.Any())
|
||||
{
|
||||
moduleFlags.Add("none");
|
||||
moduleFlags.Add("none".ToIdentifier());
|
||||
}
|
||||
foreach (string flag in newFlags)
|
||||
foreach (Identifier flag in newFlags)
|
||||
{
|
||||
if (flag == "none" && newFlags.Count() > 1) { continue; }
|
||||
moduleFlags.Add(flag.ToLowerInvariant());
|
||||
moduleFlags.Add(flag);
|
||||
}
|
||||
}
|
||||
public void SetAllowAttachTo(IEnumerable<string> allowAttachTo)
|
||||
public void SetAllowAttachTo(IEnumerable<Identifier> allowAttachTo)
|
||||
{
|
||||
allowAttachToModules.Clear();
|
||||
if (!allowAttachTo.Any())
|
||||
{
|
||||
allowAttachToModules.Add("any");
|
||||
allowAttachToModules.Add("any".ToIdentifier());
|
||||
}
|
||||
foreach (string flag in allowAttachTo)
|
||||
foreach (Identifier flag in allowAttachTo)
|
||||
{
|
||||
if (flag == "any" && allowAttachTo.Count() > 1) { continue; }
|
||||
allowAttachToModules.Add(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAllowedLocationTypes(IEnumerable<string> allowedLocationTypes)
|
||||
public void SetAllowedLocationTypes(IEnumerable<Identifier> allowedLocationTypes)
|
||||
{
|
||||
this.allowedLocationTypes.Clear();
|
||||
foreach (string locationType in allowedLocationTypes)
|
||||
foreach (Identifier locationType in allowedLocationTypes)
|
||||
{
|
||||
if (locationType.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (locationType == "any") { continue; }
|
||||
this.allowedLocationTypes.Add(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,31 @@ namespace Barotrauma
|
||||
{
|
||||
class PriceInfo
|
||||
{
|
||||
public readonly int Price;
|
||||
public readonly bool CanBeBought;
|
||||
public int Price { get; }
|
||||
public bool CanBeBought { get; }
|
||||
//minimum number of items available at a given store
|
||||
public readonly int MinAvailableAmount;
|
||||
public int MinAvailableAmount { get; }
|
||||
//maximum number of items available at a given store
|
||||
public readonly int MaxAvailableAmount;
|
||||
public int MaxAvailableAmount { get; }
|
||||
/// <summary>
|
||||
/// Can the item be a Daily Special or a Requested Good
|
||||
/// </summary>
|
||||
public bool CanBeSpecial { get; }
|
||||
/// <summary>
|
||||
/// The item isn't available in stores unless the level's difficulty is above this value
|
||||
/// </summary>
|
||||
public int MinLevelDifficulty { get; }
|
||||
/// <summary>
|
||||
/// The cost of item when sold by the store. Higher modifier means the item costs more to buy from the store.
|
||||
/// </summary>
|
||||
public float BuyingPriceMultiplier { get; } = 1f;
|
||||
public bool DisplayNonEmpty { get; } = false;
|
||||
public Identifier StoreIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when both <see cref="MinAvailableAmount"/> and <see cref="MaxAvailableAmount"/> are set to 0.
|
||||
/// </summary>
|
||||
public const int DefaultAmount = 5;
|
||||
/// <summary>
|
||||
/// Can the item be a Daily Special or a Requested Good
|
||||
/// </summary>
|
||||
public readonly bool CanBeSpecial;
|
||||
/// <summary>
|
||||
/// The item isn't available in stores unless the level's difficulty is above this value
|
||||
/// </summary>
|
||||
public readonly int MinLevelDifficulty;
|
||||
/// <summary>
|
||||
/// The cost of item when sold by the store. Higher modifier means the item costs more to buy from the store.
|
||||
/// </summary>
|
||||
public readonly float BuyingPriceMultiplier = 1f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Support for the old style of determining item prices
|
||||
@@ -41,14 +43,16 @@ namespace Barotrauma
|
||||
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
CanBeBought = true;
|
||||
var minAmount = GetMinAmount(element);
|
||||
int minAmount = GetMinAmount(element);
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f)
|
||||
public PriceInfo(int price, bool canBeBought,
|
||||
int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f,
|
||||
bool displayNonEmpty = false, string storeIdentifier = null)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
@@ -58,46 +62,64 @@ namespace Barotrauma
|
||||
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
|
||||
MinLevelDifficulty = minLevelDifficulty;
|
||||
CanBeSpecial = canBeSpecial;
|
||||
DisplayNonEmpty = displayNonEmpty;
|
||||
StoreIdentifier = new Identifier(storeIdentifier);
|
||||
}
|
||||
|
||||
public static List<Tuple<string, PriceInfo>> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
public static List<PriceInfo> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
{
|
||||
var priceInfos = new List<PriceInfo>();
|
||||
defaultPrice = null;
|
||||
var basePrice = element.GetAttributeInt("baseprice", 0);
|
||||
var soldByDefault = element.GetAttributeBool("soldbydefault", true);
|
||||
var minAmount = GetMinAmount(element);
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
var minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
var canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
var buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
var priceInfos = new List<Tuple<string, PriceInfo>>();
|
||||
|
||||
int basePrice = element.GetAttributeInt("baseprice", 0);
|
||||
int minAmount = GetMinAmount(element);
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
int minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
bool canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
|
||||
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
{
|
||||
var priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
var sold = childElement.GetAttributeBool("sold", soldByDefault);
|
||||
priceInfos.Add(new Tuple<string, PriceInfo>(childElement.GetAttributeString("locationtype", "").ToLowerInvariant(),
|
||||
new PriceInfo(price: (int)(priceMultiplier * basePrice), canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty), childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier))));
|
||||
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
bool sold = childElement.GetAttributeBool("sold", soldByDefault);
|
||||
int storeMinLevelDifficulty = childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty);
|
||||
float storeBuyingMultiplier = childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier);
|
||||
string backwardsCompatibleIdentifier = childElement.GetAttributeString("locationtype", "");
|
||||
if (!string.IsNullOrEmpty(backwardsCompatibleIdentifier))
|
||||
{
|
||||
backwardsCompatibleIdentifier = $"merchant{backwardsCompatibleIdentifier}";
|
||||
}
|
||||
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
|
||||
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
|
||||
var priceInfo = new PriceInfo((int)(priceMultiplier * basePrice),
|
||||
sold,
|
||||
sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
storeMinLevelDifficulty,
|
||||
storeBuyingMultiplier,
|
||||
displayNonEmpty,
|
||||
storeIdentifier);
|
||||
priceInfos.Add(priceInfo);
|
||||
}
|
||||
|
||||
var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true);
|
||||
defaultPrice = new PriceInfo(basePrice, canBeBoughtAtOtherLocations,
|
||||
minAmount: canBeBoughtAtOtherLocations ? minAmount : 0,
|
||||
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty, buyingPriceMultiplier);
|
||||
|
||||
bool soldElsewhere = soldByDefault && element.GetAttributeBool("soldelsewhere", element.GetAttributeBool("soldeverywhere", false));
|
||||
defaultPrice = new PriceInfo(basePrice,
|
||||
soldElsewhere,
|
||||
soldElsewhere ? minAmount : 0,
|
||||
soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty,
|
||||
buyingPriceMultiplier,
|
||||
displayNonEmpty);
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
private static int GetMinAmount(XElement element, int defaultValue = 0) => element != null ?
|
||||
element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) : defaultValue;
|
||||
element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) :
|
||||
defaultValue;
|
||||
|
||||
private static int GetMaxAmount(XElement element, int defaultValue = 0) => element != null ?
|
||||
element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) : defaultValue;
|
||||
element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) :
|
||||
defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!subs.Any()) yield return CoroutineStatus.Success;
|
||||
|
||||
Character.Controlled = null;
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
#if CLIENT
|
||||
Character.Controlled = null;
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
#endif
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
|
||||
Level.Loaded.TopBarrier.Enabled = false;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Abilities;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
@@ -56,9 +57,9 @@ namespace Barotrauma
|
||||
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
|
||||
|
||||
#if DEBUG
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
#else
|
||||
[Serialize(false, true)]
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
#endif
|
||||
public bool Indestructible
|
||||
{
|
||||
@@ -75,7 +76,7 @@ namespace Barotrauma
|
||||
|
||||
public override Sprite Sprite
|
||||
{
|
||||
get { return prefab.sprite; }
|
||||
get { return base.Prefab.Sprite; }
|
||||
}
|
||||
|
||||
public bool IsPlatform
|
||||
@@ -91,7 +92,7 @@ namespace Barotrauma
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return prefab.Name; }
|
||||
get { return base.Prefab.Name.Value; }
|
||||
}
|
||||
|
||||
public bool HasBody
|
||||
@@ -115,7 +116,7 @@ namespace Barotrauma
|
||||
|
||||
private float? maxHealth;
|
||||
|
||||
[Serialize(100.0f, true), Editable(MinValueFloat = 0)]
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0)]
|
||||
public float MaxHealth
|
||||
{
|
||||
get => maxHealth ?? Prefab.Health;
|
||||
@@ -124,7 +125,7 @@ namespace Barotrauma
|
||||
|
||||
private float crushDepth;
|
||||
|
||||
[Serialize(Level.DefaultRealWorldCrushDepth, true)]
|
||||
[Serialize(Level.DefaultRealWorldCrushDepth, IsPropertySaveable.Yes)]
|
||||
public float CrushDepth
|
||||
{
|
||||
get => crushDepth;
|
||||
@@ -163,29 +164,26 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public StructurePrefab Prefab => prefab as StructurePrefab;
|
||||
public new StructurePrefab Prefab => base.Prefab as StructurePrefab;
|
||||
|
||||
public HashSet<string> Tags
|
||||
{
|
||||
get { return prefab.Tags; }
|
||||
}
|
||||
public ImmutableHashSet<Identifier> Tags => Prefab.Tags;
|
||||
|
||||
protected Color spriteColor;
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", true)]
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes)]
|
||||
public Color SpriteColor
|
||||
{
|
||||
get { return spriteColor; }
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true)]
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool UseDropShadow
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", true, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
|
||||
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
|
||||
public Vector2 DropShadowOffset
|
||||
{
|
||||
get;
|
||||
@@ -201,7 +199,7 @@ namespace Barotrauma
|
||||
if (scale == value) { return; }
|
||||
scale = MathHelper.Clamp(value, 0.1f, 10.0f);
|
||||
|
||||
float relativeScale = scale / prefab.Scale;
|
||||
float relativeScale = scale / base.Prefab.Scale;
|
||||
|
||||
if (!ResizeHorizontal || !ResizeVertical)
|
||||
{
|
||||
@@ -230,7 +228,7 @@ namespace Barotrauma
|
||||
|
||||
protected Vector2 textureScale = Vector2.One;
|
||||
|
||||
[Editable(DecimalCount = 3, MinValueFloat = 0.01f, MaxValueFloat = 10f, ValueStep = 0.1f), Serialize("1.0, 1.0", false)]
|
||||
[Editable(DecimalCount = 3, MinValueFloat = 0.01f, MaxValueFloat = 10f, ValueStep = 0.1f), Serialize("1.0, 1.0", IsPropertySaveable.No)]
|
||||
public Vector2 TextureScale
|
||||
{
|
||||
get { return textureScale; }
|
||||
@@ -250,7 +248,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
protected Vector2 textureOffset = Vector2.Zero;
|
||||
[Editable(MinValueFloat = -1000f, MaxValueFloat = 1000f, ValueStep = 10f), Serialize("0.0, 0.0", true)]
|
||||
[Editable(MinValueFloat = -1000f, MaxValueFloat = 1000f, ValueStep = 10f), Serialize("0.0, 0.0", IsPropertySaveable.Yes)]
|
||||
public Vector2 TextureOffset
|
||||
{
|
||||
get { return textureOffset; }
|
||||
@@ -343,14 +341,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool NoAITarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -406,7 +404,7 @@ namespace Barotrauma
|
||||
rect = rectangle;
|
||||
TextureScale = sp.TextureScale;
|
||||
|
||||
spriteColor = prefab.SpriteColor;
|
||||
spriteColor = base.Prefab.SpriteColor;
|
||||
if (sp.IsHorizontal.HasValue)
|
||||
{
|
||||
IsHorizontal = sp.IsHorizontal.Value;
|
||||
@@ -461,7 +459,7 @@ namespace Barotrauma
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
|
||||
#if CLIENT
|
||||
foreach (XElement subElement in sp.ConfigElement.Elements())
|
||||
foreach (var subElement in sp.ConfigElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("light", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -524,7 +522,7 @@ namespace Barotrauma
|
||||
{
|
||||
defaultRect = defaultRect
|
||||
};
|
||||
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties)
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> property in SerializableProperties)
|
||||
{
|
||||
if (!property.Value.Attributes.OfType<Editable>().Any()) { continue; }
|
||||
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
|
||||
@@ -572,13 +570,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (FlippedX && IsHorizontal)
|
||||
{
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width);
|
||||
width = prefab.sprite.SourceRect.Width;
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / base.Prefab.Sprite.SourceRect.Width);
|
||||
width = base.Prefab.Sprite.SourceRect.Width;
|
||||
}
|
||||
else if (FlippedY && !IsHorizontal)
|
||||
{
|
||||
ysections = (int)Math.Ceiling((float)rect.Height / prefab.sprite.SourceRect.Height);
|
||||
width = prefab.sprite.SourceRect.Height;
|
||||
ysections = (int)Math.Ceiling((float)rect.Height / base.Prefab.Sprite.SourceRect.Height);
|
||||
width = base.Prefab.Sprite.SourceRect.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1184,7 +1182,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (damageDiff < 0.0f)
|
||||
{
|
||||
attacker.Info?.IncreaseSkillLevel("mechanical",
|
||||
attacker.Info?.IncreaseSkillLevel("mechanical".ToIdentifier(),
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f));
|
||||
}
|
||||
}
|
||||
@@ -1374,10 +1372,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Structure Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
public static Structure Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
string name = element.GetAttribute("name").Value;
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
|
||||
StructurePrefab prefab = FindPrefab(name, identifier);
|
||||
if (prefab == null)
|
||||
@@ -1398,7 +1396,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
bool hasDamage = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -1421,7 +1419,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case "upgrade":
|
||||
{
|
||||
var upgradeIdentifier = subElement.GetAttributeString("identifier", string.Empty);
|
||||
var upgradeIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier);
|
||||
int level = subElement.GetAttributeInt("level", 1);
|
||||
if (upgradePrefab != null)
|
||||
@@ -1442,12 +1440,12 @@ namespace Barotrauma
|
||||
if (element.GetAttributeBool("flippedy", false)) { s.FlipY(false); }
|
||||
|
||||
//structures with a body drop a shadow by default
|
||||
if (element.Attribute("usedropshadow") == null)
|
||||
if (element.GetAttribute("usedropshadow") == null)
|
||||
{
|
||||
s.UseDropShadow = prefab.Body;
|
||||
}
|
||||
|
||||
if (element.Attribute("noaitarget") == null)
|
||||
if (element.GetAttribute("noaitarget") == null)
|
||||
{
|
||||
s.NoAITarget = prefab.NoAITarget;
|
||||
}
|
||||
@@ -1460,18 +1458,18 @@ namespace Barotrauma
|
||||
return s;
|
||||
}
|
||||
|
||||
public static StructurePrefab FindPrefab(string name, string identifier)
|
||||
public static StructurePrefab FindPrefab(string name, Identifier identifier)
|
||||
{
|
||||
StructurePrefab prefab = null;
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
//legacy support:
|
||||
//1. attempt to find a prefab with an empty identifier and a matching name
|
||||
prefab = MapEntityPrefab.Find(name, "") as StructurePrefab;
|
||||
//2. not found, attempt to find a prefab with a matching name
|
||||
if (prefab == null) prefab = MapEntityPrefab.Find(name) as StructurePrefab;
|
||||
if (prefab == null) { prefab = MapEntityPrefab.Find(name) as StructurePrefab; }
|
||||
//3. not found, attempt to find a prefab that uses the previous name as an identifier
|
||||
if (prefab == null) prefab = MapEntityPrefab.Find(null, name) as StructurePrefab;
|
||||
if (prefab == null) { prefab = MapEntityPrefab.Find(null, name) as StructurePrefab; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1488,8 +1486,8 @@ namespace Barotrauma
|
||||
int height = ResizeVertical ? rect.Height : defaultRect.Height;
|
||||
|
||||
element.Add(
|
||||
new XAttribute("name", prefab.Name),
|
||||
new XAttribute("identifier", prefab.Identifier),
|
||||
new XAttribute("name", base.Prefab.Name),
|
||||
new XAttribute("identifier", base.Prefab.Identifier),
|
||||
new XAttribute("ID", ID),
|
||||
new XAttribute("rect",
|
||||
(int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using System.Collections.Immutable;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
@@ -15,169 +16,98 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<StructurePrefab> Prefabs = new PrefabCollection<StructurePrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override LocalizedString Name { get; }
|
||||
|
||||
private string name;
|
||||
public override string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
public readonly ContentXElement ConfigElement;
|
||||
|
||||
public XElement ConfigElement { get; private set; }
|
||||
|
||||
private bool canSpriteFlipX, canSpriteFlipY;
|
||||
|
||||
private float health;
|
||||
|
||||
//default size
|
||||
private Vector2 size;
|
||||
|
||||
//does the structure have a physics body
|
||||
[Serialize(false, false)]
|
||||
public bool Body
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//rotation of the physics body in degrees
|
||||
[Serialize(0.0f, false)]
|
||||
public float BodyRotation
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, false)]
|
||||
public float BodyWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, false)]
|
||||
public float BodyHeight
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//in display units
|
||||
[Serialize("0.0,0.0", false)]
|
||||
public Vector2 BodyOffset
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool Platform
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool AllowAttachItems
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float MinHealth
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, false)]
|
||||
public float Health
|
||||
{
|
||||
get { return health; }
|
||||
set { health = Math.Max(value, MinHealth); }
|
||||
}
|
||||
|
||||
[Serialize(true, false)]
|
||||
public bool IndestructibleInOutposts
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool CastShadow
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public readonly bool CanSpriteFlipX;
|
||||
public readonly bool CanSpriteFlipY;
|
||||
|
||||
/// <summary>
|
||||
/// If null, the orientation is determined automatically based on the dimensions of the structure instances
|
||||
/// </summary>
|
||||
public bool? IsHorizontal
|
||||
public readonly bool? IsHorizontal;
|
||||
|
||||
public Vector2 ScaledSize => Size * Scale;
|
||||
|
||||
public readonly Sprite BackgroundSprite;
|
||||
|
||||
public override Sprite Sprite { get; }
|
||||
|
||||
public override string OriginalName { get; }
|
||||
|
||||
public override ImmutableHashSet<Identifier> Tags { get; }
|
||||
|
||||
public override ImmutableHashSet<Identifier> AllowedLinks { get; }
|
||||
|
||||
public override MapEntityCategory Category { get; }
|
||||
|
||||
public override ImmutableHashSet<string> Aliases { get; }
|
||||
|
||||
//does the structure have a physics body
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Body { get; private set; }
|
||||
|
||||
//rotation of the physics body in degrees
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float BodyRotation { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float BodyWidth { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float BodyHeight { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No)]
|
||||
public Vector2 BodyOffset { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Platform { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AllowAttachItems { get; private set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float MinHealth { get; private set; }
|
||||
|
||||
private float health;
|
||||
[Serialize(100.0f, IsPropertySaveable.No)]
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
get { return health; }
|
||||
private set { health = Math.Max(value, MinHealth); }
|
||||
}
|
||||
|
||||
[Serialize(Direction.None, false)]
|
||||
public Direction StairDirection
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool IndestructibleInOutposts { get; private set; }
|
||||
|
||||
[Serialize(45.0f, false)]
|
||||
public float StairAngle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool CastShadow { get; private set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool NoAITarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
[Serialize(Direction.None, IsPropertySaveable.No)]
|
||||
public Direction StairDirection { get; private set; }
|
||||
|
||||
public bool CanSpriteFlipX
|
||||
{
|
||||
get { return canSpriteFlipX; }
|
||||
}
|
||||
[Serialize(45.0f, IsPropertySaveable.No)]
|
||||
public float StairAngle { get; private set; }
|
||||
|
||||
public bool CanSpriteFlipY
|
||||
{
|
||||
get { return canSpriteFlipY; }
|
||||
}
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool NoAITarget { get; private set; }
|
||||
|
||||
[Serialize("0,0", true)]
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
private set { size = value; }
|
||||
}
|
||||
[Serialize("0,0", IsPropertySaveable.Yes)]
|
||||
public Vector2 Size { get; private set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string DamageSound { get; private set; }
|
||||
|
||||
public Vector2 ScaledSize => size * Scale;
|
||||
|
||||
protected Vector2 textureScale = Vector2.One;
|
||||
[Editable(DecimalCount = 3), Serialize("1.0, 1.0", true)]
|
||||
[Editable(DecimalCount = 3), Serialize("1.0, 1.0", IsPropertySaveable.Yes)]
|
||||
public Vector2 TextureScale
|
||||
{
|
||||
get { return textureScale; }
|
||||
set
|
||||
private set
|
||||
{
|
||||
textureScale = new Vector2(
|
||||
MathHelper.Clamp(value.X, 0.01f, 10),
|
||||
@@ -185,263 +115,228 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Sprite BackgroundSprite
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
foreach (ContentFile file in files)
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
string originalName = element.GetAttributeString("name", "");
|
||||
if (identifier.IsEmpty && !string.IsNullOrEmpty(originalName))
|
||||
{
|
||||
LoadFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
var rootElement = doc.Root;
|
||||
if (rootElement.IsOverride())
|
||||
{
|
||||
foreach (var element in rootElement.Elements())
|
||||
string categoryStr = element.GetAttributeString("category", "Misc");
|
||||
if (Enum.TryParse(categoryStr, true, out MapEntityCategory category) && category.HasFlag(MapEntityCategory.Legacy))
|
||||
{
|
||||
foreach (var childElement in element.Elements())
|
||||
{
|
||||
Load(childElement, true, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
foreach (var childElement in element.Elements())
|
||||
{
|
||||
Load(childElement, true, file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Load(element, false, file);
|
||||
}
|
||||
identifier = $"legacystructure_{originalName.Replace(" ", "")}".ToIdentifier();
|
||||
}
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public static void RemoveByFile(string filePath)
|
||||
public StructurePrefab(ContentXElement element, StructureFile file) : base(element, file)
|
||||
{
|
||||
Prefabs.RemoveByFile(filePath);
|
||||
}
|
||||
OriginalName = element.GetAttributeString("name", "");
|
||||
ConfigElement = element;
|
||||
|
||||
private static StructurePrefab Load(XElement element, bool allowOverride, ContentFile file)
|
||||
{
|
||||
StructurePrefab sp = new StructurePrefab
|
||||
{
|
||||
originalName = element.GetAttributeString("name", ""),
|
||||
FilePath = file.Path,
|
||||
ContentPackage = file.ContentPackage
|
||||
};
|
||||
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", "");
|
||||
var parentType = element.Parent?.GetAttributeIdentifier("prefabtype", Identifier.Empty) ?? Identifier.Empty;
|
||||
|
||||
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", "");
|
||||
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
string fallbackNameIdentifier = element.GetAttributeString("fallbacknameidentifier", "");
|
||||
Identifier fallbackNameIdentifier = element.GetAttributeIdentifier("fallbacknameidentifier", "");
|
||||
|
||||
string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", "");
|
||||
Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", "");
|
||||
|
||||
if (string.IsNullOrEmpty(sp.originalName))
|
||||
Name = TextManager.Get(nameIdentifier.IsEmpty
|
||||
? $"EntityName.{Identifier}"
|
||||
: $"EntityName.{nameIdentifier}",
|
||||
$"EntityName.{fallbackNameIdentifier}");
|
||||
|
||||
if (parentType == "wrecked")
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameIdentifier))
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + nameIdentifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
Name = TextManager.GetWithVariable("wreckeditemformat", "[name]", Name);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sp.name))
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, returnNull: true) ?? $"Not defined ({sp.identifier})";
|
||||
}
|
||||
sp.Tags = new HashSet<string>();
|
||||
Name = Name.Fallback(OriginalName);
|
||||
|
||||
var tags = new HashSet<Identifier>();
|
||||
string joinedTags = element.GetAttributeString("tags", "");
|
||||
if (string.IsNullOrEmpty(joinedTags)) joinedTags = element.GetAttributeString("Tags", "");
|
||||
foreach (string tag in joinedTags.Split(','))
|
||||
{
|
||||
sp.Tags.Add(tag.Trim().ToLowerInvariant());
|
||||
tags.Add(tag.Trim().ToIdentifier());
|
||||
}
|
||||
|
||||
if (element.Attribute("ishorizontal") != null)
|
||||
if (element.GetAttribute("ishorizontal") != null)
|
||||
{
|
||||
sp.IsHorizontal = element.GetAttributeBool("ishorizontal", false);
|
||||
IsHorizontal = element.GetAttributeBool("ishorizontal", false);
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
#if CLIENT
|
||||
var decorativeSprites = new List<DecorativeSprite>();
|
||||
var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
|
||||
#endif
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "sprite":
|
||||
sp.sprite = new Sprite(subElement, lazyLoad: true);
|
||||
if (subElement.Attribute("sourcerect") == null &&
|
||||
subElement.Attribute("sheetindex") == null)
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
if (subElement.GetAttribute("sourcerect") == null &&
|
||||
subElement.GetAttribute("sheetindex") == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!");
|
||||
DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + Name + "\"!");
|
||||
}
|
||||
#if CLIENT
|
||||
if (subElement.GetAttributeBool("fliphorizontal", false))
|
||||
sp.sprite.effects = SpriteEffects.FlipHorizontally;
|
||||
Sprite.effects = SpriteEffects.FlipHorizontally;
|
||||
if (subElement.GetAttributeBool("flipvertical", false))
|
||||
sp.sprite.effects = SpriteEffects.FlipVertically;
|
||||
Sprite.effects = SpriteEffects.FlipVertically;
|
||||
#endif
|
||||
sp.canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
|
||||
sp.canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
|
||||
CanSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
|
||||
CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
|
||||
|
||||
if (subElement.Attribute("name") == null && !string.IsNullOrWhiteSpace(sp.Name))
|
||||
if (subElement.GetAttribute("name") == null && !Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
sp.sprite.Name = sp.Name;
|
||||
Sprite.Name = Name.Value;
|
||||
}
|
||||
sp.sprite.EntityID = sp.identifier;
|
||||
Sprite.EntityIdentifier = Identifier;
|
||||
break;
|
||||
case "backgroundsprite":
|
||||
sp.BackgroundSprite = new Sprite(subElement, lazyLoad: true);
|
||||
if (subElement.Attribute("sourcerect") == null && sp.sprite != null)
|
||||
BackgroundSprite = new Sprite(subElement, lazyLoad: true);
|
||||
if (subElement.GetAttribute("sourcerect") == null && Sprite != null)
|
||||
{
|
||||
sp.BackgroundSprite.SourceRect = sp.sprite.SourceRect;
|
||||
sp.BackgroundSprite.size = sp.sprite.size;
|
||||
sp.BackgroundSprite.size.X *= sp.sprite.SourceRect.Width;
|
||||
sp.BackgroundSprite.size.Y *= sp.sprite.SourceRect.Height;
|
||||
sp.BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
|
||||
BackgroundSprite.SourceRect = Sprite.SourceRect;
|
||||
BackgroundSprite.size = Sprite.size;
|
||||
BackgroundSprite.size.X *= Sprite.SourceRect.Width;
|
||||
BackgroundSprite.size.Y *= Sprite.SourceRect.Height;
|
||||
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; }
|
||||
sp.BackgroundSpriteColor = subElement.GetAttributeColor("color", Color.White);
|
||||
if (subElement.GetAttributeBool("fliphorizontal", false)) { BackgroundSprite.effects = SpriteEffects.FlipHorizontally; }
|
||||
if (subElement.GetAttributeBool("flipvertical", false)) { BackgroundSprite.effects = SpriteEffects.FlipVertically; }
|
||||
BackgroundSpriteColor = subElement.GetAttributeColor("color", Color.White);
|
||||
#endif
|
||||
break;
|
||||
case "decorativesprite":
|
||||
#if CLIENT
|
||||
string decorativeSpriteFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
if (subElement.DoesAttributeReferenceFileNameAlone("texture"))
|
||||
{
|
||||
decorativeSpriteFolder = Path.GetDirectoryName(file.Path);
|
||||
}
|
||||
|
||||
int groupID = 0;
|
||||
DecorativeSprite decorativeSprite = null;
|
||||
if (subElement.Attribute("texture") == null)
|
||||
if (subElement.GetAttribute("texture") == null)
|
||||
{
|
||||
groupID = subElement.GetAttributeInt("randomgroupid", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
decorativeSprite = new DecorativeSprite(subElement, decorativeSpriteFolder, lazyLoad: true);
|
||||
sp.DecorativeSprites.Add(decorativeSprite);
|
||||
decorativeSprites.Add(decorativeSprite);
|
||||
groupID = decorativeSprite.RandomGroupID;
|
||||
}
|
||||
if (!sp.DecorativeSpriteGroups.ContainsKey(groupID))
|
||||
if (!decorativeSpriteGroups.ContainsKey(groupID))
|
||||
{
|
||||
sp.DecorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
|
||||
decorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
|
||||
}
|
||||
sp.DecorativeSpriteGroups[groupID].Add(decorativeSprite);
|
||||
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 CLIENT
|
||||
DecorativeSprites = decorativeSprites.ToImmutableArray();
|
||||
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
||||
#endif
|
||||
|
||||
string categoryStr = element.GetAttributeString("category", "Structure");
|
||||
if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category))
|
||||
{
|
||||
category = MapEntityCategory.Structure;
|
||||
category = MapEntityCategory.Structure;
|
||||
}
|
||||
sp.Category = category;
|
||||
Category = category;
|
||||
|
||||
if (category.HasFlag(MapEntityCategory.Legacy))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sp.identifier))
|
||||
{
|
||||
sp.identifier = "legacystructure_" + sp.name.ToLowerInvariant().Replace(" ", "");
|
||||
}
|
||||
}
|
||||
|
||||
sp.Aliases =
|
||||
(element.GetAttributeStringArray("aliases", null) ??
|
||||
element.GetAttributeStringArray("Aliases", new string[0])).ToHashSet();
|
||||
Aliases =
|
||||
(element.GetAttributeStringArray("aliases", null, convertToLowerInvariant: true) ??
|
||||
element.GetAttributeStringArray("Aliases", Array.Empty<string>(), convertToLowerInvariant: true)).ToImmutableHashSet();
|
||||
|
||||
string nonTranslatedName = element.GetAttributeString("name", null) ?? element.Name.ToString();
|
||||
sp.Aliases.Add(nonTranslatedName.ToLowerInvariant());
|
||||
Aliases.Add(nonTranslatedName.ToLowerInvariant());
|
||||
|
||||
SerializableProperty.DeserializeProperties(sp, element);
|
||||
if (sp.Body)
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
if (Body)
|
||||
{
|
||||
sp.Tags.Add("wall");
|
||||
tags.Add("wall".ToIdentifier());
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sp.Description))
|
||||
if (Description.IsNullOrEmpty())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(descriptionIdentifier))
|
||||
if (!descriptionIdentifier.IsEmpty)
|
||||
{
|
||||
sp.Description = TextManager.Get("EntityDescription." + descriptionIdentifier, returnNull: true) ?? string.Empty;
|
||||
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(nameIdentifier))
|
||||
else if (nameIdentifier.IsEmpty)
|
||||
{
|
||||
sp.Description = TextManager.Get("EntityDescription." + sp.identifier, returnNull: true) ?? string.Empty;
|
||||
Description = TextManager.Get($"EntityDescription.{Identifier}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.Description = TextManager.Get("EntityDescription." + nameIdentifier, true) ?? string.Empty;
|
||||
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("size") == null)
|
||||
if (element.GetAttribute("size") == null)
|
||||
{
|
||||
sp.size = Vector2.Zero;
|
||||
if (element.Attribute("width") == null && element.Attribute("height") == null)
|
||||
Size = Vector2.Zero;
|
||||
if (element.GetAttribute("width") == null && element.GetAttribute("height") == null)
|
||||
{
|
||||
sp.size.X = sp.sprite.SourceRect.Width;
|
||||
sp.size.Y = sp.sprite.SourceRect.Height;
|
||||
Size = Sprite.SourceRect.Size.ToVector2();
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.size.X = element.GetAttributeFloat("width", 0.0f);
|
||||
sp.size.Y = element.GetAttributeFloat("height", 0.0f);
|
||||
Size = new Vector2(
|
||||
element.GetAttributeFloat("width", 0.0f),
|
||||
element.GetAttributeFloat("height", 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sp.Category = MapEntityCategory.Wrecked;
|
||||
sp.Subcategory = "Thalamus";
|
||||
Category = MapEntityCategory.Wrecked;
|
||||
Subcategory = "Thalamus";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sp.identifier))
|
||||
if (Identifier == Identifier.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
"Structure prefab \"" + sp.name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.");
|
||||
"Structure prefab \"" + Name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.");
|
||||
}
|
||||
Prefabs.Add(sp, allowOverride);
|
||||
return sp;
|
||||
#if DEBUG
|
||||
if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(OriginalName))
|
||||
{
|
||||
DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Tags = tags.ToImmutableHashSet();
|
||||
AllowedLinks = ImmutableHashSet<Identifier>.Empty;
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
None = 0, Left = 1, Right = 2
|
||||
}
|
||||
|
||||
partial class Submarine : Entity, IServerSerializable
|
||||
partial class Submarine : Entity, IServerPositionSync
|
||||
{
|
||||
public SubmarineInfo Info { get; private set; }
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace Barotrauma
|
||||
get { return subBody == null ? Vector2.Zero : subBody.Velocity; }
|
||||
set
|
||||
{
|
||||
if (subBody == null) return;
|
||||
if (subBody == null) { return; }
|
||||
subBody.Velocity = value;
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ namespace Barotrauma
|
||||
public int CalculateBasePrice()
|
||||
{
|
||||
int minPrice = 1000;
|
||||
float volume = Hull.hullList.Where(h => h.Submarine == this).Sum(h => h.Volume);
|
||||
float volume = Hull.HullList.Where(h => h.Submarine == this).Sum(h => h.Volume);
|
||||
float itemValue = Item.ItemList.Where(it => it.Submarine == this).Sum(it => it.Prefab.GetMinPrice() ?? 0);
|
||||
float price = volume / 500.0f + itemValue / 100.0f;
|
||||
System.Diagnostics.Debug.Assert(price >= 0);
|
||||
@@ -300,7 +300,7 @@ namespace Barotrauma
|
||||
|
||||
private float ballastFloraTimer;
|
||||
public bool ImmuneToBallastFlora { get; set; }
|
||||
public void AttemptBallastFloraInfection(string identifier, float deltaTime, float probability)
|
||||
public void AttemptBallastFloraInfection(Identifier identifier, float deltaTime, float probability)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (ImmuneToBallastFlora) { return; }
|
||||
@@ -557,7 +557,7 @@ namespace Barotrauma
|
||||
public Rectangle CalculateDimensions(bool onlyHulls = true)
|
||||
{
|
||||
List<MapEntity> entities = onlyHulls ?
|
||||
Hull.hullList.FindAll(h => h.Submarine == this).Cast<MapEntity>().ToList() :
|
||||
Hull.HullList.FindAll(h => h.Submarine == this).Cast<MapEntity>().ToList() :
|
||||
MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
|
||||
|
||||
//ignore items whose body is disabled (wires, items inside cabinets)
|
||||
@@ -969,8 +969,6 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
//if (PlayerInput.KeyHit(InputType.Crouch) && (this == MainSub)) FlipX();
|
||||
|
||||
if (Info.IsWreck)
|
||||
{
|
||||
WreckAI?.Update(deltaTime);
|
||||
@@ -990,7 +988,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (e.Submarine == this)
|
||||
{
|
||||
Spawner.AddToRemoveQueue(e);
|
||||
Spawner.AddEntityToRemoveQueue(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,7 +1112,7 @@ namespace Barotrauma
|
||||
float waterVolume = 0.0f;
|
||||
float volume = 0.0f;
|
||||
float excessWater = 0.0f;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != this) { continue; }
|
||||
waterVolume += hull.WaterVolume;
|
||||
@@ -1212,7 +1210,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool IsConnectedTo(Submarine otherSub) => this == otherSub || GetConnectedSubs().Contains(otherSub);
|
||||
|
||||
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
|
||||
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.HullList);
|
||||
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
|
||||
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
|
||||
public List<WayPoint> GetWaypoints(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, WayPoint.WayPointList);
|
||||
@@ -1285,7 +1283,7 @@ namespace Barotrauma
|
||||
if (element.Name != "Structure") { continue; }
|
||||
|
||||
string name = element.GetAttributeString("name", "");
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
||||
StructurePrefab prefab = Structure.FindPrefab(name, identifier);
|
||||
if (prefab == null || !prefab.Body) { continue; }
|
||||
|
||||
@@ -1348,7 +1346,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 center = Vector2.Zero;
|
||||
var matchingHulls = Hull.hullList.FindAll(h => h.Submarine == this);
|
||||
var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this);
|
||||
|
||||
if (matchingHulls.Any())
|
||||
{
|
||||
@@ -1538,7 +1536,16 @@ namespace Barotrauma
|
||||
Rectangle dimensions = VisibleBorders;
|
||||
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
|
||||
var cargoContainers = GetCargoContainers();
|
||||
element.Add(new XAttribute("cargocapacity", cargoContainers.Sum(c => c.container.Capacity)));
|
||||
int cargoCapacity = cargoContainers.Sum(c => c.container.Capacity);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
|
||||
{
|
||||
cargoCapacity += linkedSub.CargoCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
element.Add(new XAttribute("cargocapacity", cargoCapacity));
|
||||
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
|
||||
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
|
||||
@@ -1654,7 +1661,7 @@ namespace Barotrauma
|
||||
Unloading = true;
|
||||
|
||||
#if CLIENT
|
||||
RemoveAllRoundSounds();
|
||||
RoundSound.RemoveAllRoundSounds();
|
||||
GameMain.LightManager?.ClearLights();
|
||||
#endif
|
||||
|
||||
@@ -1697,6 +1704,8 @@ namespace Barotrauma
|
||||
GameMain.World?.Clear();
|
||||
GameMain.World = null;
|
||||
|
||||
Powered.Grids.Clear();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
Unloading = false;
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Barotrauma
|
||||
Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero;
|
||||
Vector2 visibleMinExtents = Vector2.Zero, visibleMaxExtents = Vector2.Zero;
|
||||
Body farseerBody = null;
|
||||
if (!Hull.hullList.Any(h => h.Submarine == sub))
|
||||
if (!Hull.HullList.Any(h => h.Submarine == sub))
|
||||
{
|
||||
farseerBody = GameMain.World.CreateRectangle(1.0f, 1.0f, 1.0f);
|
||||
if (showWarningMessages)
|
||||
@@ -156,7 +156,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != submarine || hull.IdFreed) { continue; }
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace Barotrauma
|
||||
{
|
||||
float waterVolume = 0.0f;
|
||||
float volume = 0.0f;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != submarine) continue;
|
||||
|
||||
@@ -850,7 +850,7 @@ namespace Barotrauma
|
||||
{
|
||||
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
|
||||
}
|
||||
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
|
||||
if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"SubmarineBody.ApplyImpact:InvalidImpulse",
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
|
||||
@@ -28,13 +28,8 @@ namespace Barotrauma
|
||||
|
||||
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; }
|
||||
}
|
||||
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
|
||||
|
||||
private Task hashTask;
|
||||
private Md5Hash hash;
|
||||
@@ -59,13 +54,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
public LocalizedString DisplayName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Description
|
||||
public LocalizedString Description
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -170,7 +165,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (requiredContentPackagesInstalled.HasValue) { return requiredContentPackagesInstalled.Value; }
|
||||
return RequiredContentPackages.All(cp => GameMain.Config.AllEnabledPackages.Any(cp2 => cp2.Name == cp));
|
||||
return RequiredContentPackages.All(reqName => ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.NameMatches(reqName)));
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -199,13 +194,14 @@ namespace Barotrauma
|
||||
|
||||
public OutpostGenerationParams OutpostGenerationParams;
|
||||
|
||||
public readonly Dictionary<string, List<Character>> OutpostNPCs = new Dictionary<string, List<Character>>();
|
||||
public readonly Dictionary<Identifier, List<Character>> OutpostNPCs = new Dictionary<Identifier, List<Character>>();
|
||||
|
||||
//constructors & generation ----------------------------------------------------
|
||||
public SubmarineInfo()
|
||||
{
|
||||
FilePath = null;
|
||||
Name = DisplayName = TextManager.Get("UnspecifiedSubFileName");
|
||||
DisplayName = TextManager.Get("UnspecifiedSubFileName");
|
||||
Name = DisplayName.Value;
|
||||
IsFileCorrupted = false;
|
||||
RequiredContentPackages = new HashSet<string>();
|
||||
}
|
||||
@@ -219,7 +215,8 @@ namespace Barotrauma
|
||||
}
|
||||
try
|
||||
{
|
||||
Name = DisplayName = Path.GetFileNameWithoutExtension(filePath);
|
||||
DisplayName = Path.GetFileNameWithoutExtension(filePath);
|
||||
Name = DisplayName.Value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -228,7 +225,7 @@ namespace Barotrauma
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(hash))
|
||||
{
|
||||
this.hash = new Md5Hash(hash);
|
||||
this.hash = Md5Hash.StringAsHash(hash);
|
||||
}
|
||||
|
||||
IsFileCorrupted = false;
|
||||
@@ -267,7 +264,7 @@ namespace Barotrauma
|
||||
GameVersion = original.GameVersion;
|
||||
Type = original.Type;
|
||||
SubmarineClass = original.SubmarineClass;
|
||||
hash = !string.IsNullOrEmpty(original.FilePath) ? original.MD5Hash : null;
|
||||
hash = !string.IsNullOrEmpty(original.FilePath) && File.Exists(original.FilePath) ? original.MD5Hash : null;
|
||||
Dimensions = original.Dimensions;
|
||||
CargoCapacity = original.CargoCapacity;
|
||||
FilePath = original.FilePath;
|
||||
@@ -300,7 +297,7 @@ namespace Barotrauma
|
||||
DebugConsole.NewMessage("Opening submarine file \"" + FilePath + "\" failed, retrying in 250 ms...");
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
if (doc == null || doc.Root == null)
|
||||
if (doc?.Root == null)
|
||||
{
|
||||
IsFileCorrupted = true;
|
||||
return;
|
||||
@@ -314,11 +311,9 @@ namespace Barotrauma
|
||||
|
||||
private void Init()
|
||||
{
|
||||
DisplayName = TextManager.Get("Submarine.Name." + Name, true);
|
||||
if (string.IsNullOrEmpty(DisplayName)) { DisplayName = Name; }
|
||||
DisplayName = TextManager.Get("Submarine.Name." + Name).Fallback(Name);
|
||||
|
||||
Description = TextManager.Get("Submarine.Description." + Name, true);
|
||||
if (string.IsNullOrEmpty(Description)) { Description = SubmarineElement.GetAttributeString("description", ""); }
|
||||
Description = TextManager.Get("Submarine.Description." + Name).Fallback(SubmarineElement.GetAttributeString("description", ""));
|
||||
|
||||
EqualityCheckVal = SubmarineElement.GetAttributeInt("checkval", 0);
|
||||
|
||||
@@ -379,7 +374,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
RequiredContentPackages.Clear();
|
||||
string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", new string[0]);
|
||||
string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", Array.Empty<string>());
|
||||
foreach (string contentPackageName in contentPackageNames)
|
||||
{
|
||||
RequiredContentPackages.Add(contentPackageName);
|
||||
@@ -405,14 +400,9 @@ namespace Barotrauma
|
||||
var vanilla = GameMain.VanillaContent;
|
||||
if (vanilla != null)
|
||||
{
|
||||
var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine)
|
||||
.Concat(vanilla.GetFilesOfType(ContentType.Wreck))
|
||||
.Concat(vanilla.GetFilesOfType(ContentType.BeaconStation))
|
||||
.Concat(vanilla.GetFilesOfType(ContentType.EnemySubmarine))
|
||||
.Concat(vanilla.GetFilesOfType(ContentType.Outpost))
|
||||
.Concat(vanilla.GetFilesOfType(ContentType.OutpostModule));
|
||||
string pathToCompare = FilePath.Replace(@"\", @"/").ToLowerInvariant();
|
||||
if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare))
|
||||
var vanillaSubs = vanilla.GetFiles<BaseSubFile>();
|
||||
string pathToCompare = FilePath.CleanUpPath();
|
||||
if (vanillaSubs.Any(sub => sub.Path == pathToCompare))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -427,7 +417,8 @@ namespace Barotrauma
|
||||
|
||||
hashTask = new Task(() =>
|
||||
{
|
||||
hash = new Md5Hash(doc, FilePath);
|
||||
hash = Md5Hash.CalculateForString(doc.ToString(), Md5Hash.StringHashOptions.IgnoreWhitespace);
|
||||
Md5Hash.Cache.Add(FilePath, hash, DateTime.UtcNow);
|
||||
});
|
||||
hashTask.Start();
|
||||
}
|
||||
@@ -459,7 +450,7 @@ namespace Barotrauma
|
||||
LeftBehindSubDockingPortOccupied = false;
|
||||
LeftBehindDockingPortIDs.Clear();
|
||||
BlockedDockingPortIDs.Clear();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("linkedsubmarine", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (subElement.Attribute("location") == null) { continue; }
|
||||
@@ -469,7 +460,7 @@ namespace Barotrauma
|
||||
LeftBehindDockingPortIDs.Add(targetDockingPortID);
|
||||
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)
|
||||
if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", Array.Empty<int>()).Length > 0)
|
||||
{
|
||||
BlockedDockingPortIDs.Add(targetDockingPortID);
|
||||
LeftBehindSubDockingPortOccupied = true;
|
||||
@@ -488,7 +479,7 @@ namespace Barotrauma
|
||||
foreach (var structureElement in SubmarineElement.GetChildElements("structure"))
|
||||
{
|
||||
string name = structureElement.Attribute("name")?.Value ?? "";
|
||||
string identifier = structureElement.GetAttributeString("identifier", "");
|
||||
Identifier identifier = structureElement.GetAttributeIdentifier("identifier", "");
|
||||
var structurePrefab = Structure.FindPrefab(name, identifier);
|
||||
if (structurePrefab == null || !structurePrefab.Body) { continue; }
|
||||
if (!structureCrushDepthsDefined && structureElement.Attribute("crushdepth") != null)
|
||||
@@ -546,7 +537,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SaveUtil.CompressStringToFile(filePath, doc.ToString());
|
||||
Md5Hash.RemoveFromCache(filePath);
|
||||
Md5Hash.Cache.Remove(filePath);
|
||||
}
|
||||
|
||||
public static void AddToSavedSubs(SubmarineInfo subInfo)
|
||||
@@ -578,73 +569,26 @@ namespace Barotrauma
|
||||
|
||||
public static void RefreshSavedSubs()
|
||||
{
|
||||
var contentPackageSubs = ContentPackage.GetFilesOfType(
|
||||
GameMain.Config.AllEnabledPackages,
|
||||
ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule,
|
||||
ContentType.Wreck, ContentType.BeaconStation, ContentType.EnemySubmarine);
|
||||
var contentPackageSubs = ContentPackageManager.EnabledPackages.All.SelectMany(c => c.GetFiles<BaseSubFile>());
|
||||
|
||||
for (int i = savedSubmarines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (File.Exists(savedSubmarines[i].FilePath))
|
||||
{
|
||||
bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
|
||||
bool isInSubmarinesFolder = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SavePath);
|
||||
bool isInContentPackage = contentPackageSubs.Any(fp => Path.GetFullPath(fp.Path).CleanUpPath() == Path.GetFullPath(savedSubmarines[i].FilePath).CleanUpPath());
|
||||
bool isInContentPackage = contentPackageSubs.Any(f => f.Path == savedSubmarines[i].FilePath);
|
||||
if (isDownloadedSub) { continue; }
|
||||
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && (isInSubmarinesFolder || isInContentPackage)) { continue; }
|
||||
if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && isInContentPackage) { continue; }
|
||||
}
|
||||
savedSubmarines[i].Dispose();
|
||||
}
|
||||
|
||||
if (!Directory.Exists(SavePath))
|
||||
List<string> filePaths = new List<string>();
|
||||
foreach (BaseSubFile subFile in contentPackageSubs)
|
||||
{
|
||||
try
|
||||
if (!filePaths.Any(fp => fp == subFile.Path))
|
||||
{
|
||||
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.HasFlag(System.IO.FileAttributes.Hidden) && !dir.Name.StartsWith(".");
|
||||
}).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.Add(subFile.Path.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,34 +597,7 @@ namespace Barotrauma
|
||||
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
|
||||
if (!subInfo.IsFileCorrupted)
|
||||
{
|
||||
savedSubmarines.Add(subInfo);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public Ladder Ladders;
|
||||
public Structure Stairs;
|
||||
|
||||
private List<string> tags;
|
||||
private HashSet<Identifier> tags;
|
||||
|
||||
public bool isObstructed;
|
||||
|
||||
@@ -78,10 +78,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Tags
|
||||
{
|
||||
get { return tags; }
|
||||
}
|
||||
public IEnumerable<Identifier> Tags => tags;
|
||||
|
||||
public JobPrefab AssignedJob { get; private set; }
|
||||
|
||||
@@ -114,7 +111,7 @@ namespace Barotrauma
|
||||
|
||||
|
||||
public WayPoint(Rectangle newRect, Submarine submarine)
|
||||
: this (MapEntityPrefab.Find(null, "waypoint"), newRect, submarine)
|
||||
: this (MapEntityPrefab.FindByIdentifier("waypoint".ToIdentifier()), newRect, submarine)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -122,8 +119,8 @@ namespace Barotrauma
|
||||
: base (prefab, submarine, id)
|
||||
{
|
||||
rect = newRect;
|
||||
idCardTags = new string[0];
|
||||
tags = new List<string>();
|
||||
idCardTags = Array.Empty<string>();
|
||||
tags = new HashSet<Identifier>();
|
||||
|
||||
#if CLIENT
|
||||
if (iconSprites == null)
|
||||
@@ -165,7 +162,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool GenerateSubWaypoints(Submarine submarine)
|
||||
{
|
||||
if (!Hull.hullList.Any())
|
||||
if (!Hull.HullList.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
|
||||
return false;
|
||||
@@ -189,13 +186,14 @@ namespace Barotrauma
|
||||
door.Body.Enabled = true;
|
||||
}
|
||||
}
|
||||
bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin");
|
||||
bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin".ToIdentifier());
|
||||
float diffFromHullEdge = 50;
|
||||
float minDist = 100.0f;
|
||||
float heightFromFloor = 110.0f;
|
||||
float hullMinHeight = 100;
|
||||
|
||||
var removals = new HashSet<WayPoint>();
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (isFlooded)
|
||||
{
|
||||
@@ -315,7 +313,16 @@ namespace Barotrauma
|
||||
for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine);
|
||||
if (previousWaypoint != null) { wayPoint.ConnectTo(previousWaypoint); }
|
||||
// Too close to stairs, will be assigned as a stair point -> remove
|
||||
if (wayPoint.FindStairs() != null)
|
||||
{
|
||||
removals.Add(wayPoint);
|
||||
continue;
|
||||
}
|
||||
if (previousWaypoint != null)
|
||||
{
|
||||
wayPoint.ConnectTo(previousWaypoint);
|
||||
}
|
||||
previousWaypoint = wayPoint;
|
||||
}
|
||||
if (previousWaypoint == null)
|
||||
@@ -512,25 +519,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
removals.ForEach(wp => wp.Remove());
|
||||
removals.Clear();
|
||||
// Stairs
|
||||
foreach (MapEntity mapEntity in mapEntityList.ToList())
|
||||
{
|
||||
if (!(mapEntity is Structure structure)) { continue; }
|
||||
if (structure.StairDirection == Direction.None) { continue; }
|
||||
WayPoint[] stairPoints = new WayPoint[3];
|
||||
float margin = -32;
|
||||
|
||||
stairPoints[0] = new WayPoint(
|
||||
new Vector2(structure.Rect.X - 32.0f,
|
||||
structure.Rect.Y - (structure.StairDirection == Direction.Left ? 80 : structure.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
|
||||
stairPoints[0] = new WayPoint(new Vector2(
|
||||
structure.Rect.X + 5,
|
||||
structure.Rect.Y - (structure.StairDirection == Direction.Left ? margin : structure.Rect.Height - 100)), SpawnType.Path, submarine);
|
||||
|
||||
stairPoints[1] = new WayPoint(
|
||||
new Vector2(structure.Rect.Right + 32.0f,
|
||||
structure.Rect.Y - (structure.StairDirection == Direction.Left ? structure.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
|
||||
stairPoints[1] = new WayPoint(new Vector2(
|
||||
structure.Rect.Right - 5,
|
||||
structure.Rect.Y - (structure.StairDirection == Direction.Left ? structure.Rect.Height - 100 : margin)), SpawnType.Path, submarine);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70));
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.5f, minDist / 2));
|
||||
if (closest == null) { continue; }
|
||||
stairPoints[i].ConnectTo(closest);
|
||||
}
|
||||
@@ -539,9 +550,8 @@ namespace Barotrauma
|
||||
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine);
|
||||
stairPoints[0].ConnectTo(stairPoints[2]);
|
||||
stairPoints[2].ConnectTo(stairPoints[1]);
|
||||
stairPoints.ForEach(wp => wp.FindStairs());
|
||||
}
|
||||
removals.ForEach(wp => wp.Remove());
|
||||
removals.Clear();
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
@@ -587,7 +597,7 @@ namespace Barotrauma
|
||||
Body pickedBody = Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)),
|
||||
prevPos, ignoredBodies, Physics.CollisionWall, false,
|
||||
(Fixture f) => f.Body.UserData is Item && ((Item)f.Body.UserData).GetComponent<Door>() != null);
|
||||
(Fixture f) => f.Body.UserData is Item pickedItem && pickedItem.GetComponent<Door>() != null);
|
||||
|
||||
Door pickedDoor = null;
|
||||
if (pickedBody != null)
|
||||
@@ -842,7 +852,11 @@ namespace Barotrauma
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, ignoreLevel: true, ignoreSubs: true, ignoreSensors: false);
|
||||
if (body != null && body != ignoredBody && !(body.UserData is Submarine))
|
||||
{
|
||||
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
|
||||
if (body.UserData is Structure)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall) && body.UserData is Item i && i.GetComponent<Door>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -876,9 +890,9 @@ namespace Barotrauma
|
||||
return WayPointList.GetRandom(wp =>
|
||||
(ignoreSubmarine || wp.Submarine == sub) &&
|
||||
wp.spawnType == spawnType &&
|
||||
(string.IsNullOrEmpty(spawnPointTag) || wp.Tags.Any(t => t.Equals(spawnPointTag, StringComparison.OrdinalIgnoreCase))) &&
|
||||
(spawnPointTag.IsNullOrEmpty() || wp.Tags.Any(t => t == spawnPointTag)) &&
|
||||
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
|
||||
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
|
||||
useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
|
||||
@@ -922,7 +936,7 @@ namespace Barotrauma
|
||||
var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.AssignedJob == null);
|
||||
if (nonJobSpecificPoints.Any())
|
||||
{
|
||||
assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)];
|
||||
assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.ServerAndClient)];
|
||||
}
|
||||
|
||||
if (assignedWayPoints[i] != null) { continue; }
|
||||
@@ -962,18 +976,15 @@ namespace Barotrauma
|
||||
FindStairs();
|
||||
}
|
||||
|
||||
private void FindStairs()
|
||||
private Structure FindStairs()
|
||||
{
|
||||
Stairs = null;
|
||||
Body pickedBody = Submarine.PickBody(SimPosition, SimPosition - Vector2.UnitY * 2.0f, null, Physics.CollisionStairs);
|
||||
if (pickedBody != null && pickedBody.UserData is Structure)
|
||||
Body pickedBody = Submarine.PickBody(SimPosition, SimPosition - new Vector2(0, 1.2f), null, Physics.CollisionStairs);
|
||||
if (pickedBody != null && pickedBody.UserData is Structure structure && structure.StairDirection != Direction.None)
|
||||
{
|
||||
Structure structure = (Structure)pickedBody.UserData;
|
||||
if (structure != null && structure.StairDirection != Direction.None)
|
||||
{
|
||||
Stairs = structure;
|
||||
}
|
||||
Stairs = structure;
|
||||
}
|
||||
return Stairs;
|
||||
}
|
||||
|
||||
public void InitializeLinks()
|
||||
@@ -985,23 +996,24 @@ namespace Barotrauma
|
||||
}
|
||||
if (ladderId > 0)
|
||||
{
|
||||
Item ladderItem = FindEntityByID(ladderId) as Item;
|
||||
if (ladderItem != null) { Ladders = ladderItem.GetComponent<Ladder>(); }
|
||||
if (FindEntityByID(ladderId) is Item ladderItem) { Ladders = ladderItem.GetComponent<Ladder>(); }
|
||||
ladderId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static WayPoint Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
public static WayPoint Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.GetAttribute("x").Value),
|
||||
int.Parse(element.GetAttribute("y").Value),
|
||||
(int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
|
||||
|
||||
|
||||
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
|
||||
WayPoint w = new WayPoint(MapEntityPrefab.Find(null, spawnType == SpawnType.Path ? "waypoint" : "spawnpoint"), rect, submarine, idRemap.GetOffsetId(element));
|
||||
w.spawnType = spawnType;
|
||||
WayPoint w = new WayPoint(MapEntityPrefab.FindByIdentifier((spawnType == SpawnType.Path ? "waypoint" : "spawnpoint").ToIdentifier()), rect, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
spawnType = spawnType
|
||||
};
|
||||
|
||||
string idCardDescString = element.GetAttributeString("idcarddesc", "");
|
||||
if (!string.IsNullOrWhiteSpace(idCardDescString))
|
||||
@@ -1014,7 +1026,7 @@ namespace Barotrauma
|
||||
w.IdCardTags = idCardTagString.Split(',');
|
||||
}
|
||||
|
||||
w.tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true).ToList();
|
||||
w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToHashSet();
|
||||
|
||||
string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant();
|
||||
if (!string.IsNullOrWhiteSpace(jobIdentifier))
|
||||
@@ -1029,9 +1041,9 @@ namespace Barotrauma
|
||||
w.gapId = idRemap.GetOffsetId(element.GetAttributeInt("gap", 0));
|
||||
|
||||
int i = 0;
|
||||
while (element.Attribute("linkedto" + i) != null)
|
||||
while (element.GetAttribute("linkedto" + i) != null)
|
||||
{
|
||||
int srcId = int.Parse(element.Attribute("linkedto" + i).Value);
|
||||
int srcId = int.Parse(element.GetAttribute("linkedto" + i).Value);
|
||||
int destId = idRemap.GetOffsetId(srcId);
|
||||
if (destId > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user