Merge branch 'dedicated-server' (TODO: make sure I didn't break anything)

Conflicts:
	Barotrauma/Barotrauma.csproj
	Barotrauma/BarotraumaShared/Source/Characters/AI/AIController.cs
	Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs
	Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs
	Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs
	Barotrauma/BarotraumaShared/Source/Characters/Attack.cs
	Barotrauma/BarotraumaShared/Source/Characters/Character.cs
	Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs
	Barotrauma/BarotraumaShared/Source/Characters/Limb.cs
	Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs
	Barotrauma/BarotraumaShared/Source/Map/Explosion.cs
This commit is contained in:
Joonas Rikkonen
2017-06-30 21:02:52 +03:00
1311 changed files with 22015 additions and 91216 deletions
@@ -0,0 +1,151 @@
using System.Collections.Generic;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using System.Linq;
using System;
namespace Barotrauma
{
class Entity
{
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
public static List<Entity> GetEntityList()
{
return dictionary.Values.ToList();
}
public static EntitySpawner Spawner;
private ushort id;
protected AITarget aiTarget;
public virtual bool Removed
{
get;
private set;
}
public ushort ID
{
get
{
return id;
}
set
{
Entity thisEntity;
if (dictionary.TryGetValue(id, out 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))
{
System.Diagnostics.Debug.WriteLine(existingEntity+" had the same ID as "+this);
dictionary.Remove(value);
dictionary.Add(id, existingEntity);
existingEntity.id = id;
}
id = value;
dictionary.Add(id, this);
}
}
public virtual Vector2 SimPosition
{
get { return Vector2.Zero; }
}
public virtual Vector2 Position
{
get { return Vector2.Zero; }
}
public virtual Vector2 WorldPosition
{
get { return Submarine == null ? Position : Submarine.Position + Position; }
}
public virtual Vector2 DrawPosition
{
get { return Submarine == null ? Position : Submarine.DrawPosition + Position; }
}
public Submarine Submarine
{
get;
set;
}
public AITarget AiTarget
{
get { return aiTarget; }
}
public Entity(Submarine submarine)
{
this.Submarine = submarine;
//give an unique ID
bool IDfound;
id = submarine == null ? (ushort)1 : submarine.IdOffset;
do
{
id += 1;
IDfound = dictionary.ContainsKey(id);
} while (IDfound);
dictionary.Add(id, this);
}
/// <summary>
/// Find an entity based on the ID
/// </summary>
public static Entity FindEntityByID(ushort ID)
{
Entity matchingEntity;
dictionary.TryGetValue(ID, out matchingEntity);
return matchingEntity;
}
public static void RemoveAll()
{
List<Entity> list = new List<Entity>(dictionary.Values);
foreach (Entity e in list)
{
try
{
e.Remove();
}
catch (Exception exception)
{
DebugConsole.ThrowError("Error while removing entity \"" + e.ToString() + "\"", exception);
}
}
dictionary.Clear();
}
public virtual void Remove()
{
dictionary.Remove(ID);
Removed = true;
}
public static void DumpIds(int count)
{
List<Entity> entities = dictionary.Values.OrderByDescending(e => e.id).ToList();
count = Math.Min(entities.Count, count);
for (int i = 0; i < count; i++)
{
DebugConsole.ThrowError(entities[i].id + ": " + entities[i].ToString());
}
}
}
}
@@ -0,0 +1,129 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
class EntityGrid
{
private List<MapEntity>[,] entities;
private Rectangle limits;
private float cellSize;
public readonly Submarine Submarine;
public EntityGrid(Submarine submarine, float cellSize)
{
this.limits = submarine.Borders;
this.Submarine = submarine;
this.cellSize = cellSize;
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++)
{
for (int y = 0; y < entities.GetLength(1); y++)
{
entities[x, y] = new List<MapEntity>();
}
}
}
public void InsertEntity(MapEntity entity)
{
Rectangle rect = entity.Rect;
//if (Submarine.Loaded != null) rect.Offset(-Submarine.HiddenSubPosition);
Rectangle indices = GetIndices(rect);
if (indices.Width < 0 || indices.X >= entities.GetLength(0) ||
indices.Height < 0 || indices.Y >= entities.GetLength(1))
{
DebugConsole.ThrowError("Error in EntityGrid.InsertEntity: " + entity + " is outside the grid");
return;
}
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++)
{
entities[x, y].Add(entity);
}
}
}
public void RemoveEntity(MapEntity entity)
{
for (int x = 0; x < entities.GetLength(0); x++)
{
for (int y = 0; y < entities.GetLength(1); y++)
{
if (entities[x, y].Contains(entity)) entities[x, y].Remove(entity);
}
}
}
public void Clear()
{
for (int x = 0; x < entities.GetLength(0); x++)
{
for (int y = 0; y < entities.GetLength(1); y++)
{
entities[x, y].Clear();
}
}
}
public static List<MapEntity> GetEntities(List<EntityGrid> entityGrids, Vector2 position, bool useWorldCoordinates = true)
{
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;
}
public List<MapEntity> GetEntities(Vector2 position)
{
if (!MathUtils.IsValid(position)) new List<MapEntity>();
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 entities[indices.X, indices.Y];
}
public Rectangle GetIndices(Rectangle rect)
{
Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor((rect.X - limits.X) / cellSize);
indices.Y = (int)Math.Floor((limits.Y - rect.Y) / cellSize);
indices.Width = (int)Math.Floor((rect.Right - limits.X) / cellSize);
indices.Height = (int)Math.Floor((limits.Y - (rect.Y - rect.Height)) / cellSize);
return indices;
}
public Point GetIndices(Vector2 position)
{
return new Point(
(int)Math.Floor((position.X - limits.X) / cellSize),
(int)Math.Floor((limits.Y - position.Y) / cellSize));
}
}
}
@@ -0,0 +1,191 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Networking;
using FarseerPhysics;
namespace Barotrauma
{
partial class Explosion
{
private Attack attack;
private float force;
public float CameraShake;
private bool sparks, shockwave, flames, smoke;
public Explosion(float range, float force, float damage, float structureDamage)
{
attack = new Attack(damage, structureDamage, 0.0f, range);
this.force = force;
sparks = true;
shockwave = true;
flames = true;
}
public Explosion(XElement element)
{
attack = new Attack(element);
force = ToolBox.GetAttributeFloat(element, "force", 0.0f);
sparks = ToolBox.GetAttributeBool(element, "sparks", true);
shockwave = ToolBox.GetAttributeBool(element, "shockwave", true);
flames = ToolBox.GetAttributeBool(element, "flames", true);
smoke = ToolBox.GetAttributeBool(element, "smoke", true);
CameraShake = ToolBox.GetAttributeFloat(element, "camerashake", attack.Range*0.1f);
}
public void Explode(Vector2 worldPosition)
{
Hull hull = Hull.FindHull(worldPosition);
ExplodeProjSpecific(worldPosition, hull);
float displayRange = attack.Range;
if (displayRange < 0.1f) return;
float cameraDist = Vector2.Distance(GameMain.GameScreen.Cam.Position, worldPosition)/2.0f;
GameMain.GameScreen.Cam.Shake = CameraShake * Math.Max((displayRange - cameraDist) / displayRange, 0.0f);
if (attack.GetStructureDamage(1.0f) > 0.0f)
{
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f));
}
if (force == 0.0f && attack.Stun == 0.0f && attack.GetDamage(1.0f) == 0.0f) return;
ApplyExplosionForces(worldPosition, attack, force);
if (flames && GameMain.Client == null)
{
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) continue;
if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.1f) continue;
item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
if (item.Condition <= 0.0f && GameMain.Server != null)
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
}
}
}
}
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull)
{
if (hull == null) return particlePos;
return new Vector2(
MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right),
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
}
public static void ApplyExplosionForces(Vector2 worldPosition, Attack attack, float force)
{
if (attack.Range <= 0.0f) return;
foreach (Character c in Character.CharacterList)
{
Vector2 explosionPos = worldPosition;
if (c.Submarine != null) explosionPos -= c.Submarine.Position;
explosionPos = ConvertUnits.ToSimUnits(explosionPos);
bool wasDead = c.IsDead;
foreach (Limb limb in c.AnimController.Limbs)
{
float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
//calculate distance from the "outer surface" of the physics body
//doesn't take the rotation of the limb into account, but should be accurate enough for this purpose
float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius);
dist = Math.Max(0.0f, dist - FarseerPhysics.ConvertUnits.ToDisplayUnits(limbRadius));
if (dist > attack.Range) continue;
float distFactor = 1.0f - dist / attack.Range;
//solid obstacles between the explosion and the limb reduce the effect of the explosion by 90%
if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) distFactor *= 0.1f;
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);
if (limb.WorldPosition != worldPosition && force > 0.0f)
{
limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.WorldPosition - worldPosition) * distFactor * force);
}
}
if (!wasDead && c.IsDead)
{
foreach (LimbJoint joint in c.AnimController.LimbJoints)
{
if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability)
{
c.AnimController.SeverLimbJoint(joint);
}
}
}
}
}
/// <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)
{
List<Structure> structureList = new List<Structure>();
float dist = 600.0f;
foreach (MapEntity entity in MapEntity.mapEntityList)
{
Structure structure = entity as Structure;
if (structure == null) continue;
if (structure.HasBody &&
!structure.IsPlatform &&
Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f)
{
structureList.Add(structure);
}
}
Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
foreach (Structure structure in structureList)
{
for (int i = 0; i < structure.SectionCount; i++)
{
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
if (distFactor <= 0.0f) continue;
structure.AddDamage(i, damage * distFactor);
if (damagedStructures.ContainsKey(structure))
{
damagedStructures[structure] += damage * distFactor;
}
else
{
damagedStructures.Add(structure, damage * distFactor);
}
}
}
return damagedStructures;
}
}
}
@@ -0,0 +1,321 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barotrauma.Networking;
#if CLIENT
using Barotrauma.Lights;
#endif
namespace Barotrauma
{
partial class FireSource
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
private int basicSoundIndex, largeSoundIndex;
private Hull hull;
private Vector2 position;
private Vector2 size;
private Entity Submarine;
public Vector2 Position
{
get { return position; }
set
{
if (!MathUtils.IsValid(value)) return;
position = value;
}
}
public Vector2 WorldPosition
{
get { return Submarine.Position + position; }
}
public Vector2 Size
{
get { return size; }
set
{
if (value == size) return;
Vector2 sizeChange = value - size;
size = value;
position.X -= sizeChange.X * 0.5f;
LimitSize();
}
}
public float DamageRange
{
get { return (float)Math.Sqrt(size.X) * 20.0f; }
}
public Hull Hull
{
get { return hull; }
}
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
{
hull = Hull.FindHull(worldPosition, spawningHull);
if (hull == null) 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)
{
fireSoundBasic = Sound.Load("Content/Sounds/fire.ogg", false);
fireSoundLarge = Sound.Load("Content/Sounds/firelarge.ogg", false);
}
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull == null ? null : hull.Submarine);
#endif
size = new Vector2(10.0f, 10.0f);
}
private void LimitSize()
{
if (hull == null) return;
position.X = Math.Max(hull.Rect.X, position.X);
position.Y = Math.Min(hull.Rect.Y, position.Y);
size.X = Math.Min(hull.Rect.Width - (position.X - hull.Rect.X), size.X);
size.Y = Math.Min(hull.Rect.Height - (hull.Rect.Y - position.Y), size.Y);
}
public static void UpdateAll(List<FireSource> fireSources, float deltaTime)
{
for (int i = fireSources.Count - 1; i >= 0; i--)
{
fireSources[i].Update(deltaTime);
}
//combine overlapping fires
for (int i = fireSources.Count - 1; i >= 0; i--)
{
for (int j = i-1; j>=0 ; j--)
{
i = Math.Min(i, fireSources.Count - 1);
j = Math.Min(j, i - 1);
if (!fireSources[i].CheckOverLap(fireSources[j])) continue;
float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X);
fireSources[j].size.X =
Math.Max(fireSources[i].position.X + fireSources[i].size.X, fireSources[j].position.X + fireSources[j].size.X)
- leftEdge;
fireSources[j].position.X = leftEdge;
fireSources[i].Remove();
}
}
}
private bool CheckOverLap(FireSource fireSource)
{
return !(position.X > fireSource.position.X + fireSource.size.X ||
position.X + size.X < fireSource.position.X);
}
public void Update(float deltaTime)
{
//the firesource will start to shrink if oxygen percentage is below 10
float growModifier = Math.Min((hull.OxygenPercentage / 10.0f) - 1.0f, 1.0f);
DamageCharacters(deltaTime);
DamageItems(deltaTime);
if (hull.Volume > 0.0f) HullWaterExtinquish(deltaTime);
hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
size.X += GrowSpeed * growModifier * deltaTime;
size.Y = MathHelper.Clamp(size.Y + GrowSpeed * growModifier * deltaTime, 10.0f, 50.0f);
if (size.X > 50.0f)
{
this.position.Y = MathHelper.Lerp(this.position.Y, hull.Rect.Y - hull.Rect.Height + size.Y, deltaTime);
}
LimitSize();
UpdateProjSpecific(growModifier);
if (GameMain.Client != null) return;
if (size.X < 1.0f) Remove();
}
partial void UpdateProjSpecific(float growModifier);
private void OnChangeHull(Vector2 pos, Hull particleHull)
{
if (particleHull == hull || particleHull==null) return;
//hull already has a firesource roughly at the particles position -> don't create a new one
if (particleHull.FireSources.Find(fs => pos.X > fs.position.X - 100.0f && pos.X < fs.position.X + fs.size.X + 100.0f) != null) return;
new FireSource(new Vector2(pos.X, particleHull.WorldRect.Y - particleHull.Rect.Height + 5.0f));
}
private void DamageCharacters(float deltaTime)
{
if (size.X <= 0.0f) return;
for (int i = 0; i < Character.CharacterList.Count; i++)
{
Character c = Character.CharacterList[i];
if (c.AnimController.CurrentHull == null || c.IsDead) continue;
float range = DamageRange;
if (c.Position.X < position.X - range || c.Position.X > position.X + size.X + range) continue;
if (c.Position.Y < position.Y - size.Y || c.Position.Y > hull.Rect.Y) continue;
float dmg = (float)Math.Sqrt(size.X) * deltaTime / c.AnimController.Limbs.Length;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.WearingItems.Find(w => w != null && w.WearableComponent.Item.FireProof) != null) continue;
limb.Burnt += dmg * 10.0f;
c.AddDamage(limb.SimPosition, DamageType.Burn, dmg, 0, 0, false);
}
}
}
private void DamageItems(float deltaTime)
{
if (size.X <= 0.0f || GameMain.Client != null) return;
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) continue;
if (item.ParentInventory != null && item.ParentInventory.Owner is Character) return;
float range = (float)Math.Sqrt(size.X) * 10.0f;
if (item.Position.X < position.X - range || item.Position.X > position.X + size.X + range) continue;
if (item.Position.Y < position.Y - size.Y || item.Position.Y > hull.Rect.Y) continue;
item.ApplyStatusEffects(ActionType.OnFire, deltaTime);
if (item.Condition <= 0.0f && GameMain.Server != null)
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
}
}
}
private void HullWaterExtinquish(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;
if (extinquishAmount < 0.0f) return;
#if CLIENT
float steamCount = Rand.Range(-5.0f, Math.Min(extinquishAmount * 100.0f, 10));
for (int i = 0; i < steamCount; i++)
{
Vector2 spawnPos = new Vector2(
WorldPosition.X + Rand.Range(0.0f, size.X),
WorldPosition.Y + 10.0f);
Vector2 speed = new Vector2((spawnPos.X - (WorldPosition.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
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;
//evaporate some of the water
hull.Volume -= extinquishAmount;
if (GameMain.Client != null) return;
if (size.X < 1.0f) Remove();
}
public void Extinguish(float deltaTime, float amount, Vector2 pos)
{
float range = 100.0f;
if (pos.X < WorldPosition.X - range || pos.X > WorldPosition.X + size.X + range) return;
if (pos.Y < WorldPosition.Y - range || pos.Y > WorldPosition.Y + 500.0f) return;
float extinquishAmount = amount * deltaTime;
#if CLIENT
float steamCount = Rand.Range(-5.0f, (float)Math.Sqrt(amount));
for (int i = 0; i < steamCount; i++)
{
Vector2 spawnPos = new Vector2(pos.X + Rand.Range(-5.0f, 5.0f), Rand.Range(position.Y - size.Y, position.Y) + 10.0f);
Vector2 speed = new Vector2((spawnPos.X - (position.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
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;
hull.Volume -= extinquishAmount;
if (GameMain.Client != null) return;
if (size.X < 1.0f) Remove();
}
public void Remove()
{
#if CLIENT
lightSource.Remove();
if (basicSoundIndex > 0)
{
Sounds.SoundManager.Stop(basicSoundIndex);
basicSoundIndex = -1;
}
if (largeSoundIndex > 0)
{
Sounds.SoundManager.Stop(largeSoundIndex);
largeSoundIndex = -1;
}
#endif
hull.RemoveFire(this);
}
}
}
@@ -0,0 +1,698 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.ObjectModel;
using Barotrauma.Items.Components;
namespace Barotrauma
{
partial class Gap : MapEntity
{
public static List<Gap> GapList = new List<Gap>();
public static bool ShowGaps = true;
public bool isHorizontal;
//private Sound waterSound;
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
private float open;
//the force of the water flow which is exerted on physics bodies
private Vector2 flowForce;
private Hull flowTargetHull;
private float higherSurface;
private float lowerSurface;
private Vector2 lerpedFlowForce;
//if set to true, hull connections of this gap won't be updated when changes are being done to hulls
public bool DisableHullRechecks;
//can ambient light get through the gap even if it's not open
public bool PassAmbientLight;
public float Open
{
get { return open; }
set { open = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
public Door ConnectedDoor;
public Structure ConnectedWall;
public Vector2 LerpedFlowForce
{
get { return lerpedFlowForce; }
}
public Hull FlowTargetHull
{
get { return flowTargetHull; }
}
public bool IsRoomToRoom
{
get
{
return linkedTo.Count == 2;
}
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
base.Rect = value;
FindHulls();
}
}
public override string Name
{
get
{
return "Gap";
}
}
public override bool SelectableInEditor
{
get
{
return ShowGaps;
}
}
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
: this (rectangle, Submarine.MainSub)
{ }
public Gap(Rectangle newRect, Submarine submarine)
: this(newRect, newRect.Width < newRect.Height, submarine)
{ }
public Gap(Rectangle newRect, bool isHorizontal, Submarine submarine)
: base (MapEntityPrefab.list.Find(m=> m.Name == "Gap"), submarine)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
flowForce = Vector2.Zero;
this.isHorizontal = isHorizontal;
open = 1.0f;
FindHulls();
GapList.Add(this);
InsertToList();
}
public override MapEntity Clone()
{
return new Gap(rect, isHorizontal, Submarine);
}
public override void Move(Vector2 amount)
{
base.Move(amount);
FindHulls();
}
public static void UpdateHulls()
{
foreach (Gap g in GapList)
{
if (g.DisableHullRechecks) continue;
g.FindHulls();
}
}
public override bool IsMouseOn(Vector2 position)
{
return (ShowGaps && Submarine.RectContains(WorldRect, position) &&
!Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -5), position));
}
private void FindHulls()
{
Hull[] hulls = new Hull[2];
linkedTo.Clear();
Vector2[] searchPos = new Vector2[2];
if (isHorizontal)
{
searchPos[0] = new Vector2(rect.X, rect.Y - rect.Height / 2);
searchPos[1] = new Vector2(rect.Right, rect.Y - rect.Height / 2);
}
else
{
searchPos[0] = new Vector2(rect.Center.X, rect.Y);
searchPos[1] = new Vector2(rect.Center.X, rect.Y - rect.Height);
}
hulls[0] = Hull.FindHullOld(searchPos[0], null, false);
hulls[1] = Hull.FindHullOld(searchPos[1], null, false);
if (hulls[0] == null && hulls[1] == null) return;
if (hulls[0]==null && hulls[1]!=null)
{
Hull temp = hulls[0];
hulls[0] = hulls[1];
hulls[1] = temp;
}
flowTargetHull = hulls[0];
for (int i = 0 ; i <2; i++)
{
if (hulls[i]==null) continue;
linkedTo.Add(hulls[i]);
if (!hulls[i].ConnectedGaps.Contains(this)) hulls[i].ConnectedGaps.Add(this);
}
}
public override void Update(float deltaTime, Camera cam)
{
flowForce = Vector2.Zero;
if (open == 0.0f)
{
lerpedFlowForce = Vector2.Zero;
return;
}
UpdateOxygen();
if (linkedTo.Count == 1)
{
//gap leading from a room to outside
UpdateRoomToOut(deltaTime);
}
else
{
//gap leading from a room to another
UpdateRoomToRoom(deltaTime);
}
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime);
if (LerpedFlowForce.LengthSquared() > 20000.0f && flowTargetHull != null && flowTargetHull.Volume < flowTargetHull.FullVolume)
{
//UpdateFlowForce();
Vector2 pos = Position;
if (isHorizontal)
{
pos.X += Math.Sign(flowForce.X);
pos.Y = MathHelper.Clamp((higherSurface + lowerSurface) / 2.0f, rect.Y - rect.Height, rect.Y) + 10;
Vector2 velocity = new Vector2(
MathHelper.Clamp(flowForce.X, -5000.0f, 5000.0f) * Rand.Range(0.5f, 0.7f),
flowForce.Y * Rand.Range(0.5f, 0.7f));
#if CLIENT
var particle = GameMain.ParticleManager.CreateParticle(
"watersplash",
(Submarine == null ? pos : pos + Submarine.Position) - Vector2.UnitY * Rand.Range(0.0f, 10.0f),
velocity, 0, flowTargetHull);
if (particle != null)
{
particle.Size = particle.Size * Math.Min(Math.Abs(flowForce.X / 1000.0f), 5.0f);
}
#endif
if (Math.Abs(flowForce.X) > 300.0f)
{
pos.X += Math.Sign(flowForce.X) * 10.0f;
pos.Y = Rand.Range(lowerSurface, rect.Y - rect.Height);
#if CLIENT
GameMain.ParticleManager.CreateParticle(
"bubbles",
Submarine == null ? pos : pos + Submarine.Position,
flowForce / 10.0f, 0, flowTargetHull);
#endif
}
}
else
{
if (Math.Sign(flowTargetHull.Rect.Y - rect.Y) != Math.Sign(lerpedFlowForce.Y)) return;
pos.Y += Math.Sign(flowForce.Y) * rect.Height / 2.0f;
for (int i = 0; i < rect.Width; i += (int)Rand.Range(80, 100))
{
pos.X = Rand.Range(rect.X, rect.X + rect.Width);
Vector2 velocity = new Vector2(
lerpedFlowForce.X * Rand.Range(0.5f, 0.7f),
Math.Max(lerpedFlowForce.Y, -100.0f) * Rand.Range(0.5f, 0.7f));
#if CLIENT
var splash = GameMain.ParticleManager.CreateParticle(
"watersplash",
Submarine == null ? pos : pos + Submarine.Position,
-velocity, 0, FlowTargetHull);
if (splash != null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f);
GameMain.ParticleManager.CreateParticle(
"bubbles",
Submarine == null ? pos : pos + Submarine.Position,
flowForce / 2.0f, 0, FlowTargetHull);
#endif
}
}
}
if (flowTargetHull != null && lerpedFlowForce != Vector2.Zero)
{
foreach (Character character in Character.CharacterList)
{
if (character.AnimController.CurrentHull != flowTargetHull) continue;
foreach (Limb limb in character.AnimController.Limbs)
{
if (!limb.inWater) continue;
float dist = Vector2.Distance(limb.WorldPosition, WorldPosition);
if (dist > lerpedFlowForce.Length()) continue;
limb.body.ApplyForce(lerpedFlowForce / dist/10.0f);
}
}
}
}
void UpdateRoomToRoom(float deltaTime)
{
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;
}
else if (hull2.Submarine != Submarine)
{
subOffset = hull2.Submarine.Position - Submarine.Position;
}
if (hull1.Volume == 0.0 && hull2.Volume == 0.0) return;
float size = (isHorizontal) ? rect.Height : rect.Width;
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = size / 100.0f * open;
//horizontal gap (such as a regular door)
if (isHorizontal)
{
higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
float delta=0.0f;
//water level is above the lower boundary of the gap
if (Math.Max(hull1.Surface+hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y +hull2.WaveY[0]) > rect.Y - size)
{
int dir = (hull1.Pressure > hull2.Pressure+subOffset.Y) ? 1 : -1;
//water flowing from the righthand room to the lefthand room
if (dir == -1)
{
if (!(hull2.Volume > 0.0f)) return;
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
flowTargetHull = hull1;
//make sure not to move more than what the room contains
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.Volume, hull2.FullVolume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - (hull1.Volume));
hull1.Volume += delta;
hull2.Volume -= delta;
if (hull1.Volume > hull1.FullVolume)
{
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure+subOffset.Y) / 2);
}
flowForce = new Vector2(-delta, 0.0f);
}
else if (dir == 1)
{
if (!(hull1.Volume > 0.0f)) return;
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
flowTargetHull = hull2;
//make sure not to move more than what the room contains
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 5.0f * sizeModifier, Math.Min(hull1.Volume, hull1.FullVolume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull2.FullVolume + Hull.MaxCompress - (hull2.Volume));
hull1.Volume -= delta;
hull2.Volume += delta;
if (hull2.Volume > hull2.FullVolume)
{
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2);
}
flowForce = new Vector2(delta, 0.0f);
}
if (delta>100.0f && subOffset == Vector2.Zero)
{
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
//float avgVel = (hull2.WaveVel[1] + hull1.WaveVel[hull1.WaveY.Length - 2]) / 2.0f;
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
{
hull1.WaveVel[hull1.WaveY.Length - 1] = (avg-(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1]))*0.1f;
hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
}
if (hull2.Volume < hull2.FullVolume - Hull.MaxCompress &&
hull2.Surface + hull2.WaveY[0] < rect.Y)
{
hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
hull2.WaveVel[1] = hull2.WaveVel[0];
}
}
}
}
else
{
//lower room is full of water
if ((hull2.Pressure + subOffset.Y) > hull1.Pressure)
{
float delta = Math.Min(hull2.Volume - hull2.FullVolume + Hull.MaxCompress / 2.0f, deltaTime * 8000.0f * sizeModifier);
flowForce = new Vector2(0.0f, Math.Min((hull2.Pressure + subOffset.Y) - hull1.Pressure, 500.0f));
delta = Math.Max(delta, 0.0f);
hull1.Volume += delta;
hull2.Volume -= delta;
flowTargetHull = hull1;
if (hull1.Volume > hull1.FullVolume)
{
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + (hull2.Pressure + subOffset.Y)) / 2);
}
}
//there's water in the upper room, drop to lower
else if (hull1.Volume > 0)
{
flowTargetHull = hull2;
//make sure the amount of water moved isn't more than what the room contains
float delta = Math.Min(hull1.Volume, deltaTime * 25000f * sizeModifier);
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, (hull2.FullVolume + Math.Max(hull1.Volume - hull1.FullVolume, 0.0f)) - hull2.Volume + Hull.MaxCompress / 4.0f);
hull1.Volume -= delta;
hull2.Volume += delta;
if (hull2.Volume > hull2.FullVolume)
{
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure - subOffset.Y) + hull2.Pressure) / 2);
}
flowForce = new Vector2(0.0f, -delta);
flowForce.X = hull1.WaveY[hull1.GetWaveIndex(rect.X)] - hull1.WaveY[hull1.GetWaveIndex(rect.Right)] * 10.0f;
//if (water2.Volume < water2.FullVolume - Hull.MaxCompress)
//{
// int posX = (int)((rect.X + size / 2.0f - water1.Rect.X) / Hull.WaveWidth);
// //water1.WaveY[posX] = -delta;
// if (posX > -1 && posX < water2.WaveVel.Length)
// water1.WaveVel[posX] = -delta * 0.01f;
// posX = (int)((rect.X + size / 2.0f - water2.Rect.X) / Hull.WaveWidth);
// //water2.WaveY[posX] = delta;
// if (posX > -1 && posX<water2.WaveVel.Length)
// water2.WaveVel[posX] = delta * 0.01f;
//}
}
}
if (open > 0.0f)
{
if (hull1.Volume > hull1.FullVolume - Hull.MaxCompress && hull2.Volume > hull2.FullVolume - Hull.MaxCompress)
{
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
hull1.LethalPressure = avgLethality;
hull2.LethalPressure = avgLethality;
}
else
{
hull1.LethalPressure = 0.0f;
hull2.LethalPressure = 0.0f;
}
}
}
void UpdateRoomToOut(float deltaTime)
{
if (linkedTo.Count != 1) return;
float size = (isHorizontal) ? rect.Height : rect.Width;
Hull hull1 = (Hull)linkedTo[0];
//a variable affecting the water flow through the gap
//the larger the gap is, the faster the water flows
float sizeModifier = size * open * open;
float delta = Hull.MaxCompress * sizeModifier * deltaTime;
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - hull1.Volume);
hull1.Volume += delta;
if (hull1.Volume > hull1.FullVolume) hull1.Pressure += 0.5f;
flowTargetHull = hull1;
if (isHorizontal)
{
//water flowing from right to left
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
{
flowForce = new Vector2(-delta, 0.0f);
}
else
{
flowForce = new Vector2(delta, 0.0f);
}
higherSurface = hull1.Surface;
lowerSurface = rect.Y;
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
hull1.Surface < rect.Y)
{
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
{
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
hull1.WaveVel[hull1.WaveY.Length - 1] += vel;
hull1.WaveVel[hull1.WaveY.Length - 2] += vel;
}
else
{
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 0.1f;
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
hull1.WaveVel[0] += vel;
hull1.WaveVel[1] += vel;
}
}
else
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
}
}
else
{
if (rect.Y > hull1.Rect.Y - hull1.Rect.Height / 2.0f)
{
flowForce = new Vector2(0.0f, -delta);
}
else
{
flowForce = new Vector2(0.0f, delta);
}
if (hull1.Volume >= hull1.FullVolume - Hull.MaxCompress)
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
}
}
}
private void UpdateOxygen()
{
if (linkedTo.Count < 2) return;
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
if (isHorizontal)
{
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + hull2.WaveY[0]) > rect.Y) return;
}
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
float totalVolume = (hull1.FullVolume + hull2.FullVolume);
float deltaOxygen = (totalOxygen * hull1.FullVolume / totalVolume) - hull1.Oxygen;
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed, Hull.OxygenDistributionSpeed);
hull1.Oxygen += deltaOxygen;
hull2.Oxygen -= deltaOxygen;
}
public static Gap FindAdjacent(List<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
{
foreach (Gap gap in gaps)
{
if (gap.Open == 0.0f || gap.IsRoomToRoom) continue;
if (gap.ConnectedWall != null)
{
int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position);
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) continue;
}
if (gap.isHorizontal)
{
if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height &&
Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
{
return gap;
}
}
else
{
if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right &&
Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
{
return gap;
}
}
}
return null;
}
public override void ShallowRemove()
{
base.ShallowRemove();
GapList.Remove(this);
foreach (Hull hull in Hull.hullList)
{
hull.ConnectedGaps.Remove(this);
}
}
public override void Remove()
{
base.Remove();
GapList.Remove(this);
foreach (Hull hull in Hull.hullList)
{
hull.ConnectedGaps.Remove(this);
}
}
public override void OnMapLoaded()
{
FindHulls();
}
public static void Load(XElement element, Submarine submarine)
{
Rectangle rect = Rectangle.Empty;
if (element.Attribute("rect") != null)
{
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
string[] rectValues = rectString.Split(',');
rect = new Rectangle(
int.Parse(rectValues[0]),
int.Parse(rectValues[1]),
int.Parse(rectValues[2]),
int.Parse(rectValues[3]));
}
else
{
rect = new Rectangle(
int.Parse(element.Attribute("x").Value),
int.Parse(element.Attribute("y").Value),
int.Parse(element.Attribute("width").Value),
int.Parse(element.Attribute("height").Value));
}
bool isHorizontal = rect.Height > rect.Width;
var horizontalAttribute = element.Attribute("horizontal");
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.linkedToID = new List<ushort>();
//int i = 0;
//while (element.Attribute("linkedto" + i) != null)
//{
// g.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
// i += 1;
//}
}
}
}
@@ -0,0 +1,830 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Lidgren.Network;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class Hull : MapEntity, IPropertyObject, IServerSerializable
{
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 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;
//how much excess water the room can contain (= more than the volume of the room)
public const float MaxCompress = 10000f;
public readonly Dictionary<string, ObjectProperty> properties;
public Dictionary<string, ObjectProperty> ObjectProperties
{
get { return properties; }
}
private float lethalPressure;
private float surface;
private float volume;
private float pressure;
private float oxygen;
private bool update;
public bool Visible = true;
float[] waveY; //displacement from the surface of the water
float[] waveVel; //velocity of the point
float[] leftDelta;
float[] rightDelta;
private float lastSentVolume, lastSentOxygen;
private float sendUpdateTimer;
public List<Gap> ConnectedGaps;
public override string Name
{
get
{
return "Hull";
}
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
base.Rect = value;
if (Submarine == null || !Submarine.Loading)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
surface = rect.Y - rect.Height + Volume / rect.Width;
Pressure = surface;
}
}
public override bool SelectableInEditor
{
get
{
return ShowHulls;
}
}
public override bool IsLinkable
{
get { return true; }
}
public float LethalPressure
{
get { return lethalPressure; }
set { lethalPressure = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public Vector2 Size
{
get { return new Vector2(rect.Width, rect.Height); }
}
public float Surface
{
get { return surface; }
}
public float Volume
{
get { return volume; }
set
{
if (!MathUtils.IsValid(value)) return;
volume = MathHelper.Clamp(value, 0.0f, FullVolume + MaxCompress);
if (volume < FullVolume) Pressure = rect.Y - rect.Height + volume / rect.Width;
if (volume > 0.0f) update = true;
}
}
[HasDefaultValue(90.0f, true)]
public float Oxygen
{
get { return oxygen; }
set
{
if (!MathUtils.IsValid(value)) return;
oxygen = MathHelper.Clamp(value, 0.0f, FullVolume);
}
}
public float OxygenPercentage
{
get { return oxygen / FullVolume * 100.0f; }
set { Oxygen = (value / 100.0f) * FullVolume; }
}
public float FullVolume
{
get { return (rect.Width * rect.Height); }
}
public float Pressure
{
get { return pressure; }
set { pressure = value; }
}
public float[] WaveY
{
get { return waveY; }
}
public float[] WaveVel
{
get { return waveVel; }
}
public List<FireSource> FireSources
{
get { return fireSources; }
}
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
: this (prefab, rectangle, Submarine.MainSub)
{
}
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine)
: base (prefab, submarine)
{
rect = rectangle;
OxygenPercentage = 100.0f;
fireSources = new List<FireSource>();
properties = ObjectProperty.GetProperties(this);
int arraySize = (rectangle.Width / WaveWidth + 1);
waveY = new float[arraySize];
waveVel = new float[arraySize];
leftDelta = new float[arraySize];
rightDelta = new float[arraySize];
surface = rect.Y - rect.Height;
aiTarget = new AITarget(this);
hullList.Add(this);
ConnectedGaps = new List<Gap>();
if (submarine==null || !submarine.Loading)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
Volume = 0.0f;
InsertToList();
}
public static Rectangle GetBorders()
{
if (!hullList.Any()) return Rectangle.Empty;
Rectangle rect = hullList[0].rect;
foreach (Hull hull in hullList)
{
if (hull.Rect.X < rect.X)
{
rect.Width += rect.X - hull.rect.X;
rect.X = hull.rect.X;
}
if (hull.rect.Right > rect.Right) rect.Width = hull.rect.Right - rect.X;
if (hull.rect.Y > rect.Y)
{
rect.Height += hull.rect.Y - rect.Y;
rect.Y = hull.rect.Y;
}
if (hull.rect.Y - hull.rect.Height < rect.Y - rect.Height) rect.Height = rect.Y - (hull.rect.Y - hull.rect.Height);
}
return rect;
}
public override MapEntity Clone()
{
return new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), rect, Submarine);
}
public static EntityGrid GenerateEntityGrid(Submarine submarine)
{
var newGrid = new EntityGrid(submarine, 200.0f);
entityGrids.Add(newGrid);
foreach (Hull hull in hullList)
{
if (hull.Submarine == submarine) newGrid.InsertEntity(hull);
}
return newGrid;
}
public void AddToGrid(Submarine submarine)
{
foreach (EntityGrid grid in entityGrids)
{
if (grid.Submarine != submarine) continue;
rect.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
grid.InsertEntity(this);
rect.Location += MathUtils.ToPoint(submarine.HiddenSubPosition);
return;
}
}
public int GetWaveIndex(Vector2 position)
{
return GetWaveIndex(position.X);
}
public int GetWaveIndex(float xPos)
{
int index = (int)(xPos - rect.X) / WaveWidth;
index = (int)MathHelper.Clamp(index, 0, waveY.Length - 1);
return index;
}
public override void Move(Vector2 amount)
{
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
if (Submarine==null || !Submarine.Loading)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
surface = rect.Y - rect.Height + Volume / rect.Width;
Pressure = surface;
}
public override void ShallowRemove()
{
base.Remove();
hullList.Remove(this);
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
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
if (entityGrids != null)
{
foreach (EntityGrid entityGrid in entityGrids)
{
entityGrid.RemoveEntity(this);
}
}
}
public override void Remove()
{
base.Remove();
hullList.Remove(this);
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
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
if (entityGrids != null)
{
foreach (EntityGrid entityGrid in entityGrids)
{
entityGrid.RemoveEntity(this);
}
}
}
public void AddFireSource(FireSource fireSource)
{
fireSources.Add(fireSource);
if (GameMain.Server != null) GameMain.Server.CreateEntityEvent(this);
}
public override void Update(float deltaTime, Camera cam)
{
Oxygen -= OxygenDetoriationSpeed * deltaTime;
if (EditWater)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
if (PlayerInput.LeftButtonHeld())
{
//waveY[GetWaveIndex(position.X - rect.X - Submarine.Position.X) / WaveWidth] = 100.0f;
Volume = Volume + 1500.0f;
}
else if (PlayerInput.RightButtonHeld())
{
Volume = Volume - 1500.0f;
}
}
}
else if (EditFire)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
if (PlayerInput.LeftButtonClicked())
{
new FireSource(position, this);
}
}
}
FireSource.UpdateAll(fireSources, deltaTime);
aiTarget.SightRange = Submarine == null ? 0.0f : Math.Max(Submarine.Velocity.Length() * 500.0f, 500.0f);
aiTarget.SoundRange -= deltaTime * 1000.0f;
float strongestFlow = 0.0f;
foreach (Gap gap in ConnectedGaps)
{
if (gap.IsRoomToRoom)
{
//only the first linked hull plays the flow sound
if (gap.linkedTo[1] == this) continue;
}
float gapFlow = gap.LerpedFlowForce.Length();
if (gapFlow > strongestFlow)
{
strongestFlow = gapFlow;
}
}
#if CLIENT
if (strongestFlow > 1.0f)
{
soundVolume = soundVolume + ((strongestFlow < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f);
soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f);
int index = (int)Math.Floor(strongestFlow / 100.0f);
index = Math.Min(index, 2);
var flowSound = SoundPlayer.flowSounds[index];
if (flowSound != currentFlowSound && soundIndex > -1)
{
Sounds.SoundManager.Stop(soundIndex);
currentFlowSound = null;
soundIndex = -1;
}
currentFlowSound = flowSound;
soundIndex = currentFlowSound.Loop(soundIndex, soundVolume, WorldPosition, Math.Min(strongestFlow*5.0f, 2000.0f));
}
else
{
if (soundIndex > -1)
{
Sounds.SoundManager.Stop(soundIndex);
currentFlowSound = null;
soundIndex = -1;
}
}
#endif
//update client hulls if the amount of water has changed by >10%
//or if oxygen percentage has changed by 5%
if (Math.Abs(lastSentVolume - volume) > FullVolume * 0.1f ||
Math.Abs(lastSentOxygen - OxygenPercentage) > 5f)
{
if (GameMain.Server != null)
{
sendUpdateTimer -= deltaTime;
if (sendUpdateTimer < 0.0f)
{
GameMain.Server.CreateEntityEvent(this);
lastSentVolume = volume;
lastSentOxygen = OxygenPercentage;
sendUpdateTimer = NetworkUpdateInterval;
}
}
}
if (!update)
{
lethalPressure = 0.0f;
return;
}
float surfaceY = rect.Y - rect.Height + Volume / rect.Width;
for (int i = 0; i < waveY.Length; i++)
{
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
#if CLIENT
if (maxDelta > Rand.Range(1.0f,10.0f))
{
var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]);
if (Submarine != null) particlePos += Submarine.Position;
GameMain.ParticleManager.CreateParticle("mist",
particlePos,
new Vector2(0.0f, -50.0f), 0.0f, this);
}
#endif
waveY[i] = waveY[i] + waveVel[i];
if (surfaceY + waveY[i] > rect.Y)
{
waveY[i] -= (surfaceY + waveY[i]) - rect.Y;
waveVel[i] = waveVel[i] * -0.5f;
}
else if (surfaceY + waveY[i] < rect.Y - rect.Height)
{
waveY[i] -= (surfaceY + waveY[i]) - (rect.Y - rect.Height);
waveVel[i] = waveVel[i] * -0.5f;
}
//acceleration
float a = -WaveStiffness * waveY[i] - waveVel[i] * WaveDampening;
waveVel[i] = waveVel[i] + a;
}
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];
rightDelta[i] = WaveSpread * (waveY[i] - waveY[i + 1]);
waveVel[i + 1] = 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];
}
}
//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);
if (volume < FullVolume)
{
LethalPressure -= 10.0f * deltaTime;
if (Volume == 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;
for (int i = 1; i < waveY.Length - 1; i++)
{
if (waveY[i] > 0.1f) return;
}
update = false;
}
}
}
public void ApplyFlowForces(float deltaTime, Item item)
{
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
{
//var pos = gap.Position - body.Position;
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position)/1000, 1f);
//pos.Normalize();
item.body.ApplyForce((gap.LerpedFlowForce/distance) * deltaTime);
}
}
public void Extinguish(float deltaTime, float amount, Vector2 position)
{
for (int i = fireSources.Count - 1; i >= 0; i-- )
{
fireSources[i].Extinguish(deltaTime, amount, position);
}
}
public void RemoveFire(FireSource fire)
{
fireSources.Remove(fire);
if (GameMain.Server != null) GameMain.Server.CreateEntityEvent(this);
}
public List<Hull> GetConnectedHulls(int? searchDepth)
{
return GetAdjacentHulls(new List<Hull>(), 0, searchDepth);
}
private List<Hull> GetAdjacentHulls(List<Hull> connectedHulls, int steps, int? searchDepth)
{
connectedHulls.Add(this);
if (searchDepth != null && steps >= searchDepth.Value) return connectedHulls;
foreach (Gap g in ConnectedGaps)
{
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
{
Hull hull = g.linkedTo[i] as Hull;
if (hull != null && !connectedHulls.Contains(hull))
{
hull.GetAdjacentHulls(connectedHulls, steps++, searchDepth);
}
}
}
return connectedHulls;
}
//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)
{
if (entityGrids == null) return null;
if (guess != null)
{
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position)) return guess;
}
var entities = EntityGrid.GetEntities(entityGrids, position, useWorldCoordinates);
foreach (Hull hull in entities)
{
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position)) return hull;
}
return null;
}
//returns the water block which contains the point (or null if it isn't inside any)
public static Hull FindHullOld(Vector2 position, Hull guess = null, bool useWorldCoordinates = true)
{
return FindHullOld(position, hullList, guess, useWorldCoordinates);
}
public static Hull FindHullOld(Vector2 position, List<Hull> hulls, Hull guess = null, bool useWorldCoordinates = true)
{
if (guess != null && hulls.Contains(guess))
{
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position)) return guess;
}
foreach (Hull hull in hulls)
{
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position)) return hull;
}
return null;
}
public static void DetectItemVisibility(Character c=null)
{
if (c==null)
{
foreach (Item it in Item.ItemList)
{
it.Visible = true;
}
}
else
{
Hull h = c.CurrentHull;
hullList.ForEach(j => j.Visible = false);
List<Hull> visibleHulls;
if (h == null || c.Submarine == null)
{
visibleHulls = hullList.FindAll(j => j.CanSeeOther(null, false));
}
else
{
visibleHulls = hullList.FindAll(j => h.CanSeeOther(j, true));
}
visibleHulls.ForEach(j => j.Visible = true);
foreach (Item it in Item.ItemList)
{
if (it.CurrentHull == null || visibleHulls.Contains(it.CurrentHull)) it.Visible = true;
else it.Visible = false;
}
}
}
private bool CanSeeOther(Hull other,bool allowIndirect=true)
{
if (other == this) return true;
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);
retVal = otherHulls.Any(h => h == other);
if (!retVal && allowIndirect) retVal = otherHulls.Any(h => h.CanSeeOther(other, false));
if (retVal) return true;
}
}
else
{
foreach (Gap g in ConnectedGaps)
{
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));
return structures.Any(st => !(st as Structure).CastShadow);
}
return false;
}
public void ServerWrite(NetBuffer message, Client c, object[] extraData = null)
{
message.WriteRangedSingle(MathHelper.Clamp(volume / FullVolume, 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.WriteRangedInteger(0, 16, Math.Min(fireSources.Count, 16));
for (int i = 0; i < Math.Min(fireSources.Count, 16); i++)
{
var fireSource = fireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
message.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
}
}
}
public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime)
{
Volume = message.ReadRangedSingle(0.0f, 1.5f, 8) * FullVolume;
OxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
bool hasFireSources = message.ReadBoolean();
int fireSourceCount = 0;
if (hasFireSources)
{
fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
{
Vector2 pos = Vector2.Zero;
float size = 0.0f;
pos.X = MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
pos.Y = MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
size = message.ReadRangedSingle(0.0f, 1.0f, 8);
pos = new Vector2(
rect.X + rect.Width * pos.X,
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);
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))
{
newFire.Remove();
continue;
}
}
}
while (fireSources.Count > fireSourceCount)
{
fireSources[fireSources.Count - 1].Remove();
}
}
public static void Load(XElement element, Submarine submarine)
{
Rectangle rect = Rectangle.Empty;
if (element.Attribute("rect") != null)
{
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
string[] rectValues = rectString.Split(',');
rect = new Rectangle(
int.Parse(rectValues[0]),
int.Parse(rectValues[1]),
int.Parse(rectValues[2]),
int.Parse(rectValues[3]));
}
else
{
rect = new Rectangle(
int.Parse(element.Attribute("x").Value),
int.Parse(element.Attribute("y").Value),
int.Parse(element.Attribute("width").Value),
int.Parse(element.Attribute("height").Value));
}
Hull h = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), rect, submarine);
h.volume = ToolBox.GetAttributeFloat(element, "pressure", 0.0f);
h.ID = (ushort)int.Parse(element.Attribute("ID").Value);
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
interface IDamageable
{
Vector2 SimPosition
{
get;
}
Vector2 WorldPosition
{
get;
}
float Health
{
get;
}
AITarget AiTarget
{
get;
}
AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
}
}
@@ -0,0 +1,484 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Voronoi2;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
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>();
cellGrid = new List<VoronoiCell>[(int)Math.Ceiling(borders.Width / gridCellSize), (int)Math.Ceiling(borders.Height / gridCellSize)];
for (int x = 0; x < borders.Width / gridCellSize; x++)
{
for (int y = 0; y < borders.Height / gridCellSize; y++)
{
cellGrid[x, y] = new List<VoronoiCell>();
}
}
foreach (GraphEdge ge in graphEdges)
{
if (ge.point1 == ge.point2) continue;
for (int i = 0; i < 2; i++)
{
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));
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);
if (cell == null)
{
cell = new VoronoiCell(site);
cellGrid[x, y].Add(cell);
cells.Add(cell);
}
if (ge.cell1 == null)
{
ge.cell1 = cell;
}
else
{
ge.cell2 = cell;
}
cell.edges.Add(ge);
}
}
return cells;
}
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
{
if (cell == null) cell = edge.AdjacentCell(null);
if (cell == null) return Vector2.UnitX;
CompareCCW compare = new CompareCCW(cell.Center);
if (compare.Compare(edge.point1, edge.point2) == -1)
{
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 = new Vector2(-normal.Y, normal.X);
if (Vector2.Dot(normal, diffToCell) < 0)
{
normal = -normal;
}
return normal;
}
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)
{
var targetCells = new List<VoronoiCell>();
for (int i = 0; i < pathNodes.Count; i++)
{
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
int searchDepth = 2;
while (searchDepth < 5)
{
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth, gridOffset);
if (cellIndex > -1)
{
targetCells.Add(cells[cellIndex]);
break;
}
searchDepth++;
}
}
return GeneratePath(targetCells, cells, cellGrid, gridCellSize, limits, wanderAmount, mirror);
}
public static List<VoronoiCell> GeneratePath(
List<VoronoiCell> targetCells, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
{
Stopwatch sw2 = new Stopwatch();
sw2.Start();
//how heavily the path "steers" towards the endpoint
//lower values will cause the path to "wander" more, higher will make it head straight to the end
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
List<GraphEdge> allowedEdges = new List<GraphEdge>();
List<VoronoiCell> pathCells = new List<VoronoiCell>();
VoronoiCell currentCell = targetCells[0];
currentCell.CellType = CellType.Path;
pathCells.Add(currentCell);
int currentTargetIndex = 1;
int iterationsLeft = cells.Count;
do
{
int edgeIndex = 0;
allowedEdges.Clear();
foreach (GraphEdge edge in currentCell.edges)
{
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
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++)
{
if (!MathUtils.LinesIntersect(currentCell.Center, targetCells[currentTargetIndex].Center,
currentCell.edges[i].point1, currentCell.edges[i].point2)) continue;
edgeIndex = i;
break;
}
}
//choose random edge (ignoring ones where the adjacent cell is outside limits)
else
{
//if (allowedEdges.Count==0)
//{
// edgeIndex = Rand.Int(currentCell.edges.Count, false);
//}
//else
//{
edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server);
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
//}
}
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
currentCell.CellType = CellType.Path;
pathCells.Add(currentCell);
iterationsLeft--;
if (currentCell == targetCells[currentTargetIndex])
{
currentTargetIndex += 1;
if (currentTargetIndex >= targetCells.Count) break;
}
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
return pathCells;
}
public static List<Body> GeneratePolygons(List<VoronoiCell> cells, out List<Vector2[]> renderTriangles, bool setSolid=true)
{
renderTriangles = null;
var bodies = new List<Body>();
List<Vector2> tempVertices = new List<Vector2>();
List<Vector2> bodyPoints = new List<Vector2>();
for (int n = cells.Count - 1; n >= 0; n-- )
{
VoronoiCell cell = cells[n];
bodyPoints.Clear();
tempVertices.Clear();
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 (tempVertices.Count < 3 || bodyPoints.Count < 2)
{
cells.RemoveAt(n);
continue;
}
renderTriangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
if (bodyPoints.Count < 2) continue;
if (bodyPoints.Count < 3)
{
foreach (Vector2 vertex in tempVertices)
{
if (bodyPoints.Contains(vertex)) continue;
bodyPoints.Add(vertex);
break;
}
}
for (int i = 0; i < bodyPoints.Count; i++)
{
cell.bodyVertices.Add(bodyPoints[i]);
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
}
if (cell.CellType == CellType.Empty) continue;
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
Body cellBody = new Body(GameMain.World);
for (int i = 0; i < triangles.Count; i++)
{
//don't create a triangle if any of the vertices are too close to each other
//(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties)
if (Vector2.Distance(triangles[i][0], triangles[i][1]) < 0.05f ||
Vector2.Distance(triangles[i][0], triangles[i][2]) < 0.05f ||
Vector2.Distance(triangles[i][1], triangles[i][2]) < 0.05f) continue;
Vertices bodyVertices = new Vertices(triangles[i]);
FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody);
}
cellBody.UserData = cell;
cellBody.SleepingAllowed = false;
cellBody.BodyType = BodyType.Kinematic;
cellBody.CollisionCategories = Physics.CollisionLevel;
cell.body = cellBody;
bodies.Add(cellBody);
}
return bodies;
}
/// <summary>
/// find the index of the cell which the point is inside
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
/// </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;
VoronoiCell closestCell = null;
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
position -= gridOffset;
int gridPosX = (int)Math.Floor(position.X / gridCellSize);
int gridPosY = (int)Math.Floor(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++)
{
float dist = Vector2.Distance(cellGrid[x, y][i].Center, position);
if (closestDist != 0.0f && dist > closestDist) continue;
closestDist = dist;
closestCell = cellGrid[x, y][i];
}
}
}
return cells.IndexOf(closestCell);
}
}
}
@@ -0,0 +1,922 @@
using FarseerPhysics;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using Voronoi2;
using Barotrauma.RuinGeneration;
namespace Barotrauma
{
partial class Level
{
public const float ShaftHeight = 1000.0f;
public static Level Loaded
{
get { return loaded; }
}
[Flags]
public enum PositionType
{
MainPath=1, Cave=2, Ruin=4
}
struct InterestingPosition
{
public readonly Vector2 Position;
public readonly PositionType PositionType;
public InterestingPosition(Vector2 position, PositionType positionType)
{
Position = position;
PositionType = positionType;
}
}
static Level loaded;
//how close the sub has to be to start/endposition to exit
public const float ExitDistance = 6000.0f;
private string seed;
public const int GridCellSize = 2000;
private List<VoronoiCell>[,] cellGrid;
//private WrappingWall[,] wrappingWalls;
//private float shaftHeight;
//List<Body> bodies;
private List<VoronoiCell> cells;
//private VertexBuffer vertexBuffer;
private Vector2 startPosition, endPosition;
private Rectangle borders;
private List<Body> bodies;
private List<InterestingPosition> positionsOfInterest;
private List<Ruin> ruins;
private Color backgroundColor;
private LevelGenerationParams generationParams;
public Vector2 StartPosition
{
get { return startPosition; }
}
public Vector2 Size
{
get { return new Vector2(borders.Width, borders.Height); }
}
public Vector2 EndPosition
{
get { return endPosition; }
}
public List<Ruin> Ruins
{
get { return ruins; }
}
//public WrappingWall[,] WrappingWalls
//{
// get { return wrappingWalls; }
//}
public string Seed
{
get { return seed; }
}
public float Difficulty
{
get;
private set;
}
public Body ShaftBody
{
get;
private set;
}
public LevelGenerationParams GenerationParams
{
get { return generationParams; }
}
public Color BackgroundColor
{
get { return backgroundColor; }
}
public Level(string seed, float difficulty, LevelGenerationParams generationParams)
{
this.seed = seed;
this.Difficulty = difficulty;
this.generationParams = generationParams;
borders = new Rectangle(0, 0, (int)generationParams.Width, (int)generationParams.Height);
}
public static Level CreateRandom(LocationConnection locationConnection)
{
string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
return new Level(seed, locationConnection.Difficulty, LevelGenerationParams.GetRandom(seed));
}
public static Level CreateRandom(string seed = "")
{
if (seed == "")
{
seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
}
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
return new Level(seed, Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), LevelGenerationParams.GetRandom(seed));
}
public void Generate(bool mirror = false)
{
Stopwatch sw = new Stopwatch();
sw.Start();
if (loaded != null) loaded.Unload();
loaded = this;
positionsOfInterest = new List<InterestingPosition>();
Voronoi voronoi = new Voronoi(1.0);
List<Vector2> sites = new List<Vector2>();
bodies = new List<Body>();
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
#if CLIENT
renderer = new LevelRenderer(this);
backgroundColor = generationParams.BackgroundColor;
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f);
#endif
float minWidth = 6500.0f;
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
}
startPosition = new Vector2(
Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server));
endPosition = new Vector2(
borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server));
List<Vector2> pathNodes = new List<Vector2>();
Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth);
pathBorders.Inflate(-minWidth*2, -minWidth*2);
pathNodes.Add(new Vector2(startPosition.X, borders.Height));
Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange;
for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server);
x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server);
x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server))
{
pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server)));
}
pathNodes.Add(new Vector2(endPosition.X, borders.Height));
if (pathNodes.Count <= 2)
{
pathNodes.Add((startPosition + endPosition) / 2);
}
List<List<Vector2>> smallTunnels = new List<List<Vector2>>();
for (int i = 0; i < generationParams.SmallTunnelCount; i++)
{
var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, Rand.RandSync.Server)];
tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right);
float tunnelLength = Rand.Range(
generationParams.SmallTunnelLengthRange.X,
generationParams.SmallTunnelLengthRange.Y,
Rand.RandSync.Server);
var tunnelNodes = MathUtils.GenerateJaggedLine(
tunnelStartPos,
new Vector2(tunnelStartPos.X, pathBorders.Bottom)+Rand.Vector(tunnelLength, Rand.RandSync.Server),
4, 1000.0f);
List<Vector2> tunnel = new List<Vector2>();
foreach (Vector2[] tunnelNode in tunnelNodes)
{
if (!pathBorders.Contains(tunnelNode[0])) continue;
tunnel.Add(tunnelNode[0]);
}
if (tunnel.Any()) smallTunnels.Add(tunnel);
}
Vector2 siteInterval = generationParams.VoronoiSiteInterval;
Vector2 siteVariance = generationParams.VoronoiSiteVariance;
for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X)
{
for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y)
{
Vector2 site = new Vector2(
x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server),
y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server));
if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length())))
{
//add some more sites around the small tunnels to generate more small voronoi cells
if (x < borders.Width - siteInterval.X) sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f);
if (y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f);
if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f);
}
if (mirror) site.X = borders.Width - site.X;
sites.Add(site);
}
}
Stopwatch sw2 = new Stopwatch();
sw2.Start();
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height);
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
//construct voronoi cells based on the graph edges
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
List<VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize,
new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror);
for (int i = 2; i < mainPath.Count; i += 3)
{
positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath));
}
List<VoronoiCell> pathCells = new List<VoronoiCell>(mainPath);
EnlargeMainPath(pathCells, minWidth);
foreach (InterestingPosition positionOfInterest in positionsOfInterest)
{
WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null);
wayPoint.MoveWithLevel = true;
}
startPosition.X = pathCells[0].Center.X;
foreach (List<Vector2> tunnel in smallTunnels)
{
if (tunnel.Count<2) continue;
//find the cell which the path starts from
int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1);
if (startCellIndex < 0) continue;
//if it wasn't one of the cells in the main path, don't create a tunnel
if (cells[startCellIndex].CellType != CellType.Path) continue;
var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders);
positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave));
if (tunnel.Count > 4) positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave));
pathCells.AddRange(newPathCells);
}
Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
cells = CleanCells(pathCells);
pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle(
(int)(borders.Width * 0.2f), 0,
(int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f))));
foreach (VoronoiCell cell in cells)
{
if (cell.Center.Y < borders.Height / 2) continue;
cell.edges.ForEach(e => e.OutsideLevel = true);
}
foreach (VoronoiCell cell in pathCells)
{
cell.edges.ForEach(e => e.OutsideLevel = false);
cell.CellType = CellType.Path;
cells.Remove(cell);
}
//generate some narrow caves
int caveAmount = 0;// Rand.Int(3, false);
List<VoronoiCell> usedCaveCells = new List<VoronoiCell>();
for (int i = 0; i < caveAmount; i++)
{
Vector2 startPoint = Vector2.Zero;
VoronoiCell startCell = null;
var caveCells = new List<VoronoiCell>();
int maxTries = 5, tries = 0;
while (tries<maxTries)
{
startCell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)];
//find an edge between the cell and the already carved path
GraphEdge startEdge =
startCell.edges.Find(e => pathCells.Contains(e.AdjacentCell(startCell)));
if (startEdge != null)
{
startPoint = (startEdge.point1 + startEdge.point2) / 2.0f;
startPoint += startPoint - startCell.Center;
//get the cells in which the cave will be carved
caveCells = GetCells(startCell.Center, 2);
//remove cells that have already been "carved" out
caveCells.RemoveAll(c => c.CellType == CellType.Path);
//if any of the cells have already been used as a cave, continue and find some other cells
if (usedCaveCells.Any(c => caveCells.Contains(c))) continue;
break;
}
tries++;
}
//couldn't find a place for a cave -> abort
if (tries >= maxTries) break;
if (!caveCells.Any()) continue;
usedCaveCells.AddRange(caveCells);
List<VoronoiCell> caveSolidCells;
var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells);
//remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones)
caveCells.ForEach(c => cells.Remove(c));
cells.AddRange(caveSolidCells);
foreach (VoronoiCell cell in cavePathCells)
{
cells.Remove(cell);
}
pathCells.AddRange(cavePathCells);
for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10)
{
positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave));
}
}
for (int x = 0; x < cellGrid.GetLength(0); x++)
{
for (int y = 0; y < cellGrid.GetLength(1); y++)
{
cellGrid[x, y].Clear();
}
}
foreach (VoronoiCell cell in cells)
{
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) continue;
cellGrid[x, y].Add(cell);
}
ruins = new List<Ruin>();
for (int i = 0; i<generationParams.RuinCount; i++)
{
GenerateRuin(mainPath);
}
startPosition.Y = borders.Height;
endPosition.Y = borders.Height;
List<VoronoiCell> cellsWithBody = new List<VoronoiCell>(cells);
List<Vector2[]> triangles;
bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles);
#if CLIENT
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
renderer.SetBodyVertices(bodyVertices.ToArray());
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells));
renderer.PlaceSprites(generationParams.BackgroundSpriteAmount);
#endif
ShaftBody = BodyFactory.CreateEdge(GameMain.World,
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
ShaftBody.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f);
ShaftBody.BodyType = BodyType.Static;
ShaftBody.CollisionCategories = Physics.CollisionLevel;
bodies.Add(ShaftBody);
foreach (VoronoiCell cell in cells)
{
foreach (GraphEdge edge in cell.edges)
{
edge.cell1 = null;
edge.cell2 = null;
edge.site1 = null;
edge.site2 = null;
}
}
//initialize MapEntities that aren't in any sub (e.g. items inside ruins)
MapEntity.MapLoaded(null);
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
if (mirror)
{
Vector2 temp = startPosition;
startPosition = endPosition;
endPosition = temp;
}
Debug.WriteLine("**********************************************************************************");
Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
Debug.WriteLine("Seed: "+seed);
Debug.WriteLine("**********************************************************************************");
}
private List<VoronoiCell> CreateBottomHoles(float holeProbability, Rectangle limits)
{
List<VoronoiCell> toBeRemoved = new List<VoronoiCell>();
foreach (VoronoiCell cell in cells)
{
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > holeProbability) continue;
if (!limits.Contains(cell.Center)) continue;
float closestDist = 0.0f;
WayPoint closestWayPoint = null;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.SpawnType != SpawnType.Path) continue;
float dist =Math.Abs(cell.Center.X - wp.WorldPosition.X);
if (closestWayPoint == null || dist < closestDist)
{
closestDist = dist;
closestWayPoint = wp;
}
}
if (closestWayPoint.WorldPosition.Y < cell.Center.Y) continue;
toBeRemoved.Add(cell);
}
return toBeRemoved;
}
private void EnlargeMainPath(List<VoronoiCell> pathCells, float minWidth)
{
List<WayPoint> wayPoints = new List<WayPoint>();
var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, borders.Height, 10, 10), null);
newWaypoint.MoveWithLevel = true;
wayPoints.Add(newWaypoint);
//WayPoint prevWaypoint = newWaypoint;
for (int i = 0; i < pathCells.Count; i++)
{
////clean "loops" from the path
//for (int n = 0; n < i; n++)
//{
// if (pathCells[n] != pathCells[i]) continue;
// pathCells.RemoveRange(n + 1, i - n);
// break;
//}
//if (i >= pathCells.Count) break;
pathCells[i].CellType = CellType.Path;
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10), null);
newWaypoint.MoveWithLevel = true;
wayPoints.Add(newWaypoint);
wayPoints[wayPoints.Count-2].linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
for (int n = 0; n < wayPoints.Count; n++)
{
if (wayPoints[n].Position != newWaypoint.Position) continue;
wayPoints[n].linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(wayPoints[n]);
break;
}
//prevWaypoint = newWaypoint;
}
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, borders.Height, 10, 10), null);
newWaypoint.MoveWithLevel = true;
wayPoints.Add(newWaypoint);
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
if (minWidth > 0.0f)
{
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
foreach (VoronoiCell removedCell in removedCells)
{
if (removedCell.CellType == CellType.Path) continue;
pathCells.Add(removedCell);
removedCell.CellType = CellType.Path;
}
}
}
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
{
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
Vector2 position = emptyCells[0].Center;
if (minDistance == 0.0f) return tooCloseCells;
float step = 100.0f;
int targetCellIndex = 1;
minDistance *= 0.5f;
do
{
tooCloseCells.AddRange(GetTooCloseCells(position, minDistance));
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);
return tooCloseCells;
}
private List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
{
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
var closeCells = GetCells(position, 3);
foreach (VoronoiCell cell in closeCells)
{
bool tooClose = false;
foreach (GraphEdge edge in cell.edges)
{
if (Vector2.Distance(edge.point1, position) < minDistance ||
Vector2.Distance(edge.point2, position) < minDistance)
{
tooClose = true;
break;
}
}
if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell);
}
return tooCloseCells;
}
/// <summary>
/// remove all cells except those that are adjacent to the empty cells
/// </summary>
private List<VoronoiCell> CleanCells(List<VoronoiCell> emptyCells)
{
List<VoronoiCell> newCells = new List<VoronoiCell>();
foreach (VoronoiCell cell in emptyCells)
{
foreach (GraphEdge edge in cell.edges)
{
VoronoiCell adjacent = edge.AdjacentCell(cell);
if (adjacent != null && !newCells.Contains(adjacent))
{
newCells.Add(adjacent);
}
}
}
return newCells;
}
private void GenerateRuin(List<VoronoiCell> mainPath)
{
Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server));
float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f;
Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].Center;
int iter = 0;
ruinPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y/2, ruinPos.Y);
while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f))
{
Vector2 weighedPathPos = ruinPos;
iter++;
foreach (VoronoiCell pathCell in mainPath)
{
float dist = Vector2.Distance(pathCell.Center, ruinPos);
if (dist > 10000.0f) continue;
Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist;
//if (weighedPathPos.Y + moveAmount.Y > borders.Bottom - ruinSize.X)
//{
// moveAmount.X = (Math.Abs(moveAmount.Y) + Math.Abs(moveAmount.X))*Math.Sign(moveAmount.X);
// moveAmount.Y = 0.0f;
//}
weighedPathPos += moveAmount;
weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y);
}
ruinPos = weighedPathPos;
if (iter > 10000) break;
}
VoronoiCell closestPathCell = null;
float closestDist = 0.0f;
foreach (VoronoiCell pathCell in mainPath)
{
float dist = Vector2.Distance(pathCell.Center, ruinPos);
if (closestPathCell == null || dist < closestDist)
{
closestPathCell = pathCell;
closestDist = dist;
}
}
var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize)));
ruins.Add(ruin);
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
for (int i = 0; i < 4; i++)
{
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin));
}
foreach (RuinShape ruinShape in ruin.RuinShapes)
{
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height));
foreach (VoronoiCell cell in tooClose)
{
if (cell.CellType == CellType.Empty) continue;
foreach (GraphEdge e in cell.edges)
{
Rectangle rect = ruinShape.Rect;
rect.Y += rect.Height;
if (MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null)
{
cell.CellType = CellType.Removed;
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
cellGrid[x, y].Remove(cell);
cells.Remove(cell);
break;
}
}
}
}
}
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float offsetFromWall = 10.0f)
{
if (!positionsOfInterest.Any()) return Size*0.5f;
Vector2 position = Vector2.Zero;
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
int tries = 0;
do
{
Vector2 startPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true);
startPos += Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.Server), Rand.RandSync.Server);
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(startPos),
ConvertUnits.ToSimUnits(endPos),
null, Physics.CollisionLevel) != null)
{
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos)*offsetFromWall;
break;
}
tries++;
if (tries == 10)
{
position = EndPosition - Vector2.UnitY * 300.0f;
}
} while (tries < 10);
return position;
}
public Vector2 GetRandomInterestingPosition(bool useSyncedRand, PositionType positionType, bool avoidSubs)
{
if (!positionsOfInterest.Any()) return Size * 0.5f;
var matchingPositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
if (avoidSubs)
{
foreach (Submarine sub in Submarine.Loaded)
{
float minDist = Math.Max(sub.Borders.Width, sub.Borders.Height);
matchingPositions.RemoveAll(p => Vector2.Distance(p.Position, sub.WorldPosition) < minDist);
}
}
if (!matchingPositions.Any())
{
return positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
}
return matchingPositions[Rand.Int(matchingPositions.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
}
public void Update(float deltaTime)
{
/*
if (Submarine.MainSub != null)
{
WrappingWall.UpdateWallShift(Submarine.MainSub.WorldPosition, wrappingWalls);
}*/
#if CLIENT
if (Hull.renderer != null)
{
Hull.renderer.ScrollWater((float)deltaTime);
}
renderer.Update(deltaTime);
#endif
}
public List<VoronoiCell> GetCells(Vector2 pos, int searchDepth = 2)
{
int gridPosX = (int)Math.Floor(pos.X / GridCellSize);
int gridPosY = (int)Math.Floor(pos.Y / GridCellSize);
int startX = Math.Max(gridPosX - searchDepth, 0);
int endX = Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1);
int startY = Math.Max(gridPosY - searchDepth, 0);
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
List<VoronoiCell> cells = new List<VoronoiCell>();
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
foreach (VoronoiCell cell in cellGrid[x, y])
{
cells.Add(cell);
}
}
}
/*
if (wrappingWalls == null) return cells;
for (int side = 0; side < 2; side++)
{
for (int n = 0; n < 2; n++)
{
if (wrappingWalls[side, n] == null) continue;
if (Vector2.Distance(wrappingWalls[side, n].MidPos, pos) > WrappingWall.WallWidth) continue;
foreach (VoronoiCell cell in wrappingWalls[side, n].Cells)
{
cells.Add(cell);
}
}
}*/
return cells;
}
private void Unload()
{
#if CLIENT
if (renderer!=null)
{
renderer.Dispose();
renderer = null;
}
#endif
if (ruins != null)
{
ruins.Clear();
ruins = null;
}
/*
if (wrappingWalls!=null)
{
for (int side = 0; side < 2; side++)
{
for (int i = 0; i < 2; i++)
{
if (wrappingWalls[side, i] != null) wrappingWalls[side, i].Dispose();
}
}
wrappingWalls = null;
}*/
cells = null;
if (bodies != null)
{
bodies.Clear();
bodies = null;
}
loaded = null;
}
}
}
@@ -0,0 +1,189 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
class LevelGenerationParams : IPropertyObject
{
private static List<LevelGenerationParams> presets;
public string Name
{
get;
private set;
}
private float width, height;
private Vector2 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;
//how far apart the nodes of the main path can be
//x = min interval, y = max interval
private Vector2 mainPathNodeIntervalRange;
private int smallTunnelCount;
//x = min length, y = max length
private Vector2 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)
//if 1.0f, the bottom will be completely open
private float bottomHoleProbability;
private int ruinCount;
public Color BackgroundColor
{
get;
set;
}
[HasDefaultValue(1000, false)]
public int BackgroundSpriteAmount
{
get;
set;
}
public Dictionary<string, ObjectProperty> ObjectProperties
{
get;
set;
}
[HasDefaultValue(100000.0f, false)]
public float Width
{
get { return width; }
set { width = Math.Max(value, 2000.0f); }
}
[HasDefaultValue(50000.0f, false)]
public float Height
{
get { return height; }
set { height = Math.Max(value, 2000.0f); }
}
public Vector2 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);
}
}
public Vector2 VoronoiSiteVariance
{
get { return voronoiSiteVariance; }
set
{
voronoiSiteVariance = new Vector2(
MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
}
}
public Vector2 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);
}
}
[HasDefaultValue(5, false)]
public int SmallTunnelCount
{
get { return smallTunnelCount; }
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
}
public Vector2 SmallTunnelLengthRange
{
get { return smallTunnelLengthRange; }
set
{
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100.0f, width);
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, width);
}
}
[HasDefaultValue(1, false)]
public int RuinCount
{
get { return ruinCount; }
set { ruinCount = MathHelper.Clamp(value, 0, 10); }
}
[HasDefaultValue(0.4f, false)]
public float BottomHoleProbability
{
get { return bottomHoleProbability; }
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
public static LevelGenerationParams GetRandom(string seed)
{
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
if (presets == null || !presets.Any())
{
DebugConsole.ThrowError("Level generation presets not found - using default presets");
return new LevelGenerationParams(null);
}
return presets[Rand.Range(0, presets.Count, Rand.RandSync.Server)];
}
private LevelGenerationParams(XElement element)
{
Name = element==null ? "default" : element.Name.ToString();
ObjectProperties = ObjectProperty.InitProperties(this, element);
Vector3 colorVector = ToolBox.GetAttributeVector3(element, "BackgroundColor", new Vector3(50, 46, 20));
BackgroundColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z);
VoronoiSiteInterval = ToolBox.GetAttributeVector2(element, "VoronoiSiteInterval", new Vector2(3000, 3000));
VoronoiSiteVariance = ToolBox.GetAttributeVector2(element, "VoronoiSiteVariance", new Vector2(voronoiSiteInterval.X, voronoiSiteInterval.Y) * 0.4f);
MainPathNodeIntervalRange = ToolBox.GetAttributeVector2(element, "MainPathNodeIntervalRange", new Vector2(5000.0f, 10000.0f));
SmallTunnelLengthRange = ToolBox.GetAttributeVector2(element, "SmallTunnelLengthRange", new Vector2(5000.0f, 10000.0f));
}
public static void LoadPresets()
{
presets = new List<LevelGenerationParams>();
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.LevelGenerationParameters);
if (!files.Any())
{
files.Add("Content/Map/LevelGenerationParameters.xml");
}
foreach (string file in files)
{
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null || doc.Root == null) return;
foreach (XElement element in doc.Root.Elements())
{
presets.Add(new LevelGenerationParams(element));
}
}
}
}
}
@@ -0,0 +1,161 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.RuinGeneration
{
/// <summary>
/// nodes of a binary tree used for generating underwater "dungeons"
/// </summary>
class BTRoom : RuinShape
{
private BTRoom[] subRooms;
public BTRoom Parent
{
get;
private set;
}
public Corridor Corridor
{
get;
set;
}
public BTRoom[] SubRooms
{
get { return subRooms; }
}
public BTRoom Adjacent
{
get;
private set;
}
public BTRoom(Rectangle rect)
{
this.rect = rect;
}
public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200)
{
subRooms = new BTRoom[2];
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < verticalProbability &&
rect.Width * minDivRatio >= minWidth)
{
SplitVertical(minDivRatio);
}
else
{
SplitHorizontal(minDivRatio);
}
subRooms[0].Parent = this;
subRooms[1].Parent = this;
subRooms[0].Adjacent = subRooms[1];
subRooms[1].Adjacent = subRooms[0];
}
private void SplitHorizontal(float minDivRatio)
{
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div)));
subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height));
}
private void SplitVertical(float minDivRatio)
{
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height));
subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height));
}
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));
}
public void Scale(Vector2 scale)
{
rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height);
}
public List<BTRoom> GetLeaves()
{
return GetLeaves(new List<BTRoom>());
}
private List<BTRoom> GetLeaves(List<BTRoom> leaves)
{
if (subRooms == null)
{
leaves.Add(this);
}
else
{
subRooms[0].GetLeaves(leaves);
subRooms[1].GetLeaves(leaves);
}
return leaves;
}
public void GenerateCorridors(int minWidth, int maxWidth, List<Corridor> corridors)
{
if (Adjacent != null && Corridor == null)
{
Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, Rand.RandSync.Server), corridors);
}
if (subRooms != null)
{
subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors);
subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors);
}
}
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<Corridor> corridors)
{
entrance.CalculateDistanceFromEntrance(1, new List<Corridor>(corridors));
}
private void CalculateDistanceFromEntrance(int currentDist, List<Corridor> corridors)
{
if (DistanceFromEntrance == 0)
{
DistanceFromEntrance = currentDist;
}
else
{
DistanceFromEntrance = Math.Min(currentDist, DistanceFromEntrance);
}
currentDist++;
for (int i = corridors.Count - 1; i >= 0; i = Math.Min(i - 1, corridors.Count - 1))
{
var corridor = corridors[i];
if (!corridor.ConnectedRooms.Contains(this)) continue;
corridors.RemoveAt(i);
corridor.ConnectedRooms[corridor.ConnectedRooms[0] == this ? 1 : 0].CalculateDistanceFromEntrance(currentDist, corridors);
}
}
}
}
@@ -0,0 +1,189 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.RuinGeneration
{
class Corridor : RuinShape
{
private bool isHorizontal;
public Rectangle Rect
{
get { return rect; }
}
public bool IsHorizontal
{
get { return isHorizontal; }
}
public BTRoom[] ConnectedRooms
{
get;
private set;
}
public Corridor(Rectangle rect)
{
this.rect = rect;
isHorizontal = rect.Width > rect.Height;
}
public Corridor(BTRoom room, int width, List<Corridor> corridors)
{
System.Diagnostics.Debug.Assert(room.Adjacent != null);
ConnectedRooms = new BTRoom[2];
ConnectedRooms[0] = room;
ConnectedRooms[1] = room.Adjacent;
Rectangle room1, room2;
room1 = room.Rect;
room2 = room.Adjacent.Rect;
isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X);
//use the leaves as starting points for the corridor
if (room.SubRooms != null)
{
var leaves1 = room.GetLeaves();
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);
}
else
{
DebugConsole.ThrowError("wat");
}
room.Corridor = this;
room.Adjacent.Corridor = this;
for (int i = corridors.Count - 1; i >= 0; i--)
{
var corridor = corridors[i];
if (corridor.rect.Intersects(this.rect))
{
if (isHorizontal && corridor.isHorizontal)
{
if (this.rect.Width < corridor.rect.Width)
return;
else
corridors.RemoveAt(i);
}
else if (!isHorizontal && !corridor.isHorizontal)
{
if (this.rect.Height < corridor.rect.Height)
return;
else
corridors.RemoveAt(i);
}
}
}
corridors.Add(this);
}
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));
}
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));
}
}
/// <summary>
/// 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)
{
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;
for (int jCount = 0; jCount < leaves2.Count; jCount++)
{
int j = (jCount + jOffset) % leaves2.Count;
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;
}
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;
}
return new BTRoom[] { leaves1[i], leaves2[j] };
}
}
return null;
}
}
}
@@ -0,0 +1,453 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Voronoi2;
namespace Barotrauma.RuinGeneration
{
abstract class RuinShape
{
protected Rectangle rect;
public Rectangle Rect
{
get { return rect; }
}
public int DistanceFromEntrance
{
get;
protected set;
}
public Vector2 Center
{
get { return rect.Center.ToVector2(); }
}
public List<Line> Walls;
public virtual void CreateWalls() { }
public Alignment GetLineAlignment(Line line)
{
if (line.A.Y == line.B.Y)
{
if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y)
{
return Alignment.Bottom;
}
else if (line.A.Y < rect.Center.Y && line.B.Y < rect.Center.Y)
{
return Alignment.Top;
}
}
else
{
if (line.A.X < rect.Center.X && line.B.X < rect.Center.X)
{
return Alignment.Left;
}
else if (line.A.X > rect.Center.X && line.B.X > rect.Center.X)
{
return Alignment.Right;
}
}
return Alignment.Center;
}
/// <summary>
/// Goes through a list of line segments and "clips off" all parts of the lines that are inside the rectangle
/// </summary>
public void SplitLines(Rectangle rectangle)
{
List<Line> newLines = new List<Line>();
foreach (Line line in Walls)
{
if (line.A.X == line.B.X) //vertical line
{
//line doesn't intersect the rectangle
if (rectangle.X > line.A.X || rectangle.Right < line.A.X ||
rectangle.Y > line.B.Y || rectangle.Bottom < line.A.Y)
{
newLines.Add(line);
}
else if (line.A.Y > rectangle.Y && line.B.Y < rectangle.Bottom)
{
continue;
}
//point A is within the rectangle -> cut a portion from the top of the line
else if (line.A.Y >= rectangle.Y && line.A.Y <= rectangle.Bottom)
{
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
}
//point B is within the rectangle -> cut a portion from the bottom of the line
else if (line.B.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom)
{
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
}
//rect is in between the lines -> split the line into two
else
{
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
}
}
else if (line.A.Y == line.B.Y) //horizontal line
{
//line doesn't intersect the rectangle
if (rectangle.X > line.B.X || rectangle.Right < line.A.X ||
rectangle.Y > line.A.Y || rectangle.Bottom < line.A.Y)
{
newLines.Add(line);
}
else if (line.A.X > rectangle.X && line.B.X < rectangle.Right)
{
continue;
}
//point A is within the rectangle -> cut a portion from the left side of the line
else if (line.A.X >= rectangle.X && line.A.X <= rectangle.Right)
{
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
}
//point B is within the rectangle -> cut a portion from the right side of the line
else if (line.B.X >= rectangle.X && line.B.X <= rectangle.Right)
{
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
}
//rect is in between the lines -> split the line into two
else
{
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
}
}
else
{
DebugConsole.ThrowError("Error in StructureGenerator.SplitLines - lines must be axis aligned");
}
}
Walls = newLines;
}
}
struct Line
{
public readonly Vector2 A, B;
public readonly RuinStructureType Type;
public Line(Vector2 a, Vector2 b, RuinStructureType type)
{
Debug.Assert(a.X <= b.X);
Debug.Assert(a.Y <= b.Y);
A = a;
B = b;
Type = type;
}
}
partial class Ruin
{
private List<BTRoom> rooms;
private List<Corridor> corridors;
private List<Line> walls;
private List<RuinShape> allShapes;
public List<RuinShape> RuinShapes
{
get { return allShapes; }
}
public List<Line> Walls
{
get { return walls; }
}
public Rectangle Area
{
get;
private set;
}
public Ruin(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
{
Area = area;
corridors = new List<Corridor>();
rooms = new List<BTRoom>();
walls = new List<Line>();
allShapes = new List<RuinShape>();
Generate(closestPathCell, caveCells, area);
}
public void Generate(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
{
corridors.Clear();
rooms.Clear();
//area = new Rectangle(area.X, area.Y - area.Height, area.Width, area.Height);
int iterations = Rand.Range(3, 4, Rand.RandSync.Server);
float verticalProbability = Rand.Range(0.4f, 0.6f, Rand.RandSync.Server);
BTRoom baseRoom = new BTRoom(area);
rooms = new List<BTRoom> { baseRoom };
for (int i = 0; i < iterations; i++)
{
rooms.ForEach(l => l.Split(0.3f, verticalProbability, 300));
rooms = baseRoom.GetLeaves();
}
foreach (BTRoom leaf in rooms)
{
leaf.Scale
(
new Vector2(Rand.Range(0.5f, 0.9f, Rand.RandSync.Server), Rand.Range(0.5f, 0.9f, Rand.RandSync.Server))
);
}
baseRoom.GenerateCorridors(200, 256, corridors);
walls = new List<Line>();
rooms.ForEach(leaf =>
{
leaf.CreateWalls();
//walls.AddRange(leaf.Walls);
});
//---------------------------
BTRoom entranceRoom = null;
float shortestDistance = 0.0f;
foreach (BTRoom leaf in rooms)
{
float distance = Vector2.Distance(leaf.Rect.Center.ToVector2(), closestPathCell.Center);
if (entranceRoom == null || distance < shortestDistance)
{
entranceRoom = leaf;
shortestDistance = distance;
}
}
rooms.Remove(entranceRoom);
//---------------------------
foreach (BTRoom leaf in rooms)
{
foreach (Corridor corridor in corridors)
{
leaf.SplitLines(corridor.Rect);
}
walls.AddRange(leaf.Walls);
}
foreach (Corridor corridor in corridors)
{
corridor.CreateWalls();
foreach (BTRoom leaf in rooms)
{
corridor.SplitLines(leaf.Rect);
}
foreach (Corridor corridor2 in corridors)
{
if (corridor == corridor2) continue;
corridor.SplitLines(corridor2.Rect);
}
walls.AddRange(corridor.Walls);
}
//leaves.Remove(entranceRoom);
BTRoom.CalculateDistancesFromEntrance(entranceRoom, corridors);
allShapes = GenerateStructures(caveCells);
}
private List<RuinShape> GenerateStructures(List<VoronoiCell> caveCells)
{
List<RuinShape> shapes = new List<RuinShape>(rooms);
shapes.AddRange(corridors);
foreach (RuinShape leaf in shapes)
{
RuinStructureType wallType = RuinStructureType.Wall;
if (!(leaf is BTRoom))
{
wallType = RuinStructureType.CorridorWall;
}
//rooms further from the entrance are more likely to have hard-to-break walls
else if (Rand.Range(0.0f, leaf.DistanceFromEntrance, Rand.RandSync.Server) > 1.5f)
{
wallType = RuinStructureType.HeavyWall;
}
//generate walls --------------------------------------------------------------
foreach (Line wall in leaf.Walls)
{
var structurePrefab = RuinStructure.GetRandom(wallType, leaf.GetLineAlignment(wall));
if (structurePrefab == null) continue;
float radius = (wall.A.X == wall.B.X) ?
(structurePrefab.Prefab as StructurePrefab).Size.X * 0.5f :
(structurePrefab.Prefab as StructurePrefab).Size.Y * 0.5f;
Rectangle rect = new Rectangle(
(int)(wall.A.X - radius),
(int)(wall.B.Y + radius),
(int)((wall.B.X - wall.A.X) + radius*2.0f),
(int)((wall.B.Y - wall.A.Y) + radius*2.0f));
//cut a section off from both ends of a horizontal wall to get nicer looking corners
if (wall.A.Y == wall.B.Y)
{
rect.Inflate(-32, 0);
if (rect.Width < Submarine.GridSize.X) continue;
}
var structure = new Structure(rect, structurePrefab.Prefab as StructurePrefab, null);
structure.MoveWithLevel = true;
structure.SetCollisionCategory(Physics.CollisionLevel);
}
//generate backgrounds --------------------------------------------------------------
var background = RuinStructure.GetRandom(RuinStructureType.Back, Alignment.Center);
if (background == null) continue;
Rectangle backgroundRect = new Rectangle(leaf.Rect.X, leaf.Rect.Y + leaf.Rect.Height, leaf.Rect.Width, leaf.Rect.Height);
new Structure(backgroundRect, (background.Prefab as StructurePrefab), null).MoveWithLevel = true;
}
//generate props --------------------------------------------------------------
for (int i = 0; i < shapes.Count*2; i++ )
{
Alignment[] alignments = new Alignment[] { Alignment.Top, Alignment.Bottom, Alignment.Right, Alignment.Left, Alignment.Center };
var prop = RuinStructure.GetRandom(RuinStructureType.Prop, alignments[Rand.Int(alignments.Length, Rand.RandSync.Server)]);
Vector2 size = (prop.Prefab is StructurePrefab) ? ((StructurePrefab)prop.Prefab).Size : Vector2.Zero;
var shape = shapes[Rand.Int(shapes.Count, Rand.RandSync.Server)];
Vector2 position = shape.Rect.Center.ToVector2();
if (prop.Alignment.HasFlag(Alignment.Top))
{
position = new Vector2(Rand.Range(shape.Rect.X+size.X, shape.Rect.Right - size.X, Rand.RandSync.Server), shape.Rect.Bottom - 64);
}
else if (prop.Alignment.HasFlag(Alignment.Bottom))
{
position = new Vector2(Rand.Range(shape.Rect.X + size.X, shape.Rect.Right - size.X, Rand.RandSync.Server), shape.Rect.Top + 64);
}
else if (prop.Alignment.HasFlag(Alignment.Right))
{
position = new Vector2(shape.Rect.Right - 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, Rand.RandSync.Server));
}
else if (prop.Alignment.HasFlag(Alignment.Left))
{
position = new Vector2(shape.Rect.X + 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, Rand.RandSync.Server));
}
if (prop.Prefab is ItemPrefab)
{
var item = new Item((ItemPrefab)prop.Prefab, position, null);
item.MoveWithLevel = true;
}
else
{
new Structure(new Rectangle(
(int)(position.X - size.X/2.0f), (int)(position.Y + size.Y/2.0f),
(int)size.X, (int)size.Y),
prop.Prefab as StructurePrefab, null).MoveWithLevel = true;
}
}
//generate doors & sensors that close them -------------------------------------------------------------
var sensorPrefab = ItemPrefab.list.Find(ip => ip.Name == "Alien Motion Sensor") as ItemPrefab;
var wirePrefab = ItemPrefab.list.Find(ip => ip.Name == "Wire") as ItemPrefab;
foreach (Corridor corridor in corridors)
{
var doorPrefab = RuinStructure.GetRandom(corridor.IsHorizontal ? RuinStructureType.Door : RuinStructureType.Hatch, Alignment.Center);
if (doorPrefab == null) continue;
//find all walls that are parallel to the corridor
var suitableWalls = corridor.IsHorizontal ?
corridor.Walls.FindAll(c => c.A.Y == c.B.Y) : corridor.Walls.FindAll(c => c.A.X == c.B.X);
if (!suitableWalls.Any()) continue;
Vector2 doorPos = corridor.Center;
//choose a random wall to place the door next to
var wall = suitableWalls[Rand.Int(suitableWalls.Count, Rand.RandSync.Server)];
if (corridor.IsHorizontal)
{
doorPos.X = (wall.A.X + wall.B.X) / 2.0f;
}
else
{
doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f;
}
var door = new Item(doorPrefab.Prefab as ItemPrefab, doorPos, null);
door.MoveWithLevel = true;
door.GetComponent<Items.Components.Door>().IsOpen = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.8f;
if (sensorPrefab == null || wirePrefab == null) continue;
var sensorRoom = corridor.ConnectedRooms.FirstOrDefault(r => r != null && rooms.Contains(r));
if (sensorRoom == null) continue;
var sensor = new Item(sensorPrefab, new Vector2(
Rand.Range(sensorRoom.Rect.X, sensorRoom.Rect.Right, Rand.RandSync.Server),
Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom, Rand.RandSync.Server)), null);
sensor.MoveWithLevel = true;
var wire = new Item(wirePrefab, sensorRoom.Center, null).GetComponent<Items.Components.Wire>();
wire.Item.MoveWithLevel = false;
var conn1 = door.Connections.Find(c => c.Name == "set_state");
conn1.AddLink(0, wire);
wire.Connect(conn1, false);
var conn2 = sensor.Connections.Find(c => c.Name == "state_out");
conn2.AddLink(0, wire);
wire.Connect(conn2, false);
}
return shapes;
}
}
}
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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
{
const string ConfigFile = "Content/Map/RuinConfig.xml";
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 prefab = ToolBox.GetAttributeString(element, "prefab", "").ToLowerInvariant();
Prefab = MapEntityPrefab.list.Find(s => s.Name.ToLowerInvariant() == prefab);
if (Prefab == null)
{
DebugConsole.ThrowError("Loading ruin structure failed - structure prefab \""+prefab+" not found");
return;
}
string alignmentStr = ToolBox.GetAttributeString(element,"alignment","Bottom");
if (!Enum.TryParse<Alignment>(alignmentStr, true, out Alignment))
{
DebugConsole.ThrowError("Error in ruin structure \""+prefab+"\" - "+alignmentStr+" is not a valid alignment");
}
string typeStr = ToolBox.GetAttributeString(element,"type","");
if (!Enum.TryParse<RuinStructureType>(typeStr,true, out Type))
{
DebugConsole.ThrowError("Error in ruin structure \"" + prefab + "\" - " + typeStr + " is not a valid type");
return;
}
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
list.Add(this);
}
private static void Load()
{
list = new List<RuinStructure>();
XDocument doc = ToolBox.TryLoadXml(ConfigFile);
if (doc == null || doc.Root == null) return;
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;
}
}
}
@@ -0,0 +1,998 @@
/*
* 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
@@ -0,0 +1,259 @@
/*
* 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 FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Barotrauma;
using System;
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 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;
}
}
}
@@ -0,0 +1,178 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Voronoi2;
namespace Barotrauma
{
partial class WrappingWall : IDisposable
{
public const float WallWidth = 20000.0f;
private Vector2 midPos;
private int slot;
private Vector2 offset;
private List<VoronoiCell> cells;
public Vector2 Offset
{
get { return offset; }
}
public List<VoronoiCell> Cells
{
get { return cells; }
}
public Vector2 MidPos
{
get { return midPos; }
}
public WrappingWall(List<VoronoiCell> pathCells, List<VoronoiCell> mapCells, Rectangle ignoredArea, int dir = -1)
{
cells = new List<VoronoiCell>();
VoronoiCell edgeCell = null;
foreach (VoronoiCell cell in mapCells)
{
if (ignoredArea.Contains(cell.Center)) continue;
if (Math.Sign(cell.Center.X - ignoredArea.Center.X) != Math.Sign(dir)) continue;
if (edgeCell == null || cell.Center.Y < edgeCell.Center.Y)
{
edgeCell = cell;
}
}
Vector2 wallSectionSize = new Vector2(2000.0f, 2000.0f);
Vector2 startPos = (dir < 0) ?
edgeCell.Center + Vector2.UnitX * WallWidth * dir :
edgeCell.Center + WallWidth * Vector2.UnitX * (dir - 1);
midPos = startPos + Vector2.UnitX * WallWidth/2;
List<Vector2> bottomVertices = new List<Vector2>();
for (float x = 0; x <= WallWidth; x += wallSectionSize.X)
{
Vector2 center = new Vector2(startPos.X + x, edgeCell.Center.Y);
float distFromCenter = Math.Abs(x - WallWidth / 2);
float distFromEdge = WallWidth / 2 - distFromCenter;
float normalizedDist = distFromEdge / (WallWidth / 2);
float variance = 1000.0f * normalizedDist;
bottomVertices.Add(center + new Vector2(Rand.Range(-variance, variance, Rand.RandSync.Server), Rand.Range(-variance, variance, Rand.RandSync.Server) *2.0f));
}
for (int i = 1; i < bottomVertices.Count; i++)
{
Vector2[] vertices = new Vector2[4];
vertices[0] = bottomVertices[i];
vertices[1] = bottomVertices[i - 1];
vertices[2] = vertices[1] + Vector2.UnitY * wallSectionSize.Y;
vertices[3] = vertices[0] + Vector2.UnitY * wallSectionSize.Y;
VoronoiCell wallCell = new VoronoiCell(vertices);
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[2].isSolid = true;
if (i > 1)
{
wallCell.edges[1].cell2 = cells[i - 2];
cells[i - 2].edges[3].cell2 = wallCell;
}
cells.Add(wallCell);
}
}
public static void UpdateWallShift(Vector2 pos, WrappingWall[,] walls)
{
if (pos.X < walls[0, 1].midPos.X && walls[0,0].midPos.X > pos.X)
{
walls[0, 0].Shift(-2);
var temp = walls[0, 0];
walls[0, 0] = walls[0, 1];
walls[0, 1] = temp;
}
else if (pos.X > walls[0, 0].midPos.X && walls[0,1].midPos.X < pos.X && walls[0,1].slot<0)
{
walls[0, 1].Shift(2);
var temp = walls[0, 0];
walls[0, 0] = walls[0, 1];
walls[0, 1] = temp;
}
else if (pos.X > walls[1, 1].midPos.X && walls[1,0].midPos.X < pos.X)
{
walls[1, 0].Shift(2);
var temp = walls[1, 0];
walls[1, 0] = walls[1, 1];
walls[1, 1] = temp;
}
else if (pos.X < walls[1, 0].midPos.X && walls[1, 1].midPos.X > pos.X && walls[1, 1].slot > 0)
{
walls[1, 1].Shift(-2);
var temp = walls[0, 0];
walls[1, 0] = walls[1, 1];
walls[1, 1] = temp;
}
}
public void Shift(int amount)
{
slot += amount;
Vector2 moveAmount = Vector2.UnitX * WallWidth * amount;
Vector2 simMoveAmount = ConvertUnits.ToSimUnits(moveAmount);
foreach (VoronoiCell cell in cells)
{
cell.body.SetTransform(cell.body.Position + simMoveAmount, 0.0f);
cell.Translation += moveAmount;
}
midPos += moveAmount;
offset += moveAmount;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
#if CLIENT
if (wallVertices != null)
{
wallVertices.Dispose();
wallVertices = null;
}
if (bodyVertices != null)
{
bodyVertices.Dispose();
bodyVertices = null;
}
#endif
}
}
}
@@ -0,0 +1,245 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LinkedSubmarinePrefab : MapEntityPrefab
{
public readonly Submarine mainSub;
public LinkedSubmarinePrefab(Submarine submarine)
{
this.mainSub = submarine;
}
protected override void CreateInstance(Rectangle rect)
{
System.Diagnostics.Debug.Assert(Submarine.MainSub != null);
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2());
}
}
partial class LinkedSubmarine : MapEntity
{
private List<Vector2> wallVertices;
private string filePath;
private bool loadSub;
private Submarine sub;
public Submarine Sub
{
get
{
return sub;
}
}
private XElement saveElement;
public override bool IsLinkable
{
get
{
return true;
}
}
public LinkedSubmarine(Submarine submarine)
: base(null, submarine)
{
linkedTo = new System.Collections.ObjectModel.ObservableCollection<MapEntity>();
linkedToID = new List<ushort>();
InsertToList();
}
public static LinkedSubmarine CreateDummy(Submarine mainSub, Submarine linkedSub)
{
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
sl.sub = linkedSub;
return sl;
}
public static LinkedSubmarine CreateDummy(Submarine mainSub, string filePath, Vector2 position)
{
XDocument doc = Submarine.OpenFile(filePath);
if (doc == null || doc.Root == null) return null;
LinkedSubmarine sl = CreateDummy(mainSub, doc.Root, position);
sl.filePath = filePath;
return sl;
}
public static LinkedSubmarine CreateDummy(Submarine mainSub, XElement element, Vector2 position)
{
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
sl.GenerateWallVertices(element);
sl.Rect = new Rectangle(
(int)sl.wallVertices.Min(v => v.X + position.X),
(int)sl.wallVertices.Max(v => v.Y + position.Y),
(int)sl.wallVertices.Max(v => v.X + position.X),
(int)sl.wallVertices.Min(v => v.Y + position.Y));
sl.rect = new Rectangle((int)position.X, (int)position.Y, 1, 1);
return sl;
}
public override bool IsMouseOn(Vector2 position)
{
return Vector2.Distance(position, WorldPosition) < 50.0f;
}
private void GenerateWallVertices(XElement rootElement)
{
List<Vector2> points = new List<Vector2>();
var wallPrefabs =
MapEntityPrefab.list.FindAll(mp => (mp is StructurePrefab) && ((StructurePrefab)mp).HasBody);
foreach (XElement element in rootElement.Elements())
{
if (element.Name != "Structure") continue;
string name = ToolBox.GetAttributeString(element, "name", "");
if (!wallPrefabs.Any(wp => wp.Name == name)) continue;
var rect = ToolBox.GetAttributeVector4(element, "rect", Vector4.Zero);
points.Add(new Vector2(rect.X, rect.Y));
points.Add(new Vector2(rect.X + rect.Z, rect.Y));
points.Add(new Vector2(rect.X, rect.Y - rect.W));
points.Add(new Vector2(rect.X + rect.Z, rect.Y - rect.W));
}
wallVertices = MathUtils.GiftWrap(points);
}
public static void Load(XElement element, Submarine submarine)
{
Vector2 pos = ToolBox.GetAttributeVector2(element, "pos", Vector2.Zero);
LinkedSubmarine linkedSub = null;
if (Screen.Selected == GameMain.EditMapScreen)
{
//string filePath = ToolBox.GetAttributeString(element, "filepath", "");
linkedSub = CreateDummy(submarine, element, pos);
linkedSub.saveElement = element;
}
else
{
linkedSub = new LinkedSubmarine(submarine);
linkedSub.saveElement = element;
string levelSeed = ToolBox.GetAttributeString(element, "location", "");
if (!string.IsNullOrWhiteSpace(levelSeed) && GameMain.GameSession.Level != null && GameMain.GameSession.Level.Seed != levelSeed)
{
linkedSub.loadSub = false;
return;
}
linkedSub.loadSub = true;
linkedSub.rect.Location = MathUtils.ToPoint(pos);
}
linkedSub.filePath = ToolBox.GetAttributeString(element, "filepath", "");
string linkedToString = ToolBox.GetAttributeString(element, "linkedto", "");
if (linkedToString != "")
{
string[] linkedToIds = linkedToString.Split(',');
for (int i = 0; i < linkedToIds.Length; i++)
{
linkedSub.linkedToID.Add((ushort)int.Parse(linkedToIds[i]));
}
}
}
public override void OnMapLoaded()
{
if (!loadSub) return;
sub = Submarine.Load(saveElement, false);
Vector2 worldPos = ToolBox.GetAttributeVector2(saveElement, "worldpos", Vector2.Zero);
if (worldPos != Vector2.Zero)
{
sub.SetPosition(worldPos);
}
else
{
sub.SetPosition(WorldPosition);
}
DockingPort linkedPort = null;
DockingPort myPort = null;
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);
}
else
{
linkedPort = ((Item)linkedItem).GetComponent<DockingPort>();
}
if (linkedPort == null)
{
return;
}
float closestDistance = 0.0f;
foreach (DockingPort port in DockingPort.list)
{
if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) continue;
float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition);
if (myPort == null || dist < closestDistance)
{
myPort = port;
closestDistance = dist;
}
}
if (myPort != null)
{
Vector2 portDiff = myPort.Item.WorldPosition - sub.WorldPosition;
Vector2 offset = (myPort.IsHorizontal ?
Vector2.UnitX * Math.Sign(linkedPort.Item.WorldPosition.X - myPort.Item.WorldPosition.X) :
Vector2.UnitY * Math.Sign(linkedPort.Item.WorldPosition.Y - myPort.Item.WorldPosition.Y));
offset *= myPort.DockedDistance;
sub.SetPosition(
(linkedPort.Item.WorldPosition - portDiff)
- offset);
myPort.Dock(linkedPort);
myPort.Lock(true);
}
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition);
sub.Submarine = Submarine;
}
}
}
@@ -0,0 +1,64 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
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; }
}
public bool Discovered;
public LocationType Type
{
get { return type; }
}
public Location(Vector2 mapPosition)
{
this.type = LocationType.Random();
this.name = RandomName(type);
this.mapPosition = mapPosition;
#if CLIENT
if (type.HasHireableCharacters)
{
hireManager = new HireManager();
hireManager.GenerateCharacters(this, HireManager.MaxAvailableCharacters);
}
#endif
Connections = new List<LocationConnection>();
}
public static Location CreateRandom(Vector2 position)
{
return new Location(position);
}
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);
}
}
}
@@ -0,0 +1,152 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Linq;
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;
private List<string> nameFormats;
private Sprite symbolSprite;
private Sprite backGround;
//<name, commonness>
private List<Tuple<JobPrefab, float>> hireableJobs;
private float totalHireableWeight;
public string Name
{
get { return name; }
}
public List<string> NameFormats
{
get { return nameFormats; }
}
public bool HasHireableCharacters
{
get { return hireableJobs.Any(); }
}
public Sprite Sprite
{
get { return symbolSprite; }
}
public Sprite Background
{
get { return backGround; }
}
private LocationType(XElement element)
{
name = element.Name.ToString();
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
totalWeight += commonness;
nameFormats = new List<string>();
foreach (XAttribute nameFormat in element.Element("nameformats").Attributes())
{
nameFormats.Add(nameFormat.Value);
}
hireableJobs = new List<Tuple<JobPrefab, float>>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "hireable") continue;
string jobName = ToolBox.GetAttributeString(subElement, "name", "");
JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName.ToLowerInvariant());
if (jobPrefab==null)
{
DebugConsole.ThrowError("Invalid job name ("+jobName+") in location type "+name);
}
float jobCommonness = ToolBox.GetAttributeFloat(subElement, "commonness", 1.0f);
totalHireableWeight += jobCommonness;
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
hireableJobs.Add(hireableJob);
}
string spritePath = ToolBox.GetAttributeString(element, "symbol", "Content/Map/beaconSymbol.png");
symbolSprite = new Sprite(spritePath, new Vector2(0.5f, 0.5f));
string backgroundPath = ToolBox.GetAttributeString(element, "background", "");
backGround = new Sprite(backgroundPath, Vector2.Zero);
}
public JobPrefab GetRandomHireable()
{
float randFloat = Rand.Range(0.0f, totalHireableWeight);
foreach (Tuple<JobPrefab, float> hireable in hireableJobs)
{
if (randFloat < hireable.Item2) return hireable.Item1;
randFloat -= hireable.Item2;
}
return null;
}
public static LocationType Random(string seed = "")
{
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);
foreach (LocationType type in list)
{
if (randInt < type.commonness) return type;
randInt -= type.commonness;
}
return null;
}
public static void Init()
{
var locationTypeFiles = GameMain.SelectedPackage.GetFilesOfType(ContentType.LocationTypes);
foreach (string file in locationTypeFiles)
{
XDocument doc = ToolBox.TryLoadXml(file);
if (doc==null)
{
return;
}
foreach (XElement element in doc.Root.Elements())
{
LocationType locationType = new LocationType(element);
list.Add(locationType);
}
}
}
}
}
@@ -0,0 +1,327 @@
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 Map
{
Vector2 difficultyIncrease = new Vector2(5.0f, 10.0f);
Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f);
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 Location highlightedLocation;
private LocationConnection selectedConnection;
public Location CurrentLocation
{
get { return currentLocation; }
}
public int CurrentLocationIndex
{
get { return locations.IndexOf(currentLocation); }
}
public Location SelectedLocation
{
get { return selectedLocation; }
}
public LocationConnection SelectedConnection
{
get { return selectedConnection; }
}
public string Seed
{
get { return seed; }
}
public static Map Load(XElement element)
{
string mapSeed = ToolBox.GetAttributeString(element, "seed", "a");
int size = ToolBox.GetAttributeInt(element, "size", 500);
Map map = new Map(mapSeed, size);
map.SetLocation(ToolBox.GetAttributeInt(element, "currentlocation", 0));
string discoveredStr = ToolBox.GetAttributeString(element, "discovered", "");
string[] discoveredStrs = discoveredStr.Split(',');
for (int i = 0; i < discoveredStrs.Length; i++)
{
int index = -1;
if (int.TryParse(discoveredStrs[i], out index)) map.locations[index].Discovered = true;
}
string passedStr = ToolBox.GetAttributeString(element, "passed", "");
string[] passedStrs = passedStr.Split(',');
for (int i = 0; i < passedStrs.Length; i++)
{
int index = -1;
if (int.TryParse(passedStrs[i], out index)) map.connections[index].Passed = true;
}
return map;
}
public Map(string seed, int size)
{
this.seed = seed;
this.size = size;
levels = new List<Level>();
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));
GenerateLocations();
currentLocation = locations[locations.Count / 2];
currentLocation.Discovered = true;
GenerateDifficulties(currentLocation, new List<LocationConnection>(connections), 10.0f);
foreach (LocationConnection connection in connections)
{
connection.Level = Level.CreateRandom(connection);
}
}
private void GenerateLocations()
{
Voronoi voronoi = new Voronoi(0.5f);
List<Vector2> sites = new List<Vector2>();
for (int i = 0; i < 50; i++)
{
sites.Add(new Vector2(Rand.Range(0.0f, size, Rand.RandSync.Server), Rand.Range(0.0f, size, Rand.RandSync.Server)));
}
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
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;
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));
for (int i = 0; i < 2; i++)
{
if (newLocations[i] != null) continue;
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 seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode());
connections.Add(new LocationConnection(newLocations[0], newLocations[1]));
}
float minDistance = 50.0f;
for (int i = connections.Count - 1; i >= 0; i--)
{
LocationConnection connection = connections[i];
if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance)
{
continue;
}
locations.Remove(connection.Locations[0]);
connections.Remove(connection);
foreach (LocationConnection connection2 in connections)
{
if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1];
if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1];
}
}
foreach (LocationConnection connection in connections)
{
connection.Locations[0].Connections.Add(connection);
connection.Locations[1].Connections.Add(connection);
}
for (int i = connections.Count - 1; i >= 0; i--)
{
i = Math.Min(i, connections.Count - 1);
LocationConnection connection = connections[i];
for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--)
{
if (connection.Locations.Contains(connections[n].Locations[0])
&& connection.Locations.Contains(connections[n].Locations[1]))
{
connections.RemoveAt(n);
}
}
}
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);
}
}
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;
currentLocation = selectedLocation;
currentLocation.Discovered = true;
selectedLocation = null;
}
public void SetLocation(int index)
{
if (index < 0 || index >= locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
currentLocation = locations[index];
currentLocation.Discovered = true;
}
}
class LocationConnection
{
private Location[] locations;
private Level level;
public float Difficulty;
public List<Vector2[]> CrackSegments;
public bool Passed;
private int missionsCompleted;
private Mission mission;
public Mission Mission
{
get
{
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);
}
return mission;
}
}
public Location[] Locations
{
get { return locations; }
}
public Level Level
{
get { return level; }
set { level = value; }
}
public LocationConnection(Location location1, Location location2)
{
locations = new Location[] { location1, location2 };
missionsCompleted = 0;
}
public Location OtherLocation(Location location)
{
if (locations[0] == location)
{
return locations[1];
}
else if (locations[1] == location)
{
return locations[0];
}
else
{
return null;
}
}
}
}
@@ -0,0 +1,390 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
//using Microsoft.Xna.Framework.Graphics;
using System.Collections.ObjectModel;
using Barotrauma.Items.Components;
namespace Barotrauma
{
abstract partial class MapEntity : Entity
{
public static List<MapEntity> mapEntityList = new List<MapEntity>();
private 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;
//protected float soundRange;
//protected float sightRange;
public bool MoveWithLevel
{
get;
set;
}
//the position and dimensions of the entity
protected Rectangle rect;
//is the mouse inside the rect
protected bool isHighlighted;
public bool IsHighlighted
{
get { return isHighlighted; }
set { isHighlighted = value; }
}
private static bool resizing;
private int resizeDirX, resizeDirY;
public virtual Rectangle Rect {
get { return rect; }
set { rect = value; }
}
public Rectangle WorldRect
{
get { return Submarine == null ? rect : new Rectangle((int)(Submarine.Position.X + rect.X), (int)(Submarine.Position.Y + rect.Y), rect.Width, rect.Height); }
}
public virtual Sprite Sprite
{
get { return null; }
}
public virtual bool DrawBelowWater
{
get
{
return Sprite != null && Sprite.Depth > 0.5f;
}
}
public virtual bool DrawOverWater
{
get
{
return !DrawBelowWater;
}
}
public virtual bool DrawDamageEffect
{
get
{
return false;
}
}
public virtual bool IsLinkable
{
get { return false; }
}
public virtual bool SelectableInEditor
{
get { return true; }
}
public override Vector2 Position
{
get
{
Vector2 rectPos = new Vector2(
rect.X + rect.Width / 2.0f,
rect.Y - rect.Height / 2.0f);
//if (MoveWithLevel) rectPos += Level.Loaded.Position;
return rectPos;
}
}
public override Vector2 SimPosition
{
get
{
return ConvertUnits.ToSimUnits(Position);
}
}
public float SoundRange
{
get
{
if (aiTarget == null) return 0.0f;
return aiTarget.SoundRange;
}
set
{
if (aiTarget == null) return;
aiTarget.SoundRange = value;
}
}
public float SightRange
{
get
{
if (aiTarget == null) return 0.0f;
return aiTarget.SightRange;
}
set { aiTarget.SightRange = value; }
}
public virtual string Name
{
get { return ""; }
}
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
{
this.prefab = prefab;
}
public virtual void Move(Vector2 amount)
{
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
}
public virtual bool IsMouseOn(Vector2 position)
{
return (Submarine.RectContains(WorldRect, position));
}
public virtual MapEntity Clone()
{
throw new NotImplementedException();
}
public static List<MapEntity> Clone(List<MapEntity> entitiesToClone)
{
List<MapEntity> clones = new List<MapEntity>();
foreach (MapEntity e in entitiesToClone)
{
Debug.Assert(e != null);
clones.Add(e.Clone());
Debug.Assert(clones.Last() != null);
}
Debug.Assert(clones.Count == entitiesToClone.Count);
//clone links between the entities
for (int i = 0; i < clones.Count; i++)
{
if (entitiesToClone[i].linkedTo == null) continue;
foreach (MapEntity linked in entitiesToClone[i].linkedTo)
{
if (!entitiesToClone.Contains(linked)) continue;
clones[i].linkedTo.Add(clones[entitiesToClone.IndexOf(linked)]);
}
}
//connect clone wires to the clone items
for (int i = 0; i < clones.Count; i++)
{
var cloneItem = clones[i] as Item;
if (cloneItem == null) continue;
var cloneWire = cloneItem.GetComponent<Wire>();
if (cloneWire == null) continue;
var originalWire = ((Item)entitiesToClone[i]).GetComponent<Wire>();
cloneWire.SetNodes(originalWire.GetNodes());
for (int n = 0; n < 2; n++)
{
if (originalWire.Connections[n] == null) continue;
var connectedItem = originalWire.Connections[n].Item;
if (connectedItem == null) continue;
//index of the item the wire is connected to
int itemIndex = entitiesToClone.IndexOf(connectedItem);
//index of the connection in the connectionpanel of the target item
int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]);
(clones[itemIndex] as Item).GetComponent<ConnectionPanel>().Connections[connectionIndex].TryAddLink(cloneWire);
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false);
}
}
return clones;
}
protected void InsertToList()
{
int i = 0;
if (Sprite==null)
{
mapEntityList.Add(this);
return;
}
while (i<mapEntityList.Count)
{
i++;
Sprite existingSprite = mapEntityList[i-1].Sprite;
if (existingSprite == null) continue;
#if CLIENT
if (existingSprite.Texture == this.Sprite.Texture) break;
#endif
}
mapEntityList.Insert(i, this);
}
public virtual bool IsVisible(Rectangle worldView)
{
return true;
}
/// <summary>
/// Remove the entity from the entity list without removing links to other entities
/// </summary>
public virtual void ShallowRemove()
{
base.Remove();
mapEntityList.Remove(this);
if (aiTarget != null) aiTarget.Remove();
}
public override void Remove()
{
base.Remove();
mapEntityList.Remove(this);
#if CLIENT
if (selectedList.Contains(this))
{
selectedList = selectedList.FindAll(e => e != this);
}
#endif
if (aiTarget != null) aiTarget.Remove();
if (linkedTo != null)
{
for (int i = linkedTo.Count - 1; i >= 0; i-- )
{
linkedTo[i].RemoveLinked(this);
}
linkedTo.Clear();
}
}
/// <summary>
/// Call Update() on every object in Entity.list
/// </summary>
public static void UpdateAll(float deltaTime, Camera cam)
{
foreach (Hull hull in Hull.hullList)
{
hull.Update(deltaTime, cam);
}
foreach (Gap gap in Gap.GapList)
{
gap.Update(deltaTime, cam);
}
foreach (Item item in Item.ItemList)
{
item.Update(deltaTime, cam);
}
Spawner?.Update();
}
public virtual void Update(float deltaTime, Camera cam) { }
public virtual void FlipX()
{
if (Submarine == null)
{
DebugConsole.ThrowError("Couldn't flip MapEntity \""+Name+"\", submarine==null");
return;
}
Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.Y = 0.0f;
Move(-relative * 2.0f);
}
/// <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)
{
foreach (MapEntity e in mapEntityList)
{
if (e.Submarine != sub) continue;
if (e.linkedToID == null) continue;
if (e.linkedToID.Count == 0) continue;
e.linkedTo.Clear();
foreach (ushort i in e.linkedToID)
{
MapEntity linked = FindEntityByID(i) as MapEntity;
if (linked != null) e.linkedTo.Add(linked);
}
}
List<LinkedSubmarine> linkedSubs = new List<LinkedSubmarine>();
for (int i = 0; i<mapEntityList.Count; i++)
{
if (mapEntityList[i].Submarine != sub) continue;
if (mapEntityList[i] is LinkedSubmarine)
{
linkedSubs.Add((LinkedSubmarine)mapEntityList[i]);
continue;
}
mapEntityList[i].OnMapLoaded();
}
Item.UpdateHulls();
Gap.UpdateHulls();
foreach (LinkedSubmarine linkedSub in linkedSubs)
{
linkedSub.OnMapLoaded();
}
}
public virtual void OnMapLoaded() { }
public void RemoveLinked(MapEntity e)
{
if (linkedTo == null) return;
if (linkedTo.Contains(e)) linkedTo.Remove(e);
}
}
}
@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
[Flags]
enum MapEntityCategory
{
Structure = 1, Machine = 2, Equipment = 4, Electrical = 8, Material = 16, Misc = 32, Alien = 64
}
partial class MapEntityPrefab
{
public static List<MapEntityPrefab> list = new List<MapEntityPrefab>();
protected string name;
public List<string> tags;
protected bool isLinkable;
public Sprite sprite;
//the position where the structure is being placed (needed when stretching the structure)
protected static Vector2 placePosition;
protected ConstructorInfo constructor;
//is it possible to stretch the entity horizontally/vertically
public bool resizeHorizontal { get; protected set; }
public bool resizeVertical { get; protected set; }
//which prefab has been selected for placing
protected static MapEntityPrefab selected;
protected int price;
public string Name
{
get { return name; }
}
public static MapEntityPrefab Selected
{
get { return selected; }
set { selected = value; }
}
public string Description
{
get;
protected set;
}
public virtual bool IsLinkable
{
get { return isLinkable; }
}
public bool ResizeHorizontal
{
get { return resizeHorizontal; }
}
public bool ResizeVertical
{
get { return resizeVertical; }
}
public MapEntityCategory Category
{
get;
protected set;
}
public Color SpriteColor
{
get;
protected set;
}
public int Price
{
get { return price; }
}
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;
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;
list.Add(ep);
ep = new MapEntityPrefab();
ep.name = "Waypoint";
ep.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) });
list.Add(ep);
//ep = new MapEntityPrefab();
//ep.name = "Linked Submarine";
//ep.Category = 0;
//list.Add(ep);
}
public MapEntityPrefab()
{
Category = MapEntityCategory.Structure;
}
public virtual void UpdatePlacing(Camera cam)
{
Vector2 placeSize = Submarine.GridSize;
if (placePosition == Vector2.Zero)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (PlayerInput.LeftButtonHeld()) placePosition = position;
}
else
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
Rectangle newRect = Submarine.AbsRect(placePosition, placeSize);
newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X);
newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y);
if (Submarine.MainSub != null)
{
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
}
if (PlayerInput.LeftButtonReleased())
{
CreateInstance(newRect);
placePosition = Vector2.Zero;
selected = null;
}
newRect.Y = -newRect.Y;
}
if (PlayerInput.RightButtonHeld())
{
placePosition = Vector2.Zero;
selected = null;
}
}
protected virtual void CreateInstance(Rectangle rect)
{
object[] lobject = new object[] { this, rect };
constructor.Invoke(lobject);
}
public static bool SelectPrefab(object selection)
{
if ((selected = selection as MapEntityPrefab) != null)
{
placePosition = Vector2.Zero;
return true;
}
else
{
return false;
}
}
//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,102 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Barotrauma
{
public class Md5Hash
{
private string hash;
private string shortHash;
public string Hash
{
get
{
return hash;
}
}
public string ShortHash
{
get
{
return shortHash;
}
}
public Md5Hash(string md5Hash)
{
this.hash = md5Hash;
shortHash = GetShortHash(md5Hash);
}
public Md5Hash(byte[] bytes)
{
hash = CalculateHash(bytes);
shortHash = GetShortHash(hash);
}
public Md5Hash(FileStream fileStream)
{
hash = CalculateHash(fileStream);
shortHash = GetShortHash(hash);
}
public Md5Hash(XDocument doc)
{
if (doc == null) return;
string docString = Regex.Replace(doc.ToString(), @"\s+", "");
byte[] inputBytes = Encoding.ASCII.GetBytes(docString);
hash = CalculateHash(inputBytes);
shortHash = GetShortHash(hash);
}
public override string ToString()
{
return hash;
}
private string CalculateHash(FileStream stream)
{
MD5 md5 = MD5.Create();
byte[] byteHash = md5.ComputeHash(stream);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < byteHash.Length; i++)
{
sb.Append(byteHash[i].ToString("X2"));
}
return sb.ToString();
}
private string CalculateHash(byte[] bytes)
{
MD5 md5 = MD5.Create();
byte[] byteHash = md5.ComputeHash(bytes);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < byteHash.Length; i++)
{
sb.Append(byteHash[i].ToString("X2"));
}
return sb.ToString();
}
public static string GetShortHash(string fullHash)
{
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
}
}
}
@@ -0,0 +1,871 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class WallSection
{
public Rectangle rect;
public float damage;
public Gap gap;
public int GapID;
//public float lastSentDamage;
public WallSection(Rectangle rect)
{
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
this.rect = rect;
damage = 0.0f;
}
public WallSection(Rectangle rect, float damage)
{
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
this.rect = rect;
this.damage = 0.0f;
}
}
partial class Structure : MapEntity, IDamageable, IServerSerializable
{
public static int wallSectionSize = 96;
public static List<Structure> WallList = new List<Structure>();
StructurePrefab prefab;
//farseer physics bodies, separated by gaps
List<Body> bodies;
//sections of the wall that are supposed to be rendered
public WallSection[] sections {
get;
private set;
}
bool isHorizontal;
public SpriteEffects SpriteEffects = SpriteEffects.None;
public bool resizeHorizontal
{
get { return prefab.resizeHorizontal; }
}
public bool resizeVertical
{
get { return prefab.resizeVertical; }
}
private bool flippedX;
public override Sprite Sprite
{
get { return prefab.sprite; }
}
public bool IsPlatform
{
get { return prefab.IsPlatform; }
}
public Direction StairDirection
{
get;
private set;
}
public override string Name
{
get { return prefab.Name; }
}
public bool HasBody
{
get { return prefab.HasBody; }
}
public bool CastShadow
{
get { return prefab.CastShadow; }
}
public bool IsHorizontal
{
get { return isHorizontal; }
}
public int SectionCount
{
get { return sections.Length; }
}
public float Health
{
get { return prefab.MaxHealth; }
}
public override bool DrawBelowWater
{
get
{
return base.DrawBelowWater || prefab.BackgroundSprite != null;
}
}
public override bool DrawOverWater
{
get
{
return !DrawDamageEffect;
}
}
public override bool DrawDamageEffect
{
get
{
return prefab.HasBody;
}
}
public List<string> Tags
{
get { return prefab.tags; }
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
Rectangle oldRect = Rect;
base.Rect = value;
if (prefab.HasBody) CreateSections();
else
{
foreach (WallSection sec in sections)
{
Rectangle secRect = sec.rect;
secRect.X -= oldRect.X; secRect.Y -= oldRect.Y;
secRect.X *= value.Width; secRect.X /= oldRect.Width;
secRect.Y *= value.Height; secRect.Y /= oldRect.Height;
secRect.Width *= value.Width; secRect.Width /= oldRect.Width;
secRect.Height *= value.Height; secRect.Height /= oldRect.Height;
secRect.X += value.X; secRect.Y += value.Y;
sec.rect = secRect;
}
}
}
}
public override void Move(Vector2 amount)
{
base.Move(amount);
for (int i = 0; i < sections.Length; i++)
{
Rectangle r = sections[i].rect;
r.X += (int)amount.X;
r.Y += (int)amount.Y;
sections[i].rect = r;
}
if (bodies != null)
{
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
foreach (Body b in bodies)
{
b.SetTransform(b.Position + simAmount, b.Rotation);
}
}
#if CLIENT
if (convexHulls!=null)
{
convexHulls.ForEach(x => x.Move(amount));
}
#endif
}
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine)
: base(sp, submarine)
{
if (rectangle.Width == 0 || rectangle.Height == 0) return;
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
rect = rectangle;
prefab = sp;
isHorizontal = (rect.Width>rect.Height);
StairDirection = prefab.StairDirection;
if (prefab.HasBody)
{
bodies = new List<Body>();
//gaps = new List<Gap>();
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
1.5f);
newBody.BodyType = BodyType.Static;
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
newBody.Friction = 0.5f;
newBody.OnCollision += OnWallCollision;
newBody.UserData = this;
newBody.CollisionCategories = (prefab.IsPlatform) ? Physics.CollisionPlatform : Physics.CollisionWall;
bodies.Add(newBody);
WallList.Add(this);
CreateSections();
}
else
{
sections = new WallSection[1];
sections[0] = new WallSection(rect);
if (StairDirection != Direction.None)
{
CreateStairBodies();
}
}
#if CLIENT
if (prefab.CastShadow)
{
GenerateConvexHull();
}
#endif
InsertToList();
}
public override MapEntity Clone()
{
return new Structure(rect, prefab, Submarine);
}
private void CreateStairBodies()
{
bodies = new List<Body>();
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0) + Submarine.GridSize.X * 3.0f),
ConvertUnits.ToSimUnits(10),
1.5f);
newBody.BodyType = BodyType.Static;
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + rect.Width / 2.0f);
stairPos += new Vector2(
(StairDirection == Direction.Right) ? -Submarine.GridSize.X * 1.5f : Submarine.GridSize.X * 1.5f,
-Submarine.GridSize.Y * 2.0f);
newBody.Position = ConvertUnits.ToSimUnits(stairPos);
newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
newBody.Friction = 0.8f;
newBody.CollisionCategories = Physics.CollisionStairs;
newBody.UserData = this;
bodies.Add(newBody);
}
private void CreateSections()
{
int xsections = 1, ysections = 1;
int width = rect.Width, height = rect.Height;
if (!HasBody)
{
if (flippedX && isHorizontal)
{
xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width);
width = prefab.sprite.SourceRect.Width;
}
else
{
xsections = 1;
ysections = 1;
}
sections = new WallSection[xsections];
}
else
{
if (isHorizontal)
{
xsections = (int)Math.Ceiling((float)rect.Width / wallSectionSize);
sections = new WallSection[xsections];
width = (int)wallSectionSize;
}
else
{
ysections = (int)Math.Ceiling((float)rect.Height / wallSectionSize);
sections = new WallSection[ysections];
height = (int)wallSectionSize;
}
}
for (int x = 0; x < xsections; x++)
{
for (int y = 0; y < ysections; y++)
{
if (flippedX)
{
Rectangle sectionRect = new Rectangle(rect.Right - (x + 1) * width, rect.Y - y * height, width, height);
int over = Math.Max(rect.X - sectionRect.X, 0);
sectionRect.X += over;
sectionRect.Width -= over;
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
sections[xsections - 1 - x + y] = new WallSection(sectionRect);
}
else
{
Rectangle sectionRect = new Rectangle(rect.X + x * width, rect.Y - y * height, width, height);
sectionRect.Width -= (int)Math.Max(sectionRect.Right - rect.Right, 0.0f);
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
sections[x + y] = new WallSection(sectionRect);
}
}
}
}
private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
{
if (isHorizontal)
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
mergedSections.Sum(x => x.rect.Width), mergedSections.First().rect.Height);
else
{
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
mergedSections.First().rect.Width, mergedSections.Sum(x => x.rect.Height));
}
}
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
{
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
return corners;
}
public override bool IsMouseOn(Vector2 position)
{
if (StairDirection == Direction.None)
{
return base.IsMouseOn(position);
}
else
{
if (!base.IsMouseOn(position)) return false;
if (StairDirection == Direction.Left)
{
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 40.0f;
}
else
{
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 40.0f;
}
}
}
public override void ShallowRemove()
{
base.ShallowRemove();
if (WallList.Contains(this)) WallList.Remove(this);
if (bodies != null)
{
foreach (Body b in bodies)
GameMain.World.RemoveBody(b);
}
if (sections != null)
{
foreach (WallSection s in sections)
{
if (s.gap != null)
{
s.gap.Remove();
s.gap = null;
}
}
}
#if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
#endif
}
public override void Remove()
{
base.Remove();
if (WallList.Contains(this)) WallList.Remove(this);
if (bodies != null)
{
foreach (Body b in bodies)
GameMain.World.RemoveBody(b);
}
if (sections != null)
{
foreach (WallSection s in sections)
{
if (s.gap != null)
{
s.gap.Remove();
s.gap = null;
}
}
}
#if CLIENT
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
#endif
}
public override bool IsVisible(Rectangle WorldView)
{
Rectangle worldRect = WorldRect;
if (worldRect.X > WorldView.Right || worldRect.Right < WorldView.X) return false;
if (worldRect.Y < WorldView.Y - WorldView.Height || worldRect.Y - worldRect.Height > WorldView.Y) return false;
return true;
}
private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
{
if (prefab.IsPlatform)
{
Limb limb;
if ((limb = f2.Body.UserData as Limb) != null)
{
if (limb.character.AnimController.IgnorePlatforms) return false;
}
}
if (f2.Body.UserData is Limb)
{
var character = ((Limb)f2.Body.UserData).character;
if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f) return true;
}
if (!prefab.IsPlatform && prefab.StairDirection == Direction.None)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
int section = FindSectionIndex(pos);
if (section > 0)
{
Vector2 normal = contact.Manifold.LocalNormal;
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal)*f2.Body.Mass*0.1f;
if (impact < 10.0f) return true;
#if CLIENT
SoundPlayer.PlayDamageSound(DamageSoundType.StructureBlunt, impact,
new Vector2(
sections[section].rect.X + sections[section].rect.Width / 2,
sections[section].rect.Y - sections[section].rect.Height / 2));
#endif
AddDamage(section, impact);
}
}
return true;
}
public WallSection GetSection(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return null;
return sections[sectionIndex];
}
public bool SectionBodyDisabled(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
return (sections[sectionIndex].damage>=prefab.MaxHealth);
}
/// <summary>
/// Sections that are leaking have a gap placed on them
/// </summary>
public bool SectionIsLeaking(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
return (sections[sectionIndex].damage >= prefab.MaxHealth*0.5f);
}
public int SectionLength(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return 0;
return (isHorizontal ? sections[sectionIndex].rect.Width : sections[sectionIndex].rect.Height);
}
public void AddDamage(int sectionIndex, float damage)
{
if (!prefab.HasBody || prefab.IsPlatform) return;
if (sectionIndex < 0 || sectionIndex > sections.Length - 1) return;
var section = sections[sectionIndex];
#if CLIENT
float particleAmount = Math.Min(Health - section.damage, damage) * Rand.Range(0.01f, 1.0f);
particleAmount = Math.Min(particleAmount + Rand.Range(-5,1), 100);
for (int i = 0; i < particleAmount; i++)
{
Vector2 particlePos = new Vector2(
Rand.Range(section.rect.X, section.rect.Right),
Rand.Range(section.rect.Y - section.rect.Height, section.rect.Y));
if (Submarine != null) particlePos += Submarine.DrawPosition;
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)));
if (particle == null) break;
}
#endif
if (GameMain.Client == null) SetDamage(sectionIndex, section.damage + damage);
}
public int FindSectionIndex(Vector2 displayPos)
{
if (!sections.Any()) return -1;
//if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize
//and we need to adjust the position accordingly
if (sections[0].rect.Width < wallSectionSize)
{
displayPos.X += wallSectionSize - sections[0].rect.Width;
}
int index = (isHorizontal) ?
(int)Math.Floor((displayPos.X - rect.X) / wallSectionSize) :
(int)Math.Floor((rect.Y - displayPos.Y) / wallSectionSize);
if (index < 0 || index > sections.Length - 1) return -1;
return index;
}
public float SectionDamage(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return 0.0f;
return sections[sectionIndex].damage;
}
public Vector2 SectionPosition(int sectionIndex, bool world = false)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return Vector2.Zero;
Vector2 sectionPos = new Vector2(
sections[sectionIndex].rect.X + sections[sectionIndex].rect.Width / 2.0f,
sections[sectionIndex].rect.Y - sections[sectionIndex].rect.Height / 2.0f);
if (world && Submarine != null) sectionPos += Submarine.Position;
return sectionPos;
}
public AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
if (Submarine != null && Submarine.GodMode) return new AttackResult(0.0f, 0.0f);
if (!prefab.HasBody || prefab.IsPlatform) return new AttackResult(0.0f, 0.0f);
Vector2 transformedPos = worldPosition;
if (Submarine != null) transformedPos -= Submarine.Position;
int i = FindSectionIndex(transformedPos);
if (i == -1) return new AttackResult(0.0f, 0.0f);
float damageAmount = attack.GetStructureDamage(deltaTime);
AddDamage(i, damageAmount);
#if CLIENT
GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f);
if (playSound && !SectionBodyDisabled(i))
{
DamageSoundType damageSoundType = (attack.DamageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash;
SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition);
}
#endif
return new AttackResult(damageAmount, 0.0f);
}
private void SetDamage(int sectionIndex, float damage)
{
if (Submarine != null && Submarine.GodMode) return;
if (!prefab.HasBody) return;
if (!MathUtils.IsValid(damage)) return;
if (GameMain.Server != null && damage != sections[sectionIndex].damage)
{
GameMain.Server.CreateEntityEvent(this);
}
if (damage < prefab.MaxHealth*0.5f)
{
if (sections[sectionIndex].gap != null)
{
//remove existing gap if damage is below 50%
sections[sectionIndex].gap.Remove();
sections[sectionIndex].gap = null;
#if CLIENT
if(CastShadow) GenerateConvexHull();
#endif
}
}
else
{
if (sections[sectionIndex].gap == null)
{
Rectangle gapRect = sections[sectionIndex].rect;
gapRect.X -= 10;
gapRect.Y += 10;
gapRect.Width += 20;
gapRect.Height += 20;
sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine);
sections[sectionIndex].gap.ConnectedWall = this;
#if CLIENT
if(CastShadow) GenerateConvexHull();
#endif
}
sections[sectionIndex].gap.Open = (damage / prefab.MaxHealth - 0.5f) * 2.0f;
}
bool hadHole = SectionBodyDisabled(sectionIndex);
sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.MaxHealth);
bool hasHole = SectionBodyDisabled(sectionIndex);
if (hadHole == hasHole) return;
//if (hasHole) Explosion.ApplyExplosionForces(sections[sectionIndex].gap.WorldPosition, 500.0f, 5.0f, 0.0f, 0.0f);
UpdateSections();
}
public void SetCollisionCategory(Category collisionCategory)
{
if (bodies == null) return;
foreach (Body body in bodies)
{
body.CollisionCategories = collisionCategory;
}
}
private void UpdateSections()
{
foreach (Body b in bodies)
{
GameMain.World.RemoveBody(b);
}
bodies.Clear();
bool hasHoles = false;
var mergedSections = new List<WallSection>();
for (int i = 0; i < sections.Length; i++ )
{
// if there is a gap and we have sections to merge, do it.
if (SectionBodyDisabled(i))
{
hasHoles = true;
if (!mergedSections.Any()) continue;
var mergedRect = GenerateMergedRect(mergedSections);
mergedSections.Clear();
CreateRectBody(mergedRect);
}
else
{
mergedSections.Add(sections[i]);
}
}
// take care of any leftover pieces
if (mergedSections.Count > 0)
{
var mergedRect = GenerateMergedRect(mergedSections);
CreateRectBody(mergedRect);
}
//if the section has holes (or is just one big hole with no bodies),
//we need a sensor for repairtools to be able to target the structure
if (hasHoles || !bodies.Any()) CreateRectBody(rect).IsSensor = true;
}
private Body CreateRectBody(Rectangle rect)
{
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
1.5f);
newBody.BodyType = BodyType.Static;
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
newBody.Friction = 0.5f;
newBody.OnCollision += OnWallCollision;
newBody.CollisionCategories = Physics.CollisionWall;
newBody.UserData = this;
bodies.Add(newBody);
return newBody;
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
for (int i = 0; i < sections.Length; i++)
{
msg.WriteRangedSingle(sections[i].damage / Health, 0.0f, 1.0f, 8);
}
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
for (int i = 0; i < sections.Length; i++)
{
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health;
SetDamage(i, damage);
}
}
public override void FlipX()
{
base.FlipX();
flippedX = !flippedX;
if (prefab.CanSpriteFlipX)
{
SpriteEffects ^= SpriteEffects.FlipHorizontally;
}
if (StairDirection != Direction.None)
{
StairDirection = StairDirection == Direction.Left ? Direction.Right : Direction.Left;
bodies.ForEach(b => GameMain.World.RemoveBody(b));
CreateStairBodies();
}
CreateSections();
}
public static void Load(XElement element, Submarine submarine)
{
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
string[] rectValues = rectString.Split(',');
Rectangle rect = new Rectangle(
int.Parse(rectValues[0]),
int.Parse(rectValues[1]),
int.Parse(rectValues[2]),
int.Parse(rectValues[3]));
string name = element.Attribute("name").Value;
Structure s = null;
foreach (MapEntityPrefab ep in MapEntityPrefab.list)
{
if (ep.Name == name)
{
s = new Structure(rect, (StructurePrefab)ep, submarine);
s.Submarine = submarine;
s.ID = (ushort)int.Parse(element.Attribute("ID").Value);
break;
}
}
if (s == null)
{
DebugConsole.ThrowError("Structure prefab " + name + " not found.");
return;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "section":
int index = ToolBox.GetAttributeInt(subElement, "i", -1);
if (index == -1) continue;
s.sections[index].damage =
ToolBox.GetAttributeFloat(subElement, "damage", 0.0f);
s.sections[index].GapID = ToolBox.GetAttributeInt(subElement, "gap", -1);
break;
}
}
}
public override void OnMapLoaded()
{
foreach (WallSection s in sections)
{
if (s.GapID == -1) continue;
s.gap = FindEntityByID((ushort)s.GapID) as Gap;
if (s.gap != null) s.gap.ConnectedWall = this;
}
}
}
}
@@ -0,0 +1,192 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma
{
partial class StructurePrefab : MapEntityPrefab
{
//public static List<StructurePrefab> list = new List<StructurePrefab>();
//does the structure have a physics body
private bool hasBody;
private bool castShadow;
private bool isPlatform;
private Direction stairDirection;
private bool canSpriteFlipX;
private float maxHealth;
//default size
private Vector2 size;
public bool HasBody
{
get { return hasBody; }
}
public bool IsPlatform
{
get { return isPlatform; }
}
public float MaxHealth
{
get { return maxHealth; }
}
public bool CastShadow
{
get { return castShadow; }
}
public Direction StairDirection
{
get { return stairDirection; }
}
public bool CanSpriteFlipX
{
get { return canSpriteFlipX; }
}
public Vector2 Size
{
get { return size; }
}
public Sprite BackgroundSprite
{
get;
private set;
}
public static void LoadAll(List<string> filePaths)
{
foreach (string filePath in filePaths)
{
XDocument doc = ToolBox.TryLoadXml(filePath);
if (doc == null || doc.Root == null) return;
foreach (XElement el in doc.Root.Elements())
{
StructurePrefab sp = Load(el);
list.Add(sp);
}
}
}
public static StructurePrefab Load(XElement element)
{
StructurePrefab sp = new StructurePrefab();
sp.name = element.Name.ToString();
sp.tags = new List<string>();
sp.tags.AddRange(ToolBox.GetAttributeString(element, "tags", "").Split(','));
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "sprite":
sp.sprite = new Sprite(subElement);
if (ToolBox.GetAttributeBool(subElement, "fliphorizontal", false))
sp.sprite.effects = SpriteEffects.FlipHorizontally;
if (ToolBox.GetAttributeBool(subElement, "flipvertical", false))
sp.sprite.effects = SpriteEffects.FlipVertically;
sp.canSpriteFlipX = ToolBox.GetAttributeBool(subElement, "canflipx", true);
break;
case "backgroundsprite":
sp.BackgroundSprite = new Sprite(subElement);
if (ToolBox.GetAttributeBool(subElement, "fliphorizontal", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally;
if (ToolBox.GetAttributeBool(subElement, "flipvertical", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipVertically;
break;
}
}
MapEntityCategory category;
if (!Enum.TryParse(ToolBox.GetAttributeString(element, "category", "Structure"), true, out category))
{
category = MapEntityCategory.Structure;
}
sp.Category = category;
sp.Description = ToolBox.GetAttributeString(element, "description", "");
sp.size = Vector2.Zero;
sp.size.X = ToolBox.GetAttributeFloat(element, "width", 0.0f);
sp.size.Y = ToolBox.GetAttributeFloat(element, "height", 0.0f);
string spriteColorStr = ToolBox.GetAttributeString(element, "spritecolor", "1.0,1.0,1.0,1.0");
sp.SpriteColor = new Color(ToolBox.ParseToVector4(spriteColorStr));
sp.maxHealth = ToolBox.GetAttributeFloat(element, "health", 100.0f);
sp.resizeHorizontal = ToolBox.GetAttributeBool(element, "resizehorizontal", false);
sp.resizeVertical = ToolBox.GetAttributeBool(element, "resizevertical", false);
sp.isPlatform = ToolBox.GetAttributeBool(element, "platform", false);
sp.stairDirection = (Direction)Enum.Parse(typeof(Direction), ToolBox.GetAttributeString(element, "stairdirection", "None"), true);
sp.castShadow = ToolBox.GetAttributeBool(element, "castshadow", false);
sp.hasBody = ToolBox.GetAttributeBool(element, "body", false);
return sp;
}
public override void UpdatePlacing(Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
if (placePosition == Vector2.Zero)
{
if (PlayerInput.LeftButtonHeld())
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
newRect.X = (int)position.X;
newRect.Y = (int)position.Y;
}
else
{
Vector2 placeSize = size;
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
newRect = Submarine.AbsRect(placePosition, placeSize);
if (PlayerInput.LeftButtonReleased())
{
//don't allow resizing width/height to zero
if ((!resizeHorizontal || placeSize.X != 0.0f) && (!resizeVertical || placeSize.Y != 0.0f))
{
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
var structure = new Structure(newRect, this, Submarine.MainSub);
structure.Submarine = Submarine.MainSub;
}
selected = null;
return;
}
}
if (PlayerInput.RightButtonHeld()) selected = null;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,548 @@
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Voronoi2;
namespace Barotrauma
{
class SubmarineBody
{
public const float DamageDepth = -30000.0f;
//private const float PressureDamageMultiplier = 0.001f;
private const float DamageMultiplier = 50.0f;
private const float Friction = 0.2f, Restitution = 0.0f;
public List<Vector2> HullVertices
{
get;
private set;
}
private float depthDamageTimer;
private readonly Submarine submarine;
public readonly PhysicsBody Body;
private List<PosInfo> memPos = new List<PosInfo>();
public Rectangle Borders
{
get;
private set;
}
public Vector2 Velocity
{
get { return Body.LinearVelocity; }
set
{
if (!MathUtils.IsValid(value)) return;
Body.LinearVelocity = value;
}
}
public Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(Body.SimPosition); }
}
public List<PosInfo> MemPos
{
get { return memPos; }
}
public bool AtDamageDepth
{
get { return Position.Y < DamageDepth; }
}
public SubmarineBody(Submarine sub)
{
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.");
}
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));
farseerBody = BodyFactory.CreateBody(GameMain.World, this);
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
Rectangle rect = wall.Rect;
FixtureFactory.AttachRectangle(
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
50.0f,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
farseerBody, this);
}
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != submarine) continue;
Rectangle rect = hull.Rect;
FixtureFactory.AttachRectangle(
ConvertUnits.ToSimUnits(rect.Width),
ConvertUnits.ToSimUnits(rect.Height),
5.0f,
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
farseerBody, this);
}
}
farseerBody.BodyType = BodyType.Dynamic;
farseerBody.CollisionCategories = Physics.CollisionWall;
farseerBody.CollidesWith =
Physics.CollisionItem |
Physics.CollisionLevel |
Physics.CollisionCharacter |
Physics.CollisionProjectile |
Physics.CollisionWall;
farseerBody.Restitution = Restitution;
farseerBody.Friction = Friction;
farseerBody.FixedRotation = true;
//mass = Body.Mass;
farseerBody.Awake = true;
farseerBody.SleepingAllowed = false;
farseerBody.IgnoreGravity = true;
farseerBody.OnCollision += OnCollision;
farseerBody.UserData = submarine;
Body = new PhysicsBody(farseerBody);
}
private List<Vector2> GenerateConvexHull()
{
List<Structure> subWalls = Structure.WallList.FindAll(wall => wall.Submarine == submarine);
if (subWalls.Count == 0)
{
return new List<Vector2> { new Vector2(-1.0f, 1.0f), new Vector2(1.0f, 1.0f), new Vector2(0.0f, -1.0f) };
}
List<Vector2> points = new List<Vector2>();
foreach (Structure wall in subWalls)
{
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y));
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y));
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y - wall.Rect.Height));
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y - wall.Rect.Height));
}
List<Vector2> hullPoints = MathUtils.GiftWrap(points);
return hullPoints;
}
public void Update(float deltaTime)
{
if (GameMain.Client != null)
{
if (memPos.Count == 0) return;
Vector2 newVelocity = Body.LinearVelocity;
Vector2 newPosition = Body.SimPosition;
Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition);
Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition);
List<Submarine> subsToMove = new List<Submarine>() { this.submarine };
subsToMove.AddRange(submarine.DockedTo);
foreach (Submarine dockedSub in submarine.DockedTo)
{
//clear the position buffer of the docked sub to prevent unnecessary position corrections
dockedSub.SubBody.memPos.Clear();
}
Submarine closestSub = null;
if (Character.Controlled == null)
{
closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
}
else
{
closestSub = Character.Controlled.Submarine;
}
bool displace = moveAmount.Length() > 100.0f;
foreach (Submarine sub in subsToMove)
{
sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
sub.PhysicsBody.LinearVelocity = newVelocity;
if (displace) sub.SubBody.DisplaceCharacters(moveAmount);
}
if (closestSub != null && subsToMove.Contains(closestSub))
{
GameMain.GameScreen.Cam.Position += moveAmount;
if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount;
if (Character.Controlled!=null) Character.Controlled.CursorPosition += moveAmount;
}
return;
}
//if outside left or right edge of the level
if (Position.X < 0 || Position.X > Level.Loaded.Size.X)
{
Rectangle worldBorders = Borders;
worldBorders.Location += MathUtils.ToPoint(Position);
//push the sub back below the upper "barrier" of the level
if (worldBorders.Y > Level.Loaded.Size.Y)
{
Body.LinearVelocity = new Vector2(
Body.LinearVelocity.X,
Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y)));
}
}
//-------------------------
Vector2 totalForce = CalculateBuoyancy();
if (Body.LinearVelocity.LengthSquared() > 0.000001f)
{
float dragCoefficient = 0.01f;
float speedLength = (Body.LinearVelocity == Vector2.Zero) ? 0.0f : Body.LinearVelocity.Length();
float drag = speedLength * speedLength * dragCoefficient * Body.Mass;
totalForce += -Vector2.Normalize(Body.LinearVelocity) * drag;
}
ApplyForce(totalForce);
UpdateDepthDamage(deltaTime);
}
/// <summary>
/// Moves away any character that is inside the bounding box of the sub (but not inside the sub)
/// </summary>
/// <param name="subTranslation">The translation that was applied to the sub before doing the displacement
/// (used for determining where to push the characters)</param>
private void DisplaceCharacters(Vector2 subTranslation)
{
Rectangle worldBorders = Borders;
worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(Body.SimPosition));
Vector2 translateDir = Vector2.Normalize(subTranslation);
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) continue;
foreach (Limb limb in c.AnimController.Limbs)
{
//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);
//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);
return;
}
}
}
private Vector2 CalculateBuoyancy()
{
float waterVolume = 0.0f;
float volume = 0.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != submarine) continue;
waterVolume += hull.Volume;
volume += hull.FullVolume;
}
float waterPercentage = volume==0.0f ? 0.0f : waterVolume / volume;
float neutralPercentage = 0.07f;
float buoyancy = neutralPercentage - waterPercentage;
if (buoyancy > 0.0f) buoyancy *= 2.0f;
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
}
public void ApplyForce(Vector2 force)
{
Body.ApplyForce(force);
}
public void SetPosition(Vector2 position)
{
Body.SetTransform(ConvertUnits.ToSimUnits(position), 0.0f);
}
private void UpdateDepthDamage(float deltaTime)
{
if (Position.Y > DamageDepth) return;
float depth = DamageDepth - Position.Y;
depthDamageTimer -= deltaTime;
if (depthDamageTimer > 0.0f) return;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
if (wall.Health < depth * 0.01f)
{
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, depth * 0.01f);
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(depth * 0.001f, 50.0f));
}
}
}
depthDamageTimer = 10.0f;
}
public bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
Limb limb = f2.Body.UserData as Limb;
if (limb!= null)
{
bool collision = HandleLimbCollision(contact, limb);
if (collision && limb.Mass > 100.0f)
{
Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition);
float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f;
ApplyImpact(impact * Math.Min(limb.Mass / 200.0f, 1), -normal, contact);
}
return collision;
}
VoronoiCell cell = f2.Body.UserData as VoronoiCell;
if (cell != null)
{
var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center);
float wallImpact = Vector2.Dot(Velocity, -collisionNormal);
ApplyImpact(wallImpact, -collisionNormal, contact);
Vector2 n;
FixedArray2<Vector2> particlePos;
contact.GetWorldManifold(out n, out particlePos);
#if CLIENT
int particleAmount = (int)(wallImpact*10.0f);
for (int i = 0; i < particleAmount; i++)
{
GameMain.ParticleManager.CreateParticle("iceshards",
ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
Rand.Vector(Rand.Range(50.0f,500.0f)) + Velocity);
}
#endif
return true;
}
Submarine sub = f2.Body.UserData as Submarine;
if (sub != null)
{
Debug.Assert(sub != submarine);
Vector2 normal;
FixedArray2<Vector2> points;
contact.GetWorldManifold(out normal, out points);
if (contact.FixtureA.Body == sub.SubBody.Body.FarseerBody)
{
normal = -normal;
}
float massRatio = sub.SubBody.Body.Mass / (sub.SubBody.Body.Mass + Body.Mass);
ApplyImpact((Vector2.Dot(Velocity - sub.Velocity, normal) / 2.0f)*massRatio, normal, contact);
return true;
}
return true;
}
private bool HandleLimbCollision(Contact contact, Limb limb)
{
if (limb.character.Submarine != null) return false;
Vector2 normal2;
FixedArray2<Vector2> points;
contact.GetWorldManifold(out normal2, out points);
Vector2 normalizedVel = limb.character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
Vector2.Zero : Vector2.Normalize(limb.character.AnimController.Collider.LinearVelocity);
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - normal2);
Hull newHull = Hull.FindHull(targetPos, null);
if (newHull == null)
{
targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
newHull = Hull.FindHull(targetPos, null);
if (newHull == null) return true;
}
var gaps = newHull.ConnectedGaps;
targetPos = limb.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);
return false;
}
private void ApplyImpact(float impact, Vector2 direction, Contact contact)
{
if (impact < 3.0f) return;
Vector2 tempNormal;
FarseerPhysics.Common.FixedArray2<Vector2> worldPoints;
contact.GetWorldManifold(out tempNormal, out worldPoints);
Vector2 lastContactPoint = worldPoints[0];
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
}
Vector2 impulse = direction * impact * 0.5f;
float length = impulse.Length();
if (length > 5.0f) impulse = (impulse / length) * 5.0f;
foreach (Character c in Character.CharacterList)
{
if (c.Submarine != submarine) continue;
if (impact > 2.0f) c.SetStun((impact - 2.0f) * 0.1f);
foreach (Limb limb in c.AnimController.Limbs)
{
limb.body.ApplyLinearImpulse(limb.Mass * impulse);
}
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse);
}
foreach (Item item in Item.ItemList)
{
if (item.Submarine != submarine || item.CurrentHull == null ||
item.body == null || !item.body.Enabled) continue;
item.body.ApplyLinearImpulse(item.body.Mass * impulse);
}
var damagedStructures = Explosion.RangedStructureDamage(ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, impact * DamageMultiplier);
//play a damage sound for the structure that took the most damage
float maxDamage = 0.0f;
Structure maxDamageStructure = null;
foreach (KeyValuePair<Structure,float> structureDamage in damagedStructures)
{
if (maxDamageStructure == null || structureDamage.Value > maxDamage)
{
maxDamage = structureDamage.Value;
maxDamageStructure = structureDamage.Key;
}
}
#if CLIENT
if (maxDamageStructure != null)
{
SoundPlayer.PlayDamageSound(
DamageSoundType.StructureBlunt,
impact * 10.0f,
ConvertUnits.ToDisplayUnits(lastContactPoint),
MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f),
maxDamageStructure.Tags);
}
#endif
}
}
}
@@ -0,0 +1,113 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
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.ShaftBody.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.ShaftBody.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;
sub.ApplyForce((Vector2.Normalize(targetPos - sub.Position) * 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;
}
}
}
@@ -0,0 +1,612 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
//using Microsoft.Xna.Framework.Graphics;
using System.Collections.ObjectModel;
using Barotrauma.Items.Components;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
public enum SpawnType { Path, Human, Enemy, Cargo };
partial class WayPoint : MapEntity
{
public static List<WayPoint> WayPointList = new List<WayPoint>();
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
protected SpawnType spawnType;
//characters spawning at the waypoint will be given an ID card with these tags
private string[] idCardTags;
//only characters with this job will be spawned at the waypoint
private JobPrefab assignedJob;
private Hull currentHull;
private ushort ladderId;
public Ladder Ladders;
private ushort gapId;
public Gap ConnectedGap
{
get;
private set;
}
public Hull CurrentHull
{
get { return currentHull; }
}
public SpawnType SpawnType
{
get { return spawnType; }
set { spawnType = value; }
}
public override string Name
{
get
{
return spawnType == SpawnType.Path ? "WayPoint" : "SpawnPoint";
}
}
public string[] IdCardTags
{
get { return idCardTags; }
private set
{
idCardTags = value;
for (int i = 0; i<idCardTags.Length; i++)
{
idCardTags[i] = idCardTags[i].Trim();
}
}
}
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.spawnType = spawnType;
ConnectedGap = gap;
}
public WayPoint(MapEntityPrefab prefab, Rectangle rectangle)
: this (rectangle, Submarine.MainSub)
{
if (prefab.Name.Contains("Spawn"))
{
spawnType = SpawnType.Human;
}
else
{
SpawnType = SpawnType.Path;
}
}
public WayPoint(Rectangle newRect, Submarine submarine)
: base (null, submarine)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
idCardTags = new string[0];
#if CLIENT
if (iconTexture==null)
{
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
}
#endif
InsertToList();
WayPointList.Add(this);
currentHull = Hull.FindHull(WorldPosition);
}
public override MapEntity Clone()
{
var clone = new WayPoint(rect, Submarine);
clone.idCardTags = idCardTags;
clone.spawnType = spawnType;
clone.assignedJob = assignedJob;
return clone;
}
public override bool IsMouseOn(Vector2 position)
{
#if CLIENT
if (IsHidden()) return false;
#endif
return base.IsMouseOn(position);
}
public static void GenerateSubWaypoints(Submarine submarine)
{
if (!Hull.hullList.Any())
{
DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
return;
}
List<WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path);
foreach (WayPoint wayPoint in existingWaypoints)
{
wayPoint.Remove();
}
float minDist = 150.0f;
float heightFromFloor = 110.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Rect.Height < 150) continue;
WayPoint prevWaypoint = null;
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);
continue;
}
for (float x = hull.Rect.X + minDist; x <= hull.Rect.Right - minDist; x += minDist)
{
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
if (prevWaypoint != null) wayPoint.ConnectTo(prevWaypoint);
prevWaypoint = wayPoint;
}
}
float outSideWaypointInterval = 200.0f;
int outsideWaypointDist = 100;
Rectangle borders = Hull.GetBorders();
borders.X -= outsideWaypointDist;
borders.Y += outsideWaypointDist;
borders.Width += outsideWaypointDist * 2;
borders.Height += outsideWaypointDist * 2;
borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
if (borders.Width <= outSideWaypointInterval*2)
{
borders.Inflate(outSideWaypointInterval*2 - borders.Width, 0);
}
if (borders.Height <= outSideWaypointInterval * 2)
{
int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height;
borders.Y += inflateAmount / 2;
borders.Height += inflateAmount;
}
WayPoint[,] cornerWaypoint = new WayPoint[2,2];
for (int i = 0; i<2; i++)
{
for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval)
{
var wayPoint = new WayPoint(
new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
SpawnType.Path, submarine);
if (x == borders.X + outSideWaypointInterval)
{
cornerWaypoint[i, 0] = wayPoint;
}
else
{
wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count-2]);
}
}
cornerWaypoint[i, 1] = WayPoint.WayPointList[WayPointList.Count - 1];
}
for (int i = 0; i < 2; i++)
{
WayPoint wayPoint = null;
for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval)
{
wayPoint = new WayPoint(
new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
SpawnType.Path, submarine);
if (y == borders.Y - borders.Height)
{
wayPoint.ConnectTo(cornerWaypoint[1, i]);
}
else
{
wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]);
}
}
wayPoint.ConnectTo(cornerWaypoint[0, i]);
}
List<Structure> stairList = new List<Structure>();
foreach (MapEntity me in mapEntityList)
{
Structure stairs = me as Structure;
if (stairs == null) continue;
if (stairs.StairDirection != Direction.None) stairList.Add(stairs);
}
foreach (Structure stairs in stairList)
{
WayPoint[] stairPoints = new WayPoint[3];
stairPoints[0] = new WayPoint(
new Vector2(stairs.Rect.X - 32.0f,
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
stairPoints[1] = new WayPoint(
new Vector2(stairs.Rect.Right + 32.0f,
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
for (int i = 0; i < 2; i++ )
{
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f,30f));
if (closest == null) continue;
stairPoints[i].ConnectTo(closest);
}
}
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position)/2, SpawnType.Path, submarine);
stairPoints[0].ConnectTo(stairPoints[2]);
stairPoints[2].ConnectTo(stairPoints[1]);
}
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Items.Components.Ladder>();
if (ladders == null) continue;
WayPoint[] ladderPoints = new WayPoint[2];
ladderPoints[0] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
ladderPoints[1] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y-1.0f), SpawnType.Path, submarine);
WayPoint prevPoint = ladderPoints[0];
Vector2 prevPos = prevPoint.SimPosition;
List<Body> ignoredBodies = new List<Body>();
while (prevPoint != ladderPoints[1])
{
var pickedBody = Submarine.PickBody(prevPos, ladderPoints[1].SimPosition, ignoredBodies);
if (pickedBody == null) break;
ignoredBodies.Add(pickedBody);
if (pickedBody.UserData is Item)
{
var door = ((Item)pickedBody.UserData).GetComponent<Door>();
if (door != null)
{
WayPoint newPoint = new WayPoint(door.Item.Position, SpawnType.Path, submarine);
newPoint.Ladders = ladders;
newPoint.ConnectedGap = door.LinkedGap;
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = ConvertUnits.ToSimUnits(door.Item.Position - Vector2.UnitY * door.Item.Rect.Height);
}
else
{
prevPos = Submarine.LastPickedPosition;
}
}
else
{
prevPos = Submarine.LastPickedPosition;
}
}
prevPoint.ConnectTo(ladderPoints[1]);
//for (float y = ladderPoints[0].Position.Y+100.0f; y < ladderPoints[1].Position.Y; y+=100.0f )
//{
// var midPoint = new WayPoint(new Vector2(item.Rect.Center.X, y), SpawnType.Path, Submarine.Loaded);
// midPoint.Ladders = ladders;
// midPoint.ConnectTo(prevPoint);
// prevPoint = midPoint;
//}
//ladderPoints[1].ConnectTo(prevPoint);
for (int i = 0; i < 2; i++)
{
ladderPoints[i].Ladders = ladders;
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);
}
}
//ladderPoints[0].ConnectTo(ladderPoints[1]);
}
foreach (Gap gap in Gap.GapList)
{
if (!gap.isHorizontal) continue;
//too small to walk through
if (gap.Rect.Height < 150.0f) continue;
var wayPoint = new WayPoint(
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor), SpawnType.Path, submarine, gap);
for (int dir = -1; dir <= 1; dir += 2)
{
float tolerance = gap.IsRoomToRoom ? 50.0f : outSideWaypointInterval / 2.0f;
WayPoint closest = wayPoint.FindClosest(
dir, true, new Vector2(-tolerance, tolerance),
gap.ConnectedDoor == null ? null : gap.ConnectedDoor.Body.FarseerBody);
if (closest != null)
{
wayPoint.ConnectTo(closest);
}
}
}
foreach (Gap gap in Gap.GapList)
{
if (gap.isHorizontal || gap.IsRoomToRoom) continue;
//too small to walk through
if (gap.Rect.Width < 100.0f) continue;
var wayPoint = new WayPoint(
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height/2), SpawnType.Path, submarine, gap);
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = wayPoint.FindClosest(dir, false, new Vector2(-outSideWaypointInterval, outSideWaypointInterval) / 2.0f);
if (closest == null) continue;
wayPoint.ConnectTo(closest);
}
}
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any());
foreach (WayPoint wp in orphans)
{
wp.Remove();
}
}
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null)
{
if (dir != -1 && dir != 1) return null;
float closestDist = 0.0f;
WayPoint closest = null;
foreach (WayPoint wp in WayPointList)
{
if (wp.SpawnType != SpawnType.Path || wp == this) continue;
float diff = 0.0f;
if (horizontalSearch)
{
if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) continue;
diff = wp.Position.X - Position.X;
}
else
{
if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) continue;
diff = wp.Position.Y - Position.Y;
}
if (Math.Sign(diff) != dir) continue;
float dist = Vector2.Distance(wp.Position, Position);
if (closest == null || dist < closestDist)
{
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true);
if (body != null && body != ignoredBody && !(body.UserData is Submarine))
{
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
}
closestDist = dist;
closest = wp;
}
}
return closest;
}
private void ConnectTo(WayPoint wayPoint2)
{
System.Diagnostics.Debug.Assert(this != wayPoint2);
if (!linkedTo.Contains(wayPoint2)) linkedTo.Add(wayPoint2);
if (!wayPoint2.linkedTo.Contains(this)) wayPoint2.linkedTo.Add(this);
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, bool useSyncedRand = false)
{
List<WayPoint> wayPoints = new List<WayPoint>();
foreach (WayPoint wp in WayPointList)
{
if (sub != null && wp.Submarine != sub) continue;
if (wp.spawnType != spawnType) continue;
if (assignedJob != null && wp.assignedJob != assignedJob.Prefab) continue;
wayPoints.Add(wp);
}
if (!wayPoints.Any()) return null;
return wayPoints[Rand.Int(wayPoints.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))];
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine, bool tryAssignWayPoint = true)
{
List<WayPoint> subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine);
List<WayPoint> unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human);
WayPoint[] assignedWayPoints = new WayPoint[crew.Count];
for (int i = 0; i < crew.Count; i++ )
{
//try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job
for (int n = 0; n < unassignedWayPoints.Count; n++)
{
if (crew[i].Job.Prefab != unassignedWayPoints[n].assignedJob) continue;
assignedWayPoints[i] = unassignedWayPoints[n];
unassignedWayPoints.RemoveAt(n);
break;
}
}
//go through the crewmembers that don't have a spawnpoint yet (if any)
for (int i = 0; i < crew.Count; i++)
{
if (assignedWayPoints[i] != null) continue;
//try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else
foreach (WayPoint wp in subWayPoints)
{
if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) continue;
assignedWayPoints[i] = wp;
break;
}
if (assignedWayPoints[i] != null) continue;
if (tryAssignWayPoint)
{
//try to assign a spawnpoint that isn't meant for any specific job
var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.assignedJob == null);
if (nonJobSpecificPoints.Any())
{
assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)];
}
}
if (assignedWayPoints[i] != null) continue;
//everything else failed -> just give a random spawnpoint
assignedWayPoints[i] = GetRandom(SpawnType.Human);
}
for (int i = 0; i < assignedWayPoints.Length; i++ )
{
if (assignedWayPoints[i]==null)
{
DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!");
assignedWayPoints[i] = WayPointList[0];
}
}
return assignedWayPoints;
}
public override void OnMapLoaded()
{
currentHull = Hull.FindHull(WorldPosition, currentHull);
if (gapId > 0) ConnectedGap = FindEntityByID(gapId) as Gap;
if (ladderId > 0)
{
var ladderItem = FindEntityByID(ladderId) as Item;
if (ladderItem != null) Ladders = ladderItem.GetComponent<Ladder>();
}
}
public static void Load(XElement element, Submarine submarine)
{
Rectangle rect = new Rectangle(
int.Parse(element.Attribute("x").Value),
int.Parse(element.Attribute("y").Value),
(int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
WayPoint w = new WayPoint(rect, submarine);
w.ID = (ushort)int.Parse(element.Attribute("ID").Value);
Enum.TryParse<SpawnType>(ToolBox.GetAttributeString(element, "spawn", "Path"), out w.spawnType);
string idCardTagString = ToolBox.GetAttributeString(element, "idcardtags", "");
if (!string.IsNullOrWhiteSpace(idCardTagString))
{
w.IdCardTags = idCardTagString.Split(',');
}
string jobName = ToolBox.GetAttributeString(element, "job", "").ToLowerInvariant();
if (!string.IsNullOrWhiteSpace(jobName))
{
w.assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName);
}
w.ladderId = (ushort)ToolBox.GetAttributeInt(element, "ladders", 0);
w.gapId = (ushort)ToolBox.GetAttributeInt(element, "gap", 0);
w.linkedToID = new List<ushort>();
int i = 0;
while (element.Attribute("linkedto" + i) != null)
{
w.linkedToID.Add((ushort)int.Parse(element.Attribute("linkedto" + i).Value));
i += 1;
}
}
public override void ShallowRemove()
{
base.ShallowRemove();
WayPointList.Remove(this);
}
public override void Remove()
{
base.Remove();
WayPointList.Remove(this);
}
}
}