38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -8,6 +8,8 @@ namespace Barotrauma
{
class Entity
{
public const ushort NullEntityID = 0;
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
public static List<Entity> GetEntityList()
{
@@ -39,18 +41,22 @@ namespace Barotrauma
{
return id;
}
set
set
{
Entity thisEntity;
if (dictionary.TryGetValue(id, out thisEntity) && thisEntity == this)
if (value == NullEntityID)
{
DebugConsole.ThrowError("Cannot set the ID of an entity to " + NullEntityID +
"! The value is reserved for entity events referring to a non-existent (e.g. removed) entity.\n" + Environment.StackTrace);
return;
}
if (dictionary.TryGetValue(id, out Entity thisEntity) && thisEntity == this)
{
dictionary.Remove(id);
}
//if there's already an entity with the same ID, give it the old ID of this one
Entity existingEntity;
if (dictionary.TryGetValue(value, out existingEntity))
if (dictionary.TryGetValue(value, out Entity existingEntity))
{
System.Diagnostics.Debug.WriteLine(existingEntity + " had the same ID as " + this + " (" + value + ")");
DebugConsole.Log(existingEntity + " had the same ID as " + this + " (" + value + ")");
dictionary.Remove(value);
dictionary.Add(id, existingEntity);
@@ -100,16 +106,29 @@ namespace Barotrauma
{
this.Submarine = submarine;
//give an unique ID
//give a unique ID
id = FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset);
dictionary.Add(id, this);
}
public static ushort FindFreeID(ushort idOffset = 0)
{
//ushort.MaxValue - 1 because 0 is a reserved value
if (dictionary.Count >= ushort.MaxValue - 1)
{
throw new Exception("Maximum amount of entities (" + (ushort.MaxValue - 1) + ") reached!");
}
idOffset = Math.Max(idOffset, (ushort)1);
bool IDfound;
id = submarine == null ? (ushort)1 : submarine.IdOffset;
ushort id = idOffset;
do
{
id += 1;
IDfound = dictionary.ContainsKey(id);
} while (IDfound);
dictionary.Add(id, this);
return id;
}
/// <summary>
@@ -205,6 +224,7 @@ namespace Barotrauma
}
dictionary.Clear();
Hull.EntityGrids.Clear();
}
/// <summary>
@@ -6,14 +6,33 @@ namespace Barotrauma
{
class EntityGrid
{
private List<MapEntity> allEntities;
private List<MapEntity>[,] entities;
private Rectangle limits;
private readonly Rectangle limits;
private float cellSize;
private readonly float cellSize;
public readonly Submarine Submarine;
public Rectangle WorldRect
{
get
{
if (Submarine == null)
{
return limits;
}
else
{
return new Rectangle(
(int)(limits.X + Submarine.WorldPosition.X),
(int)(limits.Y + Submarine.WorldPosition.Y),
limits.Width, limits.Height);
}
}
}
public EntityGrid(Submarine submarine, float cellSize)
{
//make the grid slightly larger than the borders of the submarine,
@@ -27,7 +46,19 @@ namespace Barotrauma
submarine.Borders.Height + padding * 2);
this.Submarine = submarine;
this.cellSize = cellSize;
InitializeGrid();
}
public EntityGrid(Rectangle worldRect, float cellSize)
{
this.limits = worldRect;
this.cellSize = cellSize;
InitializeGrid();
}
private void InitializeGrid()
{
allEntities = new List<MapEntity>();
entities = new List<MapEntity>[(int)Math.Ceiling(limits.Width / cellSize), (int)Math.Ceiling(limits.Height / cellSize)];
for (int x = 0; x < entities.GetLength(0); x++)
{
@@ -51,13 +82,14 @@ namespace Barotrauma
return;
}
for (int x = Math.Max(indices.X, 0); x <= Math.Min(indices.Width, entities.GetLength(0)-1); x++)
for (int x = Math.Max(indices.X, 0); x <= Math.Min(indices.Width, entities.GetLength(0) - 1); x++)
{
for (int y = Math.Max(indices.Y,0); y <= Math.Min(indices.Height, entities.GetLength(1)-1); y++)
for (int y = Math.Max(indices.Y, 0); y <= Math.Min(indices.Height, entities.GetLength(1) - 1); y++)
{
entities[x, y].Add(entity);
}
}
allEntities.Add(entity);
}
public void RemoveEntity(MapEntity entity)
@@ -69,6 +101,7 @@ namespace Barotrauma
if (entities[x, y].Contains(entity)) entities[x, y].Remove(entity);
}
}
allEntities.Remove(entity);
}
public void Clear()
@@ -80,38 +113,24 @@ namespace Barotrauma
entities[x, y].Clear();
}
}
allEntities.Clear();
}
public static List<MapEntity> GetEntities(List<EntityGrid> entityGrids, Vector2 position, bool useWorldCoordinates = true)
public IEnumerable<MapEntity> GetAllEntities()
{
List<MapEntity> entities = new List<MapEntity>();
foreach (EntityGrid entityGrid in entityGrids)
{
Vector2 transformedPosition = position;
if (useWorldCoordinates)
{
transformedPosition -= entityGrid.Submarine.Position;
}
entities.AddRange(entityGrid.GetEntities(transformedPosition));
}
return entities;
return allEntities;
}
public List<MapEntity> GetEntities(Vector2 position)
{
if (!MathUtils.IsValid(position)) new List<MapEntity>();
if (!MathUtils.IsValid(position)) return null;
if (Submarine != null) position -= Submarine.HiddenSubPosition;
Point indices = GetIndices(position);
if (indices.X < 0 || indices.Y < 0 || indices.X >= entities.GetLength(0) || indices.Y >= entities.GetLength(1))
{
return new List<MapEntity>();
return null;
}
return entities[indices.X, indices.Y];
}
@@ -4,19 +4,22 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class Explosion
{
private static List<Triplet<Explosion, Vector2, float>> prevExplosions = new List<Triplet<Explosion, Vector2, float>>();
private Attack attack;
private float force;
public float CameraShake;
private bool sparks, shockwave, flames, smoke, flash;
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
private float empStrength;
@@ -25,24 +28,27 @@ namespace Barotrauma
public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f)
{
attack = new Attack(damage, structureDamage, 0.0f, range);
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range);
attack.SeverLimbsProbability = 1.0f;
this.force = force;
this.empStrength = empStrength;
sparks = true;
shockwave = true;
smoke = true;
flames = true;
underwaterBubble = true;
}
public Explosion(XElement element)
public Explosion(XElement element, string parentDebugName)
{
attack = new Attack(element);
attack = new Attack(element, parentDebugName + ", Explosion");
force = element.GetAttributeFloat("force", 0.0f);
sparks = element.GetAttributeBool("sparks", true);
shockwave = element.GetAttributeBool("shockwave", true);
flames = element.GetAttributeBool("flames", true);
underwaterBubble = element.GetAttributeBool("underwaterbubble", true);
smoke = element.GetAttributeBool("smoke", true);
flash = element.GetAttributeBool("flash", true);
@@ -53,9 +59,20 @@ namespace Barotrauma
CameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f);
}
public void Explode(Vector2 worldPosition)
public List<Triplet<Explosion, Vector2, float>> GetRecentExplosions(float maxSecondsAgo)
{
return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo);
}
public void Explode(Vector2 worldPosition, Entity damageSource)
{
prevExplosions.Add(new Triplet<Explosion, Vector2, float>(this, worldPosition, (float)Timing.TotalTime));
if (prevExplosions.Count > 100)
{
prevExplosions.RemoveAt(0);
}
Hull hull = Hull.FindHull(worldPosition);
ExplodeProjSpecific(worldPosition, hull);
@@ -85,7 +102,7 @@ namespace Barotrauma
//damage repairable power-consuming items
var powered = item.GetComponent<Powered>();
if (powered == null || !powered.VulnerableToEMP) continue;
if (item.FixRequirements.Count > 0)
if (item.Repairables.Any())
{
item.Condition -= 100 * empStrength * distFactor;
}
@@ -99,9 +116,9 @@ namespace Barotrauma
}
}
if (force == 0.0f && attack.Stun == 0.0f && attack.GetDamage(1.0f) == 0.0f) return;
if (force == 0.0f && attack.Stun == 0.0f && attack.GetTotalDamage(false) == 0.0f) return;
ApplyExplosionForces(worldPosition, attack, force);
DamageCharacters(worldPosition, attack, force, damageSource);
if (flames && GameMain.Client == null)
{
@@ -142,7 +159,7 @@ namespace Barotrauma
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
}
public static void ApplyExplosionForces(Vector2 worldPosition, Attack attack, float force)
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource)
{
if (attack.Range <= 0.0f) return;
@@ -151,6 +168,9 @@ namespace Barotrauma
Vector2 explosionPos = worldPosition;
if (c.Submarine != null) explosionPos -= c.Submarine.Position;
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
bool underWater = hull == null || explosionPos.Y < hull.Surface;
explosionPos = ConvertUnits.ToSimUnits(explosionPos);
Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
@@ -171,13 +191,33 @@ namespace Barotrauma
if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) distFactor *= 0.1f;
distFactors.Add(limb, distFactor);
c.AddDamage(limb.WorldPosition, DamageType.None,
attack.GetDamage(1.0f) / c.AnimController.Limbs.Length * distFactor,
attack.GetBleedingDamage(1.0f) / c.AnimController.Limbs.Length * distFactor,
attack.Stun * distFactor,
false);
List<Affliction> modifiedAfflictions = new List<Affliction>();
foreach (Affliction affliction in attack.Afflictions)
{
modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length));
}
c.LastDamageSource = damageSource;
Character attacker = null;
if (damageSource is Item item)
{
attacker = item.GetComponent<Projectile>()?.User;
if (attacker == null) attacker = item.GetComponent<MeleeWeapon>()?.User;
}
c.AddDamage(limb.WorldPosition, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
if (attack.StatusEffects != null && attack.StatusEffects.Any())
{
attack.SetUser(attacker);
var statusEffectTargets = new List<ISerializableEntity>() { c, limb };
foreach (StatusEffect statusEffect in attack.StatusEffects)
{
statusEffect.Apply(ActionType.OnUse, 1.0f, damageSource, statusEffectTargets);
statusEffect.Apply(ActionType.Always, 1.0f, damageSource, statusEffectTargets);
statusEffect.Apply(underWater ? ActionType.InWater : ActionType.NotInWater, 1.0f, damageSource, statusEffectTargets);
}
}
if (limb.WorldPosition != worldPosition && force > 0.0f)
{
Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition);
@@ -211,7 +251,7 @@ namespace Barotrauma
/// <summary>
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
/// </summary>
public static Dictionary<Structure,float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage)
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage)
{
List<Structure> structureList = new List<Structure>();
float dist = 600.0f;
@@ -1,8 +1,9 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.Networking;
#if CLIENT
using Barotrauma.Sounds;
using Barotrauma.Lights;
using Barotrauma.Particles;
#endif
@@ -13,9 +14,7 @@ namespace Barotrauma
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
private int basicSoundIndex, largeSoundIndex;
private Hull hull;
private Vector2 position;
@@ -40,7 +39,7 @@ namespace Barotrauma
public Vector2 WorldPosition
{
get { return Submarine.Position + position; }
get { return Submarine == null ? position : Submarine.Position + position; }
}
public Vector2 Size
@@ -71,24 +70,21 @@ namespace Barotrauma
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
{
hull = Hull.FindHull(worldPosition, spawningHull);
if (hull == null) return;
if (hull == null || worldPosition.Y < hull.WorldSurface) return;
if (!isNetworkMessage && GameMain.Client != null) return;
hull.AddFireSource(this);
Submarine = hull.Submarine;
this.position = worldPosition - new Vector2(-5.0f, 5.0f) - Submarine.Position;
#if CLIENT
if (fireSoundBasic == null)
position = worldPosition - new Vector2(-5.0f, 5.0f);
if (hull.Submarine != null)
{
fireSoundBasic = Sound.Load("Content/Sounds/fire.ogg", false);
fireSoundLarge = Sound.Load("Content/Sounds/firelarge.ogg", false);
Submarine = hull.Submarine;
position -= Submarine.Position;
}
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull == null ? null : hull.Submarine);
#if CLIENT
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull?.Submarine);
#endif
size = new Vector2(10.0f, 10.0f);
@@ -154,7 +150,7 @@ namespace Barotrauma
DamageCharacters(deltaTime);
DamageItems(deltaTime);
if (hull.WaterVolume > 0.0f) HullWaterExtinquish(deltaTime);
if (hull.WaterVolume > 0.0f) HullWaterExtinguish(deltaTime);
hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
@@ -203,8 +199,10 @@ namespace Barotrauma
float dmg = (float)Math.Sqrt(size.X) * deltaTime / c.AnimController.Limbs.Length;
foreach (Limb limb in c.AnimController.Limbs)
{
c.AddDamage(limb.SimPosition, DamageType.Burn, dmg, 0, 0, false);
c.LastDamageSource = null;
c.DamageLimb(WorldPosition, limb, new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(dmg) }, 0.0f, false, 0.0f);
}
c.ApplyStatusEffects(ActionType.OnFire, deltaTime);
}
}
@@ -253,15 +251,15 @@ namespace Barotrauma
}
}
private void HullWaterExtinquish(float deltaTime)
private void HullWaterExtinguish(float deltaTime)
{
//the higher the surface of the water is relative to the firesource, the faster it puts out the fire
float extinquishAmount = (hull.Surface - (position.Y - size.Y)) * deltaTime;
float extinguishAmount = (hull.Surface - (position.Y - size.Y)) * deltaTime;
if (extinquishAmount < 0.0f) return;
if (extinguishAmount < 0.0f) return;
#if CLIENT
float steamCount = Rand.Range(-5.0f, Math.Min(extinquishAmount * 100.0f, 10));
float steamCount = Rand.Range(-5.0f, Math.Min(extinguishAmount * 100.0f, 10));
for (int i = 0; i < steamCount; i++)
{
Vector2 spawnPos = new Vector2(
@@ -272,18 +270,14 @@ namespace Barotrauma
var particle = GameMain.ParticleManager.CreateParticle("steam",
spawnPos, speed, 0.0f, hull);
if (particle == null) continue;
particle.Size *= MathHelper.Clamp(size.X / 10.0f, 0.5f, 3.0f);
}
#endif
position.X += extinquishAmount / 2.0f;
size.X -= extinquishAmount;
position.X += extinguishAmount / 2.0f;
size.X -= extinguishAmount;
//evaporate some of the water
hull.WaterVolume -= extinquishAmount;
hull.WaterVolume -= extinguishAmount;
if (GameMain.Client != null) return;
@@ -292,7 +286,7 @@ namespace Barotrauma
public void Extinguish(float deltaTime, float amount)
{
float extinquishAmount = amount * deltaTime;
float extinguishAmount = amount * deltaTime;
#if CLIENT
float steamCount = Rand.Range(-5.0f, (float)Math.Sqrt(amount));
@@ -304,17 +298,13 @@ namespace Barotrauma
var particle = GameMain.ParticleManager.CreateParticle("steam",
spawnPos, speed, 0.0f, hull);
if (particle == null) continue;
particle.Size *= MathHelper.Clamp(size.X / 10.0f, 0.5f, 3.0f);
}
#endif
position.X += extinquishAmount / 2.0f;
size.X -= extinquishAmount;
position.X += extinguishAmount / 2.0f;
size.X -= extinguishAmount;
hull.WaterVolume -= extinquishAmount;
hull.WaterVolume -= extinguishAmount;
if (GameMain.Client != null) return;
@@ -330,18 +320,7 @@ namespace Barotrauma
{
#if CLIENT
lightSource.Remove();
if (basicSoundIndex > 0)
{
Sounds.SoundManager.Stop(basicSoundIndex);
basicSoundIndex = -1;
}
if (largeSoundIndex > 0)
{
Sounds.SoundManager.Stop(largeSoundIndex);
largeSoundIndex = -1;
}
foreach (Decal d in burnDecals)
{
d.StopFadeIn();
+50 -22
View File
@@ -16,8 +16,12 @@ namespace Barotrauma
const float OutsideColliderRaycastInterval = 0.1f;
public readonly bool IsHorizontal;
public bool IsHorizontal
{
get;
private set;
}
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
private float open;
@@ -91,15 +95,7 @@ namespace Barotrauma
return "Gap";
}
}
public override bool SelectableInEditor
{
get
{
return ShowGaps;
}
}
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
: this (rectangle, Submarine.MainSub)
{ }
@@ -109,7 +105,7 @@ namespace Barotrauma
{ }
public Gap(Rectangle newRect, bool isHorizontal, Submarine submarine)
: base (MapEntityPrefab.Find("Gap"), submarine)
: base (MapEntityPrefab.Find(null, "gap"), submarine)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
@@ -161,6 +157,40 @@ namespace Barotrauma
!Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -5), position);
}
public void AutoOrient()
{
Vector2 searchPosLeft = new Vector2(rect.X, rect.Y - rect.Height / 2);
Hull hullLeft = Hull.FindHullOld(searchPosLeft, null, false);
Vector2 searchPosRight = new Vector2(rect.Right, rect.Y - rect.Height / 2);
Hull hullRight = Hull.FindHullOld(searchPosRight, null, false);
if (hullLeft != null && hullRight != null && hullLeft != hullRight)
{
IsHorizontal = true;
return;
}
Vector2 searchPosTop = new Vector2(rect.Center.X, rect.Y);
Hull hullTop = Hull.FindHullOld(searchPosTop, null, false);
Vector2 searchPosBottom = new Vector2(rect.Center.X, rect.Y - rect.Height);
Hull hullBottom = Hull.FindHullOld(searchPosBottom, null, false);
if (hullTop != null && hullBottom != null && hullTop != hullBottom)
{
IsHorizontal = false;
return;
}
if ((hullLeft == null) != (hullRight == null))
{
IsHorizontal = true;
}
else if ((hullTop == null) != (hullBottom == null))
{
IsHorizontal = false;
}
}
private void FindHulls()
{
Hull[] hulls = new Hull[2];
@@ -289,17 +319,15 @@ namespace Barotrauma
if (linkedTo.Count < 2) return;
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
Vector2 subOffset = Vector2.Zero;
if (hull1.Submarine != Submarine)
{
subOffset =Submarine.Position - hull1.Submarine.Position;
subOffset = Submarine.Position - hull1.Submarine.Position;
}
else if (hull2.Submarine != Submarine)
{
subOffset = hull2.Submarine.Position - Submarine.Position;
}
if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) return;
@@ -391,7 +419,7 @@ namespace Barotrauma
else
{
//lower room is full of water
if (hull2.Pressure + subOffset.Y > hull1.Pressure)
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
{
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + Hull.MaxCompress, deltaTime * 8000.0f * sizeModifier);
@@ -606,7 +634,7 @@ namespace Barotrauma
hull2.Oxygen -= deltaOxygen;
}
public static Gap FindAdjacent(List<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
{
foreach (Gap gap in gaps)
{
@@ -666,7 +694,7 @@ namespace Barotrauma
if (!DisableHullRechecks) FindHulls();
}
public static void Load(XElement element, Submarine submarine)
public static Gap Load(XElement element, Submarine submarine)
{
Rectangle rect = Rectangle.Empty;
@@ -687,15 +715,15 @@ namespace Barotrauma
bool isHorizontal = rect.Height > rect.Width;
var horizontalAttribute = element.Attribute("horizontal");
if (horizontalAttribute!=null)
if (horizontalAttribute != null)
{
isHorizontal = horizontalAttribute.Value.ToString() == "true";
}
Gap g = new Gap(rect, isHorizontal, submarine);
g.ID = (ushort)int.Parse(element.Attribute("ID").Value);
g.ID = (ushort)int.Parse(element.Attribute("ID").Value);
g.linkedToID = new List<ushort>();
return g;
}
public override XElement Save(XElement parentElement)
+331 -116
View File
@@ -1,4 +1,6 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
@@ -13,30 +15,20 @@ namespace Barotrauma
const float NetworkUpdateInterval = 0.5f;
public static List<Hull> hullList = new List<Hull>();
private static List<EntityGrid> entityGrids = new List<EntityGrid>();
public static List<EntityGrid> EntityGrids
{
get
{
return entityGrids;
}
}
public static List<EntityGrid> EntityGrids { get; } = new List<EntityGrid>();
public static bool ShowHulls = true;
public static bool EditWater, EditFire;
private List<FireSource> fireSources;
public const float OxygenDistributionSpeed = 500.0f;
public const float OxygenDetoriationSpeed = 0.3f;
public const float OxygenConsumptionSpeed = 1000.0f;
public const int WaveWidth = 16;
const float WaveStiffness = 0.003f;
const float WaveSpread = 0.05f;
const float WaveDampening = 0.01f;
public const int WaveWidth = 32;
public static float WaveStiffness = 0.02f;
public static float WaveSpread = 0.05f;
public static float WaveDampening = 0.05f;
//how much excess water the room can contain (= more than the volume of the room)
public const float MaxCompress = 10000f;
@@ -48,7 +40,7 @@ namespace Barotrauma
private float lethalPressure;
private float surface;
private float surface, drawSurface;
private float waterVolume;
private float pressure;
@@ -76,6 +68,13 @@ namespace Barotrauma
return "Hull";
}
}
[Editable, Serialize("", true)]
public string RoomName
{
get;
set;
}
public override Rectangle Rect
{
@@ -93,19 +92,11 @@ namespace Barotrauma
Gap.UpdateHulls();
}
surface = rect.Y - rect.Height + WaterVolume / rect.Width;
surface = drawSurface = rect.Y - rect.Height + WaterVolume / rect.Width;
Pressure = surface;
}
}
public override bool SelectableInEditor
{
get
{
return ShowHulls;
}
}
public override bool Linkable
{
get { return true; }
@@ -122,11 +113,33 @@ namespace Barotrauma
get { return new Vector2(rect.Width, rect.Height); }
}
public float CeilingHeight
{
get;
private set;
}
public float Surface
{
get { return surface; }
}
public float DrawSurface
{
get { return drawSurface; }
set
{
if (Math.Abs(drawSurface - value) < 0.00001f) return;
drawSurface = MathHelper.Clamp(value, rect.Y - rect.Height, rect.Y);
update = true;
}
}
public float WorldSurface
{
get { return Submarine == null ? surface : surface + Submarine.Position.Y; }
}
public float WaterVolume
{
get { return waterVolume; }
@@ -139,7 +152,7 @@ namespace Barotrauma
}
}
[Serialize(90.0f, true)]
[Serialize(100000.0f, true)]
public float Oxygen
{
get { return oxygen; }
@@ -177,10 +190,7 @@ namespace Barotrauma
get { return waveVel; }
}
public List<FireSource> FireSources
{
get { return fireSources; }
}
public List<FireSource> FireSources { get; private set; }
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
: this (prefab, rectangle, Submarine.MainSub)
@@ -195,11 +205,11 @@ namespace Barotrauma
OxygenPercentage = 100.0f;
fireSources = new List<FireSource>();
FireSources = new List<FireSource>();
properties = SerializableProperty.GetProperties(this);
int arraySize = (rectangle.Width / WaveWidth + 1);
int arraySize = (int)Math.Ceiling((float)rectangle.Width / WaveWidth + 1);
waveY = new float[arraySize];
waveVel = new float[arraySize];
@@ -255,15 +265,20 @@ namespace Barotrauma
public override MapEntity Clone()
{
return new Hull(MapEntityPrefab.Find("Hull"), rect, Submarine);
return new Hull(MapEntityPrefab.Find(null, "hull"), rect, Submarine);
}
public static EntityGrid GenerateEntityGrid(Rectangle worldRect)
{
var newGrid = new EntityGrid(worldRect, 200.0f);
EntityGrids.Add(newGrid);
return newGrid;
}
public static EntityGrid GenerateEntityGrid(Submarine submarine)
{
var newGrid = new EntityGrid(submarine, 200.0f);
entityGrids.Add(newGrid);
EntityGrids.Add(newGrid);
foreach (Hull hull in hullList)
{
if (hull.Submarine == submarine) newGrid.InsertEntity(hull);
@@ -271,9 +286,27 @@ namespace Barotrauma
return newGrid;
}
public override void OnMapLoaded()
{
CeilingHeight = Rect.Height;
Body lowerPickedBody = Submarine.PickBody(SimPosition, SimPosition - new Vector2(0.0f, ConvertUnits.ToSimUnits(rect.Height / 2.0f + 0.1f)), null, Physics.CollisionWall);
if (lowerPickedBody != null)
{
Vector2 lowerPickedPos = Submarine.LastPickedPosition;
if (Submarine.PickBody(SimPosition, SimPosition + new Vector2(0.0f, ConvertUnits.ToSimUnits(rect.Height / 2.0f + 0.1f)), null, Physics.CollisionWall) != null)
{
Vector2 upperPickedPos = Submarine.LastPickedPosition;
CeilingHeight = ConvertUnits.ToDisplayUnits(upperPickedPos.Y - lowerPickedPos.Y);
}
}
}
public void AddToGrid(Submarine submarine)
{
foreach (EntityGrid grid in entityGrids)
foreach (EntityGrid grid in EntityGrids)
{
if (grid.Submarine != submarine) continue;
@@ -303,13 +336,13 @@ namespace Barotrauma
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
if (Submarine==null || !Submarine.Loading)
if (Submarine == null || !Submarine.Loading)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
surface = rect.Y - rect.Height + WaterVolume / rect.Width;
surface = drawSurface = rect.Y - rect.Height + WaterVolume / rect.Width;
Pressure = surface;
}
@@ -324,24 +357,16 @@ namespace Barotrauma
Gap.UpdateHulls();
}
List<FireSource> fireSourcesToRemove = new List<FireSource>(fireSources);
List<FireSource> fireSourcesToRemove = new List<FireSource>(FireSources);
foreach (FireSource fireSource in fireSourcesToRemove)
{
fireSource.Remove();
}
fireSources.Clear();
#if CLIENT
if (soundIndex > -1)
{
Sounds.SoundManager.Stop(soundIndex);
soundIndex = -1;
}
#endif
FireSources.Clear();
if (entityGrids != null)
if (EntityGrids != null)
{
foreach (EntityGrid entityGrid in entityGrids)
foreach (EntityGrid entityGrid in EntityGrids)
{
entityGrid.RemoveEntity(this);
}
@@ -353,30 +378,22 @@ namespace Barotrauma
base.Remove();
hullList.Remove(this);
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
if (Submarine != null && !Submarine.Loading && !Submarine.Unloading)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
List<FireSource> fireSourcesToRemove = new List<FireSource>(fireSources);
List<FireSource> fireSourcesToRemove = new List<FireSource>(FireSources);
foreach (FireSource fireSource in fireSourcesToRemove)
{
fireSource.Remove();
}
fireSources.Clear();
#if CLIENT
if (soundIndex > -1)
{
Sounds.SoundManager.Stop(soundIndex);
soundIndex = -1;
}
#endif
FireSources.Clear();
if (entityGrids != null)
if (EntityGrids != null)
{
foreach (EntityGrid entityGrid in entityGrids)
foreach (EntityGrid entityGrid in EntityGrids)
{
entityGrid.RemoveEntity(this);
}
@@ -385,7 +402,7 @@ namespace Barotrauma
public void AddFireSource(FireSource fireSource)
{
fireSources.Add(fireSource);
FireSources.Add(fireSource);
if (GameMain.Server != null && !IdFreed) GameMain.Server.CreateEntityEvent(this);
}
@@ -396,7 +413,7 @@ namespace Barotrauma
Oxygen -= OxygenDetoriationSpeed * deltaTime;
FireSource.UpdateAll(fireSources, deltaTime);
FireSource.UpdateAll(FireSources, deltaTime);
aiTarget.SightRange = Submarine == null ? 0.0f : Math.Max(Submarine.Velocity.Length() * 500.0f, 500.0f);
aiTarget.SoundRange -= deltaTime * 1000.0f;
@@ -424,20 +441,34 @@ namespace Barotrauma
lethalPressure = 0.0f;
return;
}
surface = Math.Max(MathHelper.Lerp(
surface,
rect.Y - rect.Height + WaterVolume / rect.Width,
deltaTime * 10.0f), rect.Y - rect.Height);
//interpolate the position of the rendered surface towards the "target surface"
drawSurface = Math.Max(MathHelper.Lerp(
drawSurface,
rect.Y - rect.Height + WaterVolume / rect.Width,
deltaTime * 10.0f), rect.Y - rect.Height);
float surfaceY = rect.Y - rect.Height + WaterVolume / rect.Width;
for (int i = 0; i < waveY.Length; i++)
{
//apply velocity
waveY[i] = waveY[i] + waveVel[i];
if (surfaceY + waveY[i] > rect.Y)
//if the wave attempts to go "through" the top of the hull, make it bounce back
if (surface + waveY[i] > rect.Y)
{
waveY[i] -= (surfaceY + waveY[i]) - rect.Y;
float excess = (surface + waveY[i]) - rect.Y;
waveY[i] -= excess;
waveVel[i] = waveVel[i] * -0.5f;
}
else if (surfaceY + waveY[i] < rect.Y - rect.Height)
//if the wave attempts to go "through" the bottom of the hull, make it bounce back
else if (surface + waveY[i] < rect.Y - rect.Height)
{
waveY[i] -= (surfaceY + waveY[i]) - (rect.Y - rect.Height);
float excess = (surface + waveY[i]) - (rect.Y - rect.Height);
waveY[i] -= excess;
waveVel[i] = waveVel[i] * -0.5f;
}
@@ -446,34 +477,87 @@ namespace Barotrauma
waveVel[i] = waveVel[i] + a;
}
//apply spread (two iterations)
for (int j = 0; j < 2; j++)
{
for (int i = 1; i < waveY.Length - 1; i++)
{
leftDelta[i] = WaveSpread * (waveY[i] - waveY[i - 1]);
waveVel[i - 1] = waveVel[i - 1] + leftDelta[i];
waveVel[i - 1] += leftDelta[i];
rightDelta[i] = WaveSpread * (waveY[i] - waveY[i + 1]);
waveVel[i + 1] = waveVel[i + 1] + rightDelta[i];
waveVel[i + 1] += rightDelta[i];
}
for (int i = 1; i < waveY.Length - 1; i++)
{
waveY[i - 1] = waveY[i - 1] + leftDelta[i];
waveY[i + 1] = waveY[i + 1] + rightDelta[i];
waveY[i - 1] += leftDelta[i];
waveY[i + 1] += rightDelta[i];
}
}
//interpolate the position of the rendered surface towards the "target surface"
surface = Math.Max(MathHelper.Lerp(surface, surfaceY, deltaTime*10.0f), rect.Y - rect.Height);
//make waves propagate through horizontal gaps
foreach (Gap gap in ConnectedGaps)
{
if (!gap.IsRoomToRoom || !gap.IsHorizontal || gap.Open <= 0.0f) continue;
if (surface > gap.Rect.Y || surface < gap.Rect.Y - gap.Rect.Height) continue;
Hull hull2 = this == gap.linkedTo[0] as Hull ? (Hull)gap.linkedTo[1] : (Hull)gap.linkedTo[0];
float otherSurfaceY = hull2.surface;
if (otherSurfaceY > gap.Rect.Y || otherSurfaceY < gap.Rect.Y - gap.Rect.Height) continue;
float surfaceDiff = (surface - otherSurfaceY) * gap.Open;
if (this != gap.linkedTo[0] as Hull)
{
//the first hull linked to the gap handles the wave propagation,
//the second just updates the surfaces to the same level
if (surfaceDiff < 32.0f)
{
hull2.waveY[hull2.waveY.Length - 1] = surfaceDiff * 0.5f;
waveY[0] = -surfaceDiff * 0.5f;
}
continue;
}
for (int j = 0; j < 2; j++)
{
int i = waveY.Length - 1;
leftDelta[i] = WaveSpread * (waveY[i] - waveY[i - 1]);
waveVel[i - 1] += leftDelta[i];
rightDelta[i] = WaveSpread * (waveY[i] - hull2.waveY[0] + surfaceDiff);
hull2.waveVel[0] += rightDelta[i];
i = 0;
hull2.leftDelta[i] = WaveSpread * (hull2.waveY[i] - waveY[waveY.Length - 1] - surfaceDiff);
waveVel[waveVel.Length - 1] += hull2.leftDelta[i];
hull2.rightDelta[i] = WaveSpread * (hull2.waveY[i] - hull2.waveY[i + 1]);
hull2.waveVel[i + 1] += hull2.rightDelta[i];
}
if (surfaceDiff < 32.0f)
{
//update surfaces to the same level
hull2.waveY[0] = surfaceDiff * 0.5f;
waveY[waveY.Length - 1] = -surfaceDiff * 0.5f;
}
else
{
hull2.waveY[0] += rightDelta[waveY.Length - 1];
waveY[waveY.Length - 1] += hull2.leftDelta[0];
}
}
if (waterVolume < Volume)
{
LethalPressure -= 10.0f * deltaTime;
if (WaterVolume <= 0.0f)
{
//wait for the surface to be lerped back to bottom and the waves to settle until disabling update
if (surface > rect.Y - rect.Height + 1) return;
if (drawSurface > rect.Y - rect.Height + 1) return;
for (int i = 1; i < waveY.Length - 1; i++)
{
if (waveY[i] > 0.1f) return;
@@ -500,26 +584,25 @@ namespace Barotrauma
public void Extinguish(float deltaTime, float amount, Vector2 position)
{
for (int i = fireSources.Count - 1; i >= 0; i-- )
for (int i = FireSources.Count - 1; i >= 0; i-- )
{
fireSources[i].Extinguish(deltaTime, amount, position);
FireSources[i].Extinguish(deltaTime, amount, position);
}
}
public void RemoveFire(FireSource fire)
{
fireSources.Remove(fire);
FireSources.Remove(fire);
if (GameMain.Server != null) GameMain.Server.CreateEntityEvent(this);
}
public List<Hull> GetConnectedHulls(int? searchDepth)
public IEnumerable<Hull> GetConnectedHulls(int? searchDepth)
{
return GetAdjacentHulls(new List<Hull>(), 0, searchDepth);
return GetAdjacentHulls(new HashSet<Hull>(), 0, searchDepth);
}
private List<Hull> GetAdjacentHulls(List<Hull> connectedHulls, int steps, int? searchDepth)
private HashSet<Hull> GetAdjacentHulls(HashSet<Hull> connectedHulls, int steps, int? searchDepth)
{
connectedHulls.Add(this);
@@ -529,31 +612,103 @@ namespace Barotrauma
{
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
{
Hull hull = g.linkedTo[i] as Hull;
if (hull != null && !connectedHulls.Contains(hull))
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
{
hull.GetAdjacentHulls(connectedHulls, steps++, searchDepth);
}
}
}
}
return connectedHulls;
}
/// <summary>
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
/// Uses a greedy algo and may not use the most optimal path. Returns float.MaxValue if no path is found.
/// </summary>
public float GetApproximateDistance(Hull target, float maxDistance)
{
return GetApproximateHullDistance(new HashSet<Hull>(), target, 0.0f, maxDistance);
}
private float GetApproximateHullDistance(HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance)
{
if (distance >= maxDistance) return float.MaxValue;
if (this == target) return distance;
connectedHulls.Add(this);
foreach (Gap g in ConnectedGaps)
{
if (g.ConnectedDoor != null)
{
//gap blocked if the door is not open or the predicted state is not open
if (!g.ConnectedDoor.IsOpen || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
{
if (g.ConnectedDoor.OpenState < 0.1f) continue;
}
}
else if (g.Open <= 0.0f)
{
continue;
}
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
{
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
{
float dist = hull.GetApproximateHullDistance(connectedHulls, target, distance + Vector2.Distance(g.Position, this.Position), maxDistance);
if (dist < float.MaxValue) return dist;
}
}
}
return float.MaxValue;
}
//returns the water block which contains the point (or null if it isn't inside any)
public static Hull FindHull(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
{
if (entityGrids == null) return null;
if (EntityGrids == null) return null;
if (guess != null)
{
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive)) return guess;
}
var entities = EntityGrid.GetEntities(entityGrids, position, useWorldCoordinates);
foreach (Hull hull in entities)
foreach (EntityGrid entityGrid in EntityGrids)
{
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position, inclusive)) return hull;
if (entityGrid.Submarine != null && !entityGrid.Submarine.Loading)
{
System.Diagnostics.Debug.Assert(!entityGrid.Submarine.Removed);
Rectangle borders = entityGrid.Submarine.Borders;
if (useWorldCoordinates)
{
Vector2 worldPos = entityGrid.Submarine.WorldPosition;
borders.Location += new Point((int)worldPos.X, (int)worldPos.Y);
}
else
{
borders.Location += new Point((int)entityGrid.Submarine.HiddenSubPosition.X, (int)entityGrid.Submarine.HiddenSubPosition.Y);
}
const float padding = 128.0f;
if (position.X < borders.X - padding || position.X > borders.Right + padding ||
position.Y > borders.Y + padding || position.Y < borders.Y - borders.Height - padding)
{
continue;
}
}
Vector2 transformedPosition = position;
if (useWorldCoordinates && entityGrid.Submarine != null) transformedPosition -= entityGrid.Submarine.Position;
var entities = entityGrid.GetEntities(transformedPosition);
if (entities == null) continue;
foreach (Hull hull in entities)
{
if (Submarine.RectContains(hull.rect, transformedPosition, inclusive)) return hull;
}
}
return null;
@@ -615,13 +770,13 @@ namespace Barotrauma
{
if (other == this) return true;
if (other != null && other.Submarine==Submarine)
if (other != null && other.Submarine == Submarine)
{
bool retVal = false;
foreach (Gap g in ConnectedGaps)
{
if (g.ConnectedWall != null && g.ConnectedWall.CastShadow) continue;
List<Hull> otherHulls = Hull.hullList.FindAll(h => h.ConnectedGaps.Contains(g) && h!=this);
List<Hull> otherHulls = hullList.FindAll(h => h.ConnectedGaps.Contains(g) && h != this);
retVal = otherHulls.Any(h => h == other);
if (!retVal && allowIndirect) retVal = otherHulls.Any(h => h.CanSeeOther(other, false));
if (retVal) return true;
@@ -631,26 +786,73 @@ namespace Barotrauma
{
foreach (Gap g in ConnectedGaps)
{
if (g.ConnectedDoor != null && !hullList.Any(h => h.ConnectedGaps.Contains(g) && h!=this)) return true;
if (g.ConnectedDoor != null && !hullList.Any(h => h.ConnectedGaps.Contains(g) && h != this)) return true;
}
List<MapEntity> structures = MapEntity.mapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
List<MapEntity> structures = mapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
return structures.Any(st => !(st as Structure).CastShadow);
}
return false;
}
public string CreateRoomName()
{
List<string> roomItems = new List<string>();
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != this) continue;
if (item.GetComponent<Items.Components.Reactor>() != null) roomItems.Add("reactor");
if (item.GetComponent<Items.Components.Engine>() != null) roomItems.Add("engine");
if (item.GetComponent<Items.Components.Steering>() != null) roomItems.Add("steering");
if (item.GetComponent<Items.Components.Sonar>() != null) roomItems.Add("sonar");
if (item.HasTag("ballast")) roomItems.Add("ballast");
}
if (roomItems.Contains("reactor"))
return TextManager.Get("ReactorRoom");
else if (roomItems.Contains("engine"))
return TextManager.Get("EngineRoom");
else if (roomItems.Contains("steering") && roomItems.Contains("sonar"))
return TextManager.Get("CommandRoom");
else if (roomItems.Contains("ballast"))
return TextManager.Get("Ballast");
if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null))
{
return TextManager.Get("Airlock");
}
Rectangle subRect = Submarine.CalculateDimensions();
Alignment roomPos;
if (rect.Y - rect.Height / 2 > subRect.Y + subRect.Height * 0.66f)
roomPos = Alignment.Top;
else if (rect.Y - rect.Height / 2 > subRect.Y + subRect.Height * 0.33f)
roomPos = Alignment.CenterY;
else
roomPos = Alignment.Bottom;
if (rect.Center.X < subRect.X + subRect.Width * 0.33f)
roomPos |= Alignment.Left;
else if (rect.Center.X < subRect.X + subRect.Width * 0.66f)
roomPos |= Alignment.CenterX;
else
roomPos |= Alignment.Right;
return TextManager.Get("Sub" + roomPos.ToString());
}
public void ServerWrite(NetBuffer message, Client c, object[] extraData = null)
{
message.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
message.WriteRangedSingle(MathHelper.Clamp(OxygenPercentage, 0.0f, 100.0f), 0.0f, 100.0f, 8);
message.Write(fireSources.Count > 0);
if (fireSources.Count > 0)
message.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{
message.WriteRangedInteger(0, 16, Math.Min(fireSources.Count, 16));
for (int i = 0; i < Math.Min(fireSources.Count, 16); i++)
message.WriteRangedInteger(0, 16, Math.Min(FireSources.Count, 16));
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = fireSources[i];
var fireSource = FireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
@@ -686,12 +888,14 @@ namespace Barotrauma
rect.Y - rect.Height + (rect.Height * pos.Y));
size = size * rect.Width;
var newFire = i < fireSources.Count ? fireSources[i] : new FireSource(pos + Submarine.Position, null, true);
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
//ignore if the fire wasn't added to this room (invalid position)?
if (!fireSources.Contains(newFire))
if (!FireSources.Contains(newFire))
{
newFire.Remove();
continue;
@@ -699,13 +903,13 @@ namespace Barotrauma
}
}
while (fireSources.Count > fireSourceCount)
while (FireSources.Count > fireSourceCount)
{
fireSources[fireSources.Count - 1].Remove();
FireSources[FireSources.Count - 1].Remove();
}
}
public static void Load(XElement element, Submarine submarine)
public static Hull Load(XElement element, Submarine submarine)
{
Rectangle rect = Rectangle.Empty;
if (element.Attribute("rect") != null)
@@ -722,17 +926,29 @@ namespace Barotrauma
int.Parse(element.Attribute("height").Value));
}
Hull h = new Hull(MapEntityPrefab.Find("Hull"), rect, submarine);
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine)
{
waterVolume = element.GetAttributeFloat("pressure", 0.0f),
ID = (ushort)int.Parse(element.Attribute("ID").Value)
};
SerializableProperty.DeserializeProperties(hull, element);
if (element.Attribute("oxygen") == null) { hull.Oxygen = hull.Volume; }
h.waterVolume = element.GetAttributeFloat("pressure", 0.0f);
h.ID = (ushort)int.Parse(element.Attribute("ID").Value);
return hull;
}
public override XElement Save(XElement parentElement)
{
XElement element = new XElement("Hull");
if (Submarine == null)
{
string errorMsg = "Error - tried to save a hull that's not a part of any submarine.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Hull.Save:WorldHull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return null;
}
XElement element = new XElement("Hull");
element.Add
(
new XAttribute("ID", ID),
@@ -742,9 +958,8 @@ namespace Barotrauma
rect.Width + "," + rect.Height),
new XAttribute("water", waterVolume)
);
SerializableProperty.SerializeProperties(this, element);
parentElement.Add(element);
return element;
}
@@ -0,0 +1,163 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class ItemAssemblyPrefab : MapEntityPrefab
{
private readonly XElement configElement;
private readonly string configPath;
[Serialize(false, false)]
public bool HideInMenus { get; set; }
public List<Pair<MapEntityPrefab, Rectangle>> DisplayEntities
{
get;
private set;
}
public Rectangle Bounds;
public ItemAssemblyPrefab(string filePath)
{
configPath = filePath;
XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc == null || doc.Root == null) return;
name = doc.Root.GetAttributeString("name", "");
identifier = doc.Root.GetAttributeString("identifier", null) ?? name.ToLowerInvariant().Replace(" ", "");
configElement = doc.Root;
Category = MapEntityCategory.ItemAssembly;
SerializableProperty.DeserializeProperties(this, configElement);
int minX = int.MaxValue, minY = int.MaxValue;
int maxX = int.MinValue, maxY = int.MinValue;
DisplayEntities = new List<Pair<MapEntityPrefab, Rectangle>>();
foreach (XElement entityElement in doc.Root.Elements())
{
string identifier = entityElement.GetAttributeString("identifier", "");
MapEntityPrefab mapEntity = List.Find(p => p.Identifier == identifier);
if (mapEntity == null)
{
string entityName = entityElement.GetAttributeString("name", "");
mapEntity = List.Find(p => p.Name == entityName);
}
Rectangle rect = entityElement.GetAttributeRect("rect", Rectangle.Empty);
if (mapEntity != null && !entityElement.GetAttributeBool("hideinassemblypreview", false))
{
DisplayEntities.Add(new Pair<MapEntityPrefab, Rectangle>(mapEntity, rect));
minX = Math.Min(minX, rect.X);
minY = Math.Min(minY, rect.Y - rect.Height);
maxX = Math.Max(maxX, rect.Right);
maxY = Math.Max(maxY, rect.Y);
}
}
Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
List.Add(this);
}
public static void Remove(string filePath)
{
var matchingAssembly = List.Find(prefab =>
prefab is ItemAssemblyPrefab assemblyPrefab &&
assemblyPrefab.configPath == filePath);
if (matchingAssembly != null)
{
List.Remove(matchingAssembly);
}
}
protected override void CreateInstance(Rectangle rect)
{
CreateInstance(rect.Location.ToVector2(), Submarine.MainSub);
}
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub)
{
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, configPath);
if (entities.Count == 0) return entities;
Vector2 offset = sub == null ? Vector2.Zero : sub.HiddenSubPosition;
foreach (MapEntity me in entities)
{
me.Move(position);
Item item = me as Item;
if (item == null) continue;
Wire wire = item.GetComponent<Wire>();
if (wire != null) wire.MoveNodes(position - offset);
}
MapEntity.MapLoaded(entities, true);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
{
MapEntity.SelectedList.Clear();
MapEntity.SelectedList.AddRange(entities);
}
#endif
return entities;
}
public void Delete()
{
List.Remove(this);
if (File.Exists(configPath))
{
try
{
File.Delete(configPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Deleting item assembly \"" + name + "\" failed.", e);
}
}
}
public static void LoadAll()
{
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Loading item assembly prefabs: ");
}
List<string> itemAssemblyFiles = new List<string>();
//find assembly files in the item assembly folder
string directoryPath = Path.Combine("Content", "Items", "Assemblies");
if (Directory.Exists(directoryPath))
{
itemAssemblyFiles.AddRange(Directory.GetFiles(directoryPath));
}
//find assembly files in selected content packages
foreach (ContentPackage cp in GameMain.Config.SelectedContentPackages)
{
foreach (string filePath in cp.GetFilesOfType(ContentType.ItemAssembly))
{
//ignore files that have already been added (= file saved to item assembly folder)
if (itemAssemblyFiles.Any(f => Path.GetFullPath(f) == Path.GetFullPath(filePath))) { continue; }
itemAssemblyFiles.Add(filePath);
}
}
foreach (string file in itemAssemblyFiles)
{
new ItemAssemblyPrefab(file);
}
}
}
}
@@ -1,359 +0,0 @@
#if CLIENT
using Barotrauma.Particles;
#endif
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma
{
partial class BackgroundSprite
{
public readonly BackgroundSpritePrefab Prefab;
public Vector3 Position;
public float Scale;
public float Rotation;
public LevelTrigger Trigger;
public BackgroundSprite(BackgroundSpritePrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
{
this.Prefab = prefab;
this.Position = position;
this.Scale = scale;
this.Rotation = rotation;
if (prefab.LevelTriggerElement != null)
{
Vector2 triggerPosition = prefab.LevelTriggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
if (rotation != 0.0f)
{
var ca = (float)Math.Cos(rotation);
var sa = (float)Math.Sin(rotation);
triggerPosition = new Vector2(
ca * triggerPosition.X + sa * triggerPosition.Y,
-sa * triggerPosition.X + ca * triggerPosition.Y);
}
this.Trigger = new LevelTrigger(prefab.LevelTriggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale);
}
#if CLIENT
if (prefab.ParticleEmitterPrefabs != null)
{
ParticleEmitters = new List<ParticleEmitter>();
foreach (ParticleEmitterPrefab emitterPrefab in prefab.ParticleEmitterPrefabs)
{
ParticleEmitters.Add(new ParticleEmitter(emitterPrefab));
}
}
if (prefab.SoundElement != null)
{
Sound = Sound.Load(prefab.SoundElement, true);
}
#endif
}
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
{
Vector2 emitterPos = localPosition * Scale;
if (Rotation != 0.0f || Prefab.SwingAmount != 0.0f)
{
float rot = Rotation + swingState * Prefab.SwingAmount;
var ca = (float)Math.Cos(rot);
var sa = (float)Math.Sin(rot);
emitterPos = new Vector2(
ca * emitterPos.X + sa * emitterPos.Y,
-sa * emitterPos.X + ca * emitterPos.Y);
}
return new Vector2(Position.X, Position.Y) + emitterPos;
}
}
partial class BackgroundSpriteManager
{
const int GridSize = 2000;
private List<BackgroundSpritePrefab> prefabs = new List<BackgroundSpritePrefab>();
private List<BackgroundSprite> sprites;
private List<BackgroundSprite>[,] spriteGrid;
private float swingTimer, swingState;
public BackgroundSpriteManager(string configPath)
{
LoadConfig(configPath);
}
public BackgroundSpriteManager(List<string> files)
{
foreach (var file in files)
{
LoadConfig(file);
}
}
private void LoadConfig(string configPath)
{
try
{
XDocument doc = XMLExtensions.TryLoadXml(configPath);
if (doc == null || doc.Root == null) return;
foreach (XElement element in doc.Root.Elements())
{
prefabs.Add(new BackgroundSpritePrefab(element));
}
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundSprites from {0}", configPath), e);
}
}
public void PlaceSprites(Level level, int amount)
{
spriteGrid = new List<BackgroundSprite>[
(int)Math.Ceiling(level.Size.X / GridSize),
(int)Math.Ceiling((level.Size.Y - level.BottomPos) / GridSize)];
sprites = new List<BackgroundSprite>();
for (int i = 0 ; i < amount; i++)
{
BackgroundSpritePrefab prefab = GetRandomPrefab(level.GenerationParams.Name);
Vector2 edgeNormal = Vector2.One;
Vector2? pos = FindSpritePosition(level, prefab, out GraphEdge selectedEdge, out edgeNormal);
if (pos == null) continue;
float rotation = 0.0f;
if (prefab.AlignWithSurface)
{
rotation = MathUtils.VectorToAngle(new Vector2(edgeNormal.Y, edgeNormal.X));
}
float randomRot = Rand.Range(prefab.RandomRotation.X, prefab.RandomRotation.Y, Rand.RandSync.Server);
rotation += level.Mirrored ? -randomRot : randomRot;
var newSprite = new BackgroundSprite(prefab,
new Vector3((Vector2)pos, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.Scale.X, prefab.Scale.Y, Rand.RandSync.Server), rotation);
//calculate the positions of the corners of the rotated sprite
Vector2 halfSize = newSprite.Prefab.Sprite.size * newSprite.Scale / 2;
var spriteCorners = new List<Vector2>
{
-halfSize, new Vector2(-halfSize.X, halfSize.Y),
halfSize, new Vector2(halfSize.X, -halfSize.Y)
};
Vector2 pivotOffset = newSprite.Prefab.Sprite.Origin * newSprite.Scale - halfSize;
pivotOffset.X = -pivotOffset.X;
pivotOffset = new Vector2(
(float)(pivotOffset.X * Math.Cos(-rotation) - pivotOffset.Y * Math.Sin(-rotation)),
(float)(pivotOffset.X * Math.Sin(-rotation) + pivotOffset.Y * Math.Cos(-rotation)));
for (int j = 0; j < 4; j++)
{
spriteCorners[j] = new Vector2(
(float)(spriteCorners[j].X * Math.Cos(-rotation) - spriteCorners[j].Y * Math.Sin(-rotation)),
(float)(spriteCorners[j].X * Math.Sin(-rotation) + spriteCorners[j].Y * Math.Cos(-rotation)));
spriteCorners[j] += pos.Value + pivotOffset;
}
float minX = spriteCorners.Min(c => c.X) - newSprite.Position.Z;
float maxX = spriteCorners.Max(c => c.X) + newSprite.Position.Z;
float minY = spriteCorners.Min(c => c.Y) - newSprite.Position.Z - level.BottomPos;
float maxY = spriteCorners.Max(c => c.Y) + newSprite.Position.Z - level.BottomPos;
#if CLIENT
if (newSprite.ParticleEmitters != null)
{
foreach (ParticleEmitter emitter in newSprite.ParticleEmitters)
{
Rectangle particleBounds = emitter.CalculateParticleBounds(pos.Value);
minX = Math.Min(minX, particleBounds.X);
maxX = Math.Max(maxX, particleBounds.Right);
minY = Math.Min(minY, particleBounds.Y - level.BottomPos);
maxY = Math.Max(maxY, particleBounds.Bottom - level.BottomPos);
}
}
#endif
sprites.Add(newSprite);
int xStart = (int)Math.Floor(minX / GridSize);
int xEnd = (int)Math.Floor(maxX / GridSize);
if (xEnd < 0 || xStart >= spriteGrid.GetLength(0)) continue;
int yStart = (int)Math.Floor(minY / GridSize);
int yEnd = (int)Math.Floor(maxY / GridSize);
if (yEnd < 0 || yStart >= spriteGrid.GetLength(1)) continue;
xStart = Math.Max(xStart, 0);
xEnd = Math.Min(xEnd, spriteGrid.GetLength(0) - 1);
yStart = Math.Max(yStart, 0);
yEnd = Math.Min(yEnd, spriteGrid.GetLength(1) - 1);
for (int x = xStart; x <= xEnd; x++)
{
for (int y = yStart; y <= yEnd; y++)
{
if (spriteGrid[x, y] == null) spriteGrid[x, y] = new List<BackgroundSprite>();
spriteGrid[x, y].Add(newSprite);
}
}
}
}
private Vector2? FindSpritePosition(Level level, BackgroundSpritePrefab prefab, out GraphEdge closestEdge, out Vector2 edgeNormal)
{
closestEdge = null;
edgeNormal = Vector2.One;
Vector2 randomPos = new Vector2(
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
if (level.Mirrored) randomPos.X = level.Size.X - randomPos.X;
if (prefab.SpawnPos == BackgroundSpritePrefab.SpawnPosType.None) return randomPos;
List<GraphEdge> edges = new List<GraphEdge>();
List<Vector2> normals = new List<Vector2>();
System.Diagnostics.Debug.Assert(level.ExtraWalls.Length == 1);
List<VoronoiCell> cells = new List<VoronoiCell>();
if (prefab.SpawnPos.HasFlag(BackgroundSpritePrefab.SpawnPosType.Wall)) cells.AddRange(level.GetCells(randomPos));
if (prefab.SpawnPos.HasFlag(BackgroundSpritePrefab.SpawnPosType.SeaFloor)) cells.AddRange(level.ExtraWalls[0].Cells);
//make sure the cells are in the same order regardless of whether the level is mirrored or not
cells.Sort((c1, c2) => { return level.Mirrored ? Math.Sign(c1.Center.X - c2.Center.X) : -Math.Sign(c1.Center.X - c2.Center.X); });
if (cells.Any())
{
VoronoiCell cell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)];
foreach (GraphEdge edge in cell.edges)
{
if (!edge.isSolid || edge.OutsideLevel) continue;
Vector2 normal = edge.GetNormal(cell);
if (prefab.Alignment.HasFlag(Alignment.Bottom) && normal.Y < -0.5f)
{
edges.Add(edge);
}
else if (prefab.Alignment.HasFlag(Alignment.Top) && normal.Y > 0.5f)
{
edges.Add(edge);
}
else if (prefab.Alignment.HasFlag(Alignment.Left) && normal.X < -0.5f)
{
edges.Add(edge);
}
else if (prefab.Alignment.HasFlag(Alignment.Right) && normal.X > 0.5f)
{
edges.Add(edge);
}
else
{
continue;
}
normals.Add(normal);
}
}
if (prefab.SpawnPos.HasFlag(BackgroundSpritePrefab.SpawnPosType.RuinWall))
{
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
Rectangle expandedArea = ruin.Area;
expandedArea.Inflate(ruin.Area.Width, ruin.Area.Height);
if (!expandedArea.Contains(randomPos)) continue;
foreach (var ruinShape in ruin.RuinShapes)
{
foreach (var wall in ruinShape.Walls)
{
if (!prefab.Alignment.HasFlag(ruinShape.GetLineAlignment(wall))) continue;
edges.Add(new GraphEdge(wall.A, wall.B));
normals.Add((wall.A + wall.B) / 2.0f - ruinShape.Center);
}
}
}
}
if (!edges.Any()) return null;
int index = Rand.Int(edges.Count, Rand.RandSync.Server);
closestEdge = edges[index];
edgeNormal = normals[index];
float length = Vector2.Distance(closestEdge.point1, closestEdge.point2);
Vector2 dir = (closestEdge.point1 - closestEdge.point2) / length;
float normalizedPos = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
if (level.Mirrored) normalizedPos = 1.0f - normalizedPos;
return Vector2.Lerp(closestEdge.point2 + dir * prefab.Sprite.size.X / 2.0f, closestEdge.point1 - dir * prefab.Sprite.size.X / 2.0f, normalizedPos);
}
public void Update(float deltaTime)
{
swingTimer += deltaTime;
swingState = (float)Math.Sin(swingTimer * 0.1f);
foreach (BackgroundSprite sprite in sprites)
{
sprite.Trigger?.Update(deltaTime);
}
UpdateProjSpecific(deltaTime);
}
partial void UpdateProjSpecific(float deltaTime);
private BackgroundSpritePrefab GetRandomPrefab(string levelType)
{
int totalCommonness = 0;
foreach (BackgroundSpritePrefab prefab in prefabs)
{
totalCommonness += prefab.GetCommonness(levelType);
}
float randomNumber = Rand.Int(totalCommonness+1, Rand.RandSync.Server);
foreach (BackgroundSpritePrefab prefab in prefabs)
{
if (randomNumber <= prefab.GetCommonness(levelType))
{
return prefab;
}
randomNumber -= prefab.GetCommonness(levelType);
}
return null;
}
}
}
@@ -1,127 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
partial class BackgroundSpritePrefab
{
[Flags]
public enum SpawnPosType
{
None = 0,
Wall = 1,
RuinWall = 2,
SeaFloor = 4
}
public readonly Alignment Alignment;
public readonly Vector2 DepthRange;
public readonly Sprite Sprite;
public readonly Vector2 Scale;
public SpawnPosType SpawnPos;
public readonly bool AlignWithSurface;
public readonly Vector2 RandomRotation;
public readonly float SwingAmount;
public readonly int Commonness;
public Dictionary<string, int> OverrideCommonness;
public readonly XElement LevelTriggerElement;
public BackgroundSpritePrefab(XElement element)
{
string alignmentStr = element.GetAttributeString("alignment", "");
if (string.IsNullOrEmpty(alignmentStr) || !Enum.TryParse(alignmentStr, out Alignment))
{
Alignment = Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right;
}
Commonness = element.GetAttributeInt("commonness", 1);
string[] spawnPosStrs = element.GetAttributeString("spawnpos", "Wall").Split(',');
foreach (string spawnPosStr in spawnPosStrs)
{
SpawnPosType parsedSpawnPos;
if (Enum.TryParse(spawnPosStr.Trim(), out parsedSpawnPos))
{
SpawnPos |= parsedSpawnPos;
}
}
Scale.X = element.GetAttributeFloat("minsize", 1.0f);
Scale.Y = element.GetAttributeFloat("maxsize", 1.0f);
DepthRange = element.GetAttributeVector2("depthrange", new Vector2(0.0f, 1.0f));
AlignWithSurface = element.GetAttributeBool("alignwithsurface", false);
RandomRotation = element.GetAttributeVector2("randomrotation", Vector2.Zero);
RandomRotation.X = MathHelper.ToRadians(RandomRotation.X);
RandomRotation.Y = MathHelper.ToRadians(RandomRotation.Y);
SwingAmount = MathHelper.ToRadians(element.GetAttributeFloat("swingamount", 0.0f));
OverrideCommonness = new Dictionary<string, int>();
foreach (XElement subElement in element.Elements())
{
switch(subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
Sprite = new Sprite(subElement);
break;
case "overridecommonness":
string levelType = subElement.GetAttributeString("leveltype", "");
if (!OverrideCommonness.ContainsKey(levelType))
{
OverrideCommonness.Add(levelType, subElement.GetAttributeInt("commonness", 1));
}
break;
case "leveltrigger":
case "trigger":
LevelTriggerElement = subElement;
break;
#if CLIENT
case "particleemitter":
if (ParticleEmitterPrefabs == null)
{
ParticleEmitterPrefabs = new List<Particles.ParticleEmitterPrefab>();
EmitterPositions = new List<Vector2>();
}
ParticleEmitterPrefabs.Add(new Particles.ParticleEmitterPrefab(subElement));
EmitterPositions.Add(subElement.GetAttributeVector2("position", Vector2.Zero));
break;
case "sound":
SoundElement = subElement;
SoundPosition = subElement.GetAttributeVector2("position", Vector2.Zero);
break;
#endif
}
}
}
public int GetCommonness(string levelType)
{
int commonness = 0;
if (!OverrideCommonness.TryGetValue(levelType, out commonness))
{
return Commonness;
}
return commonness;
}
}
}
@@ -13,161 +13,6 @@ namespace Barotrauma
{
static partial class CaveGenerator
{
public static List<VoronoiCell> CarveCave(List<VoronoiCell> cells, Vector2 startPoint, out List<VoronoiCell> newCells)
{
Voronoi voronoi = new Voronoi(1.0);
List<Vector2> sites = new List<Vector2>();
float siteInterval = 400.0f;
float siteVariance = siteInterval * 0.4f;
Vector4 edges = new Vector4(
cells.Min(x => x.edges.Min(e => e.point1.X)),
cells.Min(x => x.edges.Min(e => e.point1.Y)),
cells.Max(x => x.edges.Max(e => e.point1.X)),
cells.Max(x => x.edges.Max(e => e.point1.Y)));
edges.X -= siteInterval * 2;
edges.Y -= siteInterval * 2;
edges.Z += siteInterval * 2;
edges.W += siteInterval * 2;
Rectangle borders = new Rectangle((int)edges.X, (int)edges.Y, (int)(edges.Z - edges.X), (int)(edges.W - edges.Y));
for (float x = edges.X + siteInterval; x < edges.Z - siteInterval; x += siteInterval)
{
for (float y = edges.Y + siteInterval; y < edges.W - siteInterval; y += siteInterval)
{
if (Rand.Int(5, Rand.RandSync.Server) == 0) continue; //skip some positions to make the cells more irregular
sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, Rand.RandSync.Server));
}
}
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, edges.X, edges.Y, edges.Z, edges.W);
List<VoronoiCell>[,] cellGrid;
newCells = GraphEdgesToCells(graphEdges, borders, 1000, out cellGrid);
foreach (VoronoiCell cell in newCells)
{
//if the cell is at the edge of the graph, remove it
if (cell.edges.Any(e =>
e.point1.X == edges.X || e.point1.X == edges.Z ||
e.point1.Y == edges.Z || e.point1.Y == edges.W))
{
cell.CellType = CellType.Removed;
continue;
}
//remove cells that aren't inside any of the original "base cells"
if (cells.Any(c => c.IsPointInside(cell.Center))) continue;
foreach (GraphEdge edge in cell.edges)
{
//mark all the cells adjacent to the removed cell as edges of the cave
var adjacent = edge.AdjacentCell(cell);
if (adjacent != null && adjacent.CellType != CellType.Removed) adjacent.CellType = CellType.Edge;
}
cell.CellType = CellType.Removed;
}
newCells.RemoveAll(newCell => newCell.CellType == CellType.Removed);
//start carving from the edge cell closest to the startPoint
VoronoiCell startCell = null;
float closestDist = 0.0f;
foreach (VoronoiCell cell in newCells)
{
if (cell.CellType != CellType.Edge) continue;
float dist = Vector2.Distance(startPoint, cell.Center);
if (dist < closestDist || startCell == null)
{
startCell = cell;
closestDist = dist;
}
}
startCell.CellType = CellType.Path;
List<VoronoiCell> path = new List<VoronoiCell>() {startCell};
VoronoiCell pathCell = startCell;
for (int i = 0; i < newCells.Count / 2; i++)
{
var allowedNextCells = new List<VoronoiCell>();
foreach (GraphEdge edge in pathCell.edges)
{
var adjacent = edge.AdjacentCell(pathCell);
if (adjacent == null ||
adjacent.CellType == CellType.Removed ||
adjacent.CellType == CellType.Edge) continue;
allowedNextCells.Add(adjacent);
}
if (allowedNextCells.Count == 0)
{
if (i>5) break;
foreach (GraphEdge edge in pathCell.edges)
{
var adjacent = edge.AdjacentCell(pathCell);
if (adjacent == null ||
adjacent.CellType == CellType.Removed) continue;
allowedNextCells.Add(adjacent);
}
if (allowedNextCells.Count == 0) break;
}
//randomly pick one of the adjacent cells as the next cell
pathCell = allowedNextCells[Rand.Int(allowedNextCells.Count, Rand.RandSync.Server)];
//randomly take steps further away from the startpoint to make the cave expand further
if (Rand.Int(4, Rand.RandSync.Server) == 0)
{
float furthestDist = 0.0f;
foreach (VoronoiCell nextCell in allowedNextCells)
{
float dist = Vector2.Distance(startCell.Center, nextCell.Center);
if (dist > furthestDist || furthestDist == 0.0f)
{
furthestDist = dist;
pathCell = nextCell;
}
}
}
pathCell.CellType = CellType.Path;
path.Add(pathCell);
}
//make sure the tunnel is always wider than minPathWidth
float minPathWidth = 100.0f;
for (int i = 0; i < path.Count; i++)
{
var cell = path[i];
foreach (GraphEdge edge in cell.edges)
{
if (edge.point1 == edge.point2) continue;
if (Vector2.Distance(edge.point1, edge.point2) > minPathWidth) continue;
GraphEdge adjacentEdge = cell.edges.Find(e => e != edge && (e.point1 == edge.point1 || e.point2 == edge.point1));
var adjacentCell = adjacentEdge.AdjacentCell(cell);
if (i>0 && (adjacentCell.CellType == CellType.Path || adjacentCell.CellType == CellType.Edge)) continue;
adjacentCell.CellType = CellType.Path;
path.Add(adjacentCell);
}
}
return path;
}
public static List<VoronoiCell> GraphEdgesToCells(List<GraphEdge> graphEdges, Rectangle borders, float gridCellSize, out List<VoronoiCell>[,] cellGrid)
{
List<VoronoiCell> cells = new List<VoronoiCell>();
@@ -183,19 +28,19 @@ namespace Barotrauma
foreach (GraphEdge ge in graphEdges)
{
if (Vector2.DistanceSquared(ge.point1, ge.point2) < 0.001f) continue;
if (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.001f) continue;
for (int i = 0; i < 2; i++)
{
Site site = (i == 0) ? ge.site1 : ge.site2;
Site site = (i == 0) ? ge.Site1 : ge.Site2;
int x = (int)(Math.Floor((site.coord.x-borders.X) / gridCellSize));
int y = (int)(Math.Floor((site.coord.y-borders.Y) / gridCellSize));
int x = (int)(Math.Floor((site.Coord.X-borders.X) / gridCellSize));
int y = (int)(Math.Floor((site.Coord.Y-borders.Y) / gridCellSize));
x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0)-1);
y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1)-1);
VoronoiCell cell = cellGrid[x,y].Find(c => c.site == site);
VoronoiCell cell = cellGrid[x,y].Find(c => c.Site == site);
if (cell == null)
{
@@ -204,15 +49,15 @@ namespace Barotrauma
cells.Add(cell);
}
if (ge.cell1 == null)
if (ge.Cell1 == null)
{
ge.cell1 = cell;
ge.Cell1 = cell;
}
else
{
ge.cell2 = cell;
ge.Cell2 = cell;
}
cell.edges.Add(ge);
cell.Edges.Add(ge);
}
}
@@ -226,20 +71,19 @@ namespace Barotrauma
if (cell == null) return Vector2.UnitX;
CompareCCW compare = new CompareCCW(cell.Center);
if (compare.Compare(edge.point1, edge.point2) == -1)
if (compare.Compare(edge.Point1, edge.Point2) == -1)
{
var temp = edge.point1;
edge.point1 = edge.point2;
edge.point2 = temp;
var temp = edge.Point1;
edge.Point1 = edge.Point2;
edge.Point2 = temp;
}
Vector2 normal = Vector2.Zero;
normal = Vector2.Normalize(edge.point2 - edge.point1);
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.point2);
normal = Vector2.Normalize(edge.Point2 - edge.Point1);
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2);
normal = new Vector2(-normal.Y, normal.X);
if (Vector2.Dot(normal, diffToCell) < 0)
{
normal = -normal;
@@ -249,8 +93,8 @@ namespace Barotrauma
}
public static List<VoronoiCell> GeneratePath(
List<Vector2> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false, Vector2? gridOffset = null)
List<Point> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
{
var targetCells = new List<VoronoiCell>();
for (int i = 0; i < pathNodes.Count; i++)
@@ -259,7 +103,7 @@ namespace Barotrauma
int searchDepth = 2;
while (searchDepth < 5)
{
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth, gridOffset);
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth);
if (cellIndex > -1)
{
targetCells.Add(cells[cellIndex]);
@@ -302,22 +146,30 @@ namespace Barotrauma
int edgeIndex = 0;
allowedEdges.Clear();
foreach (GraphEdge edge in currentCell.edges)
foreach (GraphEdge edge in currentCell.Edges)
{
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
allowedEdges.Add(edge);
var adjacentCell = edge.AdjacentCell(currentCell);
if (limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y))
{
allowedEdges.Add(edge);
}
}
//steer towards target
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > wanderAmount || allowedEdges.Count == 0)
{
for (int i = 0; i < currentCell.edges.Count; i++)
double smallestDist = double.PositiveInfinity;
for (int i = 0; i < currentCell.Edges.Count; i++)
{
if (!MathUtils.LinesIntersect(currentCell.Center, targetCells[currentTargetIndex].Center,
currentCell.edges[i].point1, currentCell.edges[i].point2)) continue;
edgeIndex = i;
break;
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
double dist = MathUtils.Distance(
adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y,
targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
if (dist < smallestDist)
{
edgeIndex = i;
smallestDist = dist;
}
}
}
//choose random edge (ignoring ones where the adjacent cell is outside limits)
@@ -325,10 +177,10 @@ namespace Barotrauma
{
edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server);
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
edgeIndex = currentCell.Edges.IndexOf(allowedEdges[edgeIndex]);
}
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
currentCell = currentCell.Edges[edgeIndex].AdjacentCell(currentCell);
currentCell.CellType = CellType.Path;
pathCells.Add(currentCell);
@@ -349,10 +201,67 @@ namespace Barotrauma
return pathCells;
}
public static List<Body> GeneratePolygons(List<VoronoiCell> cells, Level level, out List<Vector2[]> renderTriangles, bool setSolid = true)
/// <summary>
/// Makes the cell rounder by subdividing the edges and offsetting them at the middle
/// </summary>
/// <param name="minEdgeLength">How small the individual subdivided edges can be (smaller values produce rounder shapes, but require more geometry)</param>
public static void RoundCell(VoronoiCell cell, float minEdgeLength = 500.0f, float roundingAmount = 0.5f, float irregularity = 0.1f)
{
List<GraphEdge> tempEdges = new List<GraphEdge>();
foreach (GraphEdge edge in cell.Edges)
{
if (!edge.IsSolid)
{
tempEdges.Add(edge);
continue;
}
List<Vector2> edgePoints = new List<Vector2>();
Vector2 edgeNormal = GetEdgeNormal(edge, cell);
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
Vector2 edgeDir = (edge.Point2 - edge.Point1);
for (int i = 0; i <= pointCount; i++)
{
if (i == 0)
{
edgePoints.Add(edge.Point1);
}
else if (i == pointCount)
{
edgePoints.Add(edge.Point2);
}
else
{
float centerF = 0.5f - Math.Abs(0.5f - (i / (float)pointCount));
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server);
edgePoints.Add(
edge.Point1 +
edgeDir * (i / (float)pointCount) -
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF);
}
}
for (int i = 0; i < pointCount; i++)
{
tempEdges.Add(new GraphEdge(edgePoints[i], edgePoints[i + 1])
{
Cell1 = edge.Cell1,
Cell2 = edge.Cell2,
IsSolid = edge.IsSolid,
Site1 = edge.Site1,
Site2 = edge.Site2,
OutsideLevel = edge.OutsideLevel
});
}
}
cell.Edges = tempEdges;
}
public static Body GeneratePolygons(List<VoronoiCell> cells, Level level, out List<Vector2[]> renderTriangles)
{
renderTriangles = new List<Vector2[]>();
var bodies = new List<Body>();
List<Vector2> tempVertices = new List<Vector2>();
List<Vector2> bodyPoints = new List<Vector2>();
@@ -363,7 +272,6 @@ namespace Barotrauma
BodyType = BodyType.Static,
CollisionCategories = Physics.CollisionLevel
};
bodies.Add(cellBody);
for (int n = cells.Count - 1; n >= 0; n-- )
{
@@ -371,19 +279,19 @@ namespace Barotrauma
bodyPoints.Clear();
tempVertices.Clear();
foreach (GraphEdge ge in cell.edges)
foreach (GraphEdge ge in cell.Edges)
{
if (Math.Abs(Vector2.Distance(ge.point1, ge.point2))<0.1f) continue;
if (!tempVertices.Contains(ge.point1)) tempVertices.Add(ge.point1);
if (!tempVertices.Contains(ge.point2)) tempVertices.Add(ge.point2);
VoronoiCell adjacentCell = ge.AdjacentCell(cell);
//if (adjacentCell!=null && cells.Contains(adjacentCell)) continue;
if (setSolid) ge.isSolid = (adjacentCell == null || !cells.Contains(adjacentCell));
if (!bodyPoints.Contains(ge.point1)) bodyPoints.Add(ge.point1);
if (!bodyPoints.Contains(ge.point2)) bodyPoints.Add(ge.point2);
if (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.01f) continue;
if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.Point1, v) < 1.0f))
{
tempVertices.Add(ge.Point1);
bodyPoints.Add(ge.Point1);
}
if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.Point2, v) < 1.0f))
{
tempVertices.Add(ge.Point2);
bodyPoints.Add(ge.Point2);
}
}
if (tempVertices.Count < 3 || bodyPoints.Count < 2)
@@ -408,7 +316,7 @@ namespace Barotrauma
for (int i = 0; i < bodyPoints.Count; i++)
{
cell.bodyVertices.Add(bodyPoints[i]);
cell.BodyVertices.Add(bodyPoints[i]);
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
}
@@ -419,12 +327,14 @@ namespace Barotrauma
for (int i = 0; i < triangles.Count; i++)
{
//don't create a triangle if any of the vertices are too close to each other
//don't create a triangle if the area of the triangle is too small
//(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties)
if (Vector2.DistanceSquared(triangles[i][0], triangles[i][1]) < 0.006f ||
Vector2.DistanceSquared(triangles[i][0], triangles[i][2]) < 0.006f ||
Vector2.DistanceSquared(triangles[i][1], triangles[i][2]) < 0.006f) continue;
Vector2 a = triangles[i][0];
Vector2 b = triangles[i][1];
Vector2 c = triangles[i][2];
float area = Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y)) / 2.0f;
if (area < 1.0f) continue;
Vertices bodyVertices = new Vertices(triangles[i]);
var newFixture = FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody);
newFixture.UserData = cell;
@@ -439,10 +349,27 @@ namespace Barotrauma
}
}
cell.body = cellBody;
cell.Body = cellBody;
}
return bodies;
return cellBody;
}
public static List<Vector2> CreateRandomChunk(float radius, int vertexCount, float radiusVariance)
{
Debug.Assert(radiusVariance < radius);
Debug.Assert(vertexCount >= 3);
List<Vector2> verts = new List<Vector2>();
float angleStep = MathHelper.TwoPi / vertexCount;
float angle = 0.0f;
for (int i = 0; i < vertexCount; i++)
{
verts.Add(new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) *
(radius + Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server)));
angle += angleStep;
}
return verts;
}
/// <summary>
@@ -451,7 +378,7 @@ namespace Barotrauma
/// </summary>
public static int FindCellIndex(Vector2 position,List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1, Vector2? offset = null)
{
float closestDist = 0.0f;
float closestDist = float.PositiveInfinity;
VoronoiCell closestCell = null;
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
@@ -466,8 +393,8 @@ namespace Barotrauma
{
for (int i = 0; i < cellGrid[x, y].Count; i++)
{
float dist = Vector2.Distance(cellGrid[x, y][i].Center, position);
if (closestDist != 0.0f && dist > closestDist) continue;
float dist = Vector2.DistanceSquared(cellGrid[x, y][i].Center, position);
if (dist > closestDist) continue;
closestDist = dist;
closestCell = cellGrid[x, y][i];
@@ -478,6 +405,32 @@ namespace Barotrauma
return cells.IndexOf(closestCell);
}
public static int FindCellIndex(Point position, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1)
{
int closestDist = int.MaxValue;
VoronoiCell closestCell = null;
int gridPosX = position.X / gridCellSize;
int gridPosY = position.Y / gridCellSize;
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
{
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
{
for (int i = 0; i < cellGrid[x, y].Count; i++)
{
int dist = MathUtils.DistanceSquared(
(int)cellGrid[x, y][i].Site.Coord.X, (int)cellGrid[x, y][i].Site.Coord.Y,
position.X, position.Y);
if (dist > closestDist) continue;
closestDist = dist;
closestCell = cellGrid[x, y][i];
}
}
}
return cells.IndexOf(closestCell);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -8,17 +8,10 @@ namespace Barotrauma
{
class Biome
{
public enum MapPlacement
{
Random = 1,
Center = 2,
Edge = 4
}
public readonly string Name;
public readonly string Description;
public readonly MapPlacement Placement;
public readonly List<int> AllowedZones = new List<int>();
public Biome(string name, string description)
{
@@ -30,46 +23,52 @@ namespace Barotrauma
{
Name = element.GetAttributeString("name", "Biome");
Description = element.GetAttributeString("description", "");
string[] placementsStrs = element.GetAttributeString("MapPlacement", "Default").Split(',');
foreach (string placementStr in placementsStrs)
{
MapPlacement parsedPlacement;
if (Enum.TryParse(placementStr.Trim(), out parsedPlacement))
{
Placement |= parsedPlacement;
}
}
string allowedZonesStr = element.GetAttributeString("AllowedZones", "1,2,3,4,5,6,7,8,9");
string[] zoneIndices = allowedZonesStr.Split(',');
for (int i = 0; i < zoneIndices.Length; i++)
{
int zoneIndex = -1;
if (!int.TryParse(zoneIndices[i].Trim(), out zoneIndex))
{
DebugConsole.ThrowError("Error in biome config \"" + Name + "\" - \"" + zoneIndices[i] + "\" is not a valid zone index.");
continue;
}
AllowedZones.Add(zoneIndex);
}
}
}
class LevelGenerationParams : ISerializableEntity
{
public static List<LevelGenerationParams> LevelParams
{
get { return levelParams; }
}
private static List<LevelGenerationParams> levelParams;
private static List<Biome> biomes;
public string Name
{
get;
private set;
}
private int minWidth, maxWidth, height;
private float width, height;
private Vector2 voronoiSiteInterval;
private Point voronoiSiteInterval;
//how much the sites are "scattered" on x- and y-axis
//if Vector2.Zero, the sites will just be placed in a regular grid pattern
private Vector2 voronoiSiteVariance;
private Point voronoiSiteVariance;
//how far apart the nodes of the main path can be
//x = min interval, y = max interval
private Vector2 mainPathNodeIntervalRange;
private Point mainPathNodeIntervalRange;
private int smallTunnelCount;
//x = min length, y = max length
private Vector2 smallTunnelLengthRange;
private Point smallTunnelLengthRange;
//how large portion of the bottom of the level should be "carved out"
//if 0.0f, the bottom will be completely solid (making the abyss unreachable)
@@ -77,36 +76,28 @@ namespace Barotrauma
private float bottomHoleProbability;
//the y-position of the ocean floor (= the position from which the bottom formations extend upwards)
private float seaFloorBaseDepth;
private int seaFloorBaseDepth;
//how much random variance there can be in the height of the formations
private float seaFloorVariance;
private int seaFloorVariance;
private int cellSubdivisionLength;
private float cellRoundingAmount;
private float cellIrregularity;
private int mountainCountMin, mountainCountMax;
private float mountainHeightMin, mountainHeightMax;
private int mountainHeightMin, mountainHeightMax;
private int ruinCount;
private float waterParticleScale;
//which biomes can this type of level appear in
private List<Biome> allowedBiomes = new List<Biome>();
public Color BackgroundColor
public IEnumerable<Biome> AllowedBiomes
{
get;
set;
}
public Color WallColor
{
get;
set;
}
[Serialize(1000, false)]
public int BackgroundSpriteAmount
{
get;
set;
get { return allowedBiomes; }
}
public Dictionary<string, SerializableProperty> SerializableProperties
@@ -115,83 +106,183 @@ namespace Barotrauma
set;
}
[Serialize(100000.0f, false)]
public float Width
[Serialize("27,30,36", true), Editable]
public Color AmbientLightColor
{
get { return width; }
set { width = Math.Max(value, 2000.0f); }
get;
set;
}
[Serialize(50000.0f, false)]
public float Height
[Serialize("20,40,50", true), Editable()]
public Color BackgroundTextureColor
{
get;
set;
}
[Serialize("20,40,50", true), Editable]
public Color BackgroundColor
{
get;
set;
}
[Serialize("255,255,255", true), Editable]
public Color WallColor
{
get;
set;
}
[Serialize(1000, true), Editable(MinValueInt = 0, MaxValueInt = 100000, ToolTip = "The total number of level objects (vegetation, vents, etc) in the level.")]
public int LevelObjectAmount
{
get;
set;
}
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
public int MinWidth
{
get { return minWidth; }
set { minWidth = Math.Max(value, 2000); }
}
[Serialize(100000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
public int MaxWidth
{
get { return maxWidth; }
set { maxWidth = Math.Max(value, 2000); }
}
[Serialize(50000, true), Editable(MinValueInt = 10000, MaxValueInt = 1000000)]
public int Height
{
get { return height; }
set { height = Math.Max(value, 2000.0f); }
set { height = Math.Max(value, 2000); }
}
public Vector2 VoronoiSiteInterval
[Serialize("3000, 3000", true), Editable(
ToolTip = "How far from each other voronoi sites are placed. " +
"Sites determine shape of the voronoi graph which the level walls are generated from. " +
"(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")]
public Point VoronoiSiteInterval
{
get { return voronoiSiteInterval; }
set
{
voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100.0f, width / 2);
voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100.0f, height / 2);
voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100, MinWidth / 2);
voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100, height / 2);
}
}
public Vector2 VoronoiSiteVariance
[Serialize("700,700", true), Editable(ToolTip = "How much random variation to apply to the positions of the voronoi sites on each axis. "+
"Small values produce roughly rectangular level walls. The larger the values are, the less uniform the shapes get.")]
public Point VoronoiSiteVariance
{
get { return voronoiSiteVariance; }
set
{
voronoiSiteVariance = new Vector2(
voronoiSiteVariance = new Point(
MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
}
}
[Serialize(1000, true), Editable(MinValueInt = 100, MaxValueInt = 10000, ToolTip = "The edges of the individual wall cells are subdivided into edges of this size. "
+ "Can be used in conjunction with the rounding values to make the cells rounder. Smaller values will make the cells look smoother, " +
"but make the level more performance-intensive as the number of polygons used in rendering and physics calculations increases.")]
public int CellSubdivisionLength
{
get { return cellSubdivisionLength; }
set
{
cellSubdivisionLength = Math.Max(value, 10);
}
}
public Vector2 MainPathNodeIntervalRange
[Serialize(0.5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, ToolTip = "How much the individual wall cells are rounded. "
+"Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
public float CellRoundingAmount
{
get { return cellRoundingAmount; }
set
{
cellRoundingAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Serialize(0.1f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, ToolTip = "How much random variance is applied to the edges of the cells. "
+ "Note that the final shape of the cells is also affected by the CellSubdivisionLength parameter.")]
public float CellIrregularity
{
get { return cellIrregularity; }
set
{
cellIrregularity = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Serialize("5000, 10000", true), Editable(ToolTip = "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
public Point MainPathNodeIntervalRange
{
get { return mainPathNodeIntervalRange; }
set
{
mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100.0f, width / 2);
mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, width / 2);
mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100, MinWidth / 2);
mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, MinWidth / 2);
}
}
[Serialize(5, false)]
[Serialize(5, true), Editable(ToolTip = "The number of small tunnels placed along the main path.")]
public int SmallTunnelCount
{
get { return smallTunnelCount; }
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
}
public Vector2 SmallTunnelLengthRange
[Serialize("5000, 10000", true), Editable(ToolTip = "The minimum and maximum length of small tunnels placed along the main path.")]
public Point SmallTunnelLengthRange
{
get { return smallTunnelLengthRange; }
set
{
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100.0f, width);
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, width);
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100, MinWidth);
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, MinWidth);
}
}
[Serialize(-300000.0f, false)]
public float SeaFloorDepth
[Serialize(100, true), Editable(MinValueInt = 0, MaxValueInt = 10000)]
public int ItemCount
{
get { return seaFloorBaseDepth; }
set { seaFloorBaseDepth = MathHelper.Clamp(value, Level.MaxEntityDepth, 0.0f); }
get;
set;
}
[Serialize(1000.0f, false)]
public float SeaFloorVariance
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
public int FloatingIceChunkCount
{
get;
set;
}
[Serialize(300000, true), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f, ToolTip = "How far below the level the sea floor is placed.")]
public int SeaFloorDepth
{
get { return seaFloorBaseDepth; }
set { seaFloorBaseDepth = MathHelper.Clamp(value, Level.MaxEntityDepth, 0); }
}
[Serialize(1000, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100000.0f, ToolTip = "Variance of the depth of the sea floor. Smaller values produce a smoother sea floor.")]
public int SeaFloorVariance
{
get { return seaFloorVariance; }
set { seaFloorVariance = value; }
}
[Serialize(0, false)]
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20, ToolTip = "The minimum number of mountains on the sea floor.")]
public int MountainCountMin
{
get { return mountainCountMin; }
@@ -201,7 +292,7 @@ namespace Barotrauma
}
}
[Serialize(0, false)]
[Serialize(0, true), Editable(MinValueInt = 0, MaxValueInt = 20, ToolTip = "The maximum number of mountains on the sea floor.")]
public int MountainCountMax
{
get { return mountainCountMax; }
@@ -210,9 +301,9 @@ namespace Barotrauma
mountainCountMax = Math.Max(value, 0);
}
}
[Serialize(1000.0f, false)]
public float MountainHeightMin
[Serialize(1000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000, ToolTip = "The minimum height of the mountains on the sea floor.")]
public int MountainHeightMin
{
get { return mountainHeightMin; }
set
@@ -220,9 +311,9 @@ namespace Barotrauma
mountainHeightMin = Math.Max(value, 0);
}
}
[Serialize(5000.0f, false)]
public float MountainHeightMax
[Serialize(5000, true), Editable(MinValueInt = 0, MaxValueInt = 1000000, ToolTip = "The maximum height of the mountains on the sea floor.")]
public int MountainHeightMax
{
get { return mountainHeightMax; }
set
@@ -231,19 +322,32 @@ namespace Barotrauma
}
}
[Serialize(1, false)]
[Serialize(1, true), Editable(MinValueInt = 0, MaxValueInt = 50, ToolTip = "The number of alien ruins in the level.")]
public int RuinCount
{
get { return ruinCount; }
set { ruinCount = MathHelper.Clamp(value, 0, 10); }
}
[Serialize(0.4f, false)]
[Serialize(0.4f, true), Editable(ToolTip = "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open.")]
public float BottomHoleProbability
{
get { return bottomHoleProbability; }
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
[Serialize(1.0f, true), Editable(ToolTip = "Scale of the water particle texture.")]
public float WaterParticleScale
{
get { return waterParticleScale; }
private set { waterParticleScale = Math.Max(value, 0.01f); }
}
public Sprite BackgroundSprite { get; private set; }
public Sprite BackgroundTopSprite { get; private set; }
public Sprite WallSprite { get; private set; }
public Sprite WallEdgeSprite { get; private set; }
public Sprite WaterParticles { get; private set; }
public static List<Biome> GetBiomes()
{
@@ -279,23 +383,8 @@ namespace Barotrauma
{
Name = element == null ? "default" : element.Name.ToString();
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
Vector3 colorVector = element.GetAttributeVector3("BackgroundColor", new Vector3(50, 46, 20));
BackgroundColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z);
colorVector = element.GetAttributeVector3("WallColor", new Vector3(255,255,255));
WallColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z);
VoronoiSiteInterval = element.GetAttributeVector2("VoronoiSiteInterval", new Vector2(3000, 3000));
VoronoiSiteVariance = element.GetAttributeVector2("VoronoiSiteVariance", new Vector2(voronoiSiteInterval.X, voronoiSiteInterval.Y) * 0.4f);
MainPathNodeIntervalRange = element.GetAttributeVector2("MainPathNodeIntervalRange", new Vector2(5000.0f, 10000.0f));
SmallTunnelLengthRange = element.GetAttributeVector2("SmallTunnelLengthRange", new Vector2(5000.0f, 10000.0f));
string biomeStr = element.GetAttributeString("biomes", "");
if (string.IsNullOrWhiteSpace(biomeStr))
{
allowedBiomes = new List<Biome>(biomes);
@@ -316,6 +405,28 @@ namespace Barotrauma
allowedBiomes.Add(matchingBiome);
}
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "background":
BackgroundSprite = new Sprite(subElement);
break;
case "backgroundtop":
BackgroundTopSprite = new Sprite(subElement);
break;
case "wall":
WallSprite = new Sprite(subElement);
break;
case "walledge":
WallEdgeSprite = new Sprite(subElement);
break;
case "waterparticles":
WaterParticles = new Sprite(subElement);
break;
}
}
}
public static void LoadPresets()
@@ -323,10 +434,10 @@ namespace Barotrauma
levelParams = new List<LevelGenerationParams>();
biomes = new List<Biome>();
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.LevelGenerationParameters);
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelGenerationParameters);
if (!files.Any())
{
files.Add("Content/Map/LevelGenerationParameters.xml");
files = new List<string>() { "Content/Map/LevelGenerationParameters.xml" };
}
List<XElement> biomeElements = new List<XElement>();
@@ -0,0 +1,119 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObject
{
public readonly LevelObjectPrefab Prefab;
public Vector3 Position;
public float Scale;
public float Rotation;
public LevelObjectPrefab ActivePrefab;
public PhysicsBody PhysicsBody
{
get;
private set;
}
public List<LevelTrigger> Triggers
{
get;
private set;
}
public bool NeedsNetworkSyncing
{
get { return Triggers.Any(t => t.NeedsNetworkSyncing); }
set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); }
}
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
{
Triggers = new List<LevelTrigger>();
ActivePrefab = Prefab = prefab;
Position = position;
Scale = scale;
Rotation = rotation;
if (prefab.PhysicsBodyElement != null)
{
PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
}
foreach (XElement triggerElement in prefab.LevelTriggerElements)
{
Vector2 triggerPosition = triggerElement.GetAttributeVector2("position", Vector2.Zero) * scale;
if (rotation != 0.0f)
{
var ca = (float)Math.Cos(rotation);
var sa = (float)Math.Sin(rotation);
triggerPosition = new Vector2(
ca * triggerPosition.X + sa * triggerPosition.Y,
-sa * triggerPosition.X + ca * triggerPosition.Y);
}
var newTrigger = new LevelTrigger(triggerElement, new Vector2(position.X, position.Y) + triggerPosition, -rotation, scale, prefab.Name);
int parentTriggerIndex = prefab.LevelTriggerElements.IndexOf(triggerElement.Parent);
if (parentTriggerIndex > -1) newTrigger.ParentTrigger = Triggers[parentTriggerIndex];
Triggers.Add(newTrigger);
}
InitProjSpecific();
}
partial void InitProjSpecific();
public Vector2 LocalToWorld(Vector2 localPosition, float swingState = 0.0f)
{
Vector2 emitterPos = localPosition * Scale;
if (Rotation != 0.0f || Prefab.SwingAmountRad != 0.0f)
{
float rot = Rotation + swingState * Prefab.SwingAmountRad;
var ca = (float)Math.Cos(rot);
var sa = (float)Math.Sin(rot);
emitterPos = new Vector2(
ca * emitterPos.X + sa * emitterPos.Y,
-sa * emitterPos.X + ca * emitterPos.Y);
}
return new Vector2(Position.X, Position.Y) + emitterPos;
}
public void Remove()
{
RemoveProjSpecific();
}
partial void RemoveProjSpecific();
public override string ToString()
{
return "LevelObject (" + ActivePrefab.Name + ")";
}
public void ServerWrite(NetBuffer msg, Client c)
{
for (int j = 0; j < Triggers.Count; j++)
{
if (!Triggers[j].UseNetworkSyncing) continue;
Triggers[j].ServerWrite(msg, c);
}
}
}
}
@@ -0,0 +1,415 @@
#if CLIENT
using Barotrauma.Particles;
#endif
using Barotrauma.Networking;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma
{
partial class LevelObjectManager : Entity, IServerSerializable
{
const int GridSize = 2000;
private List<LevelObject> objects;
private List<LevelObject>[,] objectGrid;
public LevelObjectManager() : base(null)
{
}
class SpawnPosition
{
public readonly GraphEdge GraphEdge;
public readonly Vector2 Normal;
public readonly LevelObjectPrefab.SpawnPosType SpawnPosType;
public readonly Alignment Alignment;
public readonly float Length;
public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
{
GraphEdge = graphEdge;
Normal = normal;
SpawnPosType = spawnPosType;
Alignment = alignment;
Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
}
public float GetSpawnProbability(LevelObjectPrefab prefab)
{
if (prefab.ClusteringAmount <= 0.0f) return Length;
float noise = (float)(
PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, prefab.ClusteringGroup) +
PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 20000.0f, GraphEdge.Point1.Y / 20000.0f, prefab.ClusteringGroup));
return Length * (float)Math.Pow(noise, prefab.ClusteringAmount);
}
}
public void PlaceObjects(Level level, int amount)
{
objectGrid = new List<LevelObject>[
level.Size.X / GridSize,
(level.Size.Y - level.BottomPos) / GridSize];
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
var levelCells = level.GetAllCells();
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor));
foreach (RuinGeneration.Ruin ruin in level.Ruins)
{
foreach (var ruinShape in ruin.RuinShapes)
{
foreach (var wall in ruinShape.Walls)
{
availableSpawnPositions.Add(new SpawnPosition(
new GraphEdge(wall.A, wall.B),
(wall.A + wall.B) / 2.0f - ruinShape.Center,
LevelObjectPrefab.SpawnPosType.RuinWall,
ruinShape.GetLineAlignment(wall)));
}
}
}
foreach (var posOfInterest in level.PositionsOfInterest)
{
if (posOfInterest.PositionType != Level.PositionType.MainPath) continue;
availableSpawnPositions.Add(new SpawnPosition(
new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
Vector2.UnitY,
LevelObjectPrefab.SpawnPosType.MainPath,
Alignment.Top));
}
objects = new List<LevelObject>();
for (int i = 0; i < amount; i++)
{
//get a random prefab and find a place to spawn it
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Name);
SpawnPosition spawnPosition = FindObjectPosition(availableSpawnPositions, level, prefab);
if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) continue;
float rotation = 0.0f;
if (prefab.AlignWithSurface && spawnPosition != null)
{
rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
}
rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.Server);
Vector2 position = Vector2.Zero;
Vector2 edgeDir = Vector2.UnitX;
if (spawnPosition == null)
{
position = new Vector2(
Rand.Range(0.0f, level.Size.X, Rand.RandSync.Server),
Rand.Range(0.0f, level.Size.Y, Rand.RandSync.Server));
}
else
{
edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.Server);
}
var newObject = new LevelObject(prefab,
new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation);
AddObject(newObject, level);
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
{
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
for (int j = 0; j < childCount; j++)
{
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
int prefabCount = matchingPrefabs.Count();
var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.Server));
if (childPrefab == null) continue;
Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server) * prefab.MinSurfaceWidth;
var childObject = new LevelObject(childPrefab,
new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.Server)),
Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.Server),
rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.Server));
AddObject(childObject, level);
}
}
}
}
private void AddObject(LevelObject newObject, Level level)
{
foreach (LevelTrigger trigger in newObject.Triggers)
{
trigger.OnTriggered += (levelTrigger, obj) =>
{
OnObjectTriggered(newObject, levelTrigger, obj);
};
}
var spriteCorners = new List<Vector2>
{
Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
};
Sprite sprite = newObject.Prefab.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
//calculate the positions of the corners of the rotated sprite
if (sprite != null)
{
Vector2 halfSize = sprite.size * newObject.Scale / 2;
spriteCorners[0] = -halfSize;
spriteCorners[1] = new Vector2(-halfSize.X, halfSize.Y);
spriteCorners[2] = halfSize;
spriteCorners[3] = new Vector2(halfSize.X, -halfSize.Y);
Vector2 pivotOffset = sprite.Origin * newObject.Scale - halfSize;
pivotOffset.X = -pivotOffset.X;
pivotOffset = new Vector2(
(float)(pivotOffset.X * Math.Cos(-newObject.Rotation) - pivotOffset.Y * Math.Sin(-newObject.Rotation)),
(float)(pivotOffset.X * Math.Sin(-newObject.Rotation) + pivotOffset.Y * Math.Cos(-newObject.Rotation)));
for (int j = 0; j < 4; j++)
{
spriteCorners[j] = new Vector2(
(float)(spriteCorners[j].X * Math.Cos(-newObject.Rotation) - spriteCorners[j].Y * Math.Sin(-newObject.Rotation)),
(float)(spriteCorners[j].X * Math.Sin(-newObject.Rotation) + spriteCorners[j].Y * Math.Cos(-newObject.Rotation)));
spriteCorners[j] += new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
}
}
float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z;
float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z;
float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z - level.BottomPos;
float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z - level.BottomPos;
foreach (LevelTrigger trigger in newObject.Triggers)
{
if (trigger.PhysicsBody == null) continue;
for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
{
trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
}
}
#if CLIENT
if (newObject.ParticleEmitters != null)
{
foreach (ParticleEmitter emitter in newObject.ParticleEmitters)
{
Rectangle particleBounds = emitter.CalculateParticleBounds(new Vector2(newObject.Position.X, newObject.Position.Y));
minX = Math.Min(minX, particleBounds.X);
maxX = Math.Max(maxX, particleBounds.Right);
minY = Math.Min(minY, particleBounds.Y - level.BottomPos);
maxY = Math.Max(maxY, particleBounds.Bottom - level.BottomPos);
}
}
#endif
objects.Add(newObject);
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
int xStart = (int)Math.Floor(minX / GridSize);
int xEnd = (int)Math.Floor(maxX / GridSize);
if (xEnd < 0 || xStart >= objectGrid.GetLength(0)) return;
int yStart = (int)Math.Floor(minY / GridSize);
int yEnd = (int)Math.Floor(maxY / GridSize);
if (yEnd < 0 || yStart >= objectGrid.GetLength(1)) return;
xStart = Math.Max(xStart, 0);
xEnd = Math.Min(xEnd, objectGrid.GetLength(0) - 1);
yStart = Math.Max(yStart, 0);
yEnd = Math.Min(yEnd, objectGrid.GetLength(1) - 1);
for (int x = xStart; x <= xEnd; x++)
{
for (int y = yStart; y <= yEnd; y++)
{
if (objectGrid[x, y] == null) objectGrid[x, y] = new List<LevelObject>();
objectGrid[x, y].Add(newObject);
}
}
}
public Microsoft.Xna.Framework.Point GetGridIndices(Vector2 worldPosition)
{
return new Microsoft.Xna.Framework.Point(
(int)Math.Floor(worldPosition.X / GridSize),
(int)Math.Floor((worldPosition.Y - Level.Loaded.BottomPos) / GridSize));
}
public IEnumerable<LevelObject> GetAllObjects()
{
return objects;
}
private readonly static List<LevelObject> objectsInRange = new List<LevelObject>();
public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
{
var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) return Enumerable.Empty<LevelObject>();
var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
if (maxIndices.X < 0 || maxIndices.Y < 0) return Enumerable.Empty<LevelObject>();
minIndices.X = Math.Max(0, minIndices.X);
minIndices.Y = Math.Max(0, minIndices.Y);
maxIndices.X = Math.Min(objectGrid.GetLength(0) - 1, maxIndices.X);
maxIndices.Y = Math.Min(objectGrid.GetLength(1) - 1, maxIndices.Y);
objectsInRange.Clear();
for (int x = minIndices.X; x <= maxIndices.X; x++)
{
for (int y = minIndices.Y; y <= maxIndices.Y; y++)
{
if (objectGrid[x, y] == null) continue;
foreach (LevelObject obj in objectGrid[x, y])
{
if (!objectsInRange.Contains(obj)) objectsInRange.Add(obj);
}
}
}
return objectsInRange;
}
private List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
{
List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
foreach (var cell in cells)
{
foreach (var edge in cell.Edges)
{
if (!edge.IsSolid || edge.OutsideLevel) continue;
Vector2 normal = edge.GetNormal(cell);
Alignment edgeAlignment = 0;
if (normal.Y < -0.5f)
edgeAlignment |= Alignment.Bottom;
else if (normal.Y > 0.5f)
edgeAlignment |= Alignment.Top;
else if (normal.X < -0.5f)
edgeAlignment |= Alignment.Left;
else if(normal.X > 0.5f)
edgeAlignment |= Alignment.Right;
availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosType, edgeAlignment));
}
}
return availableSpawnPositions;
}
private SpawnPosition FindObjectPosition(List<SpawnPosition> availableSpawnPositions, Level level, LevelObjectPrefab prefab)
{
if (prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.None) return null;
var suitableSpawnPositions = availableSpawnPositions.Where(sp =>
prefab.SpawnPos.HasFlag(sp.SpawnPosType) && sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment)).ToList();
return ToolBox.SelectWeightedRandom(suitableSpawnPositions, suitableSpawnPositions.Select(sp => sp.GetSpawnProbability(prefab)).ToList(), Rand.RandSync.Server);
}
public void Update(float deltaTime)
{
foreach (LevelObject obj in objects)
{
if (GameMain.Server != null)
{
if (obj.NeedsNetworkSyncing)
{
GameMain.Server.CreateEntityEvent(this, new object[] { obj });
obj.NeedsNetworkSyncing = false;
}
}
obj.ActivePrefab = obj.Prefab;
for (int i = 0; i < obj.Triggers.Count; i++)
{
obj.Triggers[i].Update(deltaTime);
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
{
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
}
}
if (obj.PhysicsBody != null)
{
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered;
obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
obj.Rotation = obj.PhysicsBody.Rotation;
}
}
UpdateProjSpecific(deltaTime);
}
partial void UpdateProjSpecific(float deltaTime);
private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
{
if (trigger.TriggerOthersDistance <= 0.0f) return;
foreach (LevelObject obj in objects)
{
if (obj == triggeredObject) continue;
foreach (LevelTrigger otherTrigger in obj.Triggers)
{
otherTrigger.OtherTriggered(triggeredObject, trigger);
}
}
}
private LevelObjectPrefab GetRandomPrefab(string levelType)
{
return ToolBox.SelectWeightedRandom(
LevelObjectPrefab.List,
LevelObjectPrefab.List.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server);
}
public override void Remove()
{
if (objects != null)
{
foreach (LevelObject obj in objects)
{
obj.Remove();
}
objects.Clear();
}
RemoveProjSpecific();
base.Remove();
}
partial void RemoveProjSpecific();
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
LevelObject obj = extraData[0] as LevelObject;
msg.WriteRangedInteger(0, objects.Count, objects.IndexOf(obj));
obj.ServerWrite(msg, c);
}
}
}
@@ -0,0 +1,387 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObjectPrefab : ISerializableEntity
{
private static List<LevelObjectPrefab> list = new List<LevelObjectPrefab>();
public static List<LevelObjectPrefab> List
{
get { return list; }
}
public class ChildObject
{
public List<string> AllowedNames;
public int MinCount, MaxCount;
public ChildObject()
{
AllowedNames = new List<string>();
MinCount = 1;
MaxCount = 1;
}
public ChildObject(XElement element)
{
AllowedNames = element.GetAttributeStringArray("names", new string[0]).ToList();
MinCount = element.GetAttributeInt("mincount", 1);
MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount);
}
}
[Flags]
public enum SpawnPosType
{
None = 0,
Wall = 1,
RuinWall = 2,
SeaFloor = 4,
MainPath = 8
}
public Sprite Sprite
{
get;
private set;
}
public Sprite SpecularSprite
{
get;
private set;
}
public DeformableSprite DeformableSprite
{
get;
private set;
}
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
public float MinSize
{
get;
private set;
}
[Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
public float MaxSize
{
get;
private set;
}
/// <summary>
/// Which sides of a wall the object can appear on.
/// </summary>
[Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), true), Editable(ToolTip = "Which sides of a wall the object can spawn on.")]
public Alignment Alignment
{
get;
private set;
}
[Serialize(SpawnPosType.Wall, false), Editable()]
public SpawnPosType SpawnPos
{
get;
private set;
}
public XElement Config
{
get;
private set;
}
public readonly List<XElement> LevelTriggerElements;
/// <summary>
/// Overrides the commonness of the object in a specific level type.
/// Key = name of the level type, value = commonness in that level type.
/// </summary>
public Dictionary<string, float> OverrideCommonness;
public XElement PhysicsBodyElement
{
get;
private set;
}
public int PhysicsBodyTriggerIndex
{
get;
private set;
}
[Serialize("0.0,1.0", true), Editable()]
public Vector2 DepthRange
{
get;
private set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f,
ToolTip = "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")]
/// <summary>
/// The tendency for the prefab to form clusters. Used as an exponent for perlin noise values
/// that are used to determine the probability for an object to spawn at a specific position.
/// </summary>
public float ClusteringAmount
{
get;
private set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f,
ToolTip = "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " +
" for an object to spawn at a specific position. Using the same (or close) value for different objects means the objects tend " +
"to form clusters in the same areas.")]
/// <summary>
/// A value between 0-1 that determines the z-coordinate to sample perlin noise from when
/// determining the probability for an object to spawn at a specific position.
/// Using the same (or close) value for different objects means the objects tend to form clusters
/// in the same areas.
/// </summary>
public float ClusteringGroup
{
get;
private set;
}
[Serialize(false, true), Editable(ToolTip = "Should the object be rotated to align it with the wall surface it spawns on.")]
public bool AlignWithSurface
{
get;
private set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f,
ToolTip = "Minimum length of a graph edge the object can spawn on.")]
/// <summary>
/// Minimum length of a graph edge the object can spawn on.
/// </summary>
public float MinSurfaceWidth
{
get;
private set;
}
private Vector2 randomRotation;
[Serialize("0.0,0.0", true), Editable(ToolTip = "How much the rotation of the object can vary (min and max values in degrees).")]
public Vector2 RandomRotation
{
get { return new Vector2(MathHelper.ToDegrees(randomRotation.X), MathHelper.ToDegrees(randomRotation.Y)); }
private set
{
randomRotation = new Vector2(MathHelper.ToRadians(value.X), MathHelper.ToRadians(value.Y));
}
}
public Vector2 RandomRotationRad => randomRotation;
private float swingAmount;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f, ToolTip = "How much the object swings (in degrees).")]
public float SwingAmount
{
get { return MathHelper.ToDegrees(swingAmount); }
private set
{
swingAmount = MathHelper.ToRadians(value);
}
}
public float SwingAmountRad => swingAmount;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How fast the object swings.")]
public float SwingFrequency
{
get;
private set;
}
[Serialize("0.0,0.0", true), Editable(ToolTip = "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")]
public Vector2 ScaleOscillation
{
get;
private set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How fast the object's scale oscillates.")]
public float ScaleOscillationFrequency
{
get;
private set;
}
[Serialize(1.0f, true), Editable(ToolTip = "How likely it is for the object to spawn in a level. "+
"This is relative to the commonness of the other objects - for example, having an object with "+
"a commonness of 1 and another with a commonness of 10 would mean the latter appears in levels 10 times as frequently as the former. "+
"The commonness value can be overridden on specific level types.")]
public float Commonness
{
get;
private set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How much the object disrupts submarine's sonar.")]
public float SonarDisruption
{
get;
private set;
}
public string Name
{
get;
set;
}
public List<ChildObject> ChildObjects
{
get;
private set;
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get; private set;
}
/// <summary>
/// A list of prefabs whose properties override this one's properties when a trigger is active.
/// E.g. if a trigger in the index 1 of the trigger list is active, the properties in index 1 in this list are used (unless it's null)
/// </summary>
public List<LevelObjectPrefab> OverrideProperties
{
get;
private set;
}
public override string ToString()
{
return "LevelObjectPrefab (" + Name + ")";
}
public static void LoadAll()
{
var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs);
if (files.Count() > 0)
{
foreach (var file in files)
{
LoadConfig(file);
}
}
else
{
LoadConfig("Content/LevelObjects/LevelObject/Prefabs.xml");
}
}
private static void LoadConfig(string configPath)
{
try
{
XDocument doc = XMLExtensions.TryLoadXml(configPath);
if (doc == null || doc.Root == null) return;
foreach (XElement element in doc.Root.Elements())
{
list.Add(new LevelObjectPrefab(element));
}
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load LevelObject prefabs from {0}", configPath), e);
}
}
public LevelObjectPrefab(XElement element)
{
ChildObjects = new List<ChildObject>();
LevelTriggerElements = new List<XElement>();
OverrideProperties = new List<LevelObjectPrefab>();
OverrideCommonness = new Dictionary<string, float>();
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (element != null)
{
Config = element;
Name = element.Name.ToString();
LoadElements(element, -1);
InitProjSpecific(element);
}
//use the maximum width of the sprite as the minimum surface width if no value is given
if (element != null && !element.Attributes("minsurfacewidth").Any())
{
if (Sprite != null) MinSurfaceWidth = Sprite.size.X * MaxSize;
if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize);
}
}
private void LoadElements(XElement element, int parentTriggerIndex)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
Sprite = new Sprite(subElement);
break;
case "specularsprite":
SpecularSprite = new Sprite(subElement);
break;
case "deformablesprite":
DeformableSprite = new DeformableSprite(subElement);
break;
case "overridecommonness":
string levelType = subElement.GetAttributeString("leveltype", "");
if (!OverrideCommonness.ContainsKey(levelType))
{
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
}
break;
case "leveltrigger":
case "trigger":
OverrideProperties.Add(null);
LevelTriggerElements.Add(subElement);
LoadElements(subElement, LevelTriggerElements.Count - 1);
break;
case "childobject":
ChildObjects.Add(new ChildObject(subElement));
break;
case "overrideproperties":
var propertyOverride = new LevelObjectPrefab(subElement);
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
if (propertyOverride.Sprite == null && propertyOverride.DeformableSprite == null)
{
propertyOverride.Sprite = Sprite;
propertyOverride.DeformableSprite = DeformableSprite;
}
break;
case "body":
case "physicsbody":
PhysicsBodyElement = subElement;
PhysicsBodyTriggerIndex = parentTriggerIndex;
break;
}
}
}
partial void InitProjSpecific(XElement element);
public float GetCommonness(string levelType)
{
if (!OverrideCommonness.TryGetValue(levelType, out float commonness))
{
return Commonness;
}
return commonness;
}
}
}
@@ -0,0 +1,600 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelTrigger
{
[Flags]
enum TriggererType
{
None = 0,
Human = 1,
Creature = 2,
Character = Human | Creature,
Submarine = 4,
Item = 8,
OtherTrigger = 16
}
public enum TriggerForceMode
{
Force, //default, apply a force to the object over time
Acceleration, //apply an acceleration to the object, ignoring it's mass
Impulse, //apply an instant force, ignoring deltaTime
LimitVelocity //clamp the velocity of the triggerer to some value
}
public Action<LevelTrigger, Entity> OnTriggered;
private PhysicsBody physicsBody;
/// <summary>
/// Effects applied to entities that are inside the trigger
/// </summary>
private List<StatusEffect> statusEffects = new List<StatusEffect>();
/// <summary>
/// Attacks applied to entities that are inside the trigger
/// </summary>
private List<Attack> attacks = new List<Attack>();
private float cameraShake;
private Vector2 unrotatedForce;
private float forceFluctuationTimer, currentForceFluctuation = 1.0f;
private HashSet<Entity> triggerers = new HashSet<Entity>();
private TriggererType triggeredBy;
private float randomTriggerInterval;
private float randomTriggerProbability;
private float randomTriggerTimer;
private float triggeredTimer;
//how far away this trigger can activate other triggers from
private float triggerOthersDistance;
private HashSet<string> tags = new HashSet<string>();
//other triggers have to have at least one of these tags to trigger this one
private HashSet<string> allowedOtherTriggerTags = new HashSet<string>();
/// <summary>
/// How long the trigger stays in the triggered state after triggerers have left
/// </summary>
private float stayTriggeredDelay;
public LevelTrigger ParentTrigger;
public Dictionary<Entity, Vector2> TriggererPosition
{
get;
private set;
}
private Vector2 worldPosition;
public Vector2 WorldPosition
{
get { return worldPosition; }
set
{
worldPosition = value;
physicsBody?.SetTransform(ConvertUnits.ToSimUnits(value), physicsBody.Rotation);
}
}
public float Rotation
{
get { return physicsBody == null ? 0.0f : physicsBody.Rotation; }
set
{
if (physicsBody == null) return;
physicsBody.SetTransform(physicsBody.Position, value);
CalculateDirectionalForce();
}
}
public PhysicsBody PhysicsBody
{
get { return physicsBody; }
}
public float TriggerOthersDistance
{
get { return triggerOthersDistance; }
}
public IEnumerable<Entity> Triggerers
{
get { return triggerers.AsEnumerable(); }
}
public bool IsTriggered
{
get
{
return (triggerers.Count > 0 || triggeredTimer > 0.0f) &&
(ParentTrigger == null || ParentTrigger.IsTriggered);
}
}
public Vector2 Force
{
get;
private set;
}
/// <summary>
/// does the force diminish by distance
/// </summary>
public bool ForceFalloff
{
get;
private set;
}
public float ForceFluctuationInterval
{
get;
private set;
}
public float ForceFluctuationStrength
{
get;
private set;
}
private TriggerForceMode forceMode;
public TriggerForceMode ForceMode
{
get { return forceMode; }
}
/// <summary>
/// Stop applying forces to objects if they're moving faster than this
/// </summary>
public float ForceVelocityLimit
{
get;
private set;
}
public float ColliderRadius
{
get;
private set;
}
public bool UseNetworkSyncing
{
get;
private set;
}
public bool NeedsNetworkSyncing
{
get;
set;
}
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f, string parentDebugName = "")
{
TriggererPosition = new Dictionary<Entity, Vector2>();
worldPosition = position;
if (element.Attributes("radius").Any() || element.Attributes("width").Any() || element.Attributes("height").Any())
{
physicsBody = new PhysicsBody(element, scale)
{
CollisionCategories = Physics.CollisionLevel,
CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall
};
physicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
physicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
physicsBody.FarseerBody.IsSensor = true;
physicsBody.FarseerBody.IsStatic = true;
physicsBody.FarseerBody.IsKinematic = true;
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
physicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
}
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
stayTriggeredDelay = element.GetAttributeFloat("staytriggereddelay", 0.0f);
randomTriggerInterval = element.GetAttributeFloat("randomtriggerinterval", 0.0f);
randomTriggerProbability = element.GetAttributeFloat("randomtriggerprobability", 0.0f);
UseNetworkSyncing = element.GetAttributeBool("networksyncing", false);
unrotatedForce =
element.Attribute("force") != null && element.Attribute("force").Value.Contains(',') ?
element.GetAttributeVector2("force", Vector2.Zero) :
new Vector2(element.GetAttributeFloat("force", 0.0f), 0.0f);
ForceFluctuationInterval = element.GetAttributeFloat("forcefluctuationinterval", 0.01f);
ForceFluctuationStrength = Math.Max(element.GetAttributeFloat("forcefluctuationstrength", 0.0f), 0.0f);
ForceFalloff = element.GetAttributeBool("forcefalloff", true);
ForceVelocityLimit = ConvertUnits.ToSimUnits(element.GetAttributeFloat("forcevelocitylimit", float.MaxValue));
string forceModeStr = element.GetAttributeString("forcemode", "Force");
if (!Enum.TryParse(forceModeStr, out forceMode))
{
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + forceModeStr + "\" is not a valid force mode.");
}
CalculateDirectionalForce();
string triggeredByStr = element.GetAttributeString("triggeredby", "Character");
if (!Enum.TryParse(triggeredByStr, out triggeredBy))
{
DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type.");
}
UpdateCollisionCategories();
triggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f);
var tagsArray = element.GetAttributeStringArray("tags", new string[0]);
foreach (string tag in tagsArray)
{
tags.Add(tag.ToLower());
}
if (triggeredBy.HasFlag(TriggererType.OtherTrigger))
{
var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]);
foreach (string tag in otherTagsArray)
{
allowedOtherTriggerTags.Add(tag.ToLower());
}
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "statuseffect":
statusEffects.Add(StatusEffect.Load(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in "+ parentDebugName));
break;
case "attack":
case "damage":
var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName);
var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step);
attack.Afflictions.Clear();
foreach (Affliction affliction in multipliedAfflictions)
{
attack.Afflictions.Add(affliction);
}
attacks.Add(attack);
break;
}
}
}
private void UpdateCollisionCategories()
{
if (physicsBody == null) return;
var collidesWith = Physics.CollisionNone;
if (triggeredBy.HasFlag(TriggererType.Character) || triggeredBy.HasFlag(TriggererType.Creature)) collidesWith |= Physics.CollisionCharacter;
if (triggeredBy.HasFlag(TriggererType.Item)) collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile;
if (triggeredBy.HasFlag(TriggererType.Submarine)) collidesWith |= Physics.CollisionWall;
physicsBody.CollidesWith = collidesWith;
}
private void CalculateDirectionalForce()
{
var ca = (float)Math.Cos(-Rotation);
var sa = (float)Math.Sin(-Rotation);
Force = new Vector2(
ca * unrotatedForce.X + sa * unrotatedForce.Y,
-sa * unrotatedForce.X + ca * unrotatedForce.Y);
}
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
{
Entity entity = GetEntity(fixtureB);
if (entity == null) return false;
if (entity is Character character)
{
if (character.CurrentHull != null) return false;
if (character.ConfigPath == Character.HumanConfigFile)
{
if (!triggeredBy.HasFlag(TriggererType.Human)) return false;
}
else
{
if (!triggeredBy.HasFlag(TriggererType.Creature)) return false;
}
}
else if (entity is Item item)
{
if (item.CurrentHull != null) return false;
if (!triggeredBy.HasFlag(TriggererType.Item)) return false;
}
else if (entity is Submarine)
{
if (!triggeredBy.HasFlag(TriggererType.Submarine)) return false;
}
if (!triggerers.Contains(entity))
{
if (!IsTriggered)
{
OnTriggered?.Invoke(this, entity);
}
TriggererPosition[entity] = entity.WorldPosition;
triggerers.Add(entity);
}
return true;
}
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB)
{
Entity entity = GetEntity(fixtureB);
if (entity == null) return;
if (entity is Character character &&
(!character.Enabled || character.Removed) &&
triggerers.Contains(entity))
{
TriggererPosition.Remove(entity);
triggerers.Remove(entity);
return;
}
//check if there are any other contacts with the entity
//(the OnSeparation callback happens when two fixtures separate,
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
ContactEdge contactEdge = fixtureA.Body.ContactList;
while (contactEdge != null)
{
if (contactEdge.Contact != null &&
contactEdge.Contact.IsTouching)
{
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
contactEdge.Contact.FixtureB :
contactEdge.Contact.FixtureA);
if (otherEntity == entity) return;
}
contactEdge = contactEdge.Next;
}
if (triggerers.Contains(entity))
{
TriggererPosition.Remove(entity);
triggerers.Remove(entity);
}
}
private Entity GetEntity(Fixture fixture)
{
if (fixture.Body == null || fixture.Body.UserData == null) return null;
if (fixture.Body.UserData is Entity entity) return entity;
if (fixture.Body.UserData is Limb limb) return limb.character;
if (fixture.Body.UserData is SubmarineBody subBody) return subBody.Submarine;
return null;
}
/// <summary>
/// Another trigger was triggered, check if this one should react to it
/// </summary>
public void OtherTriggered(LevelObject levelObject, LevelTrigger otherTrigger)
{
if (!triggeredBy.HasFlag(TriggererType.OtherTrigger) || stayTriggeredDelay <= 0.0f) return;
//check if the other trigger has appropriate tags
if (allowedOtherTriggerTags.Count > 0)
{
if (!allowedOtherTriggerTags.Any(t => otherTrigger.tags.Contains(t))) return;
}
if (Vector2.DistanceSquared(WorldPosition, otherTrigger.WorldPosition) <= otherTrigger.triggerOthersDistance * otherTrigger.triggerOthersDistance)
{
bool wasAlreadyTriggered = IsTriggered;
triggeredTimer = stayTriggeredDelay;
if (!wasAlreadyTriggered)
{
OnTriggered?.Invoke(this, null);
}
}
}
public void Update(float deltaTime)
{
if (ParentTrigger != null && !ParentTrigger.IsTriggered) return;
triggerers.RemoveWhere(t => t.Removed);
if (!UseNetworkSyncing || GameMain.Client == null)
{
if (ForceFluctuationStrength > 0.0f)
{
forceFluctuationTimer += deltaTime;
if (forceFluctuationTimer > ForceFluctuationInterval)
{
NeedsNetworkSyncing = true;
currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
forceFluctuationTimer = 0.0f;
}
}
if (randomTriggerProbability > 0.0f)
{
randomTriggerTimer += deltaTime;
if (randomTriggerTimer > randomTriggerInterval)
{
if (Rand.Range(0.0f, 1.0f) < randomTriggerProbability)
{
NeedsNetworkSyncing = true;
triggeredTimer = stayTriggeredDelay;
}
randomTriggerTimer = 0.0f;
}
}
}
if (stayTriggeredDelay > 0.0f)
{
if (triggerers.Count == 0)
{
triggeredTimer -= deltaTime;
}
else
{
triggeredTimer = stayTriggeredDelay;
}
}
foreach (Entity triggerer in triggerers)
{
foreach (StatusEffect effect in statusEffects)
{
if (triggerer is Character)
{
effect.Apply(effect.type, deltaTime, triggerer, (Character)triggerer);
}
else if (triggerer is Item)
{
effect.Apply(effect.type, deltaTime, triggerer, ((Item)triggerer).AllPropertyObjects);
}
}
if (triggerer is IDamageable damageable)
{
foreach (Attack attack in attacks)
{
attack.DoDamage(null, damageable, WorldPosition, deltaTime, false);
}
}
else if (triggerer is Submarine submarine)
{
foreach (Attack attack in attacks)
{
float structureDamage = attack.GetStructureDamage(deltaTime);
if (structureDamage > 0.0f)
{
Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage);
}
}
}
if (Force.LengthSquared() > 0.01f)
{
if (triggerer is Character character)
{
ApplyForce(character.AnimController.Collider, deltaTime);
foreach (Limb limb in character.AnimController.Limbs)
{
ApplyForce(limb.body, deltaTime);
}
}
else if (triggerer is Submarine submarine)
{
ApplyForce(submarine.SubBody.Body, deltaTime);
}
}
if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake);
}
}
}
private void ApplyForce(PhysicsBody body, float deltaTime)
{
float distFactor = 1.0f;
if (ForceFalloff)
{
distFactor = 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(body.SimPosition, PhysicsBody.SimPosition)) / ColliderRadius;
if (distFactor < 0.0f) return;
}
switch (ForceMode)
{
case TriggerForceMode.Force:
if (ForceVelocityLimit < 1000.0f)
body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
else
body.ApplyForce(Force * currentForceFluctuation * distFactor);
break;
case TriggerForceMode.Acceleration:
if (ForceVelocityLimit < 1000.0f)
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit);
else
body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor);
break;
case TriggerForceMode.Impulse:
if (ForceVelocityLimit < 1000.0f)
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, ForceVelocityLimit);
else
body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor);
break;
case TriggerForceMode.LimitVelocity:
float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor;
if (body.LinearVelocity.LengthSquared() > maxVel * maxVel)
{
body.ApplyForce(
Vector2.Normalize(-body.LinearVelocity) *
Force.Length() * body.Mass * currentForceFluctuation * distFactor);
}
break;
}
}
public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
{
Vector2 baseVel = GetWaterFlowVelocity();
if (baseVel.LengthSquared() < 0.1f) return Vector2.Zero;
float triggerSize = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
float dist = Vector2.Distance(viewPosition, WorldPosition);
if (dist > triggerSize) return Vector2.Zero;
return baseVel * (1.0f - dist / triggerSize);
}
public Vector2 GetWaterFlowVelocity()
{
if (Force == Vector2.Zero) return Vector2.Zero;
Vector2 vel = Force;
if (ForceMode == TriggerForceMode.Acceleration)
{
vel *= 1000.0f;
}
else if (ForceMode == TriggerForceMode.Impulse)
{
vel /= (float)Timing.Step;
}
return vel.ClampLength(ConvertUnits.ToDisplayUnits(ForceVelocityLimit)) * currentForceFluctuation;
}
public void ServerWrite(NetBuffer msg, Client c)
{
if (ForceFluctuationStrength > 0.0f)
{
msg.WriteRangedSingle(MathHelper.Clamp(currentForceFluctuation, 0.0f, 1.0f), 0.0f, 1.0f, 8);
}
if (stayTriggeredDelay > 0.0f)
{
msg.WriteRangedSingle(MathHelper.Clamp(triggeredTimer, 0.0f, stayTriggeredDelay), 0.0f, stayTriggeredDelay, 16);
}
}
}
}
@@ -1,160 +0,0 @@
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelTrigger
{
private PhysicsBody physicsBody;
/// <summary>
/// Effects applied to entities that are inside the trigger
/// </summary>
private List<StatusEffect> statusEffects = new List<StatusEffect>();
/// <summary>
/// Attacks applied to entities that are inside the trigger
/// </summary>
private List<Attack> attacks = new List<Attack>();
private List<Entity> triggerers = new List<Entity>();
private float cameraShake;
private Vector2 force;
public Vector2 WorldPosition
{
get { return physicsBody.Position; }
set { physicsBody.SetTransform(ConvertUnits.ToSimUnits(value), physicsBody.Rotation); }
}
public float Rotation
{
get { return physicsBody.Rotation; }
set { physicsBody.SetTransform(physicsBody.Position, value); }
}
public PhysicsBody PhysicsBody
{
get { return physicsBody; }
}
public LevelTrigger(XElement element, Vector2 position, float rotation, float scale = 1.0f)
{
physicsBody = new PhysicsBody(element, scale);
physicsBody.CollisionCategories = Physics.CollisionLevel;
physicsBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionProjectile | Physics.CollisionWall;
physicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
physicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
physicsBody.FarseerBody.IsSensor = true;
physicsBody.FarseerBody.IsStatic = true;
physicsBody.FarseerBody.IsKinematic = true;
physicsBody.SetTransform(ConvertUnits.ToSimUnits(position), rotation);
cameraShake = element.GetAttributeFloat("camerashake", 0.0f);
force = element.GetAttributeVector2("force", Vector2.Zero);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "statuseffect":
statusEffects.Add(StatusEffect.Load(subElement));
break;
case "attack":
case "damage":
attacks.Add(new Attack(subElement));
break;
}
}
}
private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact)
{
Entity entity = GetEntity(fixtureB);
if (entity == null) return false;
if (!triggerers.Contains(entity))
{
triggerers.Add(entity);
}
return true;
}
private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB)
{
Entity entity = GetEntity(fixtureB);
if (entity == null) return;
if (triggerers.Contains(entity))
{
triggerers.Remove(entity);
}
}
private Entity GetEntity(Fixture fixture)
{
if (fixture.Body == null || fixture.Body.UserData == null) return null;
var entity = fixture.Body.UserData as Entity;
if (entity != null) return entity;
var limb = fixture.Body.UserData as Limb;
if (limb != null) return limb.character;
return null;
}
public void Update(float deltaTime)
{
triggerers.RemoveAll(t => t.Removed);
foreach (Entity triggerer in triggerers)
{
foreach (StatusEffect effect in statusEffects)
{
if (triggerer is Character)
{
effect.Apply(effect.type, deltaTime, triggerer, (Character)triggerer);
}
else if (triggerer is Item)
{
effect.Apply(effect.type, deltaTime, triggerer, ((Item)triggerer).AllPropertyObjects);
}
}
IDamageable damageable = triggerer as IDamageable;
if (damageable != null)
{
foreach (Attack attack in attacks)
{
attack.DoDamage(null, damageable, WorldPosition, deltaTime, false);
}
}
if (force != Vector2.Zero)
{
if (triggerer is Character)
{
((Character)triggerer).AnimController.Collider.ApplyForce(force * deltaTime);
}
else if (triggerer is Submarine)
{
((Submarine)triggerer).ApplyForce(force * deltaTime);
}
}
if (triggerer == Character.Controlled || triggerer == Character.Controlled?.Submarine)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake);
}
}
}
}
}
@@ -1,4 +1,5 @@
using FarseerPhysics.Dynamics;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -9,15 +10,65 @@ namespace Barotrauma
{
partial class LevelWall : IDisposable
{
private List<VoronoiCell> cells;
private List<VoronoiCell> cells;
public List<VoronoiCell> Cells
{
get { return cells; }
}
private List<Body> bodies;
private Body body;
public Body Body
{
get { return body; }
}
private float moveState;
private float moveLength;
private Vector2 moveAmount;
public Vector2 MoveAmount
{
get { return moveAmount; }
set
{
moveAmount = value;
moveLength = moveAmount.Length();
}
}
public float MoveSpeed;
private Vector2? originalPos;
public float MoveState
{
get { return moveState; }
set { moveState = MathHelper.Clamp(value, 0.0f, MathHelper.TwoPi); }
}
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false)
{
if (giftWrap)
{
vertices = MathUtils.GiftWrap(vertices);
}
VoronoiCell wallCell = new VoronoiCell(vertices.ToArray());
for (int i = 0; i < wallCell.Edges.Count; i++)
{
wallCell.Edges[i].Cell1 = wallCell;
wallCell.Edges[i].IsSolid = true;
}
cells = new List<VoronoiCell>() { wallCell };
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
#if CLIENT
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
SetBodyVertices(bodyVertices.ToArray(), color);
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
#endif
}
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
{
cells = new List<VoronoiCell>();
@@ -31,40 +82,53 @@ namespace Barotrauma
VoronoiCell wallCell = new VoronoiCell(vertices);
wallCell.CellType = CellType.Edge;
wallCell.edges[0].cell1 = wallCell;
wallCell.edges[1].cell1 = wallCell;
wallCell.edges[2].cell1 = wallCell;
wallCell.edges[3].cell1 = wallCell;
wallCell.edges[0].isSolid = true;
wallCell.Edges[0].Cell1 = wallCell;
wallCell.Edges[1].Cell1 = wallCell;
wallCell.Edges[2].Cell1 = wallCell;
wallCell.Edges[3].Cell1 = wallCell;
wallCell.Edges[0].IsSolid = true;
if (i > 1)
{
wallCell.edges[3].cell2 = cells[i - 1];
cells[i - 1].edges[1].cell2 = wallCell;
wallCell.Edges[3].Cell2 = cells[i - 1];
cells[i - 1].Edges[1].Cell2 = wallCell;
}
cells.Add(wallCell);
}
bodies = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles, false);
foreach (var body in bodies)
{
body.CollisionCategories = Physics.CollisionLevel;
}
body = CaveGenerator.GeneratePolygons(cells, level, out List<Vector2[]> triangles);
body.CollisionCategories = Physics.CollisionLevel;
#if CLIENT
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
SetBodyVertices(bodyVertices.ToArray(), color);
SetWallVertices(CaveGenerator.GenerateWallShapes(cells, level), color);
#endif
}
public void Update(float deltaTime)
{
if (body.BodyType == BodyType.Static) return;
Vector2 bodyPos = ConvertUnits.ToDisplayUnits(body.Position);
Cells.ForEach(c => c.Translation = bodyPos);
if (!originalPos.HasValue) originalPos = bodyPos;
if (moveLength > 0.0f && MoveSpeed > 0.0f)
{
moveState += MoveSpeed / moveLength * deltaTime;
moveState %= MathHelper.TwoPi;
Vector2 targetPos = ConvertUnits.ToSimUnits(originalPos.Value + moveAmount * (float)Math.Sin(moveState));
body.ApplyForce((targetPos - body.Position).ClampLength(1.0f) * body.Mass);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
@@ -82,12 +146,6 @@ namespace Barotrauma
bodyVertices = null;
}
#endif
if (bodies != null)
{
bodies.Clear();
bodies = null;
}
}
}
}
@@ -44,7 +44,7 @@ namespace Barotrauma.RuinGeneration
{
subRooms = new BTRoom[2];
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < verticalProbability &&
if (Rand.Range(0.0f, rect.Height / (float)rect.Width, Rand.RandSync.Server) < verticalProbability &&
rect.Width * minDivRatio >= minWidth)
{
SplitVertical(minDivRatio);
@@ -78,13 +78,13 @@ namespace Barotrauma.RuinGeneration
public override void CreateWalls()
{
Walls = new List<Line>();
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.Wall));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.Wall));
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
Walls = new List<Line>
{
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)),
new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)),
new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)),
new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom))
};
}
public void Scale(Vector2 scale)
@@ -126,33 +126,52 @@ namespace Barotrauma.RuinGeneration
}
}
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<Corridor> corridors)
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<BTRoom> rooms, List<Corridor> corridors)
{
entrance.CalculateDistanceFromEntrance(1, new List<Corridor>(corridors));
entrance.CalculateDistanceFromEntrance(0, rooms, new List<Corridor>(corridors));
}
private void CalculateDistanceFromEntrance(int currentDist, List<Corridor> corridors)
private void CalculateDistanceFromEntrance(int currentDist, List<BTRoom> rooms, List<Corridor> corridors)
{
if (DistanceFromEntrance == 0)
{
DistanceFromEntrance = currentDist;
}
else
{
DistanceFromEntrance = Math.Min(currentDist, DistanceFromEntrance);
}
DistanceFromEntrance = DistanceFromEntrance == 0 ? currentDist : Math.Min(currentDist, DistanceFromEntrance);
currentDist++;
for (int i = corridors.Count - 1; i >= 0; i = Math.Min(i - 1, corridors.Count - 1))
var roomRect = Rect;
roomRect.Inflate(5, 5);
foreach (var corridor in corridors)
{
var corridor = corridors[i];
var corridorRect = corridor.Rect;
corridorRect.Inflate(5, 5);
if (!corridorRect.Intersects(roomRect)) continue;
if (!corridor.ConnectedRooms.Contains(this)) continue;
corridor.DistanceFromEntrance = corridor.DistanceFromEntrance == 0 ?
DistanceFromEntrance + 1 :
Math.Min(corridor.DistanceFromEntrance, DistanceFromEntrance + 1);
corridors.RemoveAt(i);
List<BTRoom> connectedRooms = new List<BTRoom>();
foreach (var otherRoom in rooms)
{
if (otherRoom == this) continue;
if (otherRoom.DistanceFromEntrance > 0 && otherRoom.DistanceFromEntrance < currentDist) continue;
corridor.ConnectedRooms[corridor.ConnectedRooms[0] == this ? 1 : 0].CalculateDistanceFromEntrance(currentDist, corridors);
var otherRoomRect = otherRoom.Rect;
otherRoomRect.Inflate(5, 5);
if (corridorRect.Intersects(otherRoomRect)) { connectedRooms.Add(otherRoom); }
}
connectedRooms.Sort((r1, r2) =>
{
return
(Math.Abs(r1.Rect.Center.X - Rect.Center.X) + Math.Abs(r1.Rect.Center.Y - Rect.Center.Y)) -
(Math.Abs(r2.Rect.Center.X - Rect.Center.X) + Math.Abs(r2.Rect.Center.Y - Rect.Center.Y));
});
for (int i = 0; i < connectedRooms.Count; i++)
{
connectedRooms[i].CalculateDistanceFromEntrance(currentDist + 1 + i, rooms, corridors);
}
}
}
}
@@ -8,13 +8,7 @@ namespace Barotrauma.RuinGeneration
class Corridor : RuinShape
{
private bool isHorizontal;
// TODO: fix implicit hiding
public Rectangle Rect
{
get { return rect; }
}
public bool IsHorizontal
{
get { return isHorizontal; }
@@ -55,40 +49,28 @@ namespace Barotrauma.RuinGeneration
var leaves2 = room.Adjacent.GetLeaves();
var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal);
room1 = suitableLeaves[0].Rect;
room2 = suitableLeaves[1].Rect;
ConnectedRooms[0] = suitableLeaves[0];
ConnectedRooms[1] = suitableLeaves[1];
}
if (isHorizontal)
{
int left = Math.Min(room1.Right, room2.Right);
int right = Math.Max(room1.X, room2.X);
int top = Math.Max(room1.Y, room2.Y);
int bottom = Math.Min(room1.Bottom, room2.Bottom);
int yPos = Rand.Range(top, bottom - width, Rand.RandSync.Server);
rect = new Rectangle(left, yPos, right - left, width);
}
else if (room1.Y > room2.Bottom || room2.Y > room1.Bottom)
{
int left = Math.Max(room1.X, room2.X);
int right = Math.Min(room1.Right, room2.Right);
int top = Math.Min(room1.Bottom, room2.Bottom);
int bottom = Math.Max(room1.Y, room2.Y);
int xPos = Rand.Range(left, right - width, Rand.RandSync.Server);
rect = new Rectangle(xPos, top, width, bottom - top);
if (suitableLeaves == null || suitableLeaves.Length < 2)
{
// No suitable leaves found due to intersections
//DebugConsole.ThrowError("Error while generating ruins. Could not find a suitable position for a corridor. The width of the corridors may be too large compared to the sizes of the rooms.");
return;
}
else
{
room1 = suitableLeaves[0].Rect;
room2 = suitableLeaves[1].Rect;
ConnectedRooms[0] = suitableLeaves[0];
ConnectedRooms[1] = suitableLeaves[1];
}
}
else
{
DebugConsole.ThrowError("wat");
rect = CalculateRectangle(room1, room2, width, isHorizontal);
if (rect.Width <= 0 || rect.Height <= 0)
{
DebugConsole.ThrowError("Error while generating ruins. Attempted to create a corridor with a width or height of <= 0");
return;
}
}
room.Corridor = this;
@@ -122,24 +104,21 @@ namespace Barotrauma.RuinGeneration
public override void CreateWalls()
{
Walls = new List<Line>();
if (IsHorizontal)
{
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.CorridorWall));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)));
}
else
{
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.CorridorWall));
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)));
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom)));
}
}
/// <summary>
/// find two rooms which have two face-two-face walls that we can place a corridor in between
/// Find two rooms which have two face-two-face walls that we can place a corridor in between
/// </summary>
/// <returns></returns>
private BTRoom[] GetSuitableLeafRooms(List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
@@ -147,7 +126,6 @@ namespace Barotrauma.RuinGeneration
int iOffset = Rand.Int(leaves1.Count, Rand.RandSync.Server);
int jOffset = Rand.Int(leaves2.Count, Rand.RandSync.Server);
for (int iCount = 0; iCount < leaves1.Count; iCount++)
{
int i = (iCount + iOffset) % leaves1.Count;
@@ -158,21 +136,17 @@ namespace Barotrauma.RuinGeneration
if (isHorizontal)
{
//if (Math.Min(leaves1[i].Rect.Bottom, leaves2[i].Rect.Bottom) - Math.Max(leaves1[i].Rect.Y, leaves2[j].Rect.Y) < width) continue;
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom-width) continue;
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y+width) continue;
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom - width) continue;
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y + width) continue;
}
else
{
//if (Math.Min(leaves1[i].Rect.Right, leaves2[i].Rect.Right) - Math.Max(leaves1[i].Rect.X, leaves2[j].Rect.X) < width) continue;
if (leaves1[i].Rect.X > leaves2[j].Rect.Right-width) continue;
if (leaves1[i].Rect.Right < leaves2[j].Rect.X+width) continue;
if (leaves1[i].Rect.X > leaves2[j].Rect.Right - width) continue;
if (leaves1[i].Rect.Right < leaves2[j].Rect.X + width) continue;
}
// Check if the given corridor rect would intersect over a third room
if (CheckForIntersection(leaves1[i], leaves2[j], leaves1, leaves2, width, isHorizontal)) continue;
return new BTRoom[] { leaves1[i], leaves2[j] };
}
@@ -181,7 +155,60 @@ namespace Barotrauma.RuinGeneration
return null;
}
private bool CheckForIntersection(BTRoom potential1, BTRoom potential2, List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
{
Rectangle potential1Rect = potential1.Rect;
Rectangle potential2Rect = potential2.Rect;
Rectangle potentialCorridorRectangle = CalculateRectangle(potential1.Rect, potential2.Rect, width, isHorizontal);
if (potentialCorridorRectangle.Width <= 0 || potentialCorridorRectangle.Height <= 0) return true; // Invalid rectangle
for (int i = 0; i < leaves1.Count; i++)
{
if (leaves1[i] == potential1) continue;
if (potentialCorridorRectangle.Intersects(leaves1[i].Rect)) return true;
}
for (int i = 0; i < leaves2.Count; i++)
{
if (leaves2[i] == potential2) continue;
if (potentialCorridorRectangle.Intersects(leaves2[i].Rect)) return true;
}
rect = potentialCorridorRectangle; // Save the rectangle that passes the test
return false;
}
private Rectangle CalculateRectangle(Rectangle rect1, Rectangle rect2, int width, bool isHorizontal)
{
if (isHorizontal)
{
int left = Math.Min(rect1.Right, rect2.Right);
int right = Math.Max(rect1.X, rect2.X);
int top = Math.Max(rect1.Y, rect2.Y);
//int bottom = Math.Min(room1.Bottom, room2.Bottom);
int yPos = top;//Rand.Range(top, bottom - width, Rand.RandSync.Server);
return new Rectangle(left, yPos, right - left, width);
}
else if (rect1.Y > rect2.Bottom || rect2.Y > rect1.Bottom)
{
int left = Math.Max(rect1.X, rect2.X);
int right = Math.Min(rect1.Right, rect2.Right);
int top = Math.Min(rect1.Bottom, rect2.Bottom);
int bottom = Math.Max(rect1.Y, rect2.Y);
int xPos = Rand.Range(left, right - width, Rand.RandSync.Server);
return new Rectangle(xPos, top, width, bottom - top);
}
else
{
DebugConsole.ThrowError("wat");
return new Rectangle();
}
}
}
}
@@ -0,0 +1,474 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma.RuinGeneration
{
[Flags]
enum RuinEntityType
{
Wall, Back, Door, Hatch, Prop
}
class RuinGenerationParams : ISerializableEntity
{
public static List<RuinGenerationParams> List
{
get
{
if (paramsList == null)
{
LoadAll();
}
return paramsList;
}
}
private static List<RuinGenerationParams> paramsList;
private string filePath;
private List<RuinRoom> roomTypeList;
public string Name => "RuinGenerationParams";
[Serialize("5000,5000", false), Editable()]
public Point SizeMin
{
get;
set;
}
[Serialize("8000,8000", false), Editable()]
public Point SizeMax
{
get;
set;
}
[Serialize(3, false), Editable(MinValueInt = 1, MaxValueInt = 10, ToolTip = "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the minimum number of times the split is done.")]
public int RoomDivisionIterationsMin
{
get;
set;
}
[Serialize(4, false), Editable(MinValueInt = 1, MaxValueInt = 10, ToolTip = "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the maximum number of times the split is done.")]
public int RoomDivisionIterationsMax
{
get;
set;
}
[Serialize(0.5f, false), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.9f, ToolTip = "The probability for the split algorithm to split the area vertically. High values tend to create tall, vertical rooms, and low values wide, horizontal rooms.")]
public float VerticalSplitProbability
{
get;
set;
}
[Serialize(400, false), Editable(ToolTip = "The splitting algorithm attempts to keep the dimensions the split areas larger than this. For example, if the width of the split areas would be smaller than this after a vertical split, the algorithm will do a horizontal split.")]
public int MinSplitWidth
{
get;
set;
}
[Serialize("0.5,0.9", false), Editable(ToolTip = "The minimum and maximum width of a room relative to the areas created by the split algorithm.")]
public Vector2 RoomWidthRange
{
get;
set;
}
[Serialize("0.5,0.9", false), Editable(ToolTip = "The minimum and maximum height of a room relative to the areas created by the split algorithm.")]
public Vector2 RoomHeightRange
{
get;
set;
}
[Serialize("200,256", false), Editable(ToolTip = "The minimum and maximum width of the corridors between rooms.")]
public Point CorridorWidthRange
{
get;
set;
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
public IEnumerable<RuinRoom> RoomTypeList
{
get { return roomTypeList; }
}
private RuinGenerationParams(XElement element)
{
roomTypeList = new List<RuinRoom>();
if (element != null)
{
foreach (XElement subElement in element.Elements())
{
roomTypeList.Add(new RuinRoom(subElement));
}
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
public static RuinGenerationParams GetRandom()
{
if (paramsList == null) { LoadAll(); }
if (paramsList.Count == 0)
{
DebugConsole.ThrowError("No ruin configuration files found in any content package.");
return new RuinGenerationParams(null);
}
return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)];
}
private static void LoadAll()
{
paramsList = new List<RuinGenerationParams>();
foreach (string configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
{
XDocument doc = XMLExtensions.TryLoadXml(configFile);
if (doc?.Root == null) continue;
var newParams = new RuinGenerationParams(doc.Root)
{
filePath = configFile
};
paramsList.Add(newParams);
}
}
public static void SaveAll()
{
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
};
foreach (RuinGenerationParams generationParams in List)
{
foreach (string configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig))
{
if (configFile != generationParams.filePath) continue;
XDocument doc = XMLExtensions.TryLoadXml(configFile);
if (doc?.Root == null) continue;
SerializableProperty.SerializeProperties(generationParams, doc.Root);
using (var writer = XmlWriter.Create(configFile, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
}
}
}
class RuinRoom : ISerializableEntity
{
public enum RoomPlacement
{
Any,
First,
Last
}
public string Name
{
get;
private set;
}
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float Commonness { get; private set; }
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
[Serialize(RoomPlacement.Any, false), Editable()]
public RoomPlacement Placement
{
get;
set;
}
[Serialize(0, false), Editable()]
public int PlacementOffset
{
get;
set;
}
[Serialize(false, false), Editable()]
public bool IsCorridor
{
get;
set;
}
[Serialize(1.0f, false), Editable()]
public float MinWaterAmount
{
get;
set;
}
[Serialize(1.0f, false), Editable()]
public float MaxWaterAmount
{
get;
set;
}
private List<RuinEntityConfig> entityList = new List<RuinEntityConfig>();
public RuinRoom(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
Name = element.GetAttributeString("name", "");
if (element != null)
{
int groupIndex = 0;
LoadEntities(element, ref groupIndex);
}
void LoadEntities(XElement element2, ref int groupIndex)
{
foreach (XElement subElement in element2.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "chooseone")
{
groupIndex++;
LoadEntities(subElement, ref groupIndex);
}
else
{
entityList.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
}
}
}
}
public RuinEntityConfig GetRandomEntity(RuinEntityType type, Alignment alignment)
{
var matchingEntities = entityList.FindAll(rs =>
rs.Type == type &&
rs.Alignment.HasFlag(alignment));
if (!matchingEntities.Any()) return null;
return ToolBox.SelectWeightedRandom(
matchingEntities,
matchingEntities.Select(s => s.Commonness).ToList(),
Rand.RandSync.Server);
}
public List<RuinEntityConfig> GetPropList(RuinShape room, Rand.RandSync randSync)
{
Dictionary<int, List<RuinEntityConfig>> propGroups = new Dictionary<int, List<RuinEntityConfig>>();
foreach (RuinEntityConfig entityConfig in entityList)
{
if (entityConfig.Type != RuinEntityType.Prop) { continue; }
if (room.Rect.Width < entityConfig.MinRoomSize.X || room.Rect.Height < entityConfig.MinRoomSize.Y) { continue; }
if (room.Rect.Width > entityConfig.MaxRoomSize.X || room.Rect.Height > entityConfig.MaxRoomSize.Y) { continue; }
if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex))
{
propGroups[entityConfig.SingleGroupIndex] = new List<RuinEntityConfig>();
}
propGroups[entityConfig.SingleGroupIndex].Add(entityConfig);
}
List<RuinEntityConfig> props = new List<RuinEntityConfig>();
foreach (KeyValuePair<int, List<RuinEntityConfig>> propGroup in propGroups)
{
if (propGroup.Key == 0)
{
props.AddRange(propGroup.Value);
}
else
{
props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]);
}
}
return props;
}
}
class RuinEntityConfig : ISerializableEntity
{
public readonly MapEntityPrefab Prefab;
public enum RelativePlacement
{
SameRoom,
NextRoom,
NextCorridor,
PreviousRoom,
PreviousCorridor,
FirstRoom,
FirstCorridor,
LastRoom,
LastCorridor
}
public class EntityConnection
{
//which type of room to search for the item to connect to
//sameroom, nextroom, previousroom, firstroom and lastroom are also valid
public string RoomName
{
get;
private set;
}
public string TargetEntityIdentifier
{
get;
private set;
}
//Identifier of the item to run the wire from. Only needed in item assemblies to determine which item in the assembly to use.
public string SourceEntityIdentifier
{
get;
private set;
}
//if set, the connection is done by running a wire from
//(Pair.First = the name of the connection in this item) to (Pair.Second = the name of the connection in the target item)
public Pair<string, string> WireConnection
{
get;
private set;
}
public EntityConnection(XElement element)
{
RoomName = element.GetAttributeString("roomname", "");
TargetEntityIdentifier = element.GetAttributeString("targetentity", "");
SourceEntityIdentifier = element.GetAttributeString("sourceentity", "");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "wire")
{
WireConnection = new Pair<string, string>(
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", ""));
}
}
}
}
[Serialize(Alignment.Bottom, false), Editable]
public Alignment Alignment { get; private set; }
[Serialize("0,0", false), Editable(ToolTip = "Minimum offset from the anchor position, relative to the size of the room."+
" For example, a value of { -0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-left corner of the room and bottom-center.")]
public Vector2 MinOffset { get; private set; }
[Serialize("0,0", false), Editable(ToolTip = "Maximum offset from the anchor position, relative to the size of the room." +
" For example, a value of { 0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-right corner of the room and bottom-center.")]
public Vector2 MaxOffset { get; private set; }
[Serialize(RuinEntityType.Prop, false), Editable]
public RuinEntityType Type { get; private set; }
[Serialize(false, false), Editable]
public bool Expand { get; private set; }
[Serialize(RelativePlacement.SameRoom, false), Editable]
public RelativePlacement PlacementRelativeToParent { get; private set; }
[Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float Commonness { get; private set; }
[Serialize(1, false)]
public int MinAmount { get; private set; }
[Serialize(1, false)]
public int MaxAmount { get; private set; }
[Serialize("0,0", false)]
public Point MinRoomSize { get; private set; }
[Serialize("100000,100000", false)]
public Point MaxRoomSize { get; private set; }
[Serialize("", false)]
public string TargetContainer { get; private set; }
public List<EntityConnection> EntityConnections { get; private set; } = new List<EntityConnection>();
public int SingleGroupIndex;
private readonly List<RuinEntityConfig> childEntities = new List<RuinEntityConfig>();
public IEnumerable<RuinEntityConfig> ChildEntities
{
get { return childEntities; }
}
public string Name => Prefab == null ? "null" : Prefab.Name;
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
} = new Dictionary<string, SerializableProperty>();
public RuinEntityConfig(XElement element)
{
string name = element.GetAttributeString("prefab", "");
Prefab = MapEntityPrefab.Find(name: null, identifier: name);
if (Prefab == null)
{
DebugConsole.ThrowError("Loading ruin entity config failed - map entity prefab \"" + name + "\" not found.");
return;
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
int gIndex = 0;
LoadChildren(element, ref gIndex);
void LoadChildren(XElement element2, ref int groupIndex)
{
foreach (XElement subElement in element2.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "connection":
case "entityconnection":
EntityConnections.Add(new EntityConnection(subElement));
break;
case "chooseone":
groupIndex++;
LoadChildren(subElement, ref groupIndex);
break;
default:
childEntities.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex });
break;
}
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,100 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.RuinGeneration
{
[Flags]
enum RuinStructureType
{
Wall = 1, CorridorWall = 2, Prop = 4, Back = 8, Door=16, Hatch=32, HeavyWall=64
}
class RuinStructure
{
private static List<RuinStructure> list;
public readonly MapEntityPrefab Prefab;
public readonly Alignment Alignment;
public readonly RuinStructureType Type;
private int commonness;
private RuinStructure(XElement element)
{
string name = element.GetAttributeString("prefab", "");
Prefab = MapEntityPrefab.Find(name);
if (Prefab == null)
{
DebugConsole.ThrowError("Loading ruin structure failed - structure prefab \"" + name + " not found");
return;
}
string alignmentStr = element.GetAttributeString("alignment", "Bottom");
if (!Enum.TryParse(alignmentStr, true, out Alignment))
{
DebugConsole.ThrowError("Error in ruin structure \"" + name + "\" - " + alignmentStr + " is not a valid alignment");
}
string typeStr = element.GetAttributeString("type", "");
if (!Enum.TryParse(typeStr, true, out Type))
{
DebugConsole.ThrowError("Error in ruin structure \"" + name + "\" - " + typeStr + " is not a valid type");
return;
}
commonness = element.GetAttributeInt("commonness", 1);
list.Add(this);
}
private static void Load()
{
list = new List<RuinStructure>();
foreach (string configFile in GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.RuinConfig))
{
XDocument doc = XMLExtensions.TryLoadXml(configFile);
if (doc == null || doc.Root == null) continue;
foreach (XElement element in doc.Root.Elements())
{
new RuinStructure(element);
}
}
}
public static RuinStructure GetRandom(RuinStructureType type, Alignment alignment)
{
if (list == null)
{
DebugConsole.Log("Loading ruin structures...");
Load();
}
var matchingStructures = list.FindAll(rs => rs.Type.HasFlag(type) && rs.Alignment.HasFlag(alignment));
if (!matchingStructures.Any()) return null;
int totalCommonness = matchingStructures.Sum(m => m.commonness);
int randomNumber = Rand.Int(totalCommonness + 1, Rand.RandSync.Server);
foreach (RuinStructure ruinStructure in matchingStructures)
{
if (randomNumber <= ruinStructure.commonness)
{
return ruinStructure;
}
randomNumber -= ruinStructure.commonness;
}
return null;
}
}
}
@@ -1,998 +0,0 @@
/*
* Created by SharpDevelop.
* User: Burhan
* Date: 17/06/2014
* Time: 11:30 م
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
/*
* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
* Bell Laboratories.
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*/
/*
* This code was originally written by Stephan Fortune in C code. I, Shane O'Sullivan,
* have since modified it, encapsulating it in a C++ class and, fixing memory leaks and
* adding accessors to the Voronoi Edges.
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*/
/*
* Java Version by Zhenyu Pan
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*/
/*
* C# Version by Burhan Joukhadar
*
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*/
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Voronoi2
{
/// <summary>
/// Description of Voronoi.
/// </summary>
public class Voronoi
{
// ************* Private members ******************
double borderMinX, borderMaxX, borderMinY, borderMaxY;
int siteidx;
double xmin, xmax, ymin, ymax, deltax, deltay;
int nvertices;
int nedges;
int nsites;
Site[] sites;
Site bottomsite;
int sqrt_nsites;
double minDistanceBetweenSites;
int PQcount;
int PQmin;
int PQhashsize;
Halfedge[] PQhash;
const int LE = 0;
const int RE = 1;
int ELhashsize;
Halfedge[] ELhash;
Halfedge ELleftend, ELrightend;
List<GraphEdge> allEdges;
// ************* Public methods ******************
// ******************************************
// constructor
public Voronoi ( double minDistanceBetweenSites )
{
siteidx = 0;
sites = null;
allEdges = null;
this.minDistanceBetweenSites = minDistanceBetweenSites;
}
/**
*
* @param xValuesIn Array of X values for each site.
* @param yValuesIn Array of Y values for each site. Must be identical length to yValuesIn
* @param minX The minimum X of the bounding box around the voronoi
* @param maxX The maximum X of the bounding box around the voronoi
* @param minY The minimum Y of the bounding box around the voronoi
* @param maxY The maximum Y of the bounding box around the voronoi
* @return
*/
// تستدعى هذه العملية لإنشاء مخطط فورونوي
public List<GraphEdge> generateVoronoi ( double[] xValuesIn, double[] yValuesIn, double minX, double maxX, double minY, double maxY )
{
sort(xValuesIn, yValuesIn, xValuesIn.Length);
// Check bounding box inputs - if mins are bigger than maxes, swap them
double temp = 0;
if ( minX > maxX )
{
temp = minX;
minX = maxX;
maxX = temp;
}
if ( minY > maxY )
{
temp = minY;
minY = maxY;
maxY = temp;
}
borderMinX = minX;
borderMinY = minY;
borderMaxX = maxX;
borderMaxY = maxY;
siteidx = 0;
voronoi_bd ();
return allEdges;
}
/*********************************************************
* Private methods - implementation details
********************************************************/
private void sort ( double[] xValuesIn, double[] yValuesIn, int count )
{
sites = null;
allEdges = new List<GraphEdge>();
nsites = count;
nvertices = 0;
nedges = 0;
double sn = (double)nsites + 4;
sqrt_nsites = (int) Math.Sqrt ( sn );
// Copy the inputs so we don't modify the originals
double[] xValues = new double[count];
double[] yValues = new double[count];
for (int i = 0; i < count; i++)
{
xValues[i] = xValuesIn[i];
yValues[i] = yValuesIn[i];
}
sortNode ( xValues, yValues, count );
}
private void qsort ( Site[] sites )
{
List<Site> listSites = new List<Site>( sites.Length );
for ( int i = 0; i < sites.Length; i++ )
{
listSites.Add ( sites[i] );
}
listSites.Sort ( new SiteSorterYX () );
// Copy back into the array
for (int i=0; i < sites.Length; i++)
{
sites[i] = listSites[i];
}
}
private void sortNode ( double[] xValues, double[] yValues, int numPoints )
{
nsites = numPoints;
sites = new Site[nsites];
xmin = xValues[0];
ymin = yValues[0];
xmax = xValues[0];
ymax = yValues[0];
for ( int i = 0; i < nsites; i++ )
{
sites[i] = new Site();
sites[i].coord.setPoint ( xValues[i], yValues[i] );
sites[i].sitenbr = i;
if ( xValues[i] < xmin )
xmin = xValues[i];
else if ( xValues[i] > xmax )
xmax = xValues[i];
if ( yValues[i] < ymin )
ymin = yValues[i];
else if ( yValues[i] > ymax )
ymax = yValues[i];
}
qsort ( sites );
deltax = xmax - xmin;
deltay = ymax - ymin;
}
private Site nextone ()
{
Site s;
if ( siteidx < nsites )
{
s = sites[siteidx];
siteidx++;
return s;
}
return null;
}
private Edge bisect ( Site s1, Site s2 )
{
double dx, dy, adx, ady;
Edge newedge;
newedge = new Edge();
newedge.reg[0] = s1;
newedge.reg[1] = s2;
newedge.ep [0] = null;
newedge.ep[1] = null;
dx = s2.coord.x - s1.coord.x;
dy = s2.coord.y - s1.coord.y;
adx = dx > 0 ? dx : -dx;
ady = dy > 0 ? dy : -dy;
newedge.c = (double)(s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy* dy) * 0.5);
if ( adx > ady )
{
newedge.a = 1.0;
newedge.b = dy / dx;
newedge.c /= dx;
}
else
{
newedge.a = dx / dy;
newedge.b = 1.0;
newedge.c /= dy;
}
newedge.edgenbr = nedges;
nedges++;
return newedge;
}
private void makevertex ( Site v )
{
v.sitenbr = nvertices;
nvertices++;
}
private bool PQinitialize ()
{
PQcount = 0;
PQmin = 0;
PQhashsize = 4 * sqrt_nsites;
PQhash = new Halfedge[ PQhashsize ];
for ( int i = 0; i < PQhashsize; i++ )
{
PQhash [i] = new Halfedge();
}
return true;
}
private int PQbucket ( Halfedge he )
{
int bucket;
bucket = (int) ((he.ystar - ymin) / deltay * PQhashsize);
if ( bucket < 0 )
bucket = 0;
if ( bucket >= PQhashsize )
bucket = PQhashsize - 1;
if ( bucket < PQmin )
PQmin = bucket;
return bucket;
}
// push the HalfEdge into the ordered linked list of vertices
private void PQinsert ( Halfedge he, Site v, double offset )
{
Halfedge last, next;
he.vertex = v;
he.ystar = (double)(v.coord.y + offset);
last = PQhash [ PQbucket (he) ];
while
(
(next = last.PQnext) != null
&&
(he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))
)
{
last = next;
}
he.PQnext = last.PQnext;
last.PQnext = he;
PQcount++;
}
// remove the HalfEdge from the list of vertices
private void PQdelete ( Halfedge he )
{
Halfedge last;
if (he.vertex != null)
{
last = PQhash [ PQbucket (he) ];
while ( last.PQnext != he )
{
last = last.PQnext;
}
last.PQnext = he.PQnext;
PQcount--;
he.vertex = null;
}
}
private bool PQempty ()
{
return ( PQcount == 0 );
}
private Point PQ_min ()
{
Point answer = new Point ();
while ( PQhash[PQmin].PQnext == null )
{
PQmin++;
}
answer.x = PQhash[PQmin].PQnext.vertex.coord.x;
answer.y = PQhash[PQmin].PQnext.ystar;
return answer;
}
private Halfedge PQextractmin ()
{
Halfedge curr;
curr = PQhash[PQmin].PQnext;
PQhash[PQmin].PQnext = curr.PQnext;
PQcount--;
return curr;
}
private Halfedge HEcreate(Edge e, int pm)
{
Halfedge answer = new Halfedge();
answer.ELedge = e;
answer.ELpm = pm;
answer.PQnext = null;
answer.vertex = null;
return answer;
}
private bool ELinitialize()
{
ELhashsize = 2 * sqrt_nsites;
ELhash = new Halfedge[ELhashsize];
for (int i = 0; i < ELhashsize; i++)
{
ELhash[i] = null;
}
ELleftend = HEcreate ( null, 0 );
ELrightend = HEcreate ( null, 0 );
ELleftend.ELleft = null;
ELleftend.ELright = ELrightend;
ELrightend.ELleft = ELleftend;
ELrightend.ELright = null;
ELhash[0] = ELleftend;
ELhash[ELhashsize - 1] = ELrightend;
return true;
}
private Halfedge ELright( Halfedge he )
{
return he.ELright;
}
private Halfedge ELleft( Halfedge he )
{
return he.ELleft;
}
private Site leftreg( Halfedge he )
{
if (he.ELedge == null)
{
return bottomsite;
}
return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]);
}
private void ELinsert( Halfedge lb, Halfedge newHe )
{
newHe.ELleft = lb;
newHe.ELright = lb.ELright;
(lb.ELright).ELleft = newHe;
lb.ELright = newHe;
}
/*
* This delete routine can't reclaim node, since pointers from hash table
* may be present.
*/
private void ELdelete( Halfedge he )
{
(he.ELleft).ELright = he.ELright;
(he.ELright).ELleft = he.ELleft;
he.deleted = true;
}
/* Get entry from hash table, pruning any deleted nodes */
private Halfedge ELgethash( int b )
{
Halfedge he;
if (b < 0 || b >= ELhashsize)
return null;
he = ELhash[b];
if (he == null || !he.deleted )
return he;
/* Hash table points to deleted half edge. Patch as necessary. */
ELhash[b] = null;
return null;
}
private Halfedge ELleftbnd( Point p )
{
int bucket;
Halfedge he;
/* Use hash table to get close to desired halfedge */
// use the hash function to find the place in the hash map that this
// HalfEdge should be
bucket = (int) ((p.x - xmin) / deltax * ELhashsize);
// make sure that the bucket position is within the range of the hash
// array
if ( bucket < 0 ) bucket = 0;
if ( bucket >= ELhashsize ) bucket = ELhashsize - 1;
he = ELgethash ( bucket );
// if the HE isn't found, search backwards and forwards in the hash map
// for the first non-null entry
if ( he == null )
{
for ( int i = 1; i < ELhashsize; i++ )
{
if ( (he = ELgethash ( bucket - i ) ) != null )
break;
if ( (he = ELgethash ( bucket + i ) ) != null )
break;
}
}
/* Now search linear list of halfedges for the correct one */
if ( he == ELleftend || ( he != ELrightend && right_of (he, p) ) )
{
// keep going right on the list until either the end is reached, or
// you find the 1st edge which the point isn't to the right of
do
{
he = he.ELright;
}
while ( he != ELrightend && right_of(he, p) );
he = he.ELleft;
}
else
// if the point is to the left of the HalfEdge, then search left for
// the HE just to the left of the point
{
do
{
he = he.ELleft;
}
while ( he != ELleftend && !right_of(he, p) );
}
/* Update hash table and reference counts */
if ( bucket > 0 && bucket < ELhashsize - 1)
{
ELhash[bucket] = he;
}
return he;
}
private void pushGraphEdge( Site leftSite, Site rightSite, Vector2 point1, Vector2 point2 )
{
GraphEdge newEdge = new GraphEdge(point1, point2);
allEdges.Add ( newEdge );
newEdge.site1 = leftSite;
newEdge.site2 = rightSite;
}
private void clip_line( Edge e )
{
double pxmin, pxmax, pymin, pymax;
Site s1, s2;
double x1 = e.reg[0].coord.x;
double y1 = e.reg[0].coord.y;
double x2 = e.reg[1].coord.x;
double y2 = e.reg[1].coord.y;
double x = x2- x1;
double y = y2 - y1;
// if the distance between the two points this line was created from is
// less than the square root of 2 عن جد؟, then ignore it
if ( Math.Sqrt ( (x*x) + (y*y) ) < minDistanceBetweenSites )
{
return;
}
pxmin = borderMinX;
pymin = borderMinY;
pxmax = borderMaxX;
pymax = borderMaxY;
if ( e.a == 1.0 && e.b >= 0.0 )
{
s1 = e.ep[1];
s2 = e.ep[0];
}
else
{
s1 = e.ep[0];
s2 = e.ep[1];
}
if ( e.a == 1.0 )
{
y1 = pymin;
if ( s1 != null && s1.coord.y > pymin )
y1 = s1.coord.y;
if ( y1 > pymax )
y1 = pymax;
x1 = e.c - e.b * y1;
y2 = pymax;
if ( s2 != null && s2.coord.y < pymax )
y2 = s2.coord.y;
if ( y2 < pymin )
y2 = pymin;
x2 = e.c - e.b * y2;
if ( ( (x1 > pxmax) & (x2 > pxmax) ) | ( (x1 < pxmin) & (x2 < pxmin) ) )
return;
if ( x1 > pxmax )
{
x1 = pxmax;
y1 = ( e.c - x1 ) / e.b;
}
if ( x1 < pxmin )
{
x1 = pxmin;
y1 = ( e.c - x1 ) / e.b;
}
if ( x2 > pxmax )
{
x2 = pxmax;
y2 = ( e.c - x2 ) / e.b;
}
if ( x2 < pxmin )
{
x2 = pxmin;
y2 = ( e.c - x2 ) / e.b;
}
}
else
{
x1 = pxmin;
if ( s1 != null && s1.coord.x > pxmin )
x1 = s1.coord.x;
if ( x1 > pxmax )
x1 = pxmax;
y1 = e.c - e.a * x1;
x2 = pxmax;
if ( s2 != null && s2.coord.x < pxmax )
x2 = s2.coord.x;
if ( x2 < pxmin )
x2 = pxmin;
y2 = e.c - e.a * x2;
if (((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin)))
return;
if ( y1 > pymax )
{
y1 = pymax;
x1 = ( e.c - y1 ) / e.a;
}
if ( y1 < pymin )
{
y1 = pymin;
x1 = ( e.c - y1 ) / e.a;
}
if ( y2 > pymax )
{
y2 = pymax;
x2 = ( e.c - y2 ) / e.a;
}
if ( y2 < pymin )
{
y2 = pymin;
x2 = ( e.c - y2 ) / e.a;
}
}
pushGraphEdge(e.reg[0], e.reg[1], new Vector2((float)x1, (float)y1), new Vector2((float)x2, (float)y2));
}
private void endpoint( Edge e, int lr, Site s )
{
e.ep[lr] = s;
if ( e.ep[RE - lr] == null )
return;
clip_line ( e );
}
/* returns true if p is to right of halfedge e */
private bool right_of(Halfedge el, Point p)
{
Edge e;
Site topsite;
bool right_of_site;
bool above, fast;
double dxp, dyp, dxs, t1, t2, t3, yl;
e = el.ELedge;
topsite = e.reg[1];
if ( p.x > topsite.coord.x )
right_of_site = true;
else
right_of_site = false;
if ( right_of_site && el.ELpm == LE )
return true;
if (!right_of_site && el.ELpm == RE )
return false;
if ( e.a == 1.0 )
{
dxp = p.x - topsite.coord.x;
dyp = p.y - topsite.coord.y;
fast = false;
if ( (!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0)) )
{
above = dyp >= e.b * dxp;
fast = above;
}
else
{
above = p.x + p.y * e.b > e.c;
if ( e.b < 0.0 )
above = !above;
if ( !above )
fast = true;
}
if ( !fast )
{
dxs = topsite.coord.x - ( e.reg[0] ).coord.x;
above = e.b * (dxp * dxp - dyp * dyp)
< dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b);
if ( e.b < 0 )
above = !above;
}
}
else // e.b == 1.0
{
yl = e.c - e.a * p.x;
t1 = p.y - yl;
t2 = p.x - topsite.coord.x;
t3 = yl - topsite.coord.y;
above = t1 * t1 > t2 * t2 + t3 * t3;
}
return ( el.ELpm == LE ? above : !above );
}
private Site rightreg(Halfedge he)
{
if (he.ELedge == (Edge) null)
// if this halfedge has no edge, return the bottom site (whatever
// that is)
{
return (bottomsite);
}
// if the ELpm field is zero, return the site 0 that this edge bisects,
// otherwise return site number 1
return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]);
}
private double dist( Site s, Site t )
{
double dx, dy;
dx = s.coord.x - t.coord.x;
dy = s.coord.y - t.coord.y;
return Math.Sqrt ( dx * dx + dy * dy );
}
// create a new site where the HalfEdges el1 and el2 intersect - note that
// the Point in the argument list is not used, don't know why it's there
private Site intersect( Halfedge el1, Halfedge el2 )
{
Edge e1, e2, e;
Halfedge el;
double d, xint, yint;
bool right_of_site;
Site v; // vertex
e1 = el1.ELedge;
e2 = el2.ELedge;
if ( e1 == null || e2 == null )
return null;
// if the two edges bisect the same parent, return null
if ( e1.reg[1] == e2.reg[1] )
return null;
d = e1.a * e2.b - e1.b * e2.a;
if ( -1.0e-10 < d && d < 1.0e-10 )
return null;
xint = ( e1.c * e2.b - e2.c * e1.b ) / d;
yint = ( e2.c * e1.a - e1.c * e2.a ) / d;
if ( (e1.reg[1].coord.y < e2.reg[1].coord.y)
|| (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x) )
{
el = el1;
e = e1;
}
else
{
el = el2;
e = e2;
}
right_of_site = xint >= e.reg[1].coord.x;
if ((right_of_site && el.ELpm == LE)
|| (!right_of_site && el.ELpm == RE))
return null;
// create a new site at the point of intersection - this is a new vector
// event waiting to happen
v = new Site();
v.coord.x = xint;
v.coord.y = yint;
return v;
}
/*
* implicit parameters: nsites, sqrt_nsites, xmin, xmax, ymin, ymax, deltax,
* deltay (can all be estimates). Performance suffers if they are wrong;
* better to make nsites, deltax, and deltay too big than too small. (?)
*/
private bool voronoi_bd()
{
Site newsite, bot, top, temp, p;
Site v;
Point newintstar = null;
int pm;
Halfedge lbnd, rbnd, llbnd, rrbnd, bisector;
Edge e;
PQinitialize();
ELinitialize();
bottomsite = nextone();
newsite = nextone();
while (true)
{
if (!PQempty())
{
newintstar = PQ_min();
}
// if the lowest site has a smaller y value than the lowest vector
// intersection,
// process the site otherwise process the vector intersection
if (newsite != null && (PQempty()
|| newsite.coord.y < newintstar.y
|| (newsite.coord.y == newintstar.y
&& newsite.coord.x < newintstar.x)))
{
/* new site is smallest -this is a site event */
// get the first HalfEdge to the LEFT of the new site
lbnd = ELleftbnd((newsite.coord));
// get the first HalfEdge to the RIGHT of the new site
rbnd = ELright(lbnd);
// if this halfedge has no edge,bot =bottom site (whatever that
// is)
bot = rightreg(lbnd);
// create a new edge that bisects
e = bisect(bot, newsite);
// create a new HalfEdge, setting its ELpm field to 0
bisector = HEcreate(e, LE);
// insert this new bisector edge between the left and right
// vectors in a linked list
ELinsert(lbnd, bisector);
// if the new bisector intersects with the left edge,
// remove the left edge's vertex, and put in the new one
if ((p = intersect(lbnd, bisector)) != null)
{
PQdelete(lbnd);
PQinsert(lbnd, p, dist(p, newsite));
}
lbnd = bisector;
// create a new HalfEdge, setting its ELpm field to 1
bisector = HEcreate(e, RE);
// insert the new HE to the right of the original bisector
// earlier in the IF stmt
ELinsert(lbnd, bisector);
// if this new bisector intersects with the new HalfEdge
if ((p = intersect(bisector, rbnd)) != null)
{
// push the HE into the ordered linked list of vertices
PQinsert(bisector, p, dist(p, newsite));
}
newsite = nextone();
} else if (!PQempty())
/* intersection is smallest - this is a vector event */
{
// pop the HalfEdge with the lowest vector off the ordered list
// of vectors
lbnd = PQextractmin();
// get the HalfEdge to the left of the above HE
llbnd = ELleft(lbnd);
// get the HalfEdge to the right of the above HE
rbnd = ELright(lbnd);
// get the HalfEdge to the right of the HE to the right of the
// lowest HE
rrbnd = ELright(rbnd);
// get the Site to the left of the left HE which it bisects
bot = leftreg(lbnd);
// get the Site to the right of the right HE which it bisects
top = rightreg(rbnd);
v = lbnd.vertex; // get the vertex that caused this event
makevertex(v); // set the vertex number - couldn't do this
// earlier since we didn't know when it would be processed
endpoint(lbnd.ELedge, lbnd.ELpm, v);
// set the endpoint of
// the left HalfEdge to be this vector
endpoint(rbnd.ELedge, rbnd.ELpm, v);
// set the endpoint of the right HalfEdge to
// be this vector
ELdelete(lbnd); // mark the lowest HE for
// deletion - can't delete yet because there might be pointers
// to it in Hash Map
PQdelete(rbnd);
// remove all vertex events to do with the right HE
ELdelete(rbnd); // mark the right HE for
// deletion - can't delete yet because there might be pointers
// to it in Hash Map
pm = LE; // set the pm variable to zero
if (bot.coord.y > top.coord.y)
// if the site to the left of the event is higher than the
// Site
{ // to the right of it, then swap them and set the 'pm'
// variable to 1
temp = bot;
bot = top;
top = temp;
pm = RE;
}
e = bisect(bot, top); // create an Edge (or line)
// that is between the two Sites. This creates the formula of
// the line, and assigns a line number to it
bisector = HEcreate(e, pm); // create a HE from the Edge 'e',
// and make it point to that edge
// with its ELedge field
ELinsert(llbnd, bisector); // insert the new bisector to the
// right of the left HE
endpoint(e, RE - pm, v); // set one endpoint to the new edge
// to be the vector point 'v'.
// If the site to the left of this bisector is higher than the
// right Site, then this endpoint
// is put in position 0; otherwise in pos 1
// if left HE and the new bisector intersect, then delete
// the left HE, and reinsert it
if ((p = intersect(llbnd, bisector)) != null)
{
PQdelete(llbnd);
PQinsert(llbnd, p, dist(p, bot));
}
// if right HE and the new bisector intersect, then
// reinsert it
if ((p = intersect(bisector, rrbnd)) != null)
{
PQinsert(bisector, p, dist(p, bot));
}
} else
{
break;
}
}
for (lbnd = ELright(ELleftend); lbnd != ELrightend; lbnd = ELright(lbnd))
{
e = lbnd.ELedge;
clip_line(e);
}
return true;
}
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, float minX, float minY, float maxX, float maxY)
{
double[] xVal = new double[sites.Count];
double[] yVal = new double[sites.Count];
for (int i = 0; i < sites.Count; i++)
{
xVal[i] = sites[i].X;
yVal[i] = sites[i].Y;
}
return generateVoronoi(xVal, yVal, minX, maxX, minY, maxY);
}
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, int width, int height)
{
double[] xVal = new double[sites.Count];
double[] yVal = new double[sites.Count];
for (int i = 0; i < sites.Count; i++)
{
xVal[i] = sites[i].X;
yVal[i] = sites[i].Y;
}
return generateVoronoi(xVal, yVal, 0, width, 0, height);
}
} // Voronoi Class End
} // namespace Voronoi2 End
@@ -1,263 +0,0 @@
/*
* Created by SharpDevelop.
* User: Burhan
* Date: 17/06/2014
* Time: 09:29 م
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
/*
Copyright 2011 James Humphreys. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY James Humphreys ``AS IS\" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of James Humphreys.
*/
/*
* C# Version by Burhan Joukhadar
*
* Permission to use, copy, modify, and distribute this software for any
* purpose without fee is hereby granted, provided that this entire notice
* is included in all copies of any software which is or includes a copy
* or modification of this software and in all copies of the supporting
* documentation for such software.
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
*/
using Barotrauma;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Voronoi2
{
public class Point
{
public double x, y;
public void setPoint ( double x, double y )
{
this.x = x;
this.y = y;
}
}
// use for sites and vertecies
public class Site
{
public Point coord;
public int sitenbr;
public void SetPoint(Vector2 point)
{
coord.setPoint(point.X, point.Y);
}
public Site ()
{
coord = new Point();
}
}
public class Edge
{
public double a = 0, b = 0, c = 0;
public Site[] ep;
public Site[] reg;
public int edgenbr;
public Edge ()
{
ep = new Site[2];
reg = new Site[2];
}
}
public class Halfedge
{
public Halfedge ELleft, ELright;
public Edge ELedge;
public bool deleted;
public int ELpm;
public Site vertex;
public double ystar;
public Halfedge PQnext;
public Halfedge ()
{
PQnext = null;
}
}
public enum CellType
{
Solid, Empty, Edge, Path, Removed
}
public class VoronoiCell
{
public List<GraphEdge> edges;
public Site site;
public List<Vector2> bodyVertices;
public Body body;
public CellType CellType;
public Vector2 Translation;
public Vector2 Center
{
get { return new Vector2((float)site.coord.x, (float)site.coord.y)+Translation; }
}
public VoronoiCell(Vector2[] vertices)
{
edges = new List<GraphEdge>();
bodyVertices = new List<Vector2>();
Vector2 midPoint = Vector2.Zero;
foreach (Vector2 vertex in vertices)
{
midPoint += vertex;
}
midPoint /= vertices.Length;
for (int i = 1; i < vertices.Length; i++ )
{
GraphEdge ge = new GraphEdge(vertices[i-1], vertices[i]);
System.Diagnostics.Debug.Assert(ge.point1 != ge.point2);
edges.Add(ge);
}
GraphEdge lastEdge = new GraphEdge(vertices[0], vertices[vertices.Length-1]);
edges.Add(lastEdge);
site = new Site();
site.SetPoint(midPoint);
}
public VoronoiCell(Site site)
{
edges = new List<GraphEdge>();
bodyVertices = new List<Vector2>();
//bodies = new List<Body>();
this.site = site;
}
public bool IsPointInside(Vector2 point)
{
foreach (GraphEdge edge in edges)
{
if (MathUtils.LinesIntersect(point, Center, edge.point1, edge.point2)) return false;
}
return true;
}
}
public class GraphEdge
{
public Vector2 point1, point2;
public Site site1, site2;
public VoronoiCell cell1, cell2;
public bool isSolid;
public bool OutsideLevel;
public Vector2 Center
{
get { return (point1 + point2) / 2.0f; }
}
public GraphEdge(Vector2 point1, Vector2 point2)
{
this.point1 = point1;
this.point2 = point2;
}
public VoronoiCell AdjacentCell(VoronoiCell cell)
{
if (cell1 == cell)
{
return cell2;
}
else if (cell2 == cell)
{
return cell1;
}
return null;
}
/// <summary>
/// Returns the normal of the edge that points outwards from the specified cell
/// </summary>
public Vector2 GetNormal(VoronoiCell cell)
{
Vector2 dir = Vector2.Normalize(point1 - point2);
Vector2 normal = new Vector2(dir.Y, -dir.X);
if (cell != null && Vector2.Dot(normal, Vector2.Normalize(Center - cell.Center)) < 0)
{
normal = -normal;
}
return normal;
}
public override string ToString()
{
return "GraphEdge (" + point1.ToString() + ", " + point2.ToString() + ")";
}
}
// للترتيب
public class SiteSorterYX : IComparer<Site>
{
public int Compare ( Site p1, Site p2 )
{
Point s1 = p1.coord;
Point s2 = p2.coord;
if ( s1.y < s2.y ) return -1;
if ( s1.y > s2.y ) return 1;
if ( s1.x < s2.x ) return -1;
if ( s1.x > s2.x ) return 1;
return 0;
}
}
}
@@ -62,8 +62,10 @@ namespace Barotrauma
public static LinkedSubmarine CreateDummy(Submarine mainSub, Submarine linkedSub)
{
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
sl.sub = linkedSub;
LinkedSubmarine sl = new LinkedSubmarine(mainSub)
{
sub = linkedSub
};
return sl;
}
@@ -114,10 +116,13 @@ namespace Barotrauma
foreach (XElement element in rootElement.Elements())
{
if (element.Name != "Structure") continue;
if (element.Name != "Structure") { continue; }
string name = element.GetAttributeString("name", "");
if (!wallPrefabs.Any(wp => wp.Name == name)) continue;
string identifier = element.GetAttributeString("identifier", "");
StructurePrefab prefab = Structure.FindPrefab(name, identifier);
if (prefab == null) { continue; }
var rect = element.GetAttributeVector4("rect", Vector4.Zero);
@@ -130,7 +135,7 @@ namespace Barotrauma
wallVertices = MathUtils.GiftWrap(points);
}
public static void Load(XElement element, Submarine submarine)
public static LinkedSubmarine Load(XElement element, Submarine submarine)
{
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
@@ -138,21 +143,21 @@ namespace Barotrauma
if (Screen.Selected == GameMain.SubEditorScreen)
{
//string filePath = ToolBox.GetAttributeString(element, "filepath", "");
linkedSub = CreateDummy(submarine, element, pos);
linkedSub.saveElement = element;
}
else
{
linkedSub = new LinkedSubmarine(submarine);
linkedSub.saveElement = element;
linkedSub = new LinkedSubmarine(submarine)
{
saveElement = element
};
string levelSeed = element.GetAttributeString("location", "");
if (!string.IsNullOrWhiteSpace(levelSeed) && GameMain.GameSession.Level != null && GameMain.GameSession.Level.Seed != levelSeed)
{
linkedSub.loadSub = false;
return;
return null;
}
linkedSub.loadSub = true;
@@ -171,7 +176,7 @@ namespace Barotrauma
linkedSub.linkedToID.Add((ushort)int.Parse(linkedToIds[i]));
}
}
return linkedSub;
}
public override void OnMapLoaded()
@@ -197,7 +202,7 @@ namespace Barotrauma
MapEntity linkedItem = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent<DockingPort>() != null);
if (linkedItem == null)
{
linkedPort = DockingPort.list.Find(dp => dp.DockingTarget != null && dp.DockingTarget.Item.Submarine == sub);
linkedPort = DockingPort.List.FirstOrDefault(dp => dp.DockingTarget != null && dp.DockingTarget.Item.Submarine == sub);
}
else
{
@@ -210,7 +215,7 @@ namespace Barotrauma
}
float closestDistance = 0.0f;
foreach (DockingPort port in DockingPort.list)
foreach (DockingPort port in DockingPort.List)
{
if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) continue;
@@ -230,10 +235,7 @@ namespace Barotrauma
Vector2.UnitY * Math.Sign(linkedPort.Item.WorldPosition.Y - myPort.Item.WorldPosition.Y));
offset *= myPort.DockedDistance;
sub.SetPosition(
(linkedPort.Item.WorldPosition - portDiff)
- offset);
sub.SetPosition((linkedPort.Item.WorldPosition - portDiff) - offset);
myPort.Dock(linkedPort);
myPort.Lock(true);
@@ -1,64 +1,134 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Location
{
private string name;
private Vector2 mapPosition;
private LocationType type;
public List<LocationConnection> Connections;
public string Name
{
get { return name; }
}
public Vector2 MapPosition
{
get { return mapPosition; }
}
private string baseName;
private int nameFormatIndex;
public bool Discovered;
public LocationType Type
public int TypeChangeTimer;
public string Name { get; private set; }
public Vector2 MapPosition { get; private set; }
public LocationType Type { get; private set; }
public int PortraitId { get; private set; }
public int MissionsCompleted;
private List<Mission> availableMissions = new List<Mission>();
public IEnumerable<Mission> AvailableMissions
{
get { return type; }
get
{
CheckMissionCompleted();
for (int i = availableMissions.Count; i < Connections.Count * 2; i++)
{
int seed = (ToolBox.StringToInt(Name) + MissionsCompleted * 10 + i) % int.MaxValue;
MTRandom rand = new MTRandom(seed);
LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count];
Location destination = connection.OtherLocation(this);
var mission = Mission.LoadRandom(new Location[] { this, destination }, rand, true, MissionType.Random, true);
if (mission == null) { continue; }
if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; }
if (GameSettings.VerboseLogging && mission != null)
{
DebugConsole.NewMessage("Generated a new mission for a location connection (seed: " + seed.ToString("X") + ", type: " + mission.Name + ")", Color.White);
}
availableMissions.Add(mission);
}
return availableMissions;
}
}
public Location(Vector2 mapPosition)
public Mission SelectedMission
{
this.type = LocationType.Random();
get;
set;
}
this.name = RandomName(type);
this.mapPosition = mapPosition;
#if CLIENT
if (type.HasHireableCharacters)
public int SelectedMissionIndex
{
get { return availableMissions.IndexOf(SelectedMission); }
set
{
hireManager = new HireManager();
hireManager.GenerateCharacters(this, HireManager.MaxAvailableCharacters);
if (value < 0 || value >= AvailableMissions.Count())
{
SelectedMission = null;
return;
}
SelectedMission = availableMissions[value];
}
#endif
}
public Location(Vector2 mapPosition, int? zone)
{
this.Type = LocationType.Random("", zone);
this.Name = RandomName(Type);
this.MapPosition = mapPosition;
PortraitId = ToolBox.StringToInt(Name);
Connections = new List<LocationConnection>();
}
public static Location CreateRandom(Vector2 position)
public static Location CreateRandom(Vector2 position, int? zone)
{
return new Location(position);
return new Location(position, zone);
}
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
{
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
return AvailableMissions.Where(m => m.Locations[1] == connection.OtherLocation(this));
}
public void ChangeType(LocationType newType)
{
if (newType == Type) return;
Type = newType;
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
}
public void CheckMissionCompleted()
{
foreach (Mission mission in availableMissions)
{
if (mission.Completed)
{
MissionsCompleted++;
}
}
availableMissions.RemoveAll(m => m.Completed);
}
private string RandomName(LocationType type)
{
string randomName = ToolBox.GetRandomLine("Content/Map/locationNames.txt");
int nameFormatIndex = Rand.Int(type.NameFormats.Count, Rand.RandSync.Server);
return type.NameFormats[nameFormatIndex].Replace("[name]", randomName);
baseName = type.GetRandomName();
nameFormatIndex = Rand.Int(type.NameFormats.Count, Rand.RandSync.Server);
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
}
public void Remove()
{
RemoveProjSpecific();
}
partial void RemoveProjSpecific();
}
}
@@ -0,0 +1,68 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class LocationConnection
{
private Location[] locations;
private Level level;
public Biome Biome;
public float Difficulty;
public List<Vector2[]> CrackSegments;
public bool Passed;
public Level Level
{
get { return level; }
set { level = value; }
}
public Vector2 CenterPos
{
get
{
return (locations[0].MapPosition + locations[1].MapPosition) / 2.0f;
}
}
public Location[] Locations
{
get { return locations; }
}
public float Length
{
get;
private set;
}
public LocationConnection(Location location1, Location location2)
{
locations = new Location[] { location1, location2 };
Length = Vector2.Distance(location1.MapPosition, location2.MapPosition);
}
public Location OtherLocation(Location location)
{
if (locations[0] == location)
{
return locations[1];
}
else if (locations[1] == location)
{
return locations[0];
}
else
{
return null;
}
}
}
}
@@ -2,6 +2,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
@@ -9,28 +11,26 @@ namespace Barotrauma
{
class LocationType
{
private static List<LocationType> list = new List<LocationType>();
//sum of the commonness-values of each location type
private static int totalWeight;
private string name;
private int commonness;
public static readonly List<LocationType> List = new List<LocationType>();
private List<string> nameFormats;
private List<string> names;
private Sprite symbolSprite;
private Sprite backGround;
private readonly List<Sprite> portraits = new List<Sprite>();
//<name, commonness>
private List<Tuple<JobPrefab, float>> hireableJobs;
private float totalHireableWeight;
public string Name
{
get { return name; }
}
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly string Name;
public readonly string DisplayName;
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
public List<string> NameFormats
{
@@ -47,55 +47,97 @@ namespace Barotrauma
get { return symbolSprite; }
}
public Sprite Background
public Color SpriteColor
{
get { return backGround; }
get;
private set;
}
private LocationType(XElement element)
{
name = element.Name.ToString();
commonness = element.GetAttributeInt("commonness", 1);
totalWeight += commonness;
Name = element.Name.ToString();
DisplayName = element.GetAttributeString("name", "Name");
nameFormats = new List<string>();
foreach (XAttribute nameFormat in element.Element("nameformats").Attributes())
{
nameFormats.Add(nameFormat.Value);
}
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
try
{
names = File.ReadAllLines(nameFile).ToList();
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to read name file for location type \""+Name+"\"!", e);
names = new List<string>() { "Name file not found" };
}
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" });
foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
{
string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
if (splitCommonnessPerZone.Length != 2 ||
!int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
!float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
{
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Name + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\"");
break;
}
CommonnessPerZone[zoneIndex] = zoneCommonness;
}
hireableJobs = new List<Tuple<JobPrefab, float>>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "hireable") continue;
string jobName = subElement.GetAttributeString("name", "");
JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName.ToLowerInvariant());
if (jobPrefab==null)
switch (subElement.Name.ToString().ToLowerInvariant())
{
DebugConsole.ThrowError("Invalid job name ("+jobName+") in location type "+name);
case "hireable":
string jobIdentifier = subElement.GetAttributeString("identifier", "");
JobPrefab jobPrefab = null;
if (jobIdentifier == "")
{
DebugConsole.ThrowError("Error in location type \""+ Name + "\" - hireable jobs should be configured using identifiers instead of names.");
jobIdentifier = subElement.GetAttributeString("name", "");
jobPrefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobIdentifier.ToLowerInvariant());
}
else
{
jobPrefab = JobPrefab.List.Find(jp => jp.Identifier.ToLowerInvariant() == jobIdentifier.ToLowerInvariant());
}
if (jobPrefab == null)
{
DebugConsole.ThrowError("Error in in location type " + Name + " - could not find a job with the identifier \"" + jobIdentifier + "\".");
continue;
}
float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
totalHireableWeight += jobCommonness;
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
hireableJobs.Add(hireableJob);
break;
case "symbol":
symbolSprite = new Sprite(subElement);
SpriteColor = subElement.GetAttributeColor("color", Color.White);
break;
case "changeto":
CanChangeTo.Add(new LocationTypeChange(subElement));
break;
case "portrait":
var portrait = new Sprite(subElement);
if (portrait != null)
{
portraits.Add(portrait);
}
break;
}
float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
totalHireableWeight += jobCommonness;
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
hireableJobs.Add(hireableJob);
}
string spritePath = element.GetAttributeString("symbol", "Content/Map/beaconSymbol.png");
symbolSprite = new Sprite(spritePath, new Vector2(0.5f, 0.5f));
string backgroundPath = element.GetAttributeString("background", "");
backGround = new Sprite(backgroundPath, Vector2.Zero);
}
public JobPrefab GetRandomHireable()
{
float randFloat = Rand.Range(0.0f, totalHireableWeight);
float randFloat = Rand.Range(0.0f, totalHireableWeight, Rand.RandSync.Server);
foreach (Tuple<JobPrefab, float> hireable in hireableJobs)
{
@@ -106,46 +148,61 @@ namespace Barotrauma
return null;
}
public static LocationType Random(string seed = "")
public Sprite GetPortrait(int portraitId)
{
Debug.Assert(list.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
if (portraits.Count == 0) { return null; }
return portraits[Math.Abs(portraitId) % portraits.Count];
}
public string GetRandomName()
{
return names[Rand.Int(names.Count, Rand.RandSync.Server)];
}
public static LocationType Random(string seed = "", int? zone = null)
{
Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
if (!string.IsNullOrWhiteSpace(seed))
{
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
}
int randInt = Rand.Int(totalWeight, Rand.RandSync.Server);
List<LocationType> allowedLocationTypes = zone.HasValue ? List.FindAll(lt => lt.CommonnessPerZone.ContainsKey(zone.Value)) : List;
foreach (LocationType type in list)
if (allowedLocationTypes.Count == 0)
{
if (randInt < type.commonness) return type;
randInt -= type.commonness;
DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
}
return null;
if (zone.HasValue)
{
return ToolBox.SelectWeightedRandom(
allowedLocationTypes,
allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(),
Rand.RandSync.Server);
}
else
{
return allowedLocationTypes[Rand.Int(allowedLocationTypes.Count, Rand.RandSync.Server)];
}
}
public static void Init()
{
var locationTypeFiles = GameMain.SelectedPackage.GetFilesOfType(ContentType.LocationTypes);
var locationTypeFiles = GameMain.Instance.GetFilesOfType(ContentType.LocationTypes);
foreach (string file in locationTypeFiles)
{
XDocument doc = XMLExtensions.TryLoadXml(file);
if (doc==null)
{
return;
}
if (doc?.Root == null) continue;
foreach (XElement element in doc.Root.Elements())
{
LocationType locationType = new LocationType(element);
list.Add(locationType);
List.Add(locationType);
}
}
}
}
}
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class LocationTypeChange
{
public readonly string ChangeTo;
public readonly float Probability;
public readonly int RequiredDuration;
public List<string> Messages = new List<string>();
//the change can't happen if there's a location of the given type next to this one
public readonly List<string> DisallowedAdjacentLocations;
//the change can only happen if there's at least one of the given types of locations next to this one
public readonly List<string> RequiredAdjacentLocations;
public LocationTypeChange(XElement element)
{
ChangeTo = element.GetAttributeString("type", "");
Probability = element.GetAttributeFloat("probability", 1.0f);
RequiredDuration = element.GetAttributeInt("requiredduration", 0);
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
RequiredAdjacentLocations = element.GetAttributeStringArray("requiredadjacentlocations", new string[0]).ToList();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "message")
{
Messages.Add(subElement.GetAttributeString("text", ""));
}
}
}
}
}
+352 -282
View File
@@ -8,141 +8,207 @@ using Voronoi2;
namespace Barotrauma
{
partial class Map
{
Vector2 difficultyIncrease = new Vector2(5.0f, 10.0f);
Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f);
{
private MapGenerationParams generationParams;
private List<Level> levels;
private List<Location> locations;
private List<LocationConnection> connections;
private string seed;
private int size;
private Location currentLocation;
private Location selectedLocation;
private LocationConnection selectedConnection;
public Action<Location, LocationConnection> OnLocationSelected;
public Action<Location> OnLocationChanged;
//from -> to
public Action<Location, Location> OnLocationChanged;
public Action<LocationConnection, Mission> OnMissionSelected;
public Location CurrentLocation
{
get { return currentLocation; }
}
public Location CurrentLocation { get; private set; }
public int CurrentLocationIndex
{
get { return locations.IndexOf(currentLocation); }
get { return Locations.IndexOf(CurrentLocation); }
}
public Location SelectedLocation
{
get { return selectedLocation; }
}
public Location SelectedLocation { get; private set; }
public int SelectedLocationIndex
{
get { return locations.IndexOf(selectedLocation); }
get { return Locations.IndexOf(SelectedLocation); }
}
public LocationConnection SelectedConnection
public int SelectedMissionIndex
{
get { return selectedConnection; }
get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; }
}
public string Seed
{
get { return seed; }
}
public LocationConnection SelectedConnection { get; private set; }
public List<Location> Locations
{
get { return locations; }
}
public string Seed { get; private set; }
public Map(string seed, int size)
{
this.seed = seed;
public List<Location> Locations { get; private set; }
this.size = size;
public Map(string seed)
{
generationParams = MapGenerationParams.Instance;
this.Seed = seed;
this.size = generationParams.Size;
levels = new List<Level>();
locations = new List<Location>();
Locations = new List<Location>();
connections = new List<LocationConnection>();
#if CLIENT
if (iceTexture == null) iceTexture = new Sprite("Content/Map/iceSurface.png", Vector2.Zero);
if (iceCraters == null) iceCraters = TextureLoader.FromFile("Content/Map/iceCraters.png");
if (iceCrack == null) iceCrack = TextureLoader.FromFile("Content/Map/iceCrack.png");
#endif
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
Rand.SetSyncedSeed(ToolBox.StringToInt(this.seed));
Generate();
GenerateLocations();
currentLocation = locations[locations.Count / 2];
currentLocation.Discovered = true;
GenerateDifficulties(currentLocation, new List<LocationConnection>(connections), 10.0f);
//start from the colony furthest away from the center
float largestDist = 0.0f;
Vector2 center = new Vector2(size, size) / 2;
foreach (Location location in Locations)
{
if (location.Type.Name != "City") continue;
float dist = Vector2.DistanceSquared(center, location.MapPosition);
if (dist > largestDist)
{
largestDist = dist;
CurrentLocation = location;
}
}
CurrentLocation.Discovered = true;
foreach (LocationConnection connection in connections)
{
connection.Level = Level.CreateRandom(connection);
}
InitProjectSpecific();
}
private void GenerateLocations()
{
Voronoi voronoi = new Voronoi(0.5f);
partial void InitProjectSpecific();
public float[,] Noise;
List<Vector2> sites = new List<Vector2>();
for (int i = 0; i < 100; i++)
private void GenerateNoiseMap(int octaves, float persistence)
{
float z = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
Noise = new float[generationParams.NoiseResolution, generationParams.NoiseResolution];
float min = float.MaxValue, max = 0.0f;
for (int x = 0; x < generationParams.NoiseResolution; x++)
{
sites.Add(new Vector2(Rand.Range(0.0f, size, Rand.RandSync.Server), Rand.Range(0.0f, size, Rand.RandSync.Server)));
for (int y = 0; y < generationParams.NoiseResolution; y++)
{
Noise[x, y] = (float)PerlinNoise.OctavePerlin(
(double)x / generationParams.NoiseResolution,
(double)y / generationParams.NoiseResolution,
z, generationParams.NoiseFrequency, octaves, persistence);
min = Math.Min(Noise[x, y], min);
max = Math.Max(Noise[x, y], max);
}
}
float radius = generationParams.NoiseResolution / 2;
Vector2 center = Vector2.One * radius;
float range = max - min;
float centerDarkenRadius = radius * generationParams.CenterDarkenRadius;
float edgeDarkenRadius = radius * generationParams.EdgeDarkenRadius;
for (int x = 0; x < generationParams.NoiseResolution; x++)
{
for (int y = 0; y < generationParams.NoiseResolution; y++)
{
//normalize the noise to 0-1 range
Noise[x, y] = (Noise[x, y] - min) / range;
float dist = Vector2.Distance(center, new Vector2(x, y));
if (dist < centerDarkenRadius)
{
float angle = (float)Math.Atan2(y - center.Y, x - center.X);
float phase = angle * generationParams.CenterDarkenWaveFrequency + Noise[x, y] * generationParams.CenterDarkenWavePhaseNoise;
float currDarkenRadius = centerDarkenRadius * (0.6f + (float)Math.Sin(phase) * 0.4f);
if (dist < currDarkenRadius)
{
float darkenAmount = 1.0f - (dist / currDarkenRadius);
Noise[x, y] = MathHelper.Lerp(Noise[x, y], Noise[x, y] * (1.0f - generationParams.CenterDarkenStrength), darkenAmount);
}
}
if (dist > edgeDarkenRadius)
{
float darkenAmount = Math.Min((dist - edgeDarkenRadius) / (radius - edgeDarkenRadius), 1.0f);
Noise[x, y] = MathHelper.Lerp(Noise[x, y], 1.0f - generationParams.EdgeDarkenStrength, darkenAmount);
}
}
}
}
partial void GenerateNoiseMapProjSpecific();
private void Generate()
{
connections.Clear();
Locations.Clear();
GenerateNoiseMap(generationParams.NoiseOctaves, generationParams.NoisePersistence);
List<Vector2> sites = new List<Vector2>();
float mapRadius = size / 2;
Vector2 mapCenter = new Vector2(mapRadius, mapRadius);
float locationRadius = mapRadius * generationParams.LocationRadius;
for (float x = mapCenter.X - locationRadius; x < mapCenter.X + locationRadius; x += generationParams.VoronoiSiteInterval)
{
for (float y = mapCenter.Y - locationRadius; y < mapCenter.Y + locationRadius; y += generationParams.VoronoiSiteInterval)
{
float noiseVal = Noise[(int)(x / size * generationParams.NoiseResolution), (int)(y / size * generationParams.NoiseResolution)];
if (Rand.Range(generationParams.VoronoiSitePlacementMinVal, 1.0f, Rand.RandSync.Server) <
noiseVal * generationParams.VoronoiSitePlacementProbability)
{
sites.Add(new Vector2(x, y));
}
}
}
Voronoi voronoi = new Voronoi(0.5f);
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
float zoneRadius = size / 2 / generationParams.DifficultyZones;
sites.Clear();
foreach (GraphEdge edge in edges)
{
if (edge.point1 == edge.point2) continue;
//remove points from the edge of the map
if (edge.point1.X == 0 || edge.point1.X == size) continue;
if (edge.point1.Y == 0 || edge.point1.Y == size) continue;
if (edge.point2.X == 0 || edge.point2.X == size) continue;
if (edge.point2.Y == 0 || edge.point2.Y == size) continue;
if (edge.Point1 == edge.Point2) continue;
if (Vector2.DistanceSquared(edge.Point1, mapCenter) >= locationRadius * locationRadius ||
Vector2.DistanceSquared(edge.Point2, mapCenter) >= locationRadius * locationRadius) continue;
Location[] newLocations = new Location[2];
newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2);
newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2));
newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2);
newLocations[1] = Locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2));
for (int i = 0; i < 2; i++)
{
if (newLocations[i] != null) continue;
Vector2[] points = new Vector2[] { edge.point1, edge.point2 };
Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 };
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
Vector2 position = points[positionIndex];
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
newLocations[i] = Location.CreateRandom(position);
locations.Add(newLocations[i]);
int zone = MathHelper.Clamp(generationParams.DifficultyZones - (int)Math.Floor(Vector2.Distance(position, mapCenter) / zoneRadius), 1, generationParams.DifficultyZones);
newLocations[i] = Location.CreateRandom(position, zone);
Locations.Add(newLocations[i]);
}
//int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode());
connections.Add(new LocationConnection(newLocations[0], newLocations[1]));
var newConnection = new LocationConnection(newLocations[0], newLocations[1]);
float centerDist = Vector2.Distance(newConnection.CenterPos, mapCenter);
newConnection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100);
connections.Add(newConnection);
}
//remove connections that are too short
float minDistance = 50.0f;
float minDistance = generationParams.MinConnectionDistance;
for (int i = connections.Count - 1; i >= 0; i--)
{
LocationConnection connection = connections[i];
@@ -152,7 +218,7 @@ namespace Barotrauma
continue;
}
locations.Remove(connection.Locations[0]);
//locations.Remove(connection.Locations[0]);
connections.Remove(connection);
foreach (LocationConnection connection2 in connections)
@@ -162,12 +228,19 @@ namespace Barotrauma
}
}
HashSet<Location> connectedLocations = new HashSet<Location>();
foreach (LocationConnection connection in connections)
{
connection.Locations[0].Connections.Add(connection);
connection.Locations[1].Connections.Add(connection);
connectedLocations.Add(connection.Locations[0]);
connectedLocations.Add(connection.Locations[1]);
}
//remove orphans
Locations.RemoveAll(c => !connectedLocations.Contains(c));
for (int i = connections.Count - 1; i >= 0; i--)
{
i = Math.Min(i, connections.Count - 1);
@@ -184,70 +257,39 @@ namespace Barotrauma
}
}
foreach (LocationConnection connection in connections)
{
Vector2 start = connection.Locations[0].MapPosition;
Vector2 end = connection.Locations[1].MapPosition;
int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f));
connection.CrackSegments = MathUtils.GenerateJaggedLine(start, end, generations, 5.0f);
float centerDist = Vector2.Distance(connection.CenterPos, mapCenter);
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100);
}
AssignBiomes();
GenerateNoiseMapProjSpecific();
}
private void AssignBiomes()
{
List<LocationConnection> biomeSeeds = new List<LocationConnection>();
float locationRadius = size * 0.5f * generationParams.LocationRadius;
List<Biome> centerBiomes = LevelGenerationParams.GetBiomes().FindAll(b => b.Placement.HasFlag(Biome.MapPlacement.Center));
if (centerBiomes.Count > 0)
var biomes = LevelGenerationParams.GetBiomes();
Vector2 centerPos = new Vector2(size, size) / 2;
for (int i = 0; i < generationParams.DifficultyZones; i++)
{
Vector2 mapCenter = new Vector2(locations.Sum(l => l.MapPosition.X), locations.Sum(l => l.MapPosition.Y)) / locations.Count;
foreach (Biome centerBiome in centerBiomes)
List<Biome> allowedBiomes = biomes.FindAll(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i));
float zoneRadius = locationRadius * ((i + 1.0f) / generationParams.DifficultyZones);
foreach (LocationConnection connection in connections)
{
LocationConnection closestConnection = null;
float closestDist = float.PositiveInfinity;
foreach (LocationConnection connection in connections)
if (connection.Biome != null) continue;
if (i == generationParams.DifficultyZones - 1 ||
Vector2.Distance(connection.Locations[0].MapPosition, centerPos) < zoneRadius ||
Vector2.Distance(connection.Locations[1].MapPosition, centerPos) < zoneRadius)
{
if (connection.Biome != null) continue;
float dist = Vector2.Distance(connection.CenterPos, mapCenter);
if (closestConnection == null || dist < closestDist)
{
closestConnection = connection;
closestDist = dist;
}
connection.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)];
}
closestConnection.Biome = centerBiome;
biomeSeeds.Add(closestConnection);
}
}
List<Biome> edgeBiomes = LevelGenerationParams.GetBiomes().FindAll(b => b.Placement.HasFlag(Biome.MapPlacement.Edge));
if (edgeBiomes.Count > 0)
{
List<LocationConnection> edges = GetMapEdges();
foreach (LocationConnection edge in edges)
{
edge.Biome = edgeBiomes[Rand.Range(0, edgeBiomes.Count, Rand.RandSync.Server)];
}
}
List<Biome> randomBiomes = LevelGenerationParams.GetBiomes().FindAll(b => b.Placement.HasFlag(Biome.MapPlacement.Random));
foreach (Biome biome in randomBiomes)
{
LocationConnection seed = connections[0];
while (seed.Biome != null)
{
seed = connections[Rand.Range(0, connections.Count, Rand.RandSync.Server)];
}
seed.Biome = biome;
biomeSeeds.Add(seed);
}
ExpandBiomes(biomeSeeds);
}
private void ExpandBiomes(List<LocationConnection> seeds)
@@ -276,7 +318,7 @@ namespace Barotrauma
private List<LocationConnection> GetMapEdges()
{
List<Vector2> verts = locations.Select(l => l.MapPosition).ToList();
List<Vector2> verts = Locations.Select(l => l.MapPosition).ToList();
List<Vector2> giftWrappedVerts = MathUtils.GiftWrap(verts);
@@ -293,95 +335,101 @@ namespace Barotrauma
return edges;
}
private void GenerateDifficulties(Location start, List<LocationConnection> locations, float currDifficulty)
{
//start.Difficulty = currDifficulty;
currDifficulty += Rand.Range(difficultyIncrease.X, difficultyIncrease.Y, Rand.RandSync.Server);
if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, Rand.RandSync.Server)) currDifficulty = 10.0f;
foreach (LocationConnection connection in start.Connections)
{
if (!locations.Contains(connection)) continue;
Location nextLocation = connection.OtherLocation(start);
locations.Remove(connection);
connection.Difficulty = currDifficulty;
GenerateDifficulties(nextLocation, locations, currDifficulty);
}
}
public void MoveToNextLocation()
{
selectedConnection.Passed = true;
Location prevLocation = CurrentLocation;
SelectedConnection.Passed = true;
currentLocation = selectedLocation;
currentLocation.Discovered = true;
selectedLocation = null;
CurrentLocation = SelectedLocation;
CurrentLocation.Discovered = true;
SelectedLocation = null;
OnLocationChanged?.Invoke(currentLocation);
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
public void SetLocation(int index)
{
if (index == -1)
{
currentLocation = null;
CurrentLocation = null;
return;
}
if (index < 0 || index >= locations.Count)
if (index < 0 || index >= Locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
currentLocation = locations[index];
currentLocation.Discovered = true;
Location prevLocation = CurrentLocation;
CurrentLocation = Locations[index];
CurrentLocation.Discovered = true;
OnLocationChanged?.Invoke(currentLocation);
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
public void SelectLocation(int index)
{
if (index == -1)
{
selectedLocation = null;
selectedConnection = null;
SelectedLocation = null;
SelectedConnection = null;
OnLocationSelected?.Invoke(null, null);
return;
}
if (index < 0 || index >= locations.Count)
if (index < 0 || index >= Locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
selectedLocation = locations[index];
selectedConnection = connections.Find(c => c.Locations.Contains(currentLocation) && c.Locations.Contains(selectedLocation));
OnLocationSelected?.Invoke(selectedLocation, selectedConnection);
SelectedLocation = Locations[index];
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
public void SelectLocation(Location location)
{
if (!locations.Contains(location))
if (!Locations.Contains(location))
{
DebugConsole.ThrowError("Failed to select a location. "+location.Name+" not found in the map.");
string errorMsg = "Failed to select a location. " + (location?.Name ?? "null") + " not found in the map.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
selectedLocation = location;
selectedConnection = connections.Find(c => c.Locations.Contains(currentLocation) && c.Locations.Contains(selectedLocation));
OnLocationSelected?.Invoke(selectedLocation, selectedConnection);
SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
public void SelectMission(int missionIndex)
{
if (SelectedConnection == null) { return; }
if (CurrentLocation == null)
{
string errorMsg = "Failed to select a mission (current location not set).";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
CurrentLocation.SelectedMissionIndex = missionIndex;
//the destination must be the same as the destination of the mission
if (CurrentLocation.SelectedMission != null &&
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
{
SelectLocation(CurrentLocation.SelectedMission.Locations[1]);
}
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
}
public void SelectRandomLocation(bool preferUndiscovered)
{
List<Location> nextLocations = currentLocation.Connections.Select(c => c.OtherLocation(currentLocation)).ToList();
List<Location> nextLocations = CurrentLocation.Connections.Select(c => c.OtherLocation(CurrentLocation)).ToList();
List<Location> undiscoveredLocations = nextLocations.FindAll(l => !l.Discovered);
if (undiscoveredLocations.Count > 0 && preferUndiscovered)
@@ -394,37 +442,127 @@ namespace Barotrauma
}
}
public void ProgressWorld()
{
foreach (Location location in Locations)
{
if (!location.Discovered) continue;
//find which types of locations this one can change to
List<LocationTypeChange> allowedTypeChanges = new List<LocationTypeChange>();
List<LocationTypeChange> readyTypeChanges = new List<LocationTypeChange>();
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
{
//check if there are any adjacent locations that would prevent the change
bool disallowedFound = false;
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Name.ToLowerInvariant() == disallowedLocationName.ToLowerInvariant()))
{
disallowedFound = true;
break;
}
}
if (disallowedFound) continue;
//check that there's a required adjacent location present
bool requiredFound = false;
foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Name.ToLowerInvariant() == requiredLocationName.ToLowerInvariant()))
{
requiredFound = true;
break;
}
}
if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) continue;
allowedTypeChanges.Add(typeChange);
if (location.TypeChangeTimer >= typeChange.RequiredDuration)
{
readyTypeChanges.Add(typeChange);
}
}
//select a random type change
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(t => t.Probability))
{
var selectedTypeChange =
ToolBox.SelectWeightedRandom(readyTypeChanges, readyTypeChanges.Select(t => t.Probability).ToList(), Rand.RandSync.Unsynced);
if (selectedTypeChange != null)
{
string prevName = location.Name;
location.ChangeType(LocationType.List.Find(lt => lt.Name.ToLowerInvariant() == selectedTypeChange.ChangeTo.ToLowerInvariant()));
ChangeLocationType(location, prevName, selectedTypeChange);
location.TypeChangeTimer = -1;
break;
}
}
if (allowedTypeChanges.Count > 0)
{
location.TypeChangeTimer++;
}
else
{
location.TypeChangeTimer = 0;
}
}
}
partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change);
partial void ClearAnimQueue();
public static Map LoadNew(XElement element)
{
string mapSeed = element.GetAttributeString("seed", "a");
int size = element.GetAttributeInt("size", 1000);
Map map = new Map(mapSeed, size);
map.Load(element);
string mapSeed = element.GetAttributeString("seed", "a");
Map map = new Map(mapSeed);
map.Load(element, false);
return map;
}
public void Load(XElement element)
public void Load(XElement element, bool showNotifications)
{
ClearAnimQueue();
SetLocation(element.GetAttributeInt("currentlocation", 0));
string discoveredStr = element.GetAttributeString("discovered", "");
string[] discoveredStrs = discoveredStr.Split(',');
for (int i = 0; i < discoveredStrs.Length; i++)
if (!Version.TryParse(element.GetAttributeString("version", ""), out _))
{
int index = -1;
if (int.TryParse(discoveredStrs[i], out index)) locations[index].Discovered = true;
DebugConsole.ThrowError("Incompatible map save file, loading the game failed.");
return;
}
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString() != "connection") continue;
int connectionIndex = subElement.GetAttributeInt("i", -1);
if (connectionIndex < 0 || connectionIndex >= connections.Count) continue;
connections[connectionIndex].Passed = true;
connections[connectionIndex].MissionsCompleted = subElement.GetAttributeInt("m", 0);
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "location":
string locationType = subElement.GetAttributeString("type", "");
Location location = Locations[subElement.GetAttributeInt("i", 0)];
int typeChangeTimer = subElement.GetAttributeInt("changetimer", 0);
int missionsCompleted = subElement.GetAttributeInt("missionscompleted", 0);
string prevLocationName = location.Name;
LocationType prevLocationType = location.Type;
location.Discovered = true;
location.ChangeType(LocationType.List.Find(lt => lt.Name.ToLowerInvariant() == locationType.ToLowerInvariant()));
location.TypeChangeTimer = typeChangeTimer;
location.MissionsCompleted = missionsCompleted;
if (showNotifications && prevLocationType != location.Type)
{
ChangeLocationType(
location,
prevLocationName,
prevLocationType.CanChangeTo.Find(c => c.ChangeTo.ToLowerInvariant() == location.Type.Name.ToLowerInvariant()));
}
break;
case "connection":
int connectionIndex = subElement.GetAttributeInt("i", 0);
connections[connectionIndex].Passed = true;
break;
}
}
}
@@ -432,123 +570,55 @@ namespace Barotrauma
{
XElement mapElement = new XElement("map");
mapElement.Add(new XAttribute("version", GameMain.Version.ToString()));
mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex));
mapElement.Add(new XAttribute("seed", Seed));
mapElement.Add(new XAttribute("size", size));
List<int> discoveredLocations = new List<int>();
for (int i = 0; i < locations.Count; i++)
for (int i = 0; i < Locations.Count; i++)
{
if (locations[i].Discovered) discoveredLocations.Add(i);
var location = Locations[i];
if (!location.Discovered) continue;
var locationElement = new XElement("location", new XAttribute("i", i));
locationElement.Add(new XAttribute("type", location.Type.Name));
if (location.TypeChangeTimer > 0)
{
locationElement.Add(new XAttribute("changetimer", location.TypeChangeTimer));
}
location.CheckMissionCompleted();
if (location.MissionsCompleted > 0)
{
locationElement.Add(new XAttribute("missionscompleted", location.MissionsCompleted));
}
mapElement.Add(locationElement);
}
mapElement.Add(new XAttribute("discovered", string.Join(",", discoveredLocations)));
for (int i = 0; i < connections.Count; i++)
{
if (!connections[i].Passed) continue;
connections[i].CheckMissionCompleted();
var connection = connections[i];
if (!connection.Passed) continue;
var connectionElement = new XElement("connection",
new XAttribute("i", i),
new XAttribute("passed", connection.Passed));
var connectionElement = new XElement("connection", new XAttribute("i", i));
if (connections[i].MissionsCompleted > 0) connectionElement.Add(new XAttribute("m", connections[i].MissionsCompleted));
mapElement.Add(connectionElement);
}
element.Add(mapElement);
}
}
class LocationConnection
{
private Location[] locations;
private Level level;
public Biome Biome;
public float Difficulty;
public List<Vector2[]> CrackSegments;
public bool Passed;
public int MissionsCompleted;
private Mission mission;
public Mission Mission
public void Remove()
{
get
foreach (Location location in Locations)
{
if (mission == null || mission.Completed)
{
if (mission != null && mission.Completed) MissionsCompleted++;
long seed = (long)locations[0].MapPosition.X + (long)locations[0].MapPosition.Y * 100;
seed += (long)locations[1].MapPosition.X * 10000 + (long)locations[1].MapPosition.Y * 1000000;
MTRandom rand = new MTRandom((int)((seed + MissionsCompleted) % int.MaxValue));
if (rand.NextDouble() < 0.3f) return null;
mission = Mission.LoadRandom(locations, rand, "", true);
if (GameSettings.VerboseLogging && mission != null)
{
DebugConsole.NewMessage("Generated a new mission for a location connection (seed: " + seed + ", type: " + mission.Name + ")", Color.White);
}
}
return mission;
location.Remove();
}
RemoveProjSpecific();
}
public Location[] Locations
{
get { return locations; }
}
public Level Level
{
get { return level; }
set { level = value; }
}
public Vector2 CenterPos
{
get
{
return (locations[0].MapPosition + locations[1].MapPosition) / 2.0f;
}
}
public LocationConnection(Location location1, Location location2)
{
locations = new Location[] { location1, location2 };
MissionsCompleted = 0;
}
public void CheckMissionCompleted()
{
if (mission != null && mission.Completed)
{
MissionsCompleted++;
mission = null;
}
}
public Location OtherLocation(Location location)
{
if (locations[0] == location)
{
return locations[1];
}
else if (locations[1] == location)
{
return locations[0];
}
else
{
return null;
}
}
partial void RemoveProjSpecific();
}
}
@@ -0,0 +1,234 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
class MapGenerationParams : ISerializableEntity
{
private static MapGenerationParams instance;
public static MapGenerationParams Instance
{
get
{
return instance;
}
}
#if DEBUG
[Serialize(false, true), Editable]
public bool ShowNoiseMap { get; set; }
[Serialize(true, true), Editable]
public bool ShowLocations { get; set; }
[Serialize(true, true), Editable]
public bool ShowLevelTypeNames { get; set; }
[Serialize(true, true), Editable]
public bool ShowOverlay { get; set; }
#else
public readonly bool ShowLocations = true;
public readonly bool ShowLevelTypeNames = false;
public readonly bool ShowOverlay = true;
#endif
[Serialize(6, true)]
public int DifficultyZones { get; set; } //Number of difficulty zones
[Serialize(2000, true)]
public int Size { get; set; }
[Serialize(20.0f, true), Editable(0.0f, 5000.0f, ToolTip = "Connections with a length smaller or equal to this generate the smallest possible levels (using the MinWidth parameter in the level generation paramaters).")]
public float SmallLevelConnectionLength { get; set; }
[Serialize(200.0f, true), Editable(0.0f, 5000.0f, ToolTip = "Connections with a length larger or equal to this generate the largest possible levels (using the MaxWidth parameter in the level generation paramaters).")]
public float LargeLevelConnectionLength { get; set; }
[Serialize(1024, true)]
public int NoiseResolution { get; set; } //Resolution of the noisemap overlay
[Serialize(10.0f, true), Editable(0.0f, 1000.0f)]
public float NoiseFrequency { get; set; }
[Serialize(8, true), Editable(1, 100)]
public int NoiseOctaves { get; set; }
[Serialize(0.5f, true), Editable(0.0f, 1.0f)]
public float NoisePersistence { get; set; }
[Serialize("200,200", true), Editable]
public Vector2 TileSpriteSize { get; set; }
[Serialize("280,80", true), Editable]
public Vector2 TileSpriteSpacing { get; set; }
[Serialize(1.0f, true), Editable(0.0f, 1.0f, ToolTip = "How dark the center of the map is (1.0f = black).")]
public float CenterDarkenStrength { get; set; }
[Serialize(0.9f, true), Editable(0.0f, 1.0f, ToolTip = "How close to the center the darkening starts (0.8f = 20% from the edge).")]
public float CenterDarkenRadius { get; set; }
[Serialize(5, true), Editable(0, 1000,
ToolTip = "The edge of the dark center area is wave-shaped, and the frequency is determined by this value." +
" I.e. how many points does the star-shaped dark area in the center have.")]
public int CenterDarkenWaveFrequency { get; set; }
[Serialize(15.0f, true), Editable(0, 1000.0f,
ToolTip = "How heavily the noise map affects the phase of the edge wave (higher value = more irregular shape).")]
public float CenterDarkenWavePhaseNoise { get; set; }
[Serialize(0.8f, true), Editable(0.0f, 1.0f, ToolTip = "How dark the edges of the map are (1.0f = black).")]
public float EdgeDarkenStrength { get; set; }
[Serialize(0.9f, true), Editable(0.0f, 1.0f, ToolTip = "How far from the center the darkening starts (0.95f = 5% from the edge).")]
public float EdgeDarkenRadius { get; set; }
[Serialize(0.9f, true), Editable(0.0f, 1.0f, ToolTip = "How far from the center locations can be placed.")]
public float LocationRadius { get; set; }
[Serialize(20.0f, true), Editable(1.0f, 100.0f,
ToolTip = "How far from each other voronoi sites are placed. "+
"Sites determine shape of the voronoi graph. Locations are placed at the vertices of the voronoi cells. "+
"(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)") ]
public float VoronoiSiteInterval { get; set; }
[Serialize(0.3f, true), Editable(0.01f, 1.0f,
ToolTip = "How likely it is for a site to be placed at a given spot (e.g. 20% probability for a site to be placed every 5 units of the map). "+
"Multiplied with the noise value in the spot, meaning that sites are less likely to appear in dark spots.")]
public float VoronoiSitePlacementProbability { get; set; }
[Serialize(0.1f, true), Editable(0.01f, 1.0f,
ToolTip = "Probability * noise ^ 2 must be higher than this for a site to be placed. "+
"= How bright the noise map must be at a given spot for a location to be placed there")]
public float VoronoiSitePlacementMinVal { get; set; }
[Serialize(10.0f, true), Editable(0.0f, 500.0f, ToolTip = "Connections smaller than this are removed.")]
public float MinConnectionDistance { get; set; }
[Serialize(0.2f, true), Editable(0.0f, 10.0f,
ToolTip = "Affects how many iterations are done when generating the jagged shape of the connections (iterations = Sqrt(connectionLength * multiplier)).")]
public float ConnectionIterationMultiplier { get; set; }
[Serialize(0.5f, true), Editable(0.0f, 10.0f, ToolTip = "How large the \"bends\" in the connections are (displacement = connectionLength * multiplier).")]
public float ConnectionDisplacementMultiplier { get; set; }
[Serialize(0.1f, true), Editable(0.0f, 10.0f, ToolTip = "ConnectionIterationMultiplier for the UI indicator lines between locations.")]
public float ConnectionIndicatorIterationMultiplier { get; set; }
[Serialize(0.1f, true), Editable(0.0f, 10.0f, ToolTip = "ConnectionDisplacementMultiplier for the UI indicator lines between locations.")]
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
public Sprite ConnectionSprite { get; private set; }
#if CLIENT
[Serialize(15.0f, true), Editable(1.0f, 1000.0f, ToolTip = "Size of the location icons in pixels when at 100% zoom.")]
public float LocationIconSize { get; set; }
[Serialize("150,150,150,255", true), Editable(ToolTip = "The color used to display the low-difficulty connections on the map.")]
public Color LowDifficultyColor { get; set; }
[Serialize("210,143,83,255", true), Editable(ToolTip = "The color used to display the medium-difficulty connections on the map.")]
public Color MediumDifficultyColor { get; set; }
[Serialize("216,154,138", true), Editable(ToolTip = "The color used to display the high-difficulty connections on the map.")]
public Color HighDifficultyColor { get; set; }
public SpriteSheet DecorativeMapSprite { get; private set; }
public SpriteSheet DecorativeGraphSprite { get; private set; }
public SpriteSheet DecorativeLineTop { get; private set; }
public SpriteSheet DecorativeLineBottom { get; private set; }
public SpriteSheet DecorativeLineCorner { get; private set; }
public SpriteSheet ReticleLarge { get; private set; }
public SpriteSheet ReticleMedium { get; private set; }
public SpriteSheet ReticleSmall { get; private set; }
public Sprite MapCircle { get; private set; }
public Sprite LocationIndicator { get; private set; }
#endif
public List<Sprite> BackgroundTileSprites { get; private set; }
public string Name
{
get { return GetType().ToString(); }
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get; private set;
}
public static void Init()
{
var files = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.MapGenerationParameters);
if (!files.Any())
{
DebugConsole.ThrowError("No map generation parameters found in the selected content packages!");
return;
}
foreach (string file in files)
{
XDocument doc = XMLExtensions.TryLoadXml(file);
if (doc?.Root == null) return;
instance = new MapGenerationParams(doc.Root);
break;
}
}
private MapGenerationParams(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
BackgroundTileSprites = new List<Sprite>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "connectionsprite":
ConnectionSprite = new Sprite(subElement);
break;
case "backgroundtile":
BackgroundTileSprites.Add(new Sprite(subElement));
break;
#if CLIENT
case "mapcircle":
MapCircle = new Sprite(subElement);
break;
case "locationindicator":
LocationIndicator = new Sprite(subElement);
break;
case "decorativemapsprite":
DecorativeMapSprite = new SpriteSheet(subElement);
break;
case "decorativegraphsprite":
DecorativeGraphSprite = new SpriteSheet(subElement);
break;
case "decorativelinetop":
DecorativeLineTop = new SpriteSheet(subElement);
break;
case "decorativelinebottom":
DecorativeLineBottom = new SpriteSheet(subElement);
break;
case "decorativelinecorner":
DecorativeLineCorner = new SpriteSheet(subElement);
break;
case "reticlelarge":
ReticleLarge = new SpriteSheet(subElement);
break;
case "reticlemedium":
ReticleMedium = new SpriteSheet(subElement);
break;
case "reticlesmall":
ReticleSmall = new SpriteSheet(subElement);
break;
#endif
}
}
}
}
}
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
namespace Barotrauma
@@ -14,12 +15,16 @@ namespace Barotrauma
{
public static List<MapEntity> mapEntityList = new List<MapEntity>();
private MapEntityPrefab prefab;
public readonly MapEntityPrefab prefab;
protected List<ushort> linkedToID;
//observable collection because some entities may need to be notified when the collection is modified
public ObservableCollection<MapEntity> linkedTo;
private bool flippedX, flippedY;
public bool FlippedX { get { return flippedX; } }
public bool FlippedY { get { return flippedY; } }
public bool ShouldBeSaved = true;
@@ -34,12 +39,9 @@ namespace Barotrauma
get { return isHighlighted; }
set { isHighlighted = value; }
}
private static bool resizing;
private int resizeDirX, resizeDirY;
public virtual Rectangle Rect
{
{
get { return rect; }
set { rect = value; }
}
@@ -58,7 +60,7 @@ namespace Barotrauma
{
get
{
return Sprite != null && Sprite.Depth > 0.5f;
return Sprite != null && SpriteDepth > 0.5f;
}
}
@@ -83,6 +85,8 @@ namespace Barotrauma
get { return false; }
}
public List<string> AllowedLinks => prefab == null ? new List<string>() : prefab.AllowedLinks;
public bool ResizeHorizontal
{
get { return prefab != null && prefab.ResizeHorizontal; }
@@ -92,11 +96,6 @@ namespace Barotrauma
get { return prefab != null && prefab.ResizeVertical; }
}
public virtual bool SelectableInEditor
{
get { return true; }
}
public override Vector2 Position
{
get
@@ -139,7 +138,17 @@ namespace Barotrauma
if (aiTarget == null) return 0.0f;
return aiTarget.SightRange;
}
set { aiTarget.SightRange = value; }
set
{
if (aiTarget == null) return;
aiTarget.SightRange = value;
}
}
public RuinGeneration.Ruin ParentRuin
{
get;
set;
}
public virtual string Name
@@ -147,9 +156,13 @@ namespace Barotrauma
get { return ""; }
}
// Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation.
private Memento<Rectangle> rectMemento;
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
{
this.prefab = prefab;
Scale = prefab != null ? prefab.Scale : 1;
}
public virtual void Move(Vector2 amount)
@@ -336,36 +349,107 @@ namespace Barotrauma
{
item.Update(deltaTime, cam);
}
UpdateAllProjSpecific(deltaTime);
Spawner?.Update();
}
static partial void UpdateAllProjSpecific(float deltaTime);
public virtual void Update(float deltaTime, Camera cam) { }
public virtual void FlipX()
/// <summary>
/// Flip the entity horizontally
/// </summary>
/// <param name="relativeToSub">Should the entity be flipped across the y-axis of the sub it's inside</param>
public virtual void FlipX(bool relativeToSub)
{
if (Submarine == null)
{
DebugConsole.ThrowError("Couldn't flip MapEntity \""+Name+"\", submarine==null");
return;
}
flippedX = !flippedX;
if (!relativeToSub || Submarine == null) return;
Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.Y = 0.0f;
Move(-relative * 2.0f);
}
/// <summary>
/// Flip the entity vertically
/// </summary>
/// <param name="relativeToSub">Should the entity be flipped across the x-axis of the sub it's inside</param>
public virtual void FlipY(bool relativeToSub)
{
flippedY = !flippedY;
if (!relativeToSub || Submarine == null) return;
Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.X = 0.0f;
Move(-relative * 2.0f);
}
public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath)
{
List<MapEntity> entities = new List<MapEntity>();
foreach (XElement element in parentElement.Elements())
{
string typeName = element.Name.ToString();
Type t;
try
{
t = Type.GetType("Barotrauma." + typeName, true, true);
if (t == null)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".");
continue;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".", e);
continue;
}
try
{
MethodInfo loadMethod = t.GetMethod("Load");
if (loadMethod == null)
{
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
}
else if (!loadMethod.ReturnType.IsSubclassOf(typeof(MapEntity)))
{
DebugConsole.ThrowError("Error loading entity of the type \"" + t.ToString() + "\" - load method does not return a valid map entity.");
}
else
{
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine });
if (newEntity != null) entities.Add((MapEntity)newEntity);
}
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e);
}
}
return entities;
}
/// <summary>
/// Update the linkedTo-lists of the entities based on the linkedToID-lists
/// Has to be done after all the entities have been loaded (an entity can't
/// be linked to some other entity that hasn't been loaded yet)
/// </summary>
public static void MapLoaded(Submarine sub)
private bool mapLoadedCalled;
public static void MapLoaded(List<MapEntity> entities, bool updateHulls)
{
foreach (MapEntity e in mapEntityList)
foreach (MapEntity e in entities)
{
if (e.Submarine != sub) continue;
if (e.mapLoadedCalled) continue;
if (e.linkedToID == null) continue;
if (e.linkedToID.Count == 0) continue;
@@ -378,25 +462,26 @@ namespace Barotrauma
}
List<LinkedSubmarine> linkedSubs = new List<LinkedSubmarine>();
for (int i = 0; i < mapEntityList.Count; i++)
for (int i = 0; i < entities.Count; i++)
{
if (mapEntityList[i].Submarine != sub) continue;
if (mapEntityList[i] is LinkedSubmarine)
if (entities[i].mapLoadedCalled) continue;
if (entities[i] is LinkedSubmarine)
{
linkedSubs.Add((LinkedSubmarine)mapEntityList[i]);
linkedSubs.Add((LinkedSubmarine)entities[i]);
continue;
}
mapEntityList[i].OnMapLoaded();
entities[i].OnMapLoaded();
}
if (sub != null)
if (updateHulls)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
entities.ForEach(e => e.mapLoadedCalled = true);
foreach (LinkedSubmarine linkedSub in linkedSubs)
{
linkedSub.OnMapLoaded();
@@ -416,6 +501,39 @@ namespace Barotrauma
if (linkedTo == null) return;
if (linkedTo.Contains(e)) linkedTo.Remove(e);
}
#region Serialized properties
// We could use NaN or nullables, but in this case the first is not preferable, because it needs to be checked every time the value is used.
// Nullable on the other requires boxing that we don't want to do too often, since it generates garbage.
public bool SpriteDepthOverrideIsSet { get; private set; }
public float SpriteOverrideDepth => SpriteDepth;
private float _spriteOverrideDepth = float.NaN;
[Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, true)]
public float SpriteDepth
{
get
{
if (SpriteDepthOverrideIsSet) { return _spriteOverrideDepth; }
return Sprite != null ? Sprite.Depth : 0;
}
set
{
if (!float.IsNaN(value))
{
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999f);
SpriteDepthOverrideIsSet = true;
}
}
}
// The value should always be copied from the prefab. Editing is enabled only for testing the scale in the sub editor (changes are not saved).
#if DEBUG
[Serialize(1f, false), Editable(0.1f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
#else
[Serialize(1f, false)]
#endif
public float Scale { get; set; } = 1;
#endregion
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma
[Flags]
enum MapEntityCategory
{
Structure = 1, Machine = 2, Equipment = 4, Electrical = 8, Material = 16, Misc = 32, Alien = 64
Structure = 1, Machine = 2, Equipment = 4, Electrical = 8, Material = 16, Misc = 32, Alien = 64, ItemAssembly = 128, Legacy = 256
}
partial class MapEntityPrefab
@@ -17,6 +17,7 @@ namespace Barotrauma
public readonly static List<MapEntityPrefab> List = new List<MapEntityPrefab>();
protected string name;
protected string identifier;
public Sprite sprite;
@@ -24,7 +25,7 @@ namespace Barotrauma
protected static Vector2 placePosition;
protected ConstructorInfo constructor;
//is it possible to stretch the entity horizontally/vertically
[Serialize(false, false)]
public bool ResizeHorizontal { get; protected set; }
@@ -33,19 +34,24 @@ namespace Barotrauma
//which prefab has been selected for placing
protected static MapEntityPrefab selected;
private int price;
public string Name
{
get { return name; }
}
public List<string> Tags
//Used to differentiate between items when saving/loading
//Allows changing the name of an item without breaking existing subs or having multiple items with the same name
public string Identifier
{
get { return identifier; }
}
public HashSet<string> Tags
{
get;
protected set;
}
} = new HashSet<string>();
public static MapEntityPrefab Selected
{
@@ -66,7 +72,12 @@ namespace Barotrauma
get;
private set;
}
/// <summary>
/// Links defined to identifiers.
/// </summary>
public List<string> AllowedLinks { get; protected set; } = new List<string>();
public MapEntityCategory Category
{
get;
@@ -80,12 +91,8 @@ namespace Barotrauma
protected set;
}
[Serialize(0, false)]
public int Price
{
get { return price; }
protected set { price = Math.Max(value, 0); }
}
[Serialize(1f, true), Editable(0.1f, 10f, DecimalCount = 3)]
public float Scale { get; protected set; }
//If a matching prefab is not found when loading a sub, the game will attempt to find a prefab with a matching alias.
//(allows changing names while keeping backwards compatibility with older sub files)
@@ -97,30 +104,44 @@ namespace Barotrauma
public static void Init()
{
MapEntityPrefab ep = new MapEntityPrefab();
ep.name = "Hull";
ep.Description = "Hulls determine which parts are considered to be \"inside the sub\". Generally every room should be enclosed by a hull.";
ep.constructor = typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
ep.ResizeHorizontal = true;
ep.ResizeVertical = true;
MapEntityPrefab ep = new MapEntityPrefab
{
identifier = "hull",
name = TextManager.Get("EntityName.hull"),
Description = TextManager.Get("EntityDescription.hull"),
constructor = typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
ResizeHorizontal = true,
ResizeVertical = true
};
List.Add(ep);
ep = new MapEntityPrefab();
ep.name = "Gap";
ep.Description = "Gaps allow water and air to flow between two hulls. ";
ep.constructor = typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
ep.ResizeHorizontal = true;
ep.ResizeVertical = true;
ep = new MapEntityPrefab
{
identifier = "gap",
name = TextManager.Get("EntityName.gap"),
Description = TextManager.Get("EntityDescription.gap"),
constructor = typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
ResizeHorizontal = true,
ResizeVertical = true
};
List.Add(ep);
ep = new MapEntityPrefab();
ep.name = "Waypoint";
ep.constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
ep = new MapEntityPrefab
{
identifier = "waypoint",
name = TextManager.Get("EntityName.waypoint"),
Description = TextManager.Get("EntityDescription.waypoint"),
constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) })
};
List.Add(ep);
ep = new MapEntityPrefab();
ep.name = "Spawnpoint";
ep.constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
ep = new MapEntityPrefab
{
identifier = "spawnpoint",
name = TextManager.Get("EntityName.spawnpoint"),
Description = TextManager.Get("EntityDescription.spawnpoint"),
constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) })
};
List.Add(ep);
}
@@ -191,24 +212,37 @@ namespace Barotrauma
}
}
public static MapEntityPrefab Find(string name, bool caseSensitive = false)
/// <summary>
/// Find a matching map entity prefab
/// </summary>
/// <param name="name">The name of the item (can be omitted when searching based on identifier)</param>
/// <param name="identifier">The identifier of the item (if null, the identifier is ignored and the search is done only based on the name)</param>
public static MapEntityPrefab Find(string name, string identifier = null, bool showErrorMessages = true)
{
if (caseSensitive)
if (name != null) name = name.ToLowerInvariant();
foreach (MapEntityPrefab prefab in List)
{
foreach (MapEntityPrefab prefab in List)
if (identifier != null)
{
if (prefab.name == name || (prefab.Aliases != null && prefab.Aliases.Contains(name))) return prefab;
if (prefab.identifier != identifier)
{
continue;
}
else
{
if (string.IsNullOrEmpty(name)) return prefab;
}
}
}
else
{
name = name.ToLowerInvariant();
foreach (MapEntityPrefab prefab in List)
if (!string.IsNullOrEmpty(name))
{
if (prefab.name.ToLowerInvariant() == name || (prefab.Aliases != null && prefab.Aliases.Any(a => a.ToLowerInvariant() == name))) return prefab;
}
}
if (showErrorMessages)
{
DebugConsole.ThrowError("Failed to find a matching MapEntityPrefab (name: \"" + name + "\", identifier: \"" + identifier + "\").\n" + Environment.StackTrace);
}
return null;
}
@@ -237,11 +271,17 @@ namespace Barotrauma
return false;
}
public bool IsLinkAllowed(MapEntityPrefab target)
{
if (target == null) { return false; }
return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(identifier)
|| target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t));
}
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
public static object GetSelected()
{
return (object)selected;
}
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
class PriceInfo
{
public readonly int BuyPrice;
//minimum number of items available at a given store
public readonly int MinAvailableAmount;
//maximum number of items available at a given store
public readonly int MaxAvailableAmount;
public PriceInfo (XElement element)
{
BuyPrice = element.GetAttributeInt("buyprice", 0);
MinAvailableAmount = element.GetAttributeInt("minamount", 0);
MaxAvailableAmount = element.GetAttributeInt("maxamount", 0);
}
}
}
@@ -0,0 +1,98 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class RoundEndCinematic
{
public bool Running
{
get;
private set;
}
private float duration;
public RoundEndCinematic(Submarine submarine, Camera cam, float duration)
: this(new List<Submarine>() { submarine }, cam, duration)
{
}
public RoundEndCinematic(List<Submarine> submarines, Camera cam, float duration)
{
if (!submarines.Any(s => s != null)) return;
this.duration = duration;
Running = true;
CoroutineManager.StartCoroutine(Update(submarines, cam));
}
private IEnumerable<object> Update(List<Submarine> subs, Camera cam)
{
if (!subs.Any()) yield return CoroutineStatus.Success;
Character.Controlled = null;
cam.TargetPos = Vector2.Zero;
#if CLIENT
GameMain.LightManager.LosEnabled = false;
#endif
Level.Loaded.TopBarrier.Enabled = false;
cam.TargetPos = Vector2.Zero;
float timer = 0.0f;
float initialZoom = cam.Zoom;
Vector2 initialCameraPos = cam.Position;
while (timer < duration)
{
if (Screen.Selected != GameMain.GameScreen)
{
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
Running = false;
yield return CoroutineStatus.Success;
}
Vector2 minPos = new Vector2(
subs.Min(s => s.WorldPosition.X - s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y - s.Borders.Height / 2));
Vector2 maxPos = new Vector2(
subs.Min(s => s.WorldPosition.X + s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y + s.Borders.Height / 2));
Vector2 cameraPos = new Vector2(
MathHelper.SmoothStep(minPos.X, maxPos.X, timer / duration),
(minPos.Y + maxPos.Y) / 2.0f);
cam.Translate(cameraPos - cam.Position);
#if CLIENT
cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration);
if (timer / duration > 0.9f)
{
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / duration) - 0.9f) * 10.0f);
}
#endif
timer += CoroutineManager.UnscaledDeltaTime;
yield return CoroutineStatus.Running;
}
Running = false;
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
yield return CoroutineStatus.Success;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ namespace Barotrauma
{
partial class StructurePrefab : MapEntityPrefab
{
private bool canSpriteFlipX;
private bool canSpriteFlipX, canSpriteFlipY;
private float health;
@@ -23,6 +23,38 @@ namespace Barotrauma
private set;
}
//rotation of the physics body in degrees
[Serialize(0.0f, false)]
public float BodyRotation
{
get;
private set;
}
//in display units
[Serialize(0.0f, false)]
public float BodyWidth
{
get;
private set;
}
//in display units
[Serialize(0.0f, false)]
public float BodyHeight
{
get;
private set;
}
//in display units
[Serialize("0.0,0.0", false)]
public Vector2 BodyOffset
{
get;
private set;
}
[Serialize(false, false)]
public bool Platform
{
@@ -30,6 +62,13 @@ namespace Barotrauma
private set;
}
[Serialize(false, false)]
public bool AllowAttachItems
{
get;
private set;
}
[Serialize(100.0f, false)]
public float Health
{
@@ -56,6 +95,11 @@ namespace Barotrauma
get { return canSpriteFlipX; }
}
public bool CanSpriteFlipY
{
get { return canSpriteFlipY; }
}
[Serialize("0,0", true)]
public Vector2 Size
{
@@ -63,13 +107,28 @@ namespace Barotrauma
private set { size = value; }
}
public Vector2 ScaledSize => size * Scale;
protected Vector2 textureScale = Vector2.One;
[Editable(DecimalCount = 3), Serialize("1.0, 1.0", true)]
public Vector2 TextureScale
{
get { return textureScale; }
set
{
textureScale = new Vector2(
MathHelper.Clamp(value.X, 0.01f, 10),
MathHelper.Clamp(value.Y, 0.01f, 10));
}
}
public Sprite BackgroundSprite
{
get;
private set;
}
public static void LoadAll(List<string> filePaths)
public static void LoadAll(IEnumerable<string> filePaths)
{
foreach (string filePath in filePaths)
{
@@ -87,11 +146,23 @@ namespace Barotrauma
public static StructurePrefab Load(XElement element)
{
StructurePrefab sp = new StructurePrefab();
sp.name = element.Name.ToString();
sp.Tags = new List<string>();
sp.Tags.AddRange(element.GetAttributeString("tags", "").Split(','));
StructurePrefab sp = new StructurePrefab
{
name = element.GetAttributeString("name", "")
};
if (string.IsNullOrEmpty(sp.name)) sp.name = element.Name.ToString();
sp.identifier = element.GetAttributeString("identifier", "");
string translatedName = TextManager.Get("EntityName." + sp.identifier, true);
if (!string.IsNullOrEmpty(translatedName)) sp.name = translatedName;
sp.Tags = new HashSet<string>();
string joinedTags = element.GetAttributeString("tags", "");
if (string.IsNullOrEmpty(joinedTags)) joinedTags = element.GetAttributeString("Tags", "");
foreach (string tag in joinedTags.Split(','))
{
sp.Tags.Add(tag.Trim().ToLowerInvariant());
}
foreach (XElement subElement in element.Elements())
{
@@ -110,7 +181,13 @@ namespace Barotrauma
sp.sprite.effects = SpriteEffects.FlipVertically;
sp.canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
sp.canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
if (subElement.Attribute("name") == null && !string.IsNullOrWhiteSpace(sp.Name))
{
sp.sprite.Name = sp.Name;
}
sp.sprite.EntityID = sp.identifier;
break;
case "backgroundsprite":
sp.BackgroundSprite = new Sprite(subElement);
@@ -124,8 +201,7 @@ namespace Barotrauma
}
}
MapEntityCategory category;
if (!Enum.TryParse(element.GetAttributeString("category", "Structure"), true, out category))
if (!Enum.TryParse(element.GetAttributeString("category", "Structure"), true, out MapEntityCategory category))
{
category = MapEntityCategory.Structure;
}
@@ -136,15 +212,40 @@ namespace Barotrauma
{
sp.Aliases = aliases.Split(',');
}
SerializableProperty.DeserializeProperties(sp, element);
string translatedDescription = TextManager.Get("EntityDescription." + sp.identifier, true);
if (!string.IsNullOrEmpty(translatedDescription)) sp.Description = translatedDescription;
//backwards compatibility
if (element.Attribute("size") == null)
{
sp.size = Vector2.Zero;
sp.size.X = element.GetAttributeFloat("width", 0.0f);
sp.size.Y = element.GetAttributeFloat("height", 0.0f);
if (element.Attribute("width") == null && element.Attribute("height") == null)
{
sp.size.X = sp.sprite.SourceRect.Width;
sp.size.Y = sp.sprite.SourceRect.Height;
}
else
{
sp.size.X = element.GetAttributeFloat("width", 0.0f);
sp.size.Y = element.GetAttributeFloat("height", 0.0f);
}
}
if (!category.HasFlag(MapEntityCategory.Legacy) && string.IsNullOrEmpty(sp.identifier))
{
DebugConsole.ThrowError(
"Structure prefab \"" + sp.name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.");
}
if (!string.IsNullOrEmpty(sp.identifier))
{
MapEntityPrefab existingPrefab = List.Find(e => e.Identifier == sp.identifier);
if (existingPrefab != null)
{
DebugConsole.ThrowError(
"Map entity prefabs \"" + sp.name + "\" and \"" + existingPrefab.Name + "\" have the same identifier!");
}
}
return sp;
@@ -153,6 +254,7 @@ namespace Barotrauma
public override void UpdatePlacing(Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
Vector2 size = ScaledSize;
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
if (placePosition == Vector2.Zero)
@@ -1,4 +1,6 @@
using Barotrauma.Networking;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.RuinGeneration;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Lidgren.Network;
@@ -68,6 +70,10 @@ namespace Barotrauma
private static List<Submarine> loaded = new List<Submarine>();
private static List<MapEntity> visibleEntities;
public static IEnumerable<MapEntity> VisibleEntities
{
get { return visibleEntities; }
}
private SubmarineBody subBody;
@@ -93,7 +99,7 @@ namespace Barotrauma
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
public string RecommendedCrewExperience;
public HashSet<string> CompatibleContentPackages = new HashSet<string>();
public HashSet<string> RequiredContentPackages = new HashSet<string>();
//properties ----------------------------------------------------
@@ -103,13 +109,25 @@ namespace Barotrauma
set { name = value; }
}
public bool OnRadar = true;
public bool OnSonar = true;
public string Description
{
get;
set;
}
public Version GameVersion
{
get;
private set;
}
public bool IsOutpost
{
get;
private set;
}
public static Vector2 LastPickedPosition
{
@@ -197,8 +215,12 @@ namespace Barotrauma
{
get
{
if (Level.Loaded == null) return false;
return (Vector2.Distance(Position + HiddenSubPosition, Level.Loaded.EndPosition) < Level.ExitDistance);
if (Level.Loaded == null) { return false; }
if (Level.Loaded.EndOutpost != null && DockedTo.Contains(Level.Loaded.EndOutpost))
{
return true;
}
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.EndPosition) < Level.ExitDistance * Level.ExitDistance);
}
}
@@ -206,8 +228,12 @@ namespace Barotrauma
{
get
{
if (Level.Loaded == null) return false;
return (Vector2.Distance(Position + HiddenSubPosition, Level.Loaded.StartPosition) < Level.ExitDistance);
if (Level.Loaded == null) { return false; }
if (Level.Loaded.StartOutpost != null && DockedTo.Contains(Level.Loaded.StartOutpost))
{
return true;
}
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.StartPosition) < Level.ExitDistance * Level.ExitDistance);
}
}
@@ -291,6 +317,7 @@ namespace Barotrauma
if (doc != null && doc.Root != null)
{
Description = doc.Root.GetAttributeString("description", "");
GameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0"));
Enum.TryParse(doc.Root.GetAttributeString("tags", ""), out tags);
Dimensions = doc.Root.GetAttributeVector2("dimensions", Vector2.Zero);
RecommendedCrewSizeMin = doc.Root.GetAttributeInt("recommendedcrewsizemin", 0);
@@ -304,13 +331,12 @@ namespace Barotrauma
RecommendedCrewExperience = "CrewExperienceMid";
else if (RecommendedCrewExperience == "Experienced")
RecommendedCrewExperience = "CrewExperienceHigh";
string[] contentPackageNames = doc.Root.GetAttributeStringArray("compatiblecontentpackages", new string[0]);
string[] contentPackageNames = doc.Root.GetAttributeStringArray("requiredcontentpackages", new string[0]);
foreach (string contentPackageName in contentPackageNames)
{
CompatibleContentPackages.Add(contentPackageName);
RequiredContentPackages.Add(contentPackageName);
}
#if CLIENT
string previewImageData = doc.Root.GetAttributeString("previewimage", "");
if (!string.IsNullOrEmpty(previewImageData))
@@ -359,6 +385,39 @@ namespace Barotrauma
tags &= ~tag;
}
public void MakeOutpost()
{
IsOutpost = true;
PhysicsBody.FarseerBody.IsStatic = true;
foreach (MapEntity me in MapEntity.mapEntityList)
{
if (me.Submarine != this) { continue; }
if (me is Item item)
{
item.Indestructible = true;
foreach (ItemComponent ic in item.components)
{
if (ic is ConnectionPanel connectionPanel)
{
//prevent rewiring
connectionPanel.Locked = true;
}
else if (ic is Pickable pickable)
{
//prevent picking up (or deattaching) items
pickable.CanBePicked = false;
pickable.CanBeSelected = false;
}
}
}
else if (me is Structure structure)
{
structure.Indestructible = true;
}
}
}
/// <summary>
/// Returns a rect that contains the borders of this sub and all subs docked to it
/// </summary>
@@ -391,8 +450,7 @@ namespace Barotrauma
/// </summary>
public List<Submarine> GetConnectedSubs()
{
List<Submarine> connectedSubs = new List<Submarine>();
connectedSubs.Add(this);
List<Submarine> connectedSubs = new List<Submarine> { this };
GetConnectedSubsRecursive(connectedSubs);
return connectedSubs;
@@ -409,67 +467,71 @@ namespace Barotrauma
}
}
public Vector2 FindSpawnPos(Vector2 spawnPos)
public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null)
{
Rectangle dockedBorders = GetDockedBorders();
Vector2 diffFromDockedBorders =
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
- new Vector2(Borders.Center.X, Borders.Y - Borders.Height / 2);
int iterations = 0;
bool wallTooClose = false;
do
int minWidth = Math.Max(submarineSize.HasValue ? submarineSize.Value.X : dockedBorders.Width, 500);
int minHeight = Math.Max(submarineSize.HasValue ? submarineSize.Value.Y : dockedBorders.Height, 1000);
//a bit of extra padding to prevent the sub from spawning in a super tight gap between walls
minHeight += 500;
float minX = float.MinValue, maxX = float.MaxValue;
foreach (VoronoiCell cell in Level.Loaded.GetAllCells())
{
Rectangle worldBorders = new Rectangle(
dockedBorders.X + (int)spawnPos.X,
dockedBorders.Y + (int)spawnPos.Y,
dockedBorders.Width,
dockedBorders.Height);
if (cell.Edges.All(e => e.Point1.Y < Level.Loaded.Size.Y - minHeight && e.Point2.Y < Level.Loaded.Size.Y - minHeight)) { continue; }
wallTooClose = false;
var nearbyCells = Level.Loaded.GetCells(
spawnPos, (int)Math.Ceiling(Math.Max(dockedBorders.Width, dockedBorders.Height) / (float)Level.GridCellSize));
foreach (VoronoiCell cell in nearbyCells)
//find the closest wall at the left and right side of the spawnpos
if (cell.Site.Coord.X < spawnPos.X)
{
if (cell.CellType == CellType.Empty) continue;
foreach (GraphEdge e in cell.edges)
{
List<Vector2> intersections = MathUtils.GetLineRectangleIntersections(e.point1, e.point2, worldBorders);
foreach (Vector2 intersection in intersections)
{
wallTooClose = true;
if (intersection.X < spawnPos.X)
{
spawnPos.X += intersection.X - worldBorders.X;
}
else
{
spawnPos.X += intersection.X - worldBorders.Right;
}
if (intersection.Y < spawnPos.Y)
{
spawnPos.Y += intersection.Y - (worldBorders.Y - worldBorders.Height);
}
else
{
spawnPos.Y += intersection.Y - worldBorders.Y;
}
spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2);
}
}
minX = Math.Max(minX, cell.Edges.Max(e => Math.Max(e.Point1.X, e.Point2.X)));
}
else
{
maxX = Math.Min(maxX, cell.Edges.Min(e => Math.Min(e.Point1.X, e.Point2.X)));
}
}
iterations++;
} while (wallTooClose && iterations < 10);
return spawnPos;
foreach (var ruin in Level.Loaded.Ruins)
{
if (ruin.Area.Y + ruin.Area.Height < Level.Loaded.Size.Y - minHeight) { continue; }
if (ruin.Area.X < spawnPos.X)
{
minX = Math.Max(minX, ruin.Area.Right + 100.0f);
}
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f;
}
else if (maxX > Level.Loaded.Size.X)
{
//no wall found at right side, spawn to the right from the left-side wall
spawnPos.X = minX + minWidth + 100.0f;
}
else
{
//walls found at both sides, use their midpoint
spawnPos.X = (minX + maxX) / 2;
}
spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10);
return spawnPos - diffFromDockedBorders;
}
//drawing ----------------------------------------------------
public static void CullEntities(Camera cam)
@@ -491,16 +553,48 @@ namespace Barotrauma
}
}
Rectangle worldView = cam.WorldView;
visibleEntities = new List<MapEntity>();
foreach (MapEntity me in MapEntity.mapEntityList)
HashSet<Ruin> visibleRuins = new HashSet<Ruin>();
if (Level.Loaded != null)
{
if (me.Submarine == null || visibleSubs.Contains(me.Submarine))
foreach (Ruin ruin in Level.Loaded.Ruins)
{
if (me.IsVisible(worldView)) visibleEntities.Add(me);
Rectangle worldBorders = new Rectangle(
ruin.Area.X - 500,
ruin.Area.Y + ruin.Area.Height + 500,
ruin.Area.Width + 1000,
ruin.Area.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleRuins.Add(ruin);
}
}
}
if (visibleEntities == null)
{
visibleEntities = new List<MapEntity>(MapEntity.mapEntityList.Count);
}
else
{
visibleEntities.Clear();
}
Rectangle worldView = cam.WorldView;
foreach (MapEntity entity in MapEntity.mapEntityList)
{
if (entity.Submarine != null)
{
if (!visibleSubs.Contains(entity.Submarine)) { continue; }
}
else if(entity.ParentRuin != null)
{
if (!visibleRuins.Contains(entity.ParentRuin)) { continue; }
}
if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); }
}
}
public void UpdateTransform()
@@ -540,6 +634,16 @@ namespace Barotrauma
Hull.hullList.FindAll(h => h.Submarine == this).Cast<MapEntity>().ToList() :
MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
//ignore items whose body is disabled (wires, items inside cabinets)
entities.RemoveAll(e =>
{
if (e is Item item)
{
if (item.body != null && !item.body.Enabled) { return true; }
}
return false;
});
if (entities.Count == 0) return Rectangle.Empty;
float minX = entities[0].Rect.X, minY = entities[0].Rect.Y - entities[0].Rect.Height;
@@ -600,7 +704,7 @@ namespace Barotrauma
}
}
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true)
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
{
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
{
@@ -616,15 +720,16 @@ namespace Barotrauma
(ignoreSensors && fixture.IsSensor) ||
fixture.CollisionCategories == Category.None ||
fixture.CollisionCategories == Physics.CollisionItem) return -1;
if (collisionCategory != null &&
if (customPredicate != null && !customPredicate(fixture)) return -1;
if (collisionCategory != null &&
!fixture.CollisionCategories.HasFlag((Category)collisionCategory) &&
!((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) return -1;
if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1;
Structure structure = fixture.Body.UserData as Structure;
if (structure != null)
if (fixture.Body.UserData is Structure structure)
{
if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform)) return -1;
}
@@ -671,8 +776,7 @@ namespace Barotrauma
if (ignoreLevel && fixture.CollisionCategories == Physics.CollisionLevel) return -1;
if (ignoreSubs && fixture.Body.UserData is Submarine) return -1;
Structure structure = fixture.Body.UserData as Structure;
if (structure != null)
if (fixture.Body.UserData is Structure structure)
{
if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1;
int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point));
@@ -720,7 +824,6 @@ namespace Barotrauma
foreach (MapEntity e in subEntities)
{
if (e is Item) continue;
if (e is LinkedSubmarine)
{
Submarine sub = ((LinkedSubmarine)e).Sub;
@@ -734,7 +837,7 @@ namespace Barotrauma
}
else
{
e.FlipX();
e.FlipX(true);
}
}
@@ -772,7 +875,7 @@ namespace Barotrauma
continue;
}
item.FlipX();
item.FlipX(true);
}
Item.UpdateHulls();
@@ -804,7 +907,7 @@ namespace Barotrauma
{
if (c.Submarine == this)
{
c.Kill(CauseOfDeath.Pressure);
c.Kill(CauseOfDeathType.Pressure, null);
c.Enabled = false;
}
}
@@ -873,13 +976,17 @@ namespace Barotrauma
//Level.Loaded.Move(-amount);
}
public static Submarine FindClosest(Vector2 worldPosition)
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false)
{
Submarine closest = null;
float closestDist = 0.0f;
foreach (Submarine sub in loaded)
{
float dist = Vector2.Distance(worldPosition, sub.WorldPosition);
if (ignoreOutposts && sub.IsOutpost)
{
continue;
}
float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition);
if (closest == null || dist < closestDist)
{
closest = sub;
@@ -1024,7 +1131,7 @@ namespace Barotrauma
try
{
ToolBox.IsProperFilenameCase(file);
doc = XDocument.Load(file);
doc = XDocument.Load(file, LoadOptions.SetBaseUri);
}
catch (Exception e)
@@ -1042,7 +1149,7 @@ namespace Barotrauma
return doc;
}
public void Load(bool unloadPrevious, XElement submarineElement = null)
public void Load(bool unloadPrevious, XElement submarineElement = null, bool showWarningMessages = true)
{
if (unloadPrevious) Unload();
@@ -1056,6 +1163,7 @@ namespace Barotrauma
submarineElement = doc.Root;
}
GameVersion = GameVersion ?? new Version(submarineElement.GetAttributeString("gameversion", "0.0.0.0"));
Description = submarineElement.GetAttributeString("description", "");
Enum.TryParse(submarineElement.GetAttributeString("tags", ""), out tags);
@@ -1076,40 +1184,10 @@ namespace Barotrauma
{
IdOffset = Math.Max(IdOffset, me.ID);
}
foreach (XElement element in submarineElement.Elements())
{
string typeName = element.Name.ToString();
Type t;
try
{
t = Type.GetType("Barotrauma." + typeName, true, true);
if (t == null)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".");
continue;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".", e);
continue;
}
try
{
MethodInfo loadMethod = t.GetMethod("Load");
loadMethod.Invoke(t, new object[] { element, this });
}
catch (Exception e)
{
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".", e);
}
}
var newEntities = MapEntity.LoadAll(this, submarineElement, filePath);
Vector2 center = Vector2.Zero;
var matchingHulls = Hull.hullList.FindAll(h => h.Submarine == this);
if (matchingHulls.Any())
@@ -1151,7 +1229,7 @@ namespace Barotrauma
}
}
subBody = new SubmarineBody(this);
subBody = new SubmarineBody(this, showWarningMessages);
subBody.SetPosition(HiddenSubPosition);
loaded.Add(this);
@@ -1171,13 +1249,34 @@ namespace Barotrauma
Loading = false;
MapEntity.MapLoaded(this);
MapEntity.MapLoaded(newEntities, true);
//WayPoint.GenerateSubWaypoints();
foreach (Hull hull in matchingHulls)
{
if (string.IsNullOrEmpty(hull.RoomName))
{
hull.RoomName = hull.CreateRoomName();
}
}
#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 && Screen.Selected != GameMain.SubEditorScreen && (GameVersion == null || GameVersion < new Version("0.8.9.0")))
{
DebugConsole.ThrowError("The submarine \"" + 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);
}
}
ID = (ushort)(ushort.MaxValue - Submarine.loaded.IndexOf(this));
}
@@ -1243,13 +1342,14 @@ namespace Barotrauma
element.Add(new XAttribute("name", name));
element.Add(new XAttribute("description", Description ?? ""));
element.Add(new XAttribute("tags", tags.ToString()));
element.Add(new XAttribute("gameversion", GameMain.Version.ToString()));
Rectangle dimensions = CalculateDimensions();
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
element.Add(new XAttribute("recommendedcrewsizemin", RecommendedCrewSizeMin));
element.Add(new XAttribute("recommendedcrewsizemax", RecommendedCrewSizeMax));
element.Add(new XAttribute("recommendedcrewexperience", RecommendedCrewExperience ?? ""));
element.Add(new XAttribute("compatiblecontentpackages", string.Join(", ", CompatibleContentPackages)));
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", RequiredContentPackages)));
foreach (MapEntity e in MapEntity.mapEntityList)
{
@@ -1270,7 +1370,7 @@ namespace Barotrauma
Unloading = true;
#if CLIENT
Sound.OnGameEnd();
RemoveAllRoundSounds(); //Sound.OnGameEnd();
if (GameMain.LightManager != null) GameMain.LightManager.ClearLights();
#endif
@@ -1310,7 +1410,7 @@ namespace Barotrauma
PhysicsBody.RemoveAll();
GameMain.World.Clear();
GameMain.World.Clear();
Unloading = false;
}
@@ -3,6 +3,7 @@ using FarseerPhysics.Collision;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using System;
@@ -15,6 +16,12 @@ namespace Barotrauma
{
class SubmarineBody
{
public const float NeutralBallastPercentage = 0.07f;
const float HorizontalDrag = 0.01f;
const float VerticalDrag = 0.05f;
const float MaxDrag = 0.1f;
public const float DamageDepth = -30000.0f;
private const float ImpactDamageMultiplier = 10.0f;
@@ -65,43 +72,36 @@ namespace Barotrauma
get { return Position.Y < DamageDepth; }
}
public SubmarineBody(Submarine sub)
public Submarine Submarine
{
get { return submarine; }
}
public SubmarineBody(Submarine sub, bool showWarningMessages = true)
{
this.submarine = sub;
Body farseerBody = null;
if (!Hull.hullList.Any())
{
Body = new PhysicsBody(1,1,1,1);
farseerBody = Body.FarseerBody;
DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed.");
farseerBody = BodyFactory.CreateRectangle(GameMain.World, 1.0f, 1.0f, 1.0f);
if (showWarningMessages)
{
DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed.");
}
}
else
{
List<Vector2> convexHull = GenerateConvexHull();
HullVertices = convexHull;
for (int i = 0; i < convexHull.Count; i++)
{
convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
}
convexHull.Reverse();
//get farseer 'vertices' from vectors
Vertices shapevertices = new Vertices(convexHull);
AABB hullAABB = shapevertices.GetAABB();
Borders = new Rectangle(
(int)ConvertUnits.ToDisplayUnits(hullAABB.LowerBound.X),
(int)ConvertUnits.ToDisplayUnits(hullAABB.UpperBound.Y),
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.X * 2.0f),
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.Y * 2.0f));
Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero;
farseerBody = BodyFactory.CreateBody(GameMain.World, this);
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
@@ -109,11 +109,17 @@ namespace Barotrauma
Rectangle rect = wall.Rect;
FixtureFactory.AttachRectangle(
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
ConvertUnits.ToSimUnits(wall.BodyWidth),
ConvertUnits.ToSimUnits(wall.BodyHeight),
50.0f,
-wall.BodyRotation,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
farseerBody, this);
minExtents.X = Math.Min(rect.X, minExtents.X);
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
}
foreach (Hull hull in Hull.hullList)
@@ -127,39 +133,68 @@ namespace Barotrauma
5.0f,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
farseerBody, this);
minExtents.X = Math.Min(rect.X, minExtents.X);
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
}
foreach (Item item in Item.ItemList)
{
if (item.StaticBodyConfig == null) continue;
if (item.StaticBodyConfig == null || item.Submarine != submarine) continue;
float radius = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f));
float width = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("width", 0.0f));
float height = ConvertUnits.ToSimUnits(item.StaticBodyConfig.GetAttributeFloat("height", 0.0f));
float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale;
float width = item.StaticBodyConfig.GetAttributeFloat("width", 0.0f) * item.Scale;
float height = item.StaticBodyConfig.GetAttributeFloat("height", 0.0f) * item.Scale;
if (width != 0.0f && height != 0.0f)
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
float simRadius = ConvertUnits.ToSimUnits(radius);
float simWidth = ConvertUnits.ToSimUnits(width);
float simHeight = ConvertUnits.ToSimUnits(height);
if (width > 0.0f && height > 0.0f)
{
FixtureFactory.AttachRectangle(width, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item;
FixtureFactory.AttachRectangle(simWidth, simHeight, 5.0f, simPos, farseerBody, this).UserData = item;
minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + width / 2, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + height / 2, maxExtents.Y);
}
else if (radius != 0.0f && width != 0.0f)
else if (radius > 0.0f && width > 0.0f)
{
FixtureFactory.AttachRectangle(width, radius * 2, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item;
FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitX * width / 2, this).UserData = item;
FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * width / 2, this).UserData = item;
FixtureFactory.AttachRectangle(simWidth, simRadius * 2, 5.0f, simPos, farseerBody, this).UserData = item;
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos - Vector2.UnitX * simWidth / 2, this).UserData = item;
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos + Vector2.UnitX * simWidth / 2, this).UserData = item;
minExtents.X = Math.Min(item.Position.X - width / 2 - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + width / 2 + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
}
else if (radius != 0.0f && height != 0.0f)
else if (radius > 0.0f && height > 0.0f)
{
FixtureFactory.AttachRectangle(radius * 2, height, 5.0f, ConvertUnits.ToSimUnits(item.Position), farseerBody, this).UserData = item;
FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) - Vector2.UnitY * height / 2, this).UserData = item;
FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position) + Vector2.UnitX * height / 2, this).UserData = item;
FixtureFactory.AttachRectangle(simRadius * 2, height, 5.0f, simPos, farseerBody, this).UserData = item;
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos - Vector2.UnitY * simHeight / 2, this).UserData = item;
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos + Vector2.UnitX * simHeight / 2, this).UserData = item;
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, maxExtents.Y);
}
else if (radius != 0.0f)
else if (radius > 0.0f)
{
FixtureFactory.AttachCircle(radius, 5.0f, farseerBody, ConvertUnits.ToSimUnits(item.Position), this).UserData = item;
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos, this).UserData = item;
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
}
}
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
}
farseerBody.BodyType = BodyType.Dynamic;
farseerBody.CollisionCategories = Physics.CollisionWall;
farseerBody.CollidesWith =
@@ -209,6 +244,8 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (Body.FarseerBody.IsStatic) { return; }
if (GameMain.Client != null)
{
if (memPos.Count == 0) return;
@@ -286,15 +323,25 @@ namespace Barotrauma
//-------------------------
Vector2 totalForce = CalculateBuoyancy();
if (Body.LinearVelocity.LengthSquared() > 0.0001f)
{
float dragCoefficient = 0.01f;
//TODO: sync current drag with clients?
float attachedMass = 0.0f;
JointEdge jointEdge = Body.FarseerBody.JointList;
while (jointEdge != null)
{
Body otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA;
Character character = (otherBody.UserData as Limb)?.character;
if (character != null) attachedMass += character.Mass;
float speedLength = (Body.LinearVelocity == Vector2.Zero) ? 0.0f : Body.LinearVelocity.Length();
float drag = speedLength * speedLength * dragCoefficient * Body.Mass;
totalForce += -Vector2.Normalize(Body.LinearVelocity) * drag;
jointEdge = jointEdge.Next;
}
float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass;
float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass;
}
ApplyForce(totalForce);
@@ -323,20 +370,23 @@ namespace Barotrauma
{
//if the character isn't inside the bounding box, continue
if (!Submarine.RectContains(worldBorders, limb.WorldPosition)) continue;
//cast a line from the position of the character to the same direction as the translation of the sub
//and see where it intersects with the bounding box
Vector2? intersection = MathUtils.GetLineRectangleIntersection(limb.WorldPosition,
limb.WorldPosition + translateDir*100000.0f, worldBorders);
if (!MathUtils.GetLineRectangleIntersection(limb.WorldPosition,
limb.WorldPosition + translateDir * 100000.0f, worldBorders, out Vector2 intersection))
{
//should never happen when casting a line out from inside the bounding box
Debug.Assert(false);
continue;
}
//should never be null when casting a line out from inside the bounding box
Debug.Assert(intersection != null);
//"+ translatedir" in order to move the character slightly away from the wall
c.AnimController.SetPosition(ConvertUnits.ToSimUnits(c.WorldPosition + ((Vector2)intersection - limb.WorldPosition)) + translateDir);
c.AnimController.SetPosition(ConvertUnits.ToSimUnits(c.WorldPosition + (intersection - limb.WorldPosition)) + translateDir);
return;
}
}
}
}
@@ -354,12 +404,13 @@ namespace Barotrauma
}
float waterPercentage = volume <= 0.0f ? 0.0f : waterVolume / volume;
float buoyancy = NeutralBallastPercentage - waterPercentage;
float neutralPercentage = 0.07f;
float buoyancy = neutralPercentage - waterPercentage;
if (buoyancy > 0.0f) buoyancy *= 2.0f;
if (buoyancy > 0.0f)
buoyancy *= 2.0f;
else
buoyancy = Math.Max(buoyancy, -0.5f);
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
}
@@ -406,26 +457,29 @@ namespace Barotrauma
{
if (f2.Body.UserData is Limb limb)
{
bool collision = CheckLimbCollision(contact, limb);
bool collision = CheckCharacterCollision(contact, limb.character);
if (collision) HandleLimbCollision(contact, limb);
return collision;
}
if (f2.Body.UserData is Character character)
{
return CheckCharacterCollision(contact, character);
}
contact.GetWorldManifold(out Vector2 normal, out FixedArray2<Vector2> points);
if (contact.FixtureA.Body == f1.Body)
{
normal = -normal;
}
if (f2.UserData is VoronoiCell cell)
{
Vector2 collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center);
if (!MathUtils.IsValid(collisionNormal)) collisionNormal = Rand.Vector(1.0f);
HandleLevelCollision(contact, collisionNormal);
HandleLevelCollision(contact, normal);
return true;
}
if (f2.Body.UserData is Structure structure)
{
contact.GetWorldManifold(out Vector2 normal, out FixedArray2<Vector2> points);
if (contact.FixtureA.Body == f1.Body)
{
normal = -normal;
}
HandleLevelCollision(contact, normal);
return true;
@@ -440,16 +494,16 @@ namespace Barotrauma
return true;
}
private bool CheckLimbCollision(Contact contact, Limb limb)
private bool CheckCharacterCollision(Contact contact, Character character)
{
if (limb.character.Submarine != null) return false;
//characters that can't enter the sub always collide regardless of gaps
if (!character.AnimController.CanEnterSubmarine) return true;
if (character.Submarine != null) return false;
Vector2 contactNormal;
FixedArray2<Vector2> points;
contact.GetWorldManifold(out contactNormal, out points);
contact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> points);
Vector2 normalizedVel = limb.character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
Vector2.Zero : Vector2.Normalize(limb.character.AnimController.Collider.LinearVelocity);
Vector2 normalizedVel = character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
Vector2.Zero : Vector2.Normalize(character.AnimController.Collider.LinearVelocity);
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
Hull newHull = Hull.FindHull(targetPos, null);
@@ -458,16 +512,17 @@ namespace Barotrauma
{
targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
newHull = Hull.FindHull(targetPos, null);
if (newHull == null) return true;
}
var gaps = newHull.ConnectedGaps;
targetPos = limb.character.WorldPosition;
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
targetPos = character.WorldPosition;
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f);
if (adjacentGap == null) return true;
var ragdoll = limb.character.AnimController;
ragdoll.FindHull(newHull.WorldPosition, true);
if (newHull != null)
{
character.AnimController.FindHull(newHull.WorldPosition, true);
}
return false;
}
@@ -508,8 +563,6 @@ namespace Barotrauma
//if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it
//not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete),
//but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage
//TODO: apply impact damage and/or gib the character that got crushed between the sub and the level?
Vector2 avgContactNormal = Vector2.Zero;
foreach (Contact levelContact in levelContacts)
{
@@ -556,7 +609,9 @@ namespace Barotrauma
Vector2 n;
FixedArray2<Vector2> contactPos;
contact.GetWorldManifold(out n, out contactPos);
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb, DamageType.Blunt, damageAmount, 0.0f, 0.0f, true, 0.0f);
limb.character.LastDamageSource = submarine;
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb,
new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageAmount) }, 0.0f, true, 0.0f);
if (limb.character.IsDead)
{
@@ -685,19 +740,23 @@ namespace Barotrauma
{
if (impact < 3.0f) return;
Vector2 tempNormal;
FixedArray2<Vector2> worldPoints;
contact.GetWorldManifold(out tempNormal, out worldPoints);
contact.GetWorldManifold(out Vector2 tempNormal, out FixedArray2<Vector2> worldPoints);
Vector2 lastContactPoint = worldPoints[0];
Vector2 impulse = direction * impact * 0.5f;
impulse = impulse.ClampLength(5.0f);
#if CLIENT
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
float angularVelocity =
(lastContactPoint.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y
- (lastContactPoint.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X;
GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f);
}
#endif
Vector2 impulse = direction * impact * 0.5f;
impulse = impulse.ClampLength(5.0f);
foreach (Character c in Character.CharacterList)
{
if (c.Submarine != submarine) continue;
@@ -739,7 +798,7 @@ namespace Barotrauma
"StructureBlunt",
impact * 10.0f,
ConvertUnits.ToDisplayUnits(lastContactPoint),
MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f),
MathHelper.Clamp(maxDamage * 4.0f, 2000.0f, 10000.0f),
maxDamageStructure.Tags);
}
#endif
@@ -1,113 +0,0 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class TransitionCinematic
{
public bool Running
{
get;
private set;
}
private float duration;
public TransitionCinematic(Submarine submarine, Camera cam, float duration)
: this(new List<Submarine>() { submarine }, cam, duration)
{
}
public TransitionCinematic(List<Submarine> submarines, Camera cam, float duration)
{
if (!submarines.Any(s => s != null)) return;
Vector2 targetPos = new Vector2(
submarines.Sum(s => s.Position.X),
submarines.Sum(s => s.Position.Y)) / submarines.Count;
if (submarines.First().AtEndPosition)
{
targetPos = Level.Loaded.EndPosition + Vector2.UnitY * 500.0f;
}
else if (submarines.First().AtStartPosition)
{
targetPos = Level.Loaded.StartPosition + Vector2.UnitY * 500.0f;
}
this.duration = duration;
Running = true;
CoroutineManager.StartCoroutine(UpdateTransitionCinematic(submarines, cam, targetPos));
}
private IEnumerable<object> UpdateTransitionCinematic(List<Submarine> subs, Camera cam, Vector2 targetPos)
{
if (!subs.Any()) yield return CoroutineStatus.Success;
Character.Controlled = null;
cam.TargetPos = Vector2.Zero;
#if CLIENT
GameMain.LightManager.LosEnabled = false;
#endif
//Vector2 diff = targetPos - sub.Position;
float targetSpeed = 10.0f;
Level.Loaded.TopBarrier.Enabled = false;
cam.TargetPos = Vector2.Zero;
float timer = 0.0f;
while (timer < duration)
{
if (Screen.Selected != GameMain.GameScreen)
{
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
Running = false;
yield return CoroutineStatus.Success;
}
cam.Zoom = Math.Max(0.2f, cam.Zoom - CoroutineManager.UnscaledDeltaTime * 0.1f);
Vector2 cameraPos = subs.First().Position + Submarine.MainSub.HiddenSubPosition;
cameraPos.Y = Math.Min(cameraPos.Y, ConvertUnits.ToDisplayUnits(Level.Loaded.TopBarrier.Position.Y) - cam.WorldView.Height / 2.0f);
cam.Translate((cameraPos - cam.Position) * CoroutineManager.UnscaledDeltaTime * 10.0f);
#if CLIENT
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, timer/duration);
#endif
foreach (Submarine sub in subs)
{
if (sub.Position == targetPos) continue;
Vector2 dir = Vector2.Normalize(targetPos - sub.Position);
if (!MathUtils.IsValid(dir)) continue;
sub.ApplyForce((dir * targetSpeed - sub.Velocity) * 500.0f);
}
timer += CoroutineManager.UnscaledDeltaTime;
yield return CoroutineStatus.Running;
}
Running = false;
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
yield return CoroutineStatus.Success;
}
}
}
@@ -4,7 +4,6 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
//using Microsoft.Xna.Framework.Graphics;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
@@ -31,6 +30,7 @@ namespace Barotrauma
private ushort ladderId;
public Ladder Ladders;
public Structure Stairs;
private ushort gapId;
public Gap ConnectedGap
@@ -39,6 +39,11 @@ namespace Barotrauma
private set;
}
public Door ConnectedDoor
{
get { return ConnectedGap?.ConnectedDoor; }
}
public Hull CurrentHull
{
get { return currentHull; }
@@ -69,15 +74,15 @@ namespace Barotrauma
private set
{
idCardTags = value;
for (int i = 0; i<idCardTags.Length; i++)
for (int i = 0; i < idCardTags.Length; i++)
{
idCardTags[i] = idCardTags[i].Trim();
idCardTags[i] = idCardTags[i].Trim().ToLowerInvariant();
}
}
}
public WayPoint(Vector2 position, SpawnType spawnType, Submarine submarine, Gap gap = null)
: this(new Rectangle((int)position.X-3, (int)position.Y+3, 6, 6), submarine)
: this(new Rectangle((int)position.X - 3, (int)position.Y + 3, 6, 6), submarine)
{
this.spawnType = spawnType;
ConnectedGap = gap;
@@ -104,7 +109,7 @@ namespace Barotrauma
idCardTags = new string[0];
#if CLIENT
if (iconTexture==null)
if (iconTexture == null)
{
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
}
@@ -118,11 +123,13 @@ namespace Barotrauma
public override MapEntity Clone()
{
var clone = new WayPoint(rect, Submarine);
clone.idCardDesc = idCardDesc;
clone.idCardTags = idCardTags;
clone.spawnType = spawnType;
clone.assignedJob = assignedJob;
var clone = new WayPoint(rect, Submarine)
{
idCardDesc = idCardDesc,
idCardTags = idCardTags,
spawnType = spawnType,
assignedJob = assignedJob
};
return clone;
}
@@ -173,7 +180,7 @@ namespace Barotrauma
WayPoint prevWaypoint = null;
if (hull.Rect.Width<minDist*3.0f)
if (hull.Rect.Width < minDist * 3.0f)
{
new WayPoint(
new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
@@ -299,7 +306,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Items.Components.Ladder>();
var ladders = item.GetComponent<Ladder>();
if (ladders == null) continue;
List<WayPoint> ladderPoints = new List<WayPoint>();
@@ -308,30 +315,47 @@ namespace Barotrauma
WayPoint prevPoint = ladderPoints[0];
Vector2 prevPos = prevPoint.SimPosition;
List<Body> ignoredBodies = new List<Body>();
for (float y = ladderPoints[0].Position.Y + 100.0f; y < item.Rect.Y - 100.0f; y += 100.0f)
for (float y = ladderPoints[0].Position.Y + 100.0f; y < item.Rect.Y - 1.0f; y += 100.0f)
{
var pickedBody = Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos,
ignoredBodies, null, false);
//first check if there's a door in the way
//(we need to create a waypoint linked to the door for NPCs to open it)
Body pickedBody = Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)),
prevPos, ignoredBodies, Physics.CollisionWall, false,
(Fixture f) => f.Body.UserData is Item && ((Item)f.Body.UserData).GetComponent<Door>() != null);
Door pickedDoor = null;
if (pickedBody != null)
{
pickedDoor = (pickedBody?.UserData as Item).GetComponent<Door>();
}
else
{
//no door, check for walls
pickedBody = Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, null, false);
}
if (pickedBody == null)
{
prevPos = Submarine.LastPickedPosition;
continue;
}
ignoredBodies.Add(pickedBody);
if (pickedBody.UserData is Item && ((Item)pickedBody.UserData).GetComponent<Door>() != null)
else
{
var door = ((Item)pickedBody.UserData).GetComponent<Door>();
ignoredBodies.Add(pickedBody);
}
WayPoint newPoint = new WayPoint(door.Item.Position, SpawnType.Path, submarine);
if (pickedDoor != null)
{
WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
newPoint.ConnectedGap = door.LinkedGap;
newPoint.ConnectedGap = pickedDoor.LinkedGap;
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(door.Item.Position.Y - door.Item.Rect.Height));
prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height));
}
else
{
@@ -343,28 +367,25 @@ namespace Barotrauma
}
}
ladderPoints.Add(new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine));
prevPoint.ConnectTo(ladderPoints[ladderPoints.Count - 1]);
for (int i = 0; i < ladderPoints.Count; i++)
if (prevPoint.rect.Y < item.Rect.Y - 10.0f)
{
ladderPoints[i].Ladders = ladders;
WayPoint newPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
newPoint.ConnectTo(prevPoint);
}
//connect ladder waypoints to hull points at the right and left side
foreach (WayPoint ladderPoint in ladderPoints)
{
ladderPoint.Ladders = ladders;
//don't connect if the waypoint is at a gap (= at the boundary of hulls and/or at a hatch)
if (ladderPoint.ConnectedGap != null) continue;
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = ladderPoints[i].FindClosest(dir, true, new Vector2(-150.0f, 10f));
WayPoint closest = ladderPoint.FindClosest(dir, true, new Vector2(-150.0f, 10f));
if (closest == null) continue;
ladderPoints[i].ConnectTo(closest);
}
if (i == ladderPoints.Count - 1 && ladderPoints.Count > 2)
{
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = ladderPoints[i].FindClosest(dir, true, new Vector2(-150.0f, 10f));
if (closest == null) continue;
ladderPoints[i].ConnectTo(closest);
}
ladderPoint.ConnectTo(closest);
}
}
}
@@ -560,12 +581,21 @@ namespace Barotrauma
if (ladderId > 0)
{
var ladderItem = FindEntityByID(ladderId) as Item;
if (ladderItem != null) Ladders = ladderItem.GetComponent<Ladder>();
}
Body pickedBody = Submarine.PickBody(SimPosition, SimPosition - Vector2.UnitY * 2.0f, null, Physics.CollisionWall | Physics.CollisionStairs);
if (pickedBody != null && pickedBody.UserData is Structure)
{
Structure structure = (Structure)pickedBody.UserData;
if (structure != null && structure.StairDirection != Direction.None)
{
Stairs = structure;
}
}
}
public static void Load(XElement element, Submarine submarine)
public static WayPoint Load(XElement element, Submarine submarine)
{
Rectangle rect = new Rectangle(
int.Parse(element.Attribute("x").Value),
@@ -576,7 +606,7 @@ namespace Barotrauma
w.ID = (ushort)int.Parse(element.Attribute("ID").Value);
Enum.TryParse<SpawnType>(element.GetAttributeString("spawn", "Path"), out w.spawnType);
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out w.spawnType);
string idCardDescString = element.GetAttributeString("idcarddesc", "");
if (!string.IsNullOrWhiteSpace(idCardDescString))
@@ -589,10 +619,12 @@ namespace Barotrauma
w.IdCardTags = idCardTagString.Split(',');
}
string jobName = element.GetAttributeString("job", "").ToLowerInvariant();
if (!string.IsNullOrWhiteSpace(jobName))
string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant();
if (!string.IsNullOrWhiteSpace(jobIdentifier))
{
w.assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName);
w.assignedJob =
JobPrefab.List.Find(jp => jp.Identifier.ToLowerInvariant() == jobIdentifier) ??
JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobIdentifier);
}
w.ladderId = (ushort)element.GetAttributeInt("ladders", 0);
@@ -605,6 +637,7 @@ namespace Barotrauma
w.linkedToID.Add((ushort)int.Parse(element.Attribute("linkedto" + i).Value));
i += 1;
}
return w;
}
public override XElement Save(XElement parentElement)
@@ -623,9 +656,7 @@ namespace Barotrauma
element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags)));
}
if (assignedJob != null) element.Add(new XAttribute("job", assignedJob.Name));
if (assignedJob != null) element.Add(new XAttribute("job", assignedJob.Identifier));
if (ConnectedGap != null) element.Add(new XAttribute("gap", ConnectedGap.ID));
if (Ladders != null) element.Add(new XAttribute("ladders", Ladders.Item.ID));