Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git
This commit is contained in:
@@ -25,6 +25,7 @@ namespace Barotrauma
|
||||
IEnumerable<string> aliases = null)
|
||||
: base(identifier)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(constructor != null);
|
||||
this.constructor = constructor;
|
||||
this.Name = TextManager.Get($"EntityName.{identifier}");
|
||||
this.Description = TextManager.Get($"EntityDescription.{identifier}");
|
||||
@@ -35,40 +36,52 @@ namespace Barotrauma
|
||||
this.Aliases = (aliases ?? Enumerable.Empty<string>()).Concat(identifier.Value.ToEnumerable()).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static CoreEntityPrefab HullPrefab { get; private set; }
|
||||
public static CoreEntityPrefab GapPrefab { get; private set; }
|
||||
public static CoreEntityPrefab WayPointPrefab { get; private set; }
|
||||
public static CoreEntityPrefab SpawnPointPrefab { get; private set; }
|
||||
|
||||
public static void InitCorePrefabs()
|
||||
{
|
||||
CoreEntityPrefab ep = new CoreEntityPrefab(
|
||||
HullPrefab = new CoreEntityPrefab(
|
||||
"hull".ToIdentifier(),
|
||||
typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
typeof(Hull).GetConstructor(new Type[] { typeof(Rectangle) }),
|
||||
resizeHorizontal: true,
|
||||
resizeVertical: true,
|
||||
linkable: true,
|
||||
allowedLinks: new Identifier[] { "hull".ToIdentifier() });
|
||||
Prefabs.Add(ep, false);
|
||||
Prefabs.Add(HullPrefab, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
GapPrefab = new CoreEntityPrefab(
|
||||
"gap".ToIdentifier(),
|
||||
typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
|
||||
typeof(Gap).GetConstructor(new Type[] { typeof(Rectangle) }),
|
||||
resizeHorizontal: true,
|
||||
resizeVertical: true);
|
||||
Prefabs.Add(ep, false);
|
||||
Prefabs.Add(GapPrefab, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
WayPointPrefab = new CoreEntityPrefab(
|
||||
"waypoint".ToIdentifier(),
|
||||
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
|
||||
Prefabs.Add(ep, false);
|
||||
Prefabs.Add(WayPointPrefab, false);
|
||||
|
||||
ep = new CoreEntityPrefab(
|
||||
SpawnPointPrefab = new CoreEntityPrefab(
|
||||
"spawnpoint".ToIdentifier(),
|
||||
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
|
||||
Prefabs.Add(ep, false);
|
||||
Prefabs.Add(SpawnPointPrefab, false);
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
if (constructor == null) return;
|
||||
object[] lobject = new object[] { this, rect };
|
||||
constructor.Invoke(lobject);
|
||||
if (this == WayPointPrefab || this == SpawnPointPrefab)
|
||||
{
|
||||
object[] lobject = new object[] { this, rect };
|
||||
constructor.Invoke(lobject);
|
||||
}
|
||||
else
|
||||
{
|
||||
object[] lobject = new object[] { rect };
|
||||
constructor.Invoke(lobject);
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
[Serialize(400, IsPropertySaveable.Yes, "How much health the root has.")]
|
||||
public int RootHealth { get; set; }
|
||||
|
||||
[Serialize(0.0005f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")]
|
||||
[Serialize(0.00025f, 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.")]
|
||||
@@ -399,6 +399,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
new XAttribute("pos", XMLExtensions.Vector2ToString(branch.Position)),
|
||||
new XAttribute("ID", branch.ID),
|
||||
new XAttribute("isroot", branch.IsRoot),
|
||||
new XAttribute("isrootgrowth", branch.IsRootGrowth),
|
||||
new XAttribute("health", branch.Health.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("maxhealth", branch.MaxHealth.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("sides", (int)branch.Sides),
|
||||
@@ -457,16 +458,34 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
foreach ((BallastFloraBranch branch, int parentBranchId) in branches)
|
||||
{
|
||||
if (parentBranchId > -1 && parentBranchId < Branches.Count)
|
||||
if (parentBranchId > -1)
|
||||
{
|
||||
branch.ParentBranch = Branches[parentBranchId];
|
||||
var parentBranch = Branches.Find(b => b.ID == parentBranchId);
|
||||
if (parentBranch == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading ballast flora: couldn't find a parent branch with the ID {parentBranchId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
branch.ParentBranch = parentBranch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
Branches.ForEach(b => b.DisconnectedFromRoot = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckDisconnectedFromRoot();
|
||||
}
|
||||
|
||||
void LoadBranch(XElement branchElement, IdRemap idRemap)
|
||||
{
|
||||
Vector2 pos = branchElement.GetAttributeVector2("pos", Vector2.Zero);
|
||||
bool isRoot = branchElement.GetAttributeBool("isroot", false);
|
||||
bool isRootGrowth = branchElement.GetAttributeBool("isrootgrowth", false);
|
||||
int flowerConfig = getInt("flowerconfig");
|
||||
int leafconfig = getInt("leafconfig");
|
||||
int id = getInt("ID");
|
||||
@@ -484,7 +503,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
MaxHealth = maxhealth,
|
||||
Sides = (TileSide) sides,
|
||||
BlockedSides = (TileSide) blockedSides,
|
||||
IsRoot = isRoot
|
||||
IsRoot = isRoot,
|
||||
IsRootGrowth = isRootGrowth
|
||||
};
|
||||
branches.Add((newBranch, parentBranchId));
|
||||
|
||||
@@ -649,10 +669,14 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
toBeRemoved.Clear();
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.ParentBranch != null && (branch.ParentBranch.DisconnectedFromRoot || branch.ParentBranch.Health <= 0.0f))
|
||||
if (!branch.IsRoot)
|
||||
{
|
||||
float speed = MathHelper.Lerp(5.0f, 0.1f, branch.ParentBranch.Health / branch.ParentBranch.MaxHealth);
|
||||
DamageBranch(branch, speed * speed * deltaTime, AttackType.CutFromRoot);
|
||||
if (branch.ParentBranch == null || branch.ParentBranch.DisconnectedFromRoot || branch.ParentBranch.Health <= 0.0f)
|
||||
{
|
||||
float parentHealth = branch.ParentBranch == null ? 0.0f : branch.ParentBranch.Health / branch.ParentBranch.MaxHealth;
|
||||
float speed = MathHelper.Lerp(5.0f, 0.1f, parentHealth);
|
||||
DamageBranch(branch, speed * speed * deltaTime, AttackType.CutFromRoot);
|
||||
}
|
||||
}
|
||||
if (branch.Health <= 0.0f)
|
||||
{
|
||||
@@ -767,7 +791,8 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
MaxHealth = RootHealth,
|
||||
Health = RootHealth,
|
||||
IsRoot = true,
|
||||
CurrentHull = Parent
|
||||
CurrentHull = Parent,
|
||||
ID = CreateID()
|
||||
};
|
||||
|
||||
Branches.Add(root);
|
||||
@@ -992,14 +1017,6 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
public void DamageBranch(BallastFloraBranch branch, float amount, AttackType type, Character? attacker = null)
|
||||
{
|
||||
float damage = amount;
|
||||
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)
|
||||
{
|
||||
@@ -1058,6 +1075,14 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
|
||||
if (damage > 0)
|
||||
{
|
||||
damage = Math.Min(damage, branch.Health);
|
||||
}
|
||||
else
|
||||
{
|
||||
damage = Math.Max(damage, branch.Health - branch.MaxHealth);
|
||||
}
|
||||
branch.Health -= damage;
|
||||
|
||||
#if SERVER
|
||||
@@ -1071,6 +1096,25 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDisconnectedFromRoot()
|
||||
{
|
||||
bool foundDisconnected;
|
||||
do
|
||||
{
|
||||
foundDisconnected = false;
|
||||
foreach (BallastFloraBranch branch in Branches)
|
||||
{
|
||||
if (branch.ParentBranch == null || branch.DisconnectedFromRoot) { continue; }
|
||||
if (branch.ParentBranch.Removed || branch.ParentBranch.DisconnectedFromRoot)
|
||||
{
|
||||
branch.DisconnectedFromRoot = true;
|
||||
foundDisconnected = true;
|
||||
}
|
||||
}
|
||||
} while (foundDisconnected);
|
||||
|
||||
}
|
||||
|
||||
public void RemoveBranch(BallastFloraBranch branch)
|
||||
{
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
@@ -1081,20 +1125,7 @@ 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);
|
||||
CheckDisconnectedFromRoot();
|
||||
|
||||
bodies.ForEachMod(body =>
|
||||
{
|
||||
@@ -1148,7 +1179,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
return;
|
||||
}
|
||||
#if SERVER
|
||||
if (!wasRemoved)
|
||||
if (!wasRemoved && Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new BranchRemoveEventData(branch));
|
||||
}
|
||||
@@ -1181,7 +1212,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
});
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new InfectEventData(item, InfectEventData.InfectState.No, null));
|
||||
if (!item.Removed && Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new InfectEventData(item, InfectEventData.InfectState.No, null));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1199,7 +1233,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
StateMachine?.State?.Exit();
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
if (Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1220,8 +1257,11 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new RemoveEventData());
|
||||
#if SERVER
|
||||
if (Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new RemoveEventData());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
public Gap(Rectangle rectangle)
|
||||
: this(rectangle, Submarine.MainSub)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -137,7 +137,7 @@ namespace Barotrauma
|
||||
{ }
|
||||
|
||||
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base(MapEntityPrefab.FindByIdentifier("gap".ToIdentifier()), submarine, id)
|
||||
: base(CoreEntityPrefab.GapPrefab, submarine, id)
|
||||
{
|
||||
this.rect = rect;
|
||||
flowForce = Vector2.Zero;
|
||||
@@ -149,11 +149,12 @@ namespace Barotrauma
|
||||
InsertToList();
|
||||
|
||||
float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2;
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize);
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionCharacter,
|
||||
findNewContacts: false);
|
||||
outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})";
|
||||
outsideCollisionBlocker.BodyType = BodyType.Static;
|
||||
outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall;
|
||||
outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter;
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
#if CLIENT
|
||||
Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
|
||||
@@ -166,7 +167,7 @@ namespace Barotrauma
|
||||
return new Gap(rect, IsHorizontal, Submarine);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -327,14 +328,6 @@ namespace Barotrauma
|
||||
{
|
||||
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
|
||||
}
|
||||
if (FlowTargetHull != null && IsRoomToRoom)
|
||||
{
|
||||
var otherRoom = linkedTo[1] == FlowTargetHull ? linkedTo[0] : linkedTo[1];
|
||||
if ((otherRoom as Hull).Volume < FlowTargetHull.Volume)
|
||||
{
|
||||
lerpedFlowForce = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
openedTimer -= deltaTime;
|
||||
|
||||
|
||||
@@ -410,8 +410,8 @@ namespace Barotrauma
|
||||
|
||||
public BallastFloraBehavior BallastFlora { get; set; }
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (prefab, rectangle, Submarine.MainSub)
|
||||
public Hull(Rectangle rectangle)
|
||||
: this (rectangle, Submarine.MainSub)
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
@@ -421,8 +421,8 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (prefab, submarine, id)
|
||||
public Hull(Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (CoreEntityPrefab.HullPrefab, submarine, id)
|
||||
{
|
||||
rect = rectangle;
|
||||
|
||||
@@ -500,7 +500,7 @@ namespace Barotrauma
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
var clone = new Hull(MapEntityPrefab.FindByIdentifier("hull".ToIdentifier()), rect, Submarine);
|
||||
var clone = new Hull(rect, Submarine);
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> property in SerializableProperties)
|
||||
{
|
||||
if (!property.Value.Attributes.OfType<Editable>().Any()) { continue; }
|
||||
@@ -590,7 +590,7 @@ namespace Barotrauma
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -1543,7 +1543,7 @@ namespace Barotrauma
|
||||
int.Parse(element.GetAttribute("height").Value));
|
||||
}
|
||||
|
||||
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine, idRemap.GetOffsetId(element))
|
||||
var hull = new Hull(rect, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
WaterVolume = element.GetAttributeFloat("pressure", 0.0f)
|
||||
};
|
||||
|
||||
@@ -10,12 +10,11 @@ using System.Net;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#warning TODO: MapEntityPrefab should be constrained further to not include item assemblies, as assemblies are effectively not entities at all
|
||||
partial class ItemAssemblyPrefab : MapEntityPrefab
|
||||
{
|
||||
public static readonly PrefabCollection<ItemAssemblyPrefab> Prefabs = new PrefabCollection<ItemAssemblyPrefab>();
|
||||
|
||||
public static readonly string VanillaSaveFolder = Path.Combine("Content", "Items", "Assemblies");
|
||||
|
||||
private readonly XElement configElement;
|
||||
|
||||
public readonly ImmutableArray<(Identifier Identifier, Rectangle Rect)> DisplayEntities;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -14,6 +11,8 @@ namespace Barotrauma
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly bool IsEndBiome;
|
||||
public readonly float MinDifficulty;
|
||||
public readonly float MaxDifficulty;
|
||||
|
||||
public readonly ImmutableHashSet<int> AllowedZones;
|
||||
|
||||
@@ -30,8 +29,9 @@ namespace Barotrauma
|
||||
element.GetAttributeString("description", ""));
|
||||
|
||||
IsEndBiome = element.GetAttributeBool("endbiome", false);
|
||||
|
||||
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
|
||||
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
|
||||
MaxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
|
||||
}
|
||||
|
||||
public static Identifier ParseIdentifier(ContentXElement element)
|
||||
|
||||
@@ -96,24 +96,31 @@ namespace Barotrauma
|
||||
public readonly Sprite WallSprite;
|
||||
public readonly Sprite WallEdgeSprite;
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, bool abyss, Rand.RandSync rand)
|
||||
public static CaveGenerationParams GetRandom(Level level, bool abyss, Rand.RandSync rand)
|
||||
{
|
||||
var caveParams = CaveParams.OrderBy(p => p.UintIdentifier).ToList();
|
||||
if (caveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
|
||||
if (caveParams.All(p => p.GetCommonness(level.LevelData, abyss) <= 0.0f))
|
||||
{
|
||||
return caveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(caveParams.ToList(), caveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
|
||||
return ToolBox.SelectWeightedRandom(caveParams.ToList(), caveParams.Select(p => p.GetCommonness(level.LevelData, abyss)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams, bool abyss)
|
||||
public float GetCommonness(LevelData levelData, bool abyss)
|
||||
{
|
||||
if (generationParams != null &&
|
||||
generationParams.Identifier != Identifier.Empty &&
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss".ToIdentifier() : generationParams.Identifier, out float commonness))
|
||||
if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss".ToIdentifier() : levelData.GenerationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
if (levelData?.Biome != null)
|
||||
{
|
||||
if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
|
||||
{
|
||||
return biomeCommonness;
|
||||
}
|
||||
}
|
||||
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,9 +157,6 @@ namespace Barotrauma
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
if (targetCells.Count == 0) { return pathCells; }
|
||||
@@ -213,10 +210,6 @@ namespace Barotrauma
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
|
||||
|
||||
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
@@ -351,7 +344,7 @@ namespace Barotrauma
|
||||
BodyType = BodyType.Static,
|
||||
CollisionCategories = Physics.CollisionLevel
|
||||
};
|
||||
GameMain.World.Add(cellBody);
|
||||
GameMain.World.Add(cellBody, findNewContacts: false);
|
||||
|
||||
for (int n = cells.Count - 1; n >= 0; n-- )
|
||||
{
|
||||
@@ -429,7 +422,9 @@ namespace Barotrauma
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f);
|
||||
Fixture fixture = new Fixture(polygon)
|
||||
Fixture fixture = new Fixture(polygon,
|
||||
Physics.CollisionLevel,
|
||||
Physics.CollisionAll)
|
||||
{
|
||||
UserData = cell
|
||||
};
|
||||
@@ -446,8 +441,6 @@ namespace Barotrauma
|
||||
}
|
||||
cell.Body = cellBody;
|
||||
}
|
||||
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
cellBody.ResetMassData();
|
||||
|
||||
return cellBody;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma
|
||||
Cave = 0x4,
|
||||
Ruin = 0x8,
|
||||
Wreck = 0x10,
|
||||
BeaconStation = 0x20, // Not used anywhere
|
||||
BeaconStation = 0x20,
|
||||
Abyss = 0x40,
|
||||
AbyssCave = 0x80
|
||||
}
|
||||
@@ -299,11 +299,50 @@ namespace Barotrauma
|
||||
/// Random integers generated during the level generation. If these values differ between clients/server,
|
||||
/// it means the levels aren't identical for some reason and there will most likely be major ID mismatches.
|
||||
/// </summary>
|
||||
public List<int> EqualityCheckValues
|
||||
public enum LevelGenStage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<int>();
|
||||
LevelGenParams,
|
||||
Size,
|
||||
GenStart,
|
||||
TunnelGen,
|
||||
AbyssGen,
|
||||
CaveGen,
|
||||
VoronoiGen,
|
||||
VoronoiGen2,
|
||||
VoronoiGen3,
|
||||
Ruins,
|
||||
FloatingIce,
|
||||
LevelBodies,
|
||||
IceSpires,
|
||||
TopAndBottom,
|
||||
PlaceLevelObjects,
|
||||
GenerateItems,
|
||||
Finish
|
||||
}
|
||||
|
||||
private readonly Dictionary<LevelGenStage, int> equalityCheckValues = Enum.GetValues(typeof(LevelGenStage))
|
||||
.Cast<LevelGenStage>()
|
||||
.Select(k => (k, 0))
|
||||
.ToDictionary();
|
||||
public IReadOnlyDictionary<LevelGenStage, int> EqualityCheckValues => equalityCheckValues;
|
||||
|
||||
private void GenerateEqualityCheckValue(LevelGenStage stage)
|
||||
{
|
||||
equalityCheckValues[stage] = Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
private void SetEqualityCheckValue(LevelGenStage stage, int value)
|
||||
{
|
||||
equalityCheckValues[stage] = value;
|
||||
}
|
||||
|
||||
private void ClearEqualityCheckValues()
|
||||
{
|
||||
foreach (LevelGenStage stage in Enum.GetValues(typeof(LevelGenStage)))
|
||||
{
|
||||
equalityCheckValues[stage] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Entity> EntitiesBeforeGenerate { get; private set; } = new List<Entity>();
|
||||
public int EntityCountBeforeGenerate { get; private set; }
|
||||
@@ -356,6 +395,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost;
|
||||
|
||||
/// <summary>
|
||||
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1)
|
||||
/// </summary>
|
||||
public static bool IsLoadedFriendlyOutpost =>
|
||||
loaded?.Type == LevelData.LevelType.Outpost &&
|
||||
(loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.FriendlyNPC || loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.Team1);
|
||||
|
||||
public LevelGenerationParams GenerationParams
|
||||
{
|
||||
get { return LevelData.GenerationParams; }
|
||||
@@ -382,7 +428,7 @@ namespace Barotrauma
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
}
|
||||
|
||||
public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
|
||||
public static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
|
||||
{
|
||||
Debug.Assert(levelData.Biome != null);
|
||||
if (levelData.Biome == null) { throw new ArgumentException("Biome was null"); }
|
||||
@@ -394,27 +440,35 @@ namespace Barotrauma
|
||||
preSelectedStartOutpost = startOutpost,
|
||||
preSelectedEndOutpost = endOutpost
|
||||
};
|
||||
level.Generate(mirror);
|
||||
level.Generate(mirror, startLocation, endLocation);
|
||||
return level;
|
||||
}
|
||||
|
||||
private void Generate(bool mirror)
|
||||
private void Generate(bool mirror, Location startLocation, Location endLocation)
|
||||
{
|
||||
Loaded?.Remove();
|
||||
Loaded = this;
|
||||
Generating = true;
|
||||
#if CLIENT
|
||||
Debug.Assert(GenerationParams.Identifier != "coldcavernstutorial" || GameMain.GameSession?.GameMode == null || GameMain.GameSession.GameMode is TutorialMode);
|
||||
#endif
|
||||
Debug.Assert(GenerationParams.AnyBiomeAllowed || GenerationParams.AllowedBiomeIdentifiers.Contains(LevelData.Biome.Identifier));
|
||||
DebugConsole.NewMessage("Level identifier: " + GenerationParams.Identifier);
|
||||
|
||||
EqualityCheckValues.Clear();
|
||||
ClearEqualityCheckValues();
|
||||
EntitiesBeforeGenerate = GetEntities().ToList();
|
||||
EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count();
|
||||
|
||||
if (LevelData.ForceOutpostGenerationParams == null)
|
||||
{
|
||||
StartLocation = GameMain.GameSession?.StartLocation;
|
||||
EndLocation = GameMain.GameSession?.EndLocation;
|
||||
StartLocation = startLocation;
|
||||
EndLocation = endLocation;
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
SetEqualityCheckValue(LevelGenStage.LevelGenParams, unchecked((int)GenerationParams.UintIdentifier));
|
||||
SetEqualityCheckValue(LevelGenStage.Size, borders.Width ^ borders.Height << 16);
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen);
|
||||
|
||||
LevelObjectManager = new LevelObjectManager();
|
||||
|
||||
@@ -462,7 +516,7 @@ namespace Barotrauma
|
||||
Rectangle pathBorders = borders;
|
||||
pathBorders.Inflate(
|
||||
-Math.Min(Math.Min(minMainPathWidth * 2, MaxSubmarineWidth), borders.Width / 5),
|
||||
-Math.Min(minMainPathWidth, borders.Height / 5));
|
||||
-Math.Min(minMainPathWidth * 2, borders.Height / 5));
|
||||
|
||||
if (pathBorders.Width <= 0) { throw new InvalidOperationException($"The width of the level's path area is invalid ({pathBorders.Width})"); }
|
||||
if (pathBorders.Height <= 0) { throw new InvalidOperationException($"The height of the level's path area is invalid ({pathBorders.Height})"); }
|
||||
@@ -477,7 +531,7 @@ namespace Barotrauma
|
||||
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y));
|
||||
endExitPosition = new Point(endPosition.X, borders.Bottom);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate the initial nodes for the main path and smaller tunnels
|
||||
@@ -552,10 +606,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
int sideTunnelCount = Rand.Range(GenerationParams.SideTunnelCount.X, GenerationParams.SideTunnelCount.Y + 1, Rand.RandSync.ServerAndClient);
|
||||
|
||||
for (int j = 0; j < sideTunnelCount; j++)
|
||||
{
|
||||
if (mainPath.Nodes.Count < 4) { break; }
|
||||
var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole && t != abyssTunnel);
|
||||
|
||||
Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.ServerAndClient)];
|
||||
if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; }
|
||||
|
||||
@@ -570,10 +626,16 @@ namespace Barotrauma
|
||||
|
||||
CalculateTunnelDistanceField(null);
|
||||
GenerateSeaFloorPositions();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.AbyssGen);
|
||||
|
||||
GenerateAbyssArea();
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.CaveGen);
|
||||
|
||||
GenerateCaves(mainPath);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate voronoi sites
|
||||
@@ -678,7 +740,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen2);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// construct the voronoi graph and cells
|
||||
@@ -796,7 +858,7 @@ namespace Barotrauma
|
||||
startPosition.X = (int)pathCells[0].Site.Coord.X;
|
||||
startExitPosition.X = startPosition.X;
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen3);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// remove unnecessary cells and create some holes at the bottom of the level
|
||||
@@ -1025,7 +1087,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.Ruins);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create some ruins
|
||||
@@ -1038,7 +1100,7 @@ namespace Barotrauma
|
||||
GenerateRuin(ruinPositions[i], mirror);
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.FloatingIce);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create floating ice chunks
|
||||
@@ -1070,7 +1132,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.LevelBodies);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// generate the bodies and rendered triangles of the cells
|
||||
@@ -1175,7 +1237,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.IceSpires);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create ice spires
|
||||
@@ -1210,7 +1272,7 @@ namespace Barotrauma
|
||||
|
||||
CreateOutposts();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.TopAndBottom);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// top barrier & sea floor
|
||||
@@ -1252,15 +1314,15 @@ namespace Barotrauma
|
||||
CreateWrecks();
|
||||
CreateBeaconStation();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.PlaceLevelObjects);
|
||||
|
||||
LevelObjectManager.PlaceObjects(this, GenerationParams.LevelObjectAmount);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenerateItems);
|
||||
|
||||
GenerateItems();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.Finish);
|
||||
|
||||
#if CLIENT
|
||||
backgroundCreatureManager.SpawnCreatures(this, GenerationParams.BackgroundCreatureAmount);
|
||||
@@ -1658,10 +1720,11 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (abyssHeight > 30000)
|
||||
{
|
||||
//if the bottom of the abyss area is below crush depth, try to move it up to keep (most) of the abyss content above crush depth
|
||||
if (abyssEndY + CrushDepth < 0)
|
||||
//but only if start of the abyss is above crush depth (no point in doing this if all of it is below crush depth)
|
||||
if (abyssEndY + CrushDepth < 0 && abyssStartY > -CrushDepth)
|
||||
{
|
||||
abyssEndY += Math.Min(-(abyssEndY + (int)CrushDepth), abyssHeight / 2);
|
||||
}
|
||||
@@ -1770,7 +1833,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var caveParams = CaveGenerationParams.GetRandom(GenerationParams, abyss: true, rand: Rand.RandSync.ServerAndClient);
|
||||
var caveParams = CaveGenerationParams.GetRandom(this, abyss: true, rand: Rand.RandSync.ServerAndClient);
|
||||
|
||||
float caveScaleRelativeToIsland = 0.7f;
|
||||
GenerateCave(
|
||||
@@ -1839,7 +1902,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < GenerationParams.CaveCount; i++)
|
||||
{
|
||||
var caveParams = CaveGenerationParams.GetRandom(GenerationParams, abyss: false, rand: Rand.RandSync.ServerAndClient);
|
||||
var caveParams = CaveGenerationParams.GetRandom(this, abyss: false, rand: Rand.RandSync.ServerAndClient);
|
||||
Point caveSize = new Point(
|
||||
Rand.Range(caveParams.MinWidth, caveParams.MaxWidth, Rand.RandSync.ServerAndClient),
|
||||
Rand.Range(caveParams.MinHeight, caveParams.MaxHeight, Rand.RandSync.ServerAndClient));
|
||||
@@ -2429,6 +2492,7 @@ namespace Barotrauma
|
||||
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier))
|
||||
{
|
||||
if (itemPrefab.LevelCommonness.TryGetValue(levelName, out float commonness) ||
|
||||
itemPrefab.LevelCommonness.TryGetValue(LevelData.Biome.Identifier, out commonness) ||
|
||||
itemPrefab.LevelCommonness.TryGetValue(Identifier.Empty, out commonness))
|
||||
{
|
||||
if (commonness <= 0.0f) { continue; }
|
||||
@@ -2606,7 +2670,7 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Level resources spawned: " + itemCount + "\n" +
|
||||
" Spawn points containing resources: " + PathPoints.Where(p => p.ClusterLocations.Any()).Count() + "/" + PathPoints.Count + "\n" +
|
||||
" Total value: "+ PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))+" mk");
|
||||
" Total value: " + PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))) + " mk");
|
||||
if (AbyssResources.Count > 0)
|
||||
{
|
||||
|
||||
@@ -3187,7 +3251,8 @@ namespace Barotrauma
|
||||
if (index < 0 || index >= bottomPositions.Count - 1) { return new Vector2(xPosition, BottomPos); }
|
||||
|
||||
float t = (xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X);
|
||||
Debug.Assert(t <= 1.0f);
|
||||
//t can go slightly outside the 0-1 due to rounding, safe to ignore
|
||||
Debug.Assert(t <= 1.001f && t >= -0.001f);
|
||||
t = MathHelper.Clamp(t, 0.0f, 1.0f);
|
||||
|
||||
float yPos = MathHelper.Lerp(bottomPositions[index].Y, bottomPositions[index + 1].Y, t);
|
||||
@@ -3469,6 +3534,8 @@ namespace Barotrauma
|
||||
}
|
||||
else if (type == SubmarineType.BeaconStation)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.BeaconStation, submarine: sub));
|
||||
|
||||
sub.ShowSonarMarker = false;
|
||||
sub.DockedTo.ForEach(s => s.ShowSonarMarker = false);
|
||||
sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
@@ -3688,6 +3755,7 @@ namespace Barotrauma
|
||||
if (wreckFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
|
||||
Wrecks = new List<Submarine>();
|
||||
return;
|
||||
}
|
||||
wreckFiles.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
@@ -3881,7 +3949,7 @@ namespace Barotrauma
|
||||
//the submarine port has to be at the top of the sub
|
||||
if (port.Item.WorldPosition.Y < Submarine.MainSub.WorldPosition.Y) { continue; }
|
||||
float dist = Math.Abs(port.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X);
|
||||
if (dist < closestDistance)
|
||||
if (dist < closestDistance || subPort.MainDockingPort)
|
||||
{
|
||||
subPort = port;
|
||||
closestDistance = dist;
|
||||
@@ -3964,11 +4032,35 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("No BeaconStation files found in the selected content packages!");
|
||||
return;
|
||||
}
|
||||
|
||||
var beaconInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsBeacon);
|
||||
for (int i = beaconStationFiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var beaconStationFile = beaconStationFiles[i];
|
||||
var matchingInfo = beaconInfos.SingleOrDefault(info => info.FilePath == beaconStationFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.BeaconStationInfo is BeaconStationInfo beaconInfo)
|
||||
{
|
||||
if (LevelData.Difficulty < beaconInfo.MinLevelDifficulty || LevelData.Difficulty > beaconInfo.MaxLevelDifficulty)
|
||||
{
|
||||
beaconStationFiles.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beaconStationFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"No BeaconStation files found for the level difficulty {LevelData.Difficulty}!");
|
||||
return;
|
||||
}
|
||||
var contentFile = beaconStationFiles.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
string beaconStationName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
|
||||
|
||||
BeaconStation = SpawnSubOnPath(beaconStationName, contentFile, SubmarineType.BeaconStation);
|
||||
if (BeaconStation == null) { return; }
|
||||
if (BeaconStation == null)
|
||||
{
|
||||
LevelData.HasBeaconStation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Item sonarItem = Item.ItemList.Find(it => it.Submarine == BeaconStation && it.GetComponent<Sonar>() != null);
|
||||
if (sonarItem == null)
|
||||
@@ -3984,6 +4076,11 @@ namespace Barotrauma
|
||||
if (!LevelData.HasBeaconStation) { return; }
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
|
||||
|
||||
if (BeaconStation == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to prepare beacon station (no beacon station in the level).");
|
||||
}
|
||||
|
||||
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||
|
||||
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
|
||||
@@ -4019,24 +4116,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (!(GameMain.NetworkMember?.IsClient ?? false))
|
||||
{
|
||||
//empty the reactor
|
||||
if (reactorContainer != null)
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
foreach (Item item in reactorContainer.Inventory.AllItems)
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
Spawner.AddItemToRemoveQueue(item);
|
||||
}
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
}
|
||||
|
||||
//remove wires
|
||||
float removeWireMinDifficulty = 20.0f;
|
||||
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (removeWireProbability > 0.0f)
|
||||
if (removeWireProbability > 0.0f && allowDisconnectedWires)
|
||||
{
|
||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire.Locked) { continue; }
|
||||
if (wire.Connections[0] != null && (wire.Connections[0].Item.NonInteractable || wire.Connections[0].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
@@ -4056,8 +4151,8 @@ namespace Barotrauma
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
#if SERVER
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -4065,23 +4160,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
if (allowDamagedWalls)
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
{
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4112,12 +4209,12 @@ namespace Barotrauma
|
||||
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
|
||||
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
|
||||
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
|
||||
pathPoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
|
||||
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
|
||||
if (!corpsePoints.Any() && !pathPoints.Any()) { continue; }
|
||||
|
||||
pathPoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
// Sort by job so that we first spawn those with a predefined job (might have special id cards)
|
||||
corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList();
|
||||
var usedJobs = new HashSet<JobPrefab>();
|
||||
int spawnCounter = 0;
|
||||
for (int j = 0; j < corpseCount; j++)
|
||||
{
|
||||
@@ -4126,18 +4223,18 @@ namespace Barotrauma
|
||||
CorpsePrefab selectedPrefab;
|
||||
if (job == null)
|
||||
{
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier));
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs, p => p.Job == "any" || p.Job == job.Identifier);
|
||||
if (selectedPrefab == null)
|
||||
{
|
||||
corpsePoints.Remove(sp);
|
||||
pathPoints.Remove(sp);
|
||||
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
||||
// Deduce the job from the selected prefab
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
}
|
||||
}
|
||||
if (selectedPrefab == null) { continue; }
|
||||
@@ -4156,28 +4253,65 @@ namespace Barotrauma
|
||||
pathPoints.Remove(sp);
|
||||
}
|
||||
|
||||
job ??= selectedPrefab.GetJobPrefab();
|
||||
job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p));
|
||||
if (job == null) { continue; }
|
||||
|
||||
if (job.Identifier == "captain" || job.Identifier == "engineer" || job.Identifier == "medicaldoctor" || job.Identifier == "securityofficer")
|
||||
{
|
||||
// Only spawn one of these jobs per wreck
|
||||
usedJobs.Add(job);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, randSync: Rand.RandSync.ServerAndClient);
|
||||
var corpse = Character.Create(CharacterPrefab.HumanSpeciesName, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
corpse.AnimController.FindHull(worldPos, setSubmarine: true);
|
||||
corpse.TeamID = CharacterTeamType.None;
|
||||
corpse.EnableDespawn = false;
|
||||
selectedPrefab.GiveItems(corpse, wreck);
|
||||
corpse.CharacterHealth.ApplyAffliction(corpse.AnimController.MainLimb, AfflictionPrefab.OxygenLow.Instantiate(200));
|
||||
bool applyBurns = Rand.Value() < 0.1f;
|
||||
bool applyDamage = Rand.Value() < 0.3f;
|
||||
foreach (var limb in corpse.AnimController.Limbs)
|
||||
{
|
||||
if (applyDamage && (limb.type == LimbType.Head || Rand.Value() < 0.5f))
|
||||
{
|
||||
var prefab = AfflictionPrefab.BiteWounds;
|
||||
float max = prefab.MaxStrength / prefab.DamageOverlayAlpha;
|
||||
corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max)));
|
||||
}
|
||||
if (applyBurns)
|
||||
{
|
||||
var prefab = AfflictionPrefab.Burn;
|
||||
float max = prefab.MaxStrength / prefab.BurnOverlayAlpha;
|
||||
corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max)));
|
||||
}
|
||||
|
||||
static float GetStrength(Limb limb, float max)
|
||||
{
|
||||
float strength = Rand.Range(0, max);
|
||||
if (limb.type != LimbType.Head)
|
||||
{
|
||||
strength = Math.Min(strength, Rand.Range(0, max));
|
||||
}
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
|
||||
corpse.GiveIdCardTags(sp);
|
||||
#if SERVER
|
||||
if (selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0)
|
||||
|
||||
bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
|
||||
if (isServerOrSingleplayer && selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0)
|
||||
{
|
||||
corpse.Wallet.Give(Rand.Range(selectedPrefab.MinMoney, selectedPrefab.MaxMoney, Rand.RandSync.Unsynced));
|
||||
}
|
||||
#endif
|
||||
|
||||
spawnCounter++;
|
||||
|
||||
static CorpsePrefab GetCorpsePrefab(Func<CorpsePrefab, bool> predicate)
|
||||
static CorpsePrefab GetCorpsePrefab(HashSet<JobPrefab> usedJobs, Func<CorpsePrefab, bool> predicate = null)
|
||||
{
|
||||
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate);
|
||||
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(p =>
|
||||
usedJobs.None(j => j.Identifier == p.Job.ToIdentifier()) &&
|
||||
p.SpawnPosition == PositionType.Wreck &&
|
||||
(predicate == null || predicate(p)));
|
||||
|
||||
return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
@@ -4270,7 +4404,7 @@ namespace Barotrauma
|
||||
blockedRects?.Clear();
|
||||
|
||||
EntitiesBeforeGenerate?.Clear();
|
||||
EqualityCheckValues?.Clear();
|
||||
ClearEqualityCheckValues();
|
||||
|
||||
if (Ruins != null)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Seed;
|
||||
|
||||
public float Difficulty;
|
||||
public readonly float Difficulty;
|
||||
|
||||
public readonly Biome Biome;
|
||||
|
||||
@@ -90,10 +90,10 @@ namespace Barotrauma
|
||||
(int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
|
||||
}
|
||||
|
||||
public LevelData(XElement element)
|
||||
public LevelData(XElement element, float? forceDifficulty = null)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "");
|
||||
Difficulty = element.GetAttributeFloat("difficulty", 0.0f);
|
||||
Difficulty = forceDifficulty ?? element.GetAttributeFloat("difficulty", 0.0f);
|
||||
Size = element.GetAttributePoint("size", new Point(1000));
|
||||
Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
|
||||
|
||||
@@ -141,8 +141,8 @@ namespace Barotrauma
|
||||
Seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName;
|
||||
Biome = locationConnection.Biome;
|
||||
Type = LevelType.LocationConnection;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome.Identifier);
|
||||
Difficulty = locationConnection.Difficulty;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Difficulty, Biome.Identifier);
|
||||
|
||||
float sizeFactor = MathUtils.InverseLerp(
|
||||
MapGenerationParams.Instance.SmallLevelConnectionLength,
|
||||
@@ -171,13 +171,13 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Instantiates level data using the properties of the location
|
||||
/// </summary>
|
||||
public LevelData(Location location)
|
||||
public LevelData(Location location, float difficulty)
|
||||
{
|
||||
Seed = location.BaseName;
|
||||
Biome = location.Biome;
|
||||
Type = LevelType.Outpost;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome.Identifier);
|
||||
Difficulty = 0.0f;
|
||||
Difficulty = difficulty;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Difficulty, Biome.Identifier);
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble());
|
||||
@@ -200,14 +200,16 @@ namespace Barotrauma
|
||||
(requireOutpost ? LevelType.Outpost : LevelType.LocationConnection) :
|
||||
generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
float selectedDifficulty = difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type, selectedDifficulty); }
|
||||
var biome =
|
||||
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.ServerAndClient),
|
||||
selectedDifficulty,
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient),
|
||||
generationParams,
|
||||
biome);
|
||||
|
||||
@@ -4,7 +4,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -68,6 +67,27 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, "If there are multiple level generation parameters available for a level in a given biome, their commonness determines how likely it is for one to get selected."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be above or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float MinLevelDifficulty
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be below or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float MaxLevelDifficulty
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("27,30,36", IsPropertySaveable.Yes), Editable]
|
||||
public Color AmbientLightColor
|
||||
{
|
||||
@@ -394,7 +414,7 @@ namespace Barotrauma
|
||||
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)]
|
||||
[Serialize(40, 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;
|
||||
@@ -536,7 +556,26 @@ namespace Barotrauma
|
||||
public Sprite WallSpriteDestroyed { get; private set; }
|
||||
public Sprite WaterParticles { get; private set; }
|
||||
|
||||
public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Identifier biome = default)
|
||||
#warning TODO: this should be in the unit test project (#3164)
|
||||
public static void CheckValidity()
|
||||
{
|
||||
foreach (Biome biome in Biome.Prefabs)
|
||||
{
|
||||
for (float i = 0.0f; i <= 100.0f; i += 0.5f)
|
||||
{
|
||||
if (GetRandom("test", LevelData.LevelType.LocationConnection, i, biome.Identifier) == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: LocationConnection, difficulty: {i}, biome: {biome.Identifier})");
|
||||
}
|
||||
if (GetRandom("test", LevelData.LevelType.Outpost, i, biome.Identifier) == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: Outpost, difficulty: {i}, biome: {biome.Identifier})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biome = default)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
@@ -545,7 +584,9 @@ namespace Barotrauma
|
||||
throw new InvalidOperationException("Level generation presets not found - using default presets");
|
||||
}
|
||||
|
||||
var matchingLevelParams = LevelParams.Where(lp =>
|
||||
var levelParamsOrdered = LevelParams.OrderBy(l => l.UintIdentifier);
|
||||
|
||||
var matchingLevelParams = levelParamsOrdered.Where(lp =>
|
||||
lp.Type == type &&
|
||||
(lp.AnyBiomeAllowed || lp.AllowedBiomeIdentifiers.Any()) &&
|
||||
!lp.AllowedBiomeIdentifiers.Contains("None".ToIdentifier()));
|
||||
@@ -559,16 +600,25 @@ namespace Barotrauma
|
||||
if (!biome.IsEmpty)
|
||||
{
|
||||
//try to find params that at least have a suitable type
|
||||
matchingLevelParams = LevelParams.Where(lp => lp.Type == type);
|
||||
matchingLevelParams = levelParamsOrdered.Where(lp => lp.Type == type);
|
||||
if (!matchingLevelParams.Any())
|
||||
{
|
||||
//still not found, give up and choose some params randomly
|
||||
matchingLevelParams = LevelParams;
|
||||
matchingLevelParams = levelParamsOrdered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchingLevelParams.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
if (!matchingLevelParams.Any(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty))
|
||||
{
|
||||
DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{biome.IfEmpty("null".ToIdentifier())}\", type: \"{type}\", difficulty: {difficulty})");
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingLevelParams = matchingLevelParams.Where(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty);
|
||||
}
|
||||
|
||||
return ToolBox.SelectWeightedRandom(matchingLevelParams, p => p.Commonness, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
public LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
|
||||
+38
-26
@@ -102,32 +102,44 @@ namespace Barotrauma
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
if (!structure.HasBody || structure.HiddenInGame) { continue; }
|
||||
|
||||
LevelObjectPrefab.SpawnPosType spawnPosType = LevelObjectPrefab.SpawnPosType.None;
|
||||
if (level.Ruins.Any(r => r.Submarine == structure.Submarine))
|
||||
{
|
||||
if (structure.IsHorizontal)
|
||||
{
|
||||
bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null;
|
||||
bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null;
|
||||
if (topHull && bottomHull ) { continue; }
|
||||
spawnPosType = LevelObjectPrefab.SpawnPosType.RuinWall;
|
||||
}
|
||||
else if (structure.Submarine?.Info?.Type == SubmarineType.Outpost)
|
||||
{
|
||||
spawnPosType = LevelObjectPrefab.SpawnPosType.OutpostWall;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)),
|
||||
bottomHull ? Vector2.UnitY : -Vector2.UnitY,
|
||||
LevelObjectPrefab.SpawnPosType.RuinWall,
|
||||
bottomHull ? Alignment.Bottom : Alignment.Top));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null;
|
||||
bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null;
|
||||
if (rightHull && leftHull) { continue; }
|
||||
if (structure.IsHorizontal)
|
||||
{
|
||||
bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null;
|
||||
bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null;
|
||||
if (topHull && bottomHull) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)),
|
||||
leftHull ? Vector2.UnitX : -Vector2.UnitX,
|
||||
LevelObjectPrefab.SpawnPosType.RuinWall,
|
||||
leftHull ? Alignment.Left : Alignment.Right));
|
||||
}
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)),
|
||||
bottomHull ? Vector2.UnitY : -Vector2.UnitY,
|
||||
spawnPosType,
|
||||
bottomHull ? Alignment.Bottom : Alignment.Top));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null;
|
||||
bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null;
|
||||
if (rightHull && leftHull) { continue; }
|
||||
|
||||
availableSpawnPositions.Add(new SpawnPosition(
|
||||
new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)),
|
||||
leftHull ? Vector2.UnitX : -Vector2.UnitX,
|
||||
spawnPosType,
|
||||
leftHull ? Alignment.Left : Alignment.Right));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +170,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
//get a random prefab and find a place to spawn it
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams, availablePrefabs);
|
||||
LevelObjectPrefab prefab = GetRandomPrefab(level, availablePrefabs);
|
||||
if (prefab == null) { continue; }
|
||||
if (!suitableSpawnPositions.ContainsKey(prefab))
|
||||
{
|
||||
@@ -583,12 +595,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(LevelGenerationParams generationParams, IList<LevelObjectPrefab> availablePrefabs)
|
||||
private LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
|
||||
{
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(generationParams)) <= 0.0f) { return null; }
|
||||
if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) { return null; }
|
||||
return ToolBox.SelectWeightedRandom(
|
||||
availablePrefabs,
|
||||
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.ServerAndClient);
|
||||
availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
|
||||
|
||||
+13
-6
@@ -44,6 +44,7 @@ namespace Barotrauma
|
||||
MainPath = 64,
|
||||
LevelStart = 128,
|
||||
LevelEnd = 256,
|
||||
OutpostWall = 512,
|
||||
Wall = MainPathWall | SidePathWall | CaveWall,
|
||||
}
|
||||
|
||||
@@ -425,15 +426,21 @@ namespace Barotrauma
|
||||
return requireCaveSpecificOverride ? 0.0f : Commonness;
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
{
|
||||
if (generationParams != null &&
|
||||
generationParams.Identifier != Identifier.Empty &&
|
||||
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
|
||||
(!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
|
||||
public float GetCommonness(LevelData levelData)
|
||||
{
|
||||
if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
|
||||
OverrideCommonness.TryGetValue(levelData.GenerationParams.Identifier, out float commonness) ||
|
||||
(!levelData.GenerationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(levelData.GenerationParams.OldIdentifier, out commonness)))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
if (levelData?.Biome != null)
|
||||
{
|
||||
if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
|
||||
{
|
||||
return biomeCommonness;
|
||||
}
|
||||
}
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
@@ -152,12 +152,6 @@ namespace Barotrauma
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
#if CLIENT
|
||||
VertexBuffer?.Dispose();
|
||||
VertexBuffer = null;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -82,6 +81,8 @@ namespace Barotrauma
|
||||
|
||||
private XElement saveElement;
|
||||
|
||||
private Vector2? positionRelativeToMainSub;
|
||||
|
||||
public override bool Linkable
|
||||
{
|
||||
get
|
||||
@@ -215,16 +216,19 @@ namespace Barotrauma
|
||||
saveElement = element
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(levelSeed) && levelData != null &&
|
||||
levelData.Seed != levelSeed && !linkedSub.purchasedLostShuttles)
|
||||
{
|
||||
linkedSub.loadSub = false;
|
||||
}
|
||||
else
|
||||
bool levelMatches = string.IsNullOrWhiteSpace(levelSeed) || levelData == null || levelData.Seed == levelSeed;
|
||||
|
||||
//don't load a sub that was left in this level if we have a submarine switch pending
|
||||
//to make sure it gets ignored during the submarine switch and item transfer (reloading and saving it during the switch makes it not considered "left behind")
|
||||
if ((levelMatches || linkedSub.purchasedLostShuttles) && GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
|
||||
{
|
||||
linkedSub.loadSub = true;
|
||||
linkedSub.rect.Location = MathUtils.ToPoint(pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
linkedSub.loadSub = false;
|
||||
}
|
||||
}
|
||||
|
||||
#warning TODO: revise
|
||||
@@ -253,6 +257,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPositionRelativeToMainSub()
|
||||
{
|
||||
if (positionRelativeToMainSub.HasValue)
|
||||
{
|
||||
Sub.SetPosition(Submarine.WorldPosition + positionRelativeToMainSub.Value);
|
||||
}
|
||||
positionRelativeToMainSub = null;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (!loadSub) { return; }
|
||||
@@ -279,14 +292,14 @@ namespace Barotrauma
|
||||
if (worldPos != Vector2.Zero)
|
||||
{
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.MirrorLevel)
|
||||
{
|
||||
{
|
||||
worldPos.X = GameMain.GameSession.LevelData.Size.X - worldPos.X;
|
||||
}
|
||||
sub.SetPosition(worldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
sub.SetPosition(WorldPosition);
|
||||
sub.SetPosition(WorldPosition);
|
||||
}
|
||||
|
||||
DockingPort linkedPort = null;
|
||||
@@ -308,8 +321,29 @@ namespace Barotrauma
|
||||
{
|
||||
linkedPort = (FindEntityByID(originalLinkedToID) as Item)?.GetComponent<DockingPort>();
|
||||
}
|
||||
if (linkedPort == null) { return; }
|
||||
}
|
||||
|
||||
if (linkedPort == null)
|
||||
{
|
||||
if (worldPos == Vector2.Zero)
|
||||
{
|
||||
Vector2 relativePos = saveElement.GetAttributeVector2("posrelativetomainsub", Vector2.Zero);
|
||||
if (relativePos != Vector2.Zero)
|
||||
{
|
||||
positionRelativeToMainSub = relativePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Something went wrong when loading a linked submarine - the save didn't include a world position, a linked port or position relative to the main sub.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sub.Submarine = Submarine;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
originalLinkedPort = linkedPort;
|
||||
|
||||
ushort originalMyId = childRemap.GetOffsetId(originalMyPortID);
|
||||
@@ -432,7 +466,7 @@ namespace Barotrauma
|
||||
if (sub != null)
|
||||
{
|
||||
bool leaveBehind = false;
|
||||
if (!sub.DockedTo.Contains(Submarine.MainSub))
|
||||
if (sub.Submarine != null && !sub.DockedTo.Contains(sub.Submarine))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndExit || Submarine.MainSub.AtStartExit);
|
||||
if (Submarine.MainSub.AtEndExit)
|
||||
@@ -457,8 +491,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (saveElement.Attribute("location") != null) saveElement.Attribute("location").Remove();
|
||||
if (saveElement.Attribute("worldpos") != null) saveElement.Attribute("worldpos").Remove();
|
||||
if (saveElement.Attribute("location") != null) { saveElement.Attribute("location").Remove(); }
|
||||
if (saveElement.Attribute("worldpos") != null) { saveElement.Attribute("worldpos").Remove(); }
|
||||
saveElement.SetAttributeValue("posrelativetomainsub", XMLExtensions.Vector2ToString(sub.WorldPosition - Submarine.WorldPosition));
|
||||
}
|
||||
saveElement.SetAttributeValue("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using StoreBalanceStatus = Barotrauma.LocationType.StoreBalanceStatus;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -92,21 +91,8 @@ namespace Barotrauma
|
||||
|
||||
public class StoreInfo
|
||||
{
|
||||
private int balance;
|
||||
|
||||
public Identifier Identifier { get; }
|
||||
public int Balance
|
||||
{
|
||||
get
|
||||
{
|
||||
return balance;
|
||||
}
|
||||
set
|
||||
{
|
||||
balance = value;
|
||||
ActiveBalanceStatus = Location.GetStoreBalanceStatus(value);
|
||||
}
|
||||
}
|
||||
public int Balance { get; set; }
|
||||
public List<PurchasedItem> Stock { get; } = new List<PurchasedItem>();
|
||||
public List<ItemPrefab> DailySpecials { get; } = new List<ItemPrefab>();
|
||||
public List<ItemPrefab> RequestedGoods { get; } = new List<ItemPrefab>();
|
||||
@@ -114,8 +100,6 @@ namespace Barotrauma
|
||||
/// In percentages. Larger values make buying more expensive and selling less profitable, and vice versa.
|
||||
/// </summary>
|
||||
public int PriceModifier { get; set; }
|
||||
public StoreBalanceStatus ActiveBalanceStatus { get; private set; }
|
||||
public Color BalanceColor => ActiveBalanceStatus.Color;
|
||||
public Location Location { get; }
|
||||
|
||||
private StoreInfo(Location location)
|
||||
@@ -298,14 +282,7 @@ namespace Barotrauma
|
||||
price = Location.DailySpecialPriceModifier * price;
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
if (Location.Reputation.Value > 0.0f)
|
||||
{
|
||||
price = MathHelper.Lerp(1.0f, 1.0f - Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MaxReputation) * price;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = MathHelper.Lerp(1.0f, 1.0f + Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MinReputation) * price;
|
||||
}
|
||||
price *= Location.GetStoreReputationModifier(true);
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -319,22 +296,13 @@ namespace Barotrauma
|
||||
float price = Location.StoreSellPriceModifier * priceInfo.Price;
|
||||
// Adjust by random price modifier
|
||||
price = (100 - PriceModifier) / 100.0f * price;
|
||||
// Adjust by current store balance
|
||||
price = ActiveBalanceStatus.SellPriceModifier * price;
|
||||
// Adjust by requested good status
|
||||
if (considerRequestedGoods && RequestedGoods.Contains(item))
|
||||
{
|
||||
price = Location.RequestGoodPriceModifier * price;
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
if (Location.Reputation.Value > 0.0f)
|
||||
{
|
||||
price = MathHelper.Lerp(1.0f, 1.0f + Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MaxReputation) * price;
|
||||
}
|
||||
else
|
||||
{
|
||||
price = MathHelper.Lerp(1.0f, 1.0f - Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MinReputation) * price;
|
||||
}
|
||||
price *= Location.GetStoreReputationModifier(false);
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -353,7 +321,6 @@ namespace Barotrauma
|
||||
private float RequestGoodPriceModifier => Type.RequestGoodPriceModifier;
|
||||
public int StoreInitialBalance => Type.StoreInitialBalance;
|
||||
private int StorePriceModifierRange => Type.StorePriceModifierRange;
|
||||
private List<StoreBalanceStatus> StoreBalanceStatuses => Type.StoreBalanceStatuses;
|
||||
|
||||
/// <summary>
|
||||
/// How many map progress steps it takes before the discounts should be updated.
|
||||
@@ -518,6 +485,19 @@ namespace Barotrauma
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
Identifier biomeId = element.GetAttributeIdentifier("biome", Identifier.Empty);
|
||||
if (biomeId != Identifier.Empty)
|
||||
{
|
||||
if (Biome.Prefabs.TryGet(biomeId, out Biome biome))
|
||||
{
|
||||
Biome = biome;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading the campaign map: could not find a biome with the identifier \"{biomeId}\".");
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeNotFound)
|
||||
{
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
@@ -806,22 +786,41 @@ namespace Barotrauma
|
||||
|
||||
static float GetConnectionWeight(Location location, LocationConnection c)
|
||||
{
|
||||
float weight = c.Passed ? 1.0f : 5.0f;
|
||||
Location destination = c.OtherLocation(location);
|
||||
if (destination != null)
|
||||
if (destination == null) { return 0; }
|
||||
float minWeight = 0.0001f;
|
||||
float lowWeight = 0.2f;
|
||||
float normalWeight = 1.0f;
|
||||
float maxWeight = 2.0f;
|
||||
float weight = c.Passed ? lowWeight : normalWeight;
|
||||
if (location.Biome.AllowedZones.Contains(1))
|
||||
{
|
||||
if (destination.MapPosition.X > location.MapPosition.X) { weight *= 2.0f; }
|
||||
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
|
||||
if (missionCount > 0)
|
||||
{
|
||||
weight /= missionCount * 2;
|
||||
}
|
||||
if (destination.IsRadiated())
|
||||
// In the first biome, give a stronger preference for locations that are farther to the right)
|
||||
float diff = destination.MapPosition.X - location.MapPosition.X;
|
||||
if (diff < 0)
|
||||
{
|
||||
weight *= 0.001f;
|
||||
weight *= 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
float maxRelevantDiff = 300;
|
||||
weight = MathHelper.Lerp(weight, maxWeight, MathUtils.InverseLerp(0, maxRelevantDiff, diff));
|
||||
}
|
||||
}
|
||||
return weight;
|
||||
else if (destination.MapPosition.X > location.MapPosition.X)
|
||||
{
|
||||
weight *= 2.0f;
|
||||
}
|
||||
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
|
||||
if (missionCount > 0)
|
||||
{
|
||||
weight /= missionCount * 2;
|
||||
}
|
||||
if (destination.IsRadiated())
|
||||
{
|
||||
weight *= 0.001f;
|
||||
}
|
||||
return MathHelper.Clamp(weight, minWeight, maxWeight);
|
||||
}
|
||||
|
||||
return InstantiateMission(prefab, connection);
|
||||
@@ -1224,6 +1223,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float GetStoreReputationModifier(bool buying)
|
||||
{
|
||||
if (buying)
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
@@ -1231,21 +1256,6 @@ namespace Barotrauma
|
||||
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public StoreBalanceStatus GetStoreBalanceStatus(int balance)
|
||||
{
|
||||
StoreBalanceStatus nextStatus = StoreBalanceStatuses[0];
|
||||
for (int i = 1; i < StoreBalanceStatuses.Count; i++)
|
||||
{
|
||||
var status = StoreBalanceStatuses[i];
|
||||
if (status.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance &&
|
||||
((float)balance / StoreInitialBalance) < status.PercentageOfInitialBalance)
|
||||
{
|
||||
nextStatus = status;
|
||||
}
|
||||
}
|
||||
return nextStatus;
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
@@ -1277,6 +1287,7 @@ namespace Barotrauma
|
||||
new XAttribute("originaltype", (Type ?? OriginalType).Identifier),
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
|
||||
@@ -88,27 +88,6 @@ namespace Barotrauma
|
||||
public int DailySpecialsCount { get; } = 1;
|
||||
public int RequestedGoodsCount { get; } = 1;
|
||||
|
||||
public List<StoreBalanceStatus> StoreBalanceStatuses { get; } = new List<StoreBalanceStatus>()
|
||||
{
|
||||
new StoreBalanceStatus(1.0f, 1.0f, Color.White),
|
||||
new StoreBalanceStatus(0.5f, 0.75f, Color.Orange),
|
||||
new StoreBalanceStatus(0.25f, 0.2f, Color.Red)
|
||||
};
|
||||
|
||||
public struct StoreBalanceStatus
|
||||
{
|
||||
public float PercentageOfInitialBalance { get; }
|
||||
public float SellPriceModifier { get; }
|
||||
public Color Color { get; }
|
||||
|
||||
public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color)
|
||||
{
|
||||
PercentageOfInitialBalance = percentage;
|
||||
SellPriceModifier = sellPriceModifier;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"LocationType (" + Identifier + ")";
|
||||
@@ -208,18 +187,6 @@ namespace Barotrauma
|
||||
RequestGoodPriceModifier = subElement.GetAttributeFloat("requestgoodpricemodifier", RequestGoodPriceModifier);
|
||||
StoreInitialBalance = subElement.GetAttributeInt("initialbalance", StoreInitialBalance);
|
||||
StorePriceModifierRange = subElement.GetAttributeInt("pricemodifierrange", StorePriceModifierRange);
|
||||
var balanceStatusElements = subElement.GetChildElements("balancestatus");
|
||||
if (balanceStatusElements.Any())
|
||||
{
|
||||
StoreBalanceStatuses.Clear();
|
||||
foreach (var balanceStatusElement in balanceStatusElements)
|
||||
{
|
||||
float percentage = balanceStatusElement.GetAttributeFloat("percentage", 1.0f);
|
||||
float modifier = balanceStatusElement.GetAttributeFloat("sellpricemodifier", 1.0f);
|
||||
Color color = balanceStatusElement.GetAttributeColor("color", Color.White);
|
||||
StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color));
|
||||
}
|
||||
}
|
||||
DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount);
|
||||
RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount);
|
||||
break;
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved campaign map from XML
|
||||
/// </summary>
|
||||
private Map(CampaignMode campaign, XElement element, CampaignSettings settings) : this(settings)
|
||||
private Map(CampaignMode campaign, XElement element) : this(campaign.Settings)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
Enabled = campaign.Settings.RadiationEnabled
|
||||
};
|
||||
break;
|
||||
}
|
||||
@@ -131,18 +131,27 @@ namespace Barotrauma
|
||||
};
|
||||
Locations[locationIndices.X].Connections.Add(connection);
|
||||
Locations[locationIndices.Y].Connections.Add(connection);
|
||||
connection.LevelData = new LevelData(subElement.Element("Level"));
|
||||
string biomeId = subElement.GetAttributeString("biome", "");
|
||||
connection.Biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeId) ??
|
||||
Biome.Prefabs.FirstOrDefault(b => !b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeId) ??
|
||||
Biome.Prefabs.First();
|
||||
connection.Difficulty = MathHelper.Clamp(connection.Difficulty, connection.Biome.MinDifficulty, connection.Biome.MaxDifficulty);
|
||||
connection.LevelData = new LevelData(subElement.Element("Level"), connection.Difficulty);
|
||||
Connections.Add(connection);
|
||||
connectionElements.Add(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility: location biomes weren't saved (or used for anything) previously,
|
||||
//assign them if they haven't been assigned
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
if (Locations.First().Biome == null)
|
||||
{
|
||||
AssignBiomes(rand);
|
||||
}
|
||||
|
||||
int startLocationindex = element.GetAttributeInt("startlocation", -1);
|
||||
if (startLocationindex > 0 && startLocationindex < Locations.Count)
|
||||
{
|
||||
@@ -199,12 +208,12 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Generate a new campaign map from the seed
|
||||
/// </summary>
|
||||
public Map(CampaignMode campaign, string seed, CampaignSettings settings) : this(settings)
|
||||
public Map(CampaignMode campaign, string seed) : this(campaign.Settings)
|
||||
{
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Generate();
|
||||
Generate(campaign.Settings);
|
||||
|
||||
if (Locations.Count == 0)
|
||||
{
|
||||
@@ -219,10 +228,7 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
SetStartLocation(location);
|
||||
}
|
||||
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
|
||||
if (CurrentLocation == null)
|
||||
@@ -230,17 +236,47 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.HasOutpost) { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
SetStartLocation(location);
|
||||
}
|
||||
}
|
||||
|
||||
void SetStartLocation(Location location)
|
||||
{
|
||||
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.");
|
||||
|
||||
int loops = campaign.CampaignMetadata.GetInt("campaign.endings".ToIdentifier(), 0);
|
||||
if (loops == 0 && (campaign.Settings.Difficulty == GameDifficulty.Easy || campaign.Settings.Difficulty == GameDifficulty.Medium))
|
||||
{
|
||||
if (StartLocation != null)
|
||||
{
|
||||
StartLocation.LevelData = new LevelData(StartLocation, 0);
|
||||
}
|
||||
|
||||
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
|
||||
foreach (var locationConnection in StartLocation.Connections)
|
||||
{
|
||||
if (locationConnection.Difficulty > 0.0f)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
locationConnection.Difficulty = 0.0f;
|
||||
locationConnection.LevelData = new LevelData(locationConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
foreach (var location in Locations)
|
||||
{
|
||||
location.UnlockInitialMissions();
|
||||
}
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
@@ -248,7 +284,7 @@ namespace Barotrauma
|
||||
|
||||
#region Generation
|
||||
|
||||
private void Generate()
|
||||
private void Generate(CampaignSettings settings)
|
||||
{
|
||||
Connections.Clear();
|
||||
Locations.Clear();
|
||||
@@ -266,7 +302,6 @@ namespace Barotrauma
|
||||
|
||||
Voronoi voronoi = new Voronoi(0.5f);
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height);
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
|
||||
Vector2 margin = new Vector2(
|
||||
Math.Min(10, Width * 0.1f),
|
||||
@@ -282,6 +317,7 @@ namespace Barotrauma
|
||||
|
||||
voronoiSites.Clear();
|
||||
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
|
||||
bool possibleStartOutpostCreated = false;
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.Point1 == edge.Point2) { continue; }
|
||||
@@ -316,12 +352,26 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
LocationType forceLocationType = null;
|
||||
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
|
||||
if (!possibleStartOutpostCreated)
|
||||
{
|
||||
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
float threshold = zoneWidth * 0.1f;
|
||||
if (position.X < threshold)
|
||||
{
|
||||
forceLocationType = locationType;
|
||||
break;
|
||||
LocationType.Prefabs.TryGet("outpost", out forceLocationType);
|
||||
possibleStartOutpostCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (forceLocationType == null)
|
||||
{
|
||||
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
|
||||
{
|
||||
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
|
||||
{
|
||||
forceLocationType = locationType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,9 +477,7 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 > zone2)
|
||||
{
|
||||
int temp = zone2;
|
||||
zone2 = zone1;
|
||||
zone1 = temp;
|
||||
(zone1, zone2) = (zone2, zone1);
|
||||
}
|
||||
|
||||
if (generationParams.GateCount[zone1] == 0) { continue; }
|
||||
@@ -495,38 +543,46 @@ namespace Barotrauma
|
||||
//remove orphans
|
||||
Locations.RemoveAll(l => !Connections.Any(c => c.Locations.Contains(l)));
|
||||
|
||||
AssignBiomes(new MTRandom(ToolBox.StringToInt(Seed)));
|
||||
|
||||
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.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.ServerAndClient), 1.0f, 100.0f);
|
||||
if (connection.Locations.Any(l => l.IsGateBetweenBiomes))
|
||||
{
|
||||
connection.Difficulty = connection.Locations.Min(l => l.Biome.MaxDifficulty);
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.Difficulty = CalculateDifficulty(connection.CenterPos.X, connection.Biome);
|
||||
}
|
||||
}
|
||||
|
||||
AssignBiomes();
|
||||
CreateEndLocation();
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location)
|
||||
{
|
||||
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)
|
||||
//Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f)
|
||||
};
|
||||
location.UnlockInitialMissions();
|
||||
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
|
||||
float GetLevelDifficulty(float areaDifficulty)
|
||||
float CalculateDifficulty(float mapPosition, Biome biome)
|
||||
{
|
||||
const float CurveModifier = 1.5f;
|
||||
const float DifficultyMultiplier = 1.14f;
|
||||
const float BaseDifficulty = -3f;
|
||||
return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty;
|
||||
float settingsFactor = settings.LevelDifficultyMultiplier;
|
||||
float minDifficulty = 0;
|
||||
float maxDifficulty = 100;
|
||||
float difficulty = mapPosition / Width * 100;
|
||||
System.Diagnostics.Debug.Assert(biome != null);
|
||||
if (biome != null)
|
||||
{
|
||||
minDifficulty = biome.MinDifficulty;
|
||||
maxDifficulty = biome.MaxDifficulty;
|
||||
float diff = 1 - settingsFactor;
|
||||
difficulty *= 1 - (1f / biome.AllowedZones.Max() * diff);
|
||||
}
|
||||
return MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +607,7 @@ namespace Barotrauma
|
||||
return Biome.Prefabs.FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex));
|
||||
}
|
||||
|
||||
private void AssignBiomes()
|
||||
private void AssignBiomes(Random rand)
|
||||
{
|
||||
var biomes = Biome.Prefabs;
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
@@ -567,7 +623,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (location.MapPosition.X < zoneX)
|
||||
{
|
||||
location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.ServerAndClient)];
|
||||
location.Biome = allowedBiomes[rand.Next() % allowedBiomes.Count];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,6 +664,11 @@ namespace Barotrauma
|
||||
|
||||
if (EndLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(locationType);
|
||||
}
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -627,7 +688,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//removed all connections from the second-to-last location, need to reconnect it
|
||||
if (!previousToEndLocation.Connections.Any())
|
||||
if (previousToEndLocation.Connections.None())
|
||||
{
|
||||
Location connectTo = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
@@ -734,6 +795,7 @@ namespace Barotrauma
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
{
|
||||
var connection = CurrentLocation.Connections.Find(c => c.Locations.Contains(prevLocation));
|
||||
@@ -741,10 +803,8 @@ namespace Barotrauma
|
||||
{
|
||||
connection.Passed = true;
|
||||
}
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
public void SelectLocation(int index)
|
||||
@@ -764,6 +824,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Location prevSelected = SelectedLocation;
|
||||
SelectedLocation = Locations[index];
|
||||
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
|
||||
SelectedConnection =
|
||||
@@ -773,7 +834,10 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
if (prevSelected != SelectedLocation)
|
||||
{
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectLocation(Location location)
|
||||
@@ -786,13 +850,17 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Location prevSelected = SelectedLocation;
|
||||
SelectedLocation = location;
|
||||
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
if (prevSelected != SelectedLocation)
|
||||
{
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectMission(IEnumerable<int> missionIndices)
|
||||
@@ -805,23 +873,24 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
if (!missionIndices.SequenceEqual(GetSelectedMissionIndices()))
|
||||
{
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
@@ -1015,8 +1084,7 @@ namespace Barotrauma
|
||||
{
|
||||
string prevName = location.Name;
|
||||
|
||||
var newType = LocationType.Prefabs[change.ChangeToType];
|
||||
if (newType == null)
|
||||
if (!LocationType.Prefabs.TryGet(change.ChangeToType, out var newType))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found.");
|
||||
return false;
|
||||
@@ -1046,9 +1114,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element, CampaignSettings settings)
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Map map = new Map(campaign, element, settings);
|
||||
Map map = new Map(campaign, element);
|
||||
map.LoadState(element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount)
|
||||
public virtual void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
@@ -493,25 +493,33 @@ namespace Barotrauma
|
||||
|
||||
protected void InsertToList()
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (Sprite == null)
|
||||
{
|
||||
mapEntityList.Add(this);
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < mapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
|
||||
Sprite existingSprite = mapEntityList[i - 1].Sprite;
|
||||
if (existingSprite == null) continue;
|
||||
#if CLIENT
|
||||
if (existingSprite.Texture == this.Sprite.Texture) break;
|
||||
#endif
|
||||
if (mapEntityList[i - 1]?.Prefab == Prefab)
|
||||
{
|
||||
mapEntityList.Insert(i, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
i = 0;
|
||||
while (i < mapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
Sprite existingSprite = mapEntityList[i - 1].Sprite;
|
||||
if (existingSprite == null) { continue; }
|
||||
if (existingSprite.Texture == this.Sprite.Texture) { break; }
|
||||
}
|
||||
#endif
|
||||
mapEntityList.Insert(i, this);
|
||||
}
|
||||
|
||||
@@ -563,6 +571,10 @@ namespace Barotrauma
|
||||
{
|
||||
mapEntityUpdateTick++;
|
||||
|
||||
#if CLIENT
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
sw.Start();
|
||||
#endif
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
|
||||
@@ -597,6 +609,12 @@ namespace Barotrauma
|
||||
Powered.UpdatePower(deltaTime * PoweredUpdateInterval);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
@@ -613,9 +631,14 @@ namespace Barotrauma
|
||||
item.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
|
||||
UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval);
|
||||
|
||||
Spawner?.Update();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BeaconStationInfo : ISerializableEntity
|
||||
{
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedWalls { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDisconnectedWires { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MinLevelDifficulty { get; set; }
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MaxLevelDifficulty { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(BeaconStationInfo original)
|
||||
{
|
||||
Name = original.Name;
|
||||
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> kvp in original.SerializableProperties)
|
||||
{
|
||||
SerializableProperties.Add(kvp.Key, kvp.Value);
|
||||
if (SerializableProperty.GetSupportedTypeName(kvp.Value.PropertyType) != null)
|
||||
{
|
||||
kvp.Value.TrySetValue(this, kvp.Value.GetValue(original));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,23 +34,6 @@ namespace Barotrauma
|
||||
return prefab;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Humans.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
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
|
||||
{
|
||||
@@ -98,9 +96,29 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
private readonly Dictionary<Identifier, int> moduleCounts = new Dictionary<Identifier, int>();
|
||||
public class ModuleCount
|
||||
{
|
||||
public Identifier Identifier;
|
||||
public int Count;
|
||||
public int Order;
|
||||
|
||||
public IReadOnlyDictionary<Identifier, int> ModuleCounts
|
||||
public ModuleCount(ContentXElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeIdentifier("flag", element.GetAttributeIdentifier("moduletype", ""));
|
||||
Count = element.GetAttributeInt("count", 0);
|
||||
Order = element.GetAttributeInt("order", 0);
|
||||
}
|
||||
|
||||
public ModuleCount(Identifier id, int count)
|
||||
{
|
||||
Identifier = id;
|
||||
Count = count;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ModuleCount> moduleCounts = new List<ModuleCount>();
|
||||
|
||||
public IReadOnlyList<ModuleCount> ModuleCounts
|
||||
{
|
||||
get { return moduleCounts; }
|
||||
}
|
||||
@@ -171,8 +189,7 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "modulecount":
|
||||
Identifier moduleFlag = subElement.GetAttributeIdentifier("flag", subElement.GetAttributeIdentifier("moduletype", ""));
|
||||
moduleCounts[moduleFlag] = subElement.GetAttributeInt("count", 0);
|
||||
moduleCounts.Add(new ModuleCount(subElement));
|
||||
break;
|
||||
case "npcs":
|
||||
var newCollection = new NpcCollection();
|
||||
@@ -200,7 +217,7 @@ namespace Barotrauma
|
||||
public int GetModuleCount(Identifier moduleFlag)
|
||||
{
|
||||
if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return int.MaxValue; }
|
||||
return moduleCounts.ContainsKey(moduleFlag) ? moduleCounts[moduleFlag] : 0;
|
||||
return moduleCounts.FirstOrDefault(m => m.Identifier == moduleFlag)?.Count ?? 0;
|
||||
}
|
||||
|
||||
public void SetModuleCount(Identifier moduleFlag, int count)
|
||||
@@ -208,11 +225,19 @@ namespace Barotrauma
|
||||
if (moduleFlag == Identifier.Empty || moduleFlag == "none") { return; }
|
||||
if (count <= 0)
|
||||
{
|
||||
moduleCounts.Remove(moduleFlag);
|
||||
moduleCounts.RemoveAll(m => m.Identifier == moduleFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
moduleCounts[moduleFlag] = count;
|
||||
var moduleCount = moduleCounts.FirstOrDefault(m => m.Identifier == moduleFlag);
|
||||
if (moduleCount == null)
|
||||
{
|
||||
moduleCounts.Add(new ModuleCount(moduleFlag, count));
|
||||
}
|
||||
else
|
||||
{
|
||||
moduleCount.Count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Barotrauma
|
||||
{
|
||||
//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".ToIdentifier()) &&
|
||||
!generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Key)))
|
||||
!generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Identifier)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -141,16 +141,11 @@ namespace Barotrauma
|
||||
|
||||
selectedModules.Clear();
|
||||
//select which module types the outpost should consist of
|
||||
List<Identifier> pendingModuleFlags;
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
#warning TODO: cursed
|
||||
pendingModuleFlags = onlyEntrance
|
||||
? generationParams.ModuleCounts
|
||||
.Keys.OrderBy(k => ToolBox.IdentifierToUint32Hash(k, md5))
|
||||
.First().ToEnumerable().ToList()
|
||||
: SelectModules(outpostModules, generationParams);
|
||||
}
|
||||
List<Identifier> pendingModuleFlags =
|
||||
onlyEntrance ?
|
||||
generationParams.ModuleCounts.First().Identifier.ToEnumerable().ToList() :
|
||||
SelectModules(outpostModules, generationParams);
|
||||
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
{
|
||||
if (flag == "none") { continue; }
|
||||
@@ -437,31 +432,27 @@ namespace Barotrauma
|
||||
var pendingModuleFlags = new List<Identifier>();
|
||||
bool availableModulesFound = true;
|
||||
|
||||
Identifier initialModuleFlag = generationParams.ModuleCounts.FirstOrDefault().Key;
|
||||
Identifier initialModuleFlag = generationParams.ModuleCounts.FirstOrDefault().Identifier;
|
||||
pendingModuleFlags.Add(initialModuleFlag);
|
||||
while (pendingModuleFlags.Count < totalModuleCount && availableModulesFound)
|
||||
{
|
||||
availableModulesFound = false;
|
||||
foreach (var moduleFlag in generationParams.ModuleCounts)
|
||||
{
|
||||
if (pendingModuleFlags.Count(m => m == moduleFlag.Key) >= generationParams.GetModuleCount(moduleFlag.Key))
|
||||
if (pendingModuleFlags.Count(m => m == moduleFlag.Identifier) >= generationParams.GetModuleCount(moduleFlag.Identifier))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag.Key)))
|
||||
if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag.Identifier)))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to add a module to the outpost (no modules with the flag \"{moduleFlag.Key}\" found).");
|
||||
DebugConsole.ThrowError($"Failed to add a module to the outpost (no modules with the flag \"{moduleFlag.Identifier}\" found).");
|
||||
continue;
|
||||
}
|
||||
availableModulesFound = true;
|
||||
pendingModuleFlags.Add(moduleFlag.Key);
|
||||
pendingModuleFlags.Add(moduleFlag.Identifier);
|
||||
}
|
||||
}
|
||||
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);
|
||||
pendingModuleFlags.OrderBy(f => generationParams.ModuleCounts.First(m => m.Identifier == f)).ThenBy(f => Rand.Value(Rand.RandSync.ServerAndClient));
|
||||
while (pendingModuleFlags.Count < totalModuleCount)
|
||||
{
|
||||
//don't place "none" modules at the end because
|
||||
@@ -610,7 +601,7 @@ namespace Barotrauma
|
||||
|
||||
Identifier flagToPlace = "none".ToIdentifier();
|
||||
SubmarineInfo nextModule = null;
|
||||
foreach (Identifier moduleFlag in pendingModuleFlags)
|
||||
foreach (Identifier moduleFlag in pendingModuleFlags.OrderByDescending(f => currentModule?.Info?.OutpostModuleInfo.AllowAttachToModules.Contains(f) ?? false))
|
||||
{
|
||||
flagToPlace = moduleFlag;
|
||||
nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType, allowDifferentLocationType);
|
||||
@@ -830,43 +821,44 @@ namespace Barotrauma
|
||||
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType, bool allowDifferentLocationType)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> availableModules = null;
|
||||
IEnumerable<SubmarineInfo> modulesWithCorrectFlags = null;
|
||||
if (moduleFlag.IsEmpty || moduleFlag.Equals("none"))
|
||||
{
|
||||
availableModules = modules
|
||||
modulesWithCorrectFlags = modules
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())));
|
||||
}
|
||||
else
|
||||
{
|
||||
availableModules = modules
|
||||
modulesWithCorrectFlags = modules
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
}
|
||||
modulesWithCorrectFlags = modulesWithCorrectFlags.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
availableModules = availableModules.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
if (prevModule != null)
|
||||
var suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));// && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
//no suitable module found, see if we can find a "generic" module that's not meant for any specific type of outpost
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
|
||||
//still not found, see if we can find something that's otherwise suitable but not meant to attach to the previous module
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
|
||||
}
|
||||
//still not found! Try if we can find a generic module that's not meant to attach to the previous module
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
|
||||
}
|
||||
}
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (allowDifferentLocationType && !modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
if (allowDifferentLocationType)
|
||||
{
|
||||
if (modulesWithCorrectFlags.Any())
|
||||
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
return ToolBox.SelectWeightedRandom(modulesWithCorrectFlags.ToList(), modulesWithCorrectFlags.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -875,7 +867,28 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
return ToolBox.SelectWeightedRandom(suitableModules.ToList(), suitableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
IEnumerable<SubmarineInfo> GetSuitable(IEnumerable<SubmarineInfo> modules, bool requireAllowAttachToPrevious, bool requireCorrectLocationType, bool disallowNonLocationTypeSpecific)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> suitable = modules;
|
||||
if (requireCorrectLocationType)
|
||||
{
|
||||
if (disallowNonLocationTypeSpecific)
|
||||
{
|
||||
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier) || !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
}
|
||||
if (requireAllowAttachToPrevious && prevModule != null)
|
||||
{
|
||||
suitable = suitable.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));
|
||||
}
|
||||
return suitable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,6 +1039,17 @@ namespace Barotrauma
|
||||
module.ThisGapPosition == OutpostModuleInfo.GapPosition.Left ||
|
||||
module.ThisGapPosition == OutpostModuleInfo.GapPosition.Right;
|
||||
|
||||
if (!module.ThisGap.linkedTo.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error during outpost generation: {module.ThisGapPosition} gap in module \"{module.Info.Name}\" was not linked to any hulls.");
|
||||
continue;
|
||||
}
|
||||
if (!module.PreviousGap.linkedTo.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error during outpost generation: {GetOpposingGapPosition(module.ThisGapPosition)} gap in module \"{module.PreviousModule.Info.Name}\" was not linked to any hulls.");
|
||||
continue;
|
||||
}
|
||||
|
||||
MapEntity leftHull = module.ThisGap.Position.X < module.PreviousGap.Position.X ? module.ThisGap.linkedTo[0] : module.PreviousGap.linkedTo[0];
|
||||
MapEntity rightHull = module.ThisGap.Position.X > module.PreviousGap.Position.X ?
|
||||
module.ThisGap.linkedTo.Count == 1 ? module.ThisGap.linkedTo[0] : module.ThisGap.linkedTo[1] :
|
||||
@@ -1077,7 +1101,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Connection c in gapToRemove.ConnectedDoor.Item.Connections)
|
||||
{
|
||||
c.Wires.ForEach(w => w?.Item.Remove());
|
||||
c.Wires.ToArray().ForEach(w => w?.Item.Remove());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1428,7 +1452,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Connection connection in linkedItem.Connections)
|
||||
{
|
||||
foreach (Wire w in connection.Wires)
|
||||
foreach (Wire w in connection.Wires.ToArray())
|
||||
{
|
||||
w?.Item.Remove();
|
||||
}
|
||||
@@ -1590,10 +1614,6 @@ namespace Barotrauma
|
||||
{
|
||||
npc.CharacterHealth.Unkillable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.ServerAndClient);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
{
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -377,7 +377,15 @@ namespace Barotrauma
|
||||
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
|
||||
foreach (Body b in Bodies)
|
||||
{
|
||||
b.SetTransform(b.Position + simAmount, b.Rotation);
|
||||
Vector2 pos = b.Position + simAmount;
|
||||
if (ignoreContacts)
|
||||
{
|
||||
b.SetTransformIgnoreContacts(ref pos, b.Rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.SetTransform(pos, b.Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1208,7 +1216,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateSections()
|
||||
{
|
||||
if (Bodies == null) return;
|
||||
if (Bodies == null) { return; }
|
||||
foreach (Body b in Bodies)
|
||||
{
|
||||
GameMain.World.Remove(b);
|
||||
@@ -1281,9 +1289,9 @@ namespace Barotrauma
|
||||
Body newBody = GameMain.World.CreateRectangle(
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
1.5f);
|
||||
newBody.BodyType = BodyType.Static;
|
||||
//newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
|
||||
1.5f,
|
||||
bodyType: BodyType.Static,
|
||||
findNewContacts: false);
|
||||
newBody.Friction = 0.5f;
|
||||
newBody.OnCollision += OnWallCollision;
|
||||
newBody.CollisionCategories = (Prefab.Platform) ? Physics.CollisionPlatform : Physics.CollisionWall;
|
||||
@@ -1292,15 +1300,16 @@ namespace Barotrauma
|
||||
Vector2 structureCenter = ConvertUnits.ToSimUnits(Position);
|
||||
if (BodyRotation != 0.0f)
|
||||
{
|
||||
newBody.Position = structureCenter + bodyOffset + new Vector2(
|
||||
Vector2 pos = structureCenter + bodyOffset + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation))
|
||||
* ConvertUnits.ToSimUnits(diffFromCenter);
|
||||
newBody.Rotation = -BodyRotation;
|
||||
newBody.SetTransformIgnoreContacts(ref pos, -BodyRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
newBody.Position = structureCenter + (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset;
|
||||
Vector2 pos = structureCenter + (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset;
|
||||
newBody.SetTransformIgnoreContacts(ref pos, newBody.Rotation);
|
||||
}
|
||||
|
||||
if (createConvexHull)
|
||||
|
||||
@@ -1302,189 +1302,200 @@ namespace Barotrauma
|
||||
public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
|
||||
{
|
||||
Loading = true;
|
||||
|
||||
loaded.Add(this);
|
||||
|
||||
Info = new SubmarineInfo(info);
|
||||
|
||||
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
|
||||
|
||||
//place the sub above the top of the level
|
||||
HiddenSubPosition = HiddenSubStartPosition;
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null)
|
||||
GameMain.World.Enabled = false;
|
||||
try
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||
}
|
||||
loaded.Add(this);
|
||||
|
||||
foreach (Submarine sub in loaded)
|
||||
{
|
||||
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
Info = new SubmarineInfo(info);
|
||||
|
||||
IdOffset = IdRemap.DetermineNewOffset();
|
||||
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
|
||||
|
||||
List<MapEntity> newEntities = new List<MapEntity>();
|
||||
if (loadEntities == null)
|
||||
{
|
||||
if (Info.SubmarineElement != null)
|
||||
//place the sub above the top of the level
|
||||
HiddenSubPosition = HiddenSubStartPosition;
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null)
|
||||
{
|
||||
newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath, IdOffset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntities = loadEntities(this);
|
||||
newEntities.ForEach(me => me.Submarine = this);
|
||||
}
|
||||
|
||||
if (newEntities != null)
|
||||
{
|
||||
foreach (var e in newEntities)
|
||||
{
|
||||
if (linkedRemap != null) { e.ResolveLinks(linkedRemap); }
|
||||
e.unresolvedLinkedToID = null;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 center = Vector2.Zero;
|
||||
var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this);
|
||||
|
||||
if (matchingHulls.Any())
|
||||
{
|
||||
Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
|
||||
Vector2 bottomRight = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
|
||||
foreach (Hull hull in matchingHulls)
|
||||
{
|
||||
if (hull.Rect.X < topLeft.X) topLeft.X = hull.Rect.X;
|
||||
if (hull.Rect.Y > topLeft.Y) topLeft.Y = hull.Rect.Y;
|
||||
|
||||
if (hull.Rect.Right > bottomRight.X) bottomRight.X = hull.Rect.Right;
|
||||
if (hull.Rect.Y - hull.Rect.Height < bottomRight.Y) bottomRight.Y = hull.Rect.Y - hull.Rect.Height;
|
||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||
}
|
||||
|
||||
center = (topLeft + bottomRight) / 2.0f;
|
||||
center.X -= center.X % GridSize.X;
|
||||
center.Y -= center.Y % GridSize.Y;
|
||||
|
||||
RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this));
|
||||
|
||||
subBody = new SubmarineBody(this, showWarningMessages);
|
||||
subBody.SetPosition(HiddenSubPosition);
|
||||
|
||||
if (info.IsOutpost)
|
||||
foreach (Submarine sub in loaded)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
|
||||
}
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
IdOffset = IdRemap.DetermineNewOffset();
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
List<MapEntity> newEntities = new List<MapEntity>();
|
||||
if (loadEntities == null)
|
||||
{
|
||||
if (Info.SubmarineElement != null)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath, IdOffset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntities = loadEntities(this);
|
||||
newEntities.ForEach(me => me.Submarine = this);
|
||||
}
|
||||
|
||||
if (newEntities != null)
|
||||
{
|
||||
foreach (var e in newEntities)
|
||||
{
|
||||
if (linkedRemap != null) { e.ResolveLinks(linkedRemap); }
|
||||
e.unresolvedLinkedToID = null;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 center = Vector2.Zero;
|
||||
var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this);
|
||||
|
||||
if (matchingHulls.Any())
|
||||
{
|
||||
Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
|
||||
Vector2 bottomRight = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
|
||||
foreach (Hull hull in matchingHulls)
|
||||
{
|
||||
if (hull.Rect.X < topLeft.X) topLeft.X = hull.Rect.X;
|
||||
if (hull.Rect.Y > topLeft.Y) topLeft.Y = hull.Rect.Y;
|
||||
|
||||
if (hull.Rect.Right > bottomRight.X) bottomRight.X = hull.Rect.Right;
|
||||
if (hull.Rect.Y - hull.Rect.Height < bottomRight.Y) bottomRight.Y = hull.Rect.Y - hull.Rect.Height;
|
||||
}
|
||||
|
||||
center = (topLeft + bottomRight) / 2.0f;
|
||||
center.X -= center.X % GridSize.X;
|
||||
center.Y -= center.Y % GridSize.Y;
|
||||
|
||||
RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this));
|
||||
|
||||
subBody = new SubmarineBody(this, showWarningMessages);
|
||||
Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
|
||||
subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f);
|
||||
|
||||
if (info.IsOutpost)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
}
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
//prevent rewiring
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
item.Indestructible = true;
|
||||
}
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
//prevent rewiring
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
//prevent deattaching items from walls
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode) { continue; }
|
||||
#endif
|
||||
holdable.CanBePicked = false;
|
||||
holdable.CanBeSelected = false;
|
||||
}
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
//prevent deattaching items from walls
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode) { continue; }
|
||||
#endif
|
||||
holdable.CanBePicked = false;
|
||||
holdable.CanBeSelected = false;
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
{
|
||||
structure.Indestructible = true;
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
}
|
||||
else if (info.IsRuin)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
}
|
||||
|
||||
if (entityGrid != null)
|
||||
{
|
||||
Hull.EntityGrids.Remove(entityGrid);
|
||||
entityGrid = null;
|
||||
}
|
||||
entityGrid = Hull.GenerateEntityGrid(this);
|
||||
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.mapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
|
||||
}
|
||||
|
||||
Loading = false;
|
||||
|
||||
MapEntity.MapLoaded(newEntities, true);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
|
||||
{
|
||||
linkedSub.LinkDummyToMainSubmarine();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Hull hull in matchingHulls)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hull.RoomName))// || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hull.RoomName = hull.CreateRoomName();
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
|
||||
{
|
||||
GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged += ResetCrushDepth;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.LightManager.OnMapLoaded();
|
||||
#endif
|
||||
//if the sub was made using an older version,
|
||||
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
|
||||
if (showWarningMessages &&
|
||||
!string.IsNullOrEmpty(Info.FilePath) &&
|
||||
Screen.Selected != GameMain.SubEditorScreen &&
|
||||
(Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0")))
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. "
|
||||
+ "The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion.");
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != this) continue;
|
||||
if (item.ParentInventory != null || item.body != null) continue;
|
||||
foreach (var light in item.GetComponents<LightComponent>())
|
||||
{
|
||||
structure.Indestructible = true;
|
||||
light.LightColor = new Color(light.LightColor, light.LightColor.A / 255.0f * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info.IsRuin)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
GenerateOutdoorNodes();
|
||||
}
|
||||
|
||||
if (entityGrid != null)
|
||||
finally
|
||||
{
|
||||
Hull.EntityGrids.Remove(entityGrid);
|
||||
entityGrid = null;
|
||||
Loading = false;
|
||||
GameMain.World.Enabled = true;
|
||||
}
|
||||
entityGrid = Hull.GenerateEntityGrid(this);
|
||||
|
||||
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
|
||||
{
|
||||
if (MapEntity.mapEntityList[i].Submarine != this) { continue; }
|
||||
MapEntity.mapEntityList[i].Move(HiddenSubPosition);
|
||||
}
|
||||
|
||||
Loading = false;
|
||||
|
||||
MapEntity.MapLoaded(newEntities, true);
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
|
||||
{
|
||||
linkedSub.LinkDummyToMainSubmarine();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Hull hull in matchingHulls)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hull.RoomName))// || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hull.RoomName = hull.CreateRoomName();
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
|
||||
{
|
||||
GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged += ResetCrushDepth;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.LightManager.OnMapLoaded();
|
||||
#endif
|
||||
//if the sub was made using an older version,
|
||||
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
|
||||
if (showWarningMessages &&
|
||||
!string.IsNullOrEmpty(Info.FilePath) &&
|
||||
Screen.Selected != GameMain.SubEditorScreen &&
|
||||
(Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0")))
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. "
|
||||
+ "The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion.");
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != this) continue;
|
||||
if (item.ParentInventory != null || item.body != null) continue;
|
||||
var lightComponent = item.GetComponent<Items.Components.LightComponent>();
|
||||
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
|
||||
}
|
||||
}
|
||||
GenerateOutdoorNodes();
|
||||
}
|
||||
|
||||
protected override ushort DetermineID(ushort id, Submarine submarine)
|
||||
@@ -1495,10 +1506,7 @@ namespace Barotrauma
|
||||
public static Submarine Load(SubmarineInfo info, bool unloadPrevious, IdRemap linkedRemap = null)
|
||||
{
|
||||
if (unloadPrevious) { Unload(); }
|
||||
|
||||
Submarine sub = new Submarine(info, false, linkedRemap: linkedRemap);
|
||||
|
||||
return sub;
|
||||
return new Submarine(info, false, linkedRemap: linkedRemap);
|
||||
}
|
||||
|
||||
private void ResetCrushDepth()
|
||||
@@ -1549,7 +1557,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("cargocapacity", cargoCapacity));
|
||||
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
|
||||
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience.ToString()));
|
||||
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", Info.RequiredContentPackages)));
|
||||
|
||||
if (Info.Type == SubmarineType.OutpostModule)
|
||||
@@ -1599,18 +1607,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.FindParentInventory(inv => inv is CharacterInventory) != null) { continue; }
|
||||
#if CLIENT
|
||||
if (Screen.Selected != GameMain.SubEditorScreen)
|
||||
{
|
||||
if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; }
|
||||
}
|
||||
else
|
||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
e.Submarine = this;
|
||||
}
|
||||
#else
|
||||
if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; }
|
||||
#endif
|
||||
|
||||
if (e.Submarine != this) { continue; }
|
||||
var rootContainer = item.GetRootContainer();
|
||||
if (rootContainer != null && rootContainer.Submarine != this) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1630,6 +1634,7 @@ namespace Barotrauma
|
||||
Type = Info.Type,
|
||||
FilePath = filePath,
|
||||
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
|
||||
BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null,
|
||||
Name = Path.GetFileNameWithoutExtension(filePath)
|
||||
};
|
||||
#if CLIENT
|
||||
@@ -1851,5 +1856,42 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public void RefreshOutdoorNodes() => OutdoorNodes.ForEach(n => n?.Waypoint?.FindHull());
|
||||
|
||||
public Item FindContainerFor(Item item, bool onlyPrimary, bool checkTransferConditions = false, bool allowConnectedSubs = false)
|
||||
{
|
||||
var connectedSubs = GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player).ToHashSet();
|
||||
Item selectedContainer = null;
|
||||
foreach (Item potentialContainer in Item.ItemList)
|
||||
{
|
||||
if (potentialContainer.Removed) { continue; }
|
||||
if (potentialContainer.NonInteractable) { continue; }
|
||||
if (potentialContainer.HiddenInGame) { continue; }
|
||||
if (allowConnectedSubs)
|
||||
{
|
||||
if (!connectedSubs.Contains(potentialContainer.Submarine)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (potentialContainer.Submarine != this) { continue; }
|
||||
}
|
||||
if (potentialContainer == item) { continue; }
|
||||
if (potentialContainer.Condition <= 0) { continue; }
|
||||
if (potentialContainer.OwnInventory == null) { continue; }
|
||||
if (potentialContainer.GetRootInventoryOwner() != potentialContainer) { continue; }
|
||||
var container = potentialContainer.GetComponent<ItemContainer>();
|
||||
if (container == null) { continue; }
|
||||
if (!potentialContainer.OwnInventory.CanBePut(item)) { continue; }
|
||||
if (!container.ShouldBeContained(item, out _)) { continue; }
|
||||
if (!item.Prefab.IsContainerPreferred(item, container, out bool isPreferencesDefined, out bool isSecondary, checkTransferConditions: checkTransferConditions) || !isPreferencesDefined || onlyPrimary && isSecondary) { continue; }
|
||||
if (potentialContainer.Submarine == this && !isSecondary)
|
||||
{
|
||||
//valid primary container in the same sub -> perfect, let's use that one
|
||||
return potentialContainer;
|
||||
}
|
||||
selectedContainer = potentialContainer;
|
||||
|
||||
}
|
||||
return selectedContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,17 @@ namespace Barotrauma
|
||||
HullVertices = convexHull;
|
||||
|
||||
|
||||
farseerBody = GameMain.World.CreateBody();
|
||||
farseerBody = GameMain.World.CreateBody(findNewContacts: false, bodyType: BodyType.Dynamic);
|
||||
var collisionCategory = Physics.CollisionWall;
|
||||
var collidesWith =
|
||||
Physics.CollisionItem |
|
||||
Physics.CollisionLevel |
|
||||
Physics.CollisionCharacter |
|
||||
Physics.CollisionProjectile |
|
||||
Physics.CollisionWall;
|
||||
farseerBody.CollisionCategories = collisionCategory;
|
||||
farseerBody.CollidesWith = collidesWith;
|
||||
farseerBody.Enabled = false;
|
||||
farseerBody.UserData = this;
|
||||
foreach (var mapEntity in MapEntity.mapEntityList)
|
||||
{
|
||||
@@ -152,7 +162,9 @@ namespace Barotrauma
|
||||
ConvertUnits.ToSimUnits(wall.BodyHeight),
|
||||
50.0f,
|
||||
-wall.BodyRotation,
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset)).UserData = wall;
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset),
|
||||
collisionCategory,
|
||||
collidesWith).UserData = wall;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +179,9 @@ namespace Barotrauma
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
100.0f,
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2))).UserData = hull;
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
|
||||
collisionCategory,
|
||||
collidesWith).UserData = hull;
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
@@ -191,47 +205,40 @@ namespace Barotrauma
|
||||
|
||||
if (width > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width, height) / 2, item.Position + new Vector2(width, height) / 2, hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f && width > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2));
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width / 2 + radius, height / 2), item.Position + new Vector2(width / 2 + radius, height / 2), hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2));
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
|
||||
SetExtents(item.Position - new Vector2(width / 2, height / 2 + radius), item.Position + new Vector2(width / 2, height / 2 + radius), hasCollider: true);
|
||||
}
|
||||
else if (radius > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos));
|
||||
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X);
|
||||
visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y);
|
||||
visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X);
|
||||
visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y);
|
||||
SetExtents(item.Position - new Vector2(radius, radius), item.Position + new Vector2(radius, radius), hasCollider: true);
|
||||
}
|
||||
item.StaticFixtures.ForEach(f => f.UserData = item);
|
||||
}
|
||||
|
||||
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
|
||||
VisibleBorders = new Rectangle((int)visibleMinExtents.X, (int)visibleMaxExtents.Y, (int)(visibleMaxExtents.X - visibleMinExtents.X), (int)(visibleMaxExtents.Y - visibleMinExtents.Y));
|
||||
}
|
||||
|
||||
farseerBody.BodyType = BodyType.Dynamic;
|
||||
farseerBody.CollisionCategories = Physics.CollisionWall;
|
||||
farseerBody.CollidesWith =
|
||||
Physics.CollisionItem |
|
||||
Physics.CollisionLevel |
|
||||
Physics.CollisionCharacter |
|
||||
Physics.CollisionProjectile |
|
||||
Physics.CollisionWall;
|
||||
|
||||
farseerBody.Enabled = true;
|
||||
farseerBody.Restitution = Restitution;
|
||||
farseerBody.Friction = Friction;
|
||||
farseerBody.FixedRotation = true;
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace Barotrauma
|
||||
public SubmarineTag Tags { get; private set; }
|
||||
|
||||
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
|
||||
public string RecommendedCrewExperience;
|
||||
|
||||
public enum CrewExperienceLevel
|
||||
{
|
||||
Unknown,
|
||||
CrewExperienceLow,
|
||||
CrewExperienceMid,
|
||||
CrewExperienceHigh
|
||||
}
|
||||
public CrewExperienceLevel RecommendedCrewExperience;
|
||||
|
||||
/// <summary>
|
||||
/// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match
|
||||
@@ -89,6 +97,7 @@ namespace Barotrauma
|
||||
public SubmarineClass SubmarineClass;
|
||||
|
||||
public OutpostModuleInfo OutpostModuleInfo { get; set; }
|
||||
public BeaconStationInfo BeaconStationInfo { get; set; }
|
||||
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
|
||||
|
||||
@@ -100,6 +109,8 @@ namespace Barotrauma
|
||||
public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined;
|
||||
public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus);
|
||||
|
||||
public bool AllowPreviewImage => Type == SubmarineType.Player;
|
||||
|
||||
public Md5Hash MD5Hash
|
||||
{
|
||||
get
|
||||
@@ -280,6 +291,10 @@ namespace Barotrauma
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
}
|
||||
if (original.BeaconStationInfo != null)
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo);
|
||||
}
|
||||
#if CLIENT
|
||||
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
|
||||
#endif
|
||||
@@ -330,7 +345,24 @@ namespace Barotrauma
|
||||
CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
|
||||
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
|
||||
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
|
||||
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
|
||||
var recommendedCrewExperience = SubmarineElement.GetAttributeIdentifier("recommendedcrewexperience", CrewExperienceLevel.Unknown.ToIdentifier());
|
||||
// Backwards compatibility
|
||||
if (recommendedCrewExperience == "Beginner")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceLow;
|
||||
}
|
||||
else if (recommendedCrewExperience == "Intermediate")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceMid;
|
||||
}
|
||||
else if (recommendedCrewExperience == "Experienced")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceHigh;
|
||||
}
|
||||
else
|
||||
{
|
||||
Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
|
||||
}
|
||||
|
||||
if (SubmarineElement?.Attribute("type") != null)
|
||||
{
|
||||
@@ -341,6 +373,10 @@ namespace Barotrauma
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(this, SubmarineElement);
|
||||
}
|
||||
else if (Type == SubmarineType.BeaconStation)
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,20 +395,6 @@ namespace Barotrauma
|
||||
SubmarineClass = SubmarineClass.Undefined;
|
||||
}
|
||||
|
||||
//backwards compatibility (use text tags instead of the actual text)
|
||||
if (RecommendedCrewExperience == "Beginner")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceLow";
|
||||
}
|
||||
else if (RecommendedCrewExperience == "Intermediate")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceMid";
|
||||
}
|
||||
else if (RecommendedCrewExperience == "Experienced")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceHigh";
|
||||
}
|
||||
|
||||
RequiredContentPackages.Clear();
|
||||
string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", Array.Empty<string>());
|
||||
foreach (string contentPackageName in contentPackageNames)
|
||||
@@ -528,10 +550,15 @@ namespace Barotrauma
|
||||
OutpostModuleInfo.Save(newElement);
|
||||
OutpostModuleInfo = new OutpostModuleInfo(this, newElement);
|
||||
}
|
||||
else if (Type == SubmarineType.BeaconStation)
|
||||
{
|
||||
BeaconStationInfo.Save(newElement);
|
||||
BeaconStationInfo = new BeaconStationInfo(this, newElement);
|
||||
}
|
||||
XDocument doc = new XDocument(newElement);
|
||||
|
||||
doc.Root.Add(new XAttribute("name", Name));
|
||||
if (previewImage != null)
|
||||
if (previewImage != null && AllowPreviewImage)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
|
||||
}
|
||||
@@ -590,6 +617,7 @@ namespace Barotrauma
|
||||
List<string> filePaths = new List<string>();
|
||||
foreach (BaseSubFile subFile in contentPackageSubs)
|
||||
{
|
||||
if (!File.Exists(subFile.Path.Value)) { continue; }
|
||||
if (!filePaths.Any(fp => fp == subFile.Path))
|
||||
{
|
||||
filePaths.Add(subFile.Path.Value);
|
||||
|
||||
@@ -109,14 +109,21 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public enum Type
|
||||
{
|
||||
WayPoint,
|
||||
SpawnPoint
|
||||
}
|
||||
|
||||
public WayPoint(Rectangle newRect, Submarine submarine)
|
||||
: this (MapEntityPrefab.FindByIdentifier("waypoint".ToIdentifier()), newRect, submarine)
|
||||
: this (Type.WayPoint, newRect, submarine)
|
||||
{
|
||||
}
|
||||
|
||||
public WayPoint(MapEntityPrefab prefab, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (prefab, submarine, id)
|
||||
public WayPoint(Type type, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
: base (type is Type.WayPoint
|
||||
? CoreEntityPrefab.WayPointPrefab
|
||||
: CoreEntityPrefab.SpawnPointPrefab, submarine, id)
|
||||
{
|
||||
rect = newRect;
|
||||
idCardTags = Array.Empty<string>();
|
||||
@@ -1010,7 +1017,7 @@ namespace Barotrauma
|
||||
|
||||
|
||||
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
|
||||
WayPoint w = new WayPoint(MapEntityPrefab.FindByIdentifier((spawnType == SpawnType.Path ? "waypoint" : "spawnpoint").ToIdentifier()), rect, submarine, idRemap.GetOffsetId(element))
|
||||
WayPoint w = new WayPoint(spawnType == SpawnType.Path ? Type.WayPoint : Type.SpawnPoint, rect, submarine, idRemap.GetOffsetId(element))
|
||||
{
|
||||
spawnType = spawnType
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user