Renamed project folders from Subsurface to Barotrauma

This commit is contained in:
Regalis
2017-06-04 15:00:53 +03:00
parent ad03c8bf0d
commit 94c6a8ea1b
697 changed files with 157 additions and 211 deletions
+147
View File
@@ -0,0 +1,147 @@
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 EntitySpawner Spawner;
private ushort id;
protected AITarget aiTarget;
public 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());
}
}
}
}
+129
View File
@@ -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));
}
}
}
+219
View File
@@ -0,0 +1,219 @@
using Microsoft.Xna.Framework;
using Barotrauma.Lights;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Networking;
using FarseerPhysics;
namespace Barotrauma
{
class Explosion
{
private Attack attack;
private float force;
public float CameraShake;
private bool sparks, shockwave, flames, smoke;
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);
if (shockwave)
{
GameMain.ParticleManager.CreateParticle("shockwave", worldPosition,
Vector2.Zero, 0.0f, hull);
}
for (int i = 0; i < attack.Range * 0.1f; i++)
{
Vector2 bubblePos = Rand.Vector(attack.Range * 0.5f);
GameMain.ParticleManager.CreateParticle("bubbles", worldPosition+bubblePos,
bubblePos, 0.0f, hull);
if (sparks)
{
GameMain.ParticleManager.CreateParticle("spark", worldPosition,
Rand.Vector(Rand.Range(500.0f, 800.0f)), 0.0f, hull);
}
if (flames)
{
GameMain.ParticleManager.CreateParticle("explosionfire", ClampParticlePos(worldPosition + Rand.Vector(50f), hull),
Rand.Vector(Rand.Range(50.0f, 100.0f)), 0.0f, hull);
}
if (smoke)
{
GameMain.ParticleManager.CreateParticle("smoke", ClampParticlePos(worldPosition + Rand.Vector(50f), hull),
Rand.Vector(Rand.Range(1.0f, 10.0f)), 0.0f, hull);
}
}
float displayRange = attack.Range;
if (displayRange < 0.1f) return;
var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null);
CoroutineManager.StartCoroutine(DimLight(light));
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.Range, force, attack.GetDamage(1.0f), attack.Stun);
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 });
}
}
}
}
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));
}
private IEnumerable<object> DimLight(LightSource light)
{
float currBrightness = 1.0f;
float startRange = light.Range;
while (light.Color.A > 0.0f)
{
light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, currBrightness);
light.Range = startRange * currBrightness;
currBrightness -= CoroutineManager.DeltaTime * 20.0f;
yield return CoroutineStatus.Running;
}
light.Remove();
yield return CoroutineStatus.Success;
}
public static void ApplyExplosionForces(Vector2 worldPosition, float range, float force, float damage = 0.0f, float stun = 0.0f)
{
if (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);
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 > range) continue;
float distFactor = 1.0f - dist / 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,
damage / c.AnimController.Limbs.Length * distFactor, 0.0f, stun * distFactor, false);
if (limb.WorldPosition == worldPosition) continue;
if (force > 0.0f)
{
limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.WorldPosition - worldPosition) * distFactor * force);
}
}
}
}
/// <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;
}
}
}
+377
View File
@@ -0,0 +1,377 @@
using Barotrauma.Lights;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barotrauma.Networking;
namespace Barotrauma
{
class FireSource
{
static Sound fireSoundBasic, fireSoundLarge;
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 5.0f;
private int basicSoundIndex, largeSoundIndex;
private Hull hull;
private LightSource lightSource;
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;
if (fireSoundBasic == null)
{
fireSoundBasic = Sound.Load("Content/Sounds/fire.ogg", false);
fireSoundLarge = Sound.Load("Content/Sounds/firelarge.ogg", false);
}
hull.AddFireSource(this);
Submarine = hull.Submarine;
this.position = worldPosition - new Vector2(-5.0f, 5.0f) - Submarine.Position;
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull == null ? null : hull.Submarine);
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)
{
float count = Rand.Range(0.0f, size.X/50.0f);
if (hull.FireSources.Any(fs => fs != this && fs.size.X > size.X))
{
if (basicSoundIndex > 0)
{
Sounds.SoundManager.Stop(basicSoundIndex);
basicSoundIndex = -1;
}
if (largeSoundIndex > 0)
{
Sounds.SoundManager.Stop(largeSoundIndex);
largeSoundIndex = -1;
}
}
else
{
if (fireSoundBasic != null)
{
basicSoundIndex = fireSoundBasic.Loop(basicSoundIndex,
Math.Min(size.X / 100.0f, 1.0f), WorldPosition + size / 2.0f, 1000.0f);
}
if (fireSoundLarge != null)
{
largeSoundIndex = fireSoundLarge.Loop(largeSoundIndex,
MathHelper.Clamp((size.X - 200.0f) / 100.0f, 0.0f, 1.0f), WorldPosition + size / 2.0f, 1000.0f);
}
}
//the firesource will start to shrink if oxygen percentage is below 10
float growModifier = Math.Min((hull.OxygenPercentage / 10.0f) - 1.0f, 1.0f);
for (int i = 0; i < count; i++)
{
Vector2 particlePos = new Vector2(
WorldPosition.X + Rand.Range(0.0f, size.X),
Rand.Range(WorldPosition.Y - size.Y, WorldPosition.Y + 20.0f));
Vector2 particleVel = new Vector2(
(particlePos.X - (WorldPosition.X + size.X / 2.0f)),
(float)Math.Sqrt(size.X) * Rand.Range(0.0f, 15.0f) * growModifier);
var particle = GameMain.ParticleManager.CreateParticle("flame",
particlePos, particleVel, 0.0f, hull);
if (particle == null) continue;
//make some of the particles create another firesource when they enter another hull
if (Rand.Int(20) == 1) particle.OnChangeHull = OnChangeHull;
particle.Size *= MathHelper.Clamp(size.X / 60.0f * Math.Max(hull.Oxygen / hull.FullVolume, 0.4f), 0.5f, 1.0f);
if (Rand.Int(5) == 1)
{
var smokeParticle = GameMain.ParticleManager.CreateParticle("smoke",
particlePos, new Vector2(particleVel.X, particleVel.Y * 0.1f), 0.0f, hull);
if (smokeParticle != null)
{
smokeParticle.Size *= MathHelper.Clamp(size.X / 100.0f * Math.Max(hull.Oxygen / hull.FullVolume, 0.4f), 0.5f, 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();
lightSource.Range = Math.Max(size.X, size.Y) * 10.0f / 2.0f;
lightSource.Color = new Color(1.0f, 0.45f, 0.3f) * Rand.Range(0.8f, 1.0f);
lightSource.Position = position + Vector2.UnitY * 30.0f;
if (GameMain.Client != null) return;
if (size.X < 1.0f) Remove();
}
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;
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);
}
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 Extinquish(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;
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);
}
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()
{
lightSource.Remove();
if (basicSoundIndex > 0)
{
Sounds.SoundManager.Stop(basicSoundIndex);
basicSoundIndex = -1;
}
if (largeSoundIndex > 0)
{
Sounds.SoundManager.Stop(largeSoundIndex);
largeSoundIndex = -1;
}
hull.RemoveFire(this);
}
}
}
+774
View File
@@ -0,0 +1,774 @@
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
{
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 Draw(SpriteBatch sb, bool editing, bool back = true)
{
if (GameMain.DebugDraw)
{
Vector2 center = new Vector2(WorldRect.X + rect.Width / 2.0f, -(WorldRect.Y - rect.Height/ 2.0f));
GUI.DrawLine(sb, center, center + new Vector2(flowForce.X, -flowForce.Y)/10.0f, Color.Red);
GUI.DrawLine(sb, center + Vector2.One * 5.0f, center + new Vector2(lerpedFlowForce.X, -lerpedFlowForce.Y) / 10.0f + Vector2.One * 5.0f, Color.Orange);
}
if (!editing || !ShowGaps) return;
Color clr = (open == 0.0f) ? Color.Red : Color.Cyan;
if (isHighlighted) clr = Color.Gold;
float depth = (ID % 255) * 0.000001f;
GUI.DrawRectangle(
sb, new Rectangle(WorldRect.X, -WorldRect.Y, rect.Width, rect.Height),
clr * 0.5f, true,
depth,
(int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
for (int i = 0; i < linkedTo.Count; i++)
{
Vector2 dir = isHorizontal ?
new Vector2(Math.Sign(linkedTo[i].Rect.Center.X - rect.Center.X), 0.0f)
: new Vector2(0.0f, Math.Sign((linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f) - (rect.Y - rect.Height / 2.0f)));
Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
arrowPos += new Vector2(dir.X * (WorldRect.Width / 2 + 10), dir.Y * (WorldRect.Height / 2 + 10));
GUI.Arrow.Draw(sb,
arrowPos, clr * 0.8f,
GUI.Arrow.Origin, MathUtils.VectorToAngle(dir) + MathHelper.PiOver2,
isHorizontal ? new Vector2(rect.Height / 16.0f, 1.0f) : new Vector2(rect.Width / 16.0f, 1.0f),
SpriteEffects.None, depth);
}
if (IsSelected)
{
GUI.DrawRectangle(sb,
new Vector2(WorldRect.X - 5, -WorldRect.Y - 5),
new Vector2(rect.Width + 10, rect.Height + 10),
Color.Red,
false,
depth,
(int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
}
}
public override void Update(Camera cam, float deltaTime)
{
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));
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);
}
if (Math.Abs(flowForce.X) > 300.0f)
{
pos.X += Math.Sign(flowForce.X) * 10.0f;
pos.Y = Rand.Range(lowerSurface, rect.Y - rect.Height);
GameMain.ParticleManager.CreateParticle(
"bubbles",
Submarine == null ? pos : pos + Submarine.Position,
flowForce / 10.0f, 0, flowTargetHull);
}
}
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));
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);
}
}
}
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 override XElement Save(XElement parentElement)
{
XElement element = new XElement("Gap");
element.Add(
new XAttribute("ID", ID),
new XAttribute("horizontal", isHorizontal ? "true" : "false"));
element.Add(new XAttribute("rect",
(int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
(int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," +
rect.Width + "," + rect.Height));
//if (linkedTo != null)
//{
// int i = 0;
// foreach (Entity e in linkedTo)
// {
// if (e == null) continue;
// element.Add(new XAttribute("linkedto" + i, e.ID));
// i += 1;
// }
//}
parentElement.Add(element);
return element;
}
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;
//}
}
}
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -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,613 @@
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 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, false) == 0) continue; //skip some positions to make the cells more irregular
sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, false));
}
}
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, false)];
//randomly take steps further away from the startpoint to make the cave expand further
if (Rand.Int(4, false) == 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++)
{
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, 2, gridOffset);
targetCells.Add(cells[cellIndex]);
}
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, false) > 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, false);
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<VertexPositionTexture> verticeList, bool setSolid=true)
{
verticeList = new List<VertexPositionTexture>();
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;
}
var triangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
for (int i = 0; i < triangles.Count; i++)
{
foreach (Vector2 vertex in triangles[i])
{
//shift the coordinates around a bit to make the texture repetition less obvious
Vector2 uvCoords = new Vector2(
vertex.X / 2000.0f + (float)Math.Sin(vertex.X / 500.0f) * 0.15f,
vertex.Y / 2000.0f + (float)Math.Sin(vertex.Y / 700.0f) * 0.15f);
verticeList.Add(new VertexPositionTexture(new Vector3(vertex, 1.0f), uvCoords));
}
}
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;
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;
}
public static VertexPositionTexture[] GenerateWallShapes(List<VoronoiCell> cells)
{
float inwardThickness = 500.0f, outWardThickness = 30.0f;
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
foreach (VoronoiCell cell in cells)
{
//if (cell.body == null) continue;
foreach (GraphEdge edge in cell.edges)
{
if (edge.cell1 != null && edge.cell1.body == null && edge.cell1.CellType != CellType.Empty) edge.cell1 = null;
if (edge.cell2 != null && edge.cell2.body == null && edge.cell2.CellType != CellType.Empty) edge.cell2 = null;
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;
}
}
}
foreach (VoronoiCell cell in cells)
{
//if (cell.body == null) continue;
foreach (GraphEdge edge in cell.edges)
{
if (!edge.isSolid) continue;
GraphEdge leftEdge = cell.edges.Find(e => e != edge && (edge.point1 == e.point1 || edge.point1 == e.point2));
GraphEdge rightEdge = cell.edges.Find(e => e != edge && (edge.point2 == e.point1 || edge.point2 == e.point2));
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
if (leftEdge == null)
{
leftNormal = GetEdgeNormal(edge, cell);
}
else
{
leftNormal = (leftEdge.isSolid) ?
Vector2.Normalize(GetEdgeNormal(leftEdge) + GetEdgeNormal(edge, cell)) :
Vector2.Normalize(leftEdge.Center - edge.point1);
}
if (!MathUtils.IsValid(leftNormal))
{
#if DEBUG
DebugConsole.ThrowError("Invalid left normal");
#endif
if (cell.body != null)
{
GameMain.World.RemoveBody(cell.body);
cell.body = null;
}
leftNormal = Vector2.UnitX;
break;
}
if (rightEdge == null)
{
rightNormal = GetEdgeNormal(edge, cell);
}
else
{
rightNormal = (rightEdge.isSolid) ?
Vector2.Normalize(GetEdgeNormal(rightEdge) + GetEdgeNormal(edge, cell)) :
Vector2.Normalize(rightEdge.Center - edge.point2);
}
if (!MathUtils.IsValid(rightNormal))
{
#if DEBUG
DebugConsole.ThrowError("Invalid right normal");
#endif
if (cell.body != null)
{
GameMain.World.RemoveBody(cell.body);
cell.body = null;
}
rightNormal = Vector2.UnitX;
break;
}
for (int i = 0; i < 2; i++)
{
Vector2[] verts = new Vector2[3];
VertexPositionTexture[] vertPos = new VertexPositionTexture[3];
if (i == 0)
{
verts[0] = edge.point1 - leftNormal * outWardThickness;
verts[1] = edge.point2 - rightNormal * outWardThickness;
verts[2] = edge.point1 + leftNormal * inwardThickness;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), Vector2.Zero);
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX);
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(0, 0.5f));
}
else
{
verts[0] = edge.point1 + leftNormal * inwardThickness;
verts[1] = edge.point2 - rightNormal * outWardThickness;
verts[2] = edge.point2 + rightNormal * inwardThickness;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(0.0f, 0.5f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX);
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(1.0f, 0.5f));
}
var comparer = new CompareCCW((verts[0] + verts[1] + verts[2]) / 3.0f);
Array.Sort(verts, vertPos, comparer);
for (int j = 0; j < 3; j++)
{
verticeList.Add(vertPos[j]);
}
}
}
}
return verticeList.ToArray();
}
/// <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);
}
}
}
+953
View File
@@ -0,0 +1,953 @@
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
{
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;
private LevelRenderer renderer;
//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, false).ToString();
}
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
return new Level(seed, Rand.Range(30.0f, 80.0f, false), 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>();
renderer = new LevelRenderer(this);
Voronoi voronoi = new Voronoi(1.0);
List<Vector2> sites = new List<Vector2>();
bodies = new List<Body>();
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
backgroundColor = generationParams.BackgroundColor;
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f);
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, false),
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));
endPosition = new Vector2(
borders.Width - Rand.Range(minWidth, minWidth * 2, false),
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));
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, false);
x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, false);
x += Rand.Range(nodeInterval.X, nodeInterval.Y, false))
{
pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false)));
}
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, false)];
tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right);
float tunnelLength = Rand.Range(
generationParams.SmallTunnelLengthRange.X,
generationParams.SmallTunnelLengthRange.Y,
false);
var tunnelNodes = MathUtils.GenerateJaggedLine(
tunnelStartPos,
new Vector2(tunnelStartPos.X, pathBorders.Bottom)+Rand.Vector(tunnelLength,false),
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, false),
y + Rand.Range(-siteVariance.Y, siteVariance.Y, false));
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, false)];
//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<VertexPositionTexture> bodyVertices;
bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out bodyVertices);
renderer.SetBodyVertices(bodyVertices.ToArray());
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells));
renderer.PlaceSprites(generationParams.BackgroundSpriteAmount);
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, false) > 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, false), Rand.Range(5000.0f, 8000.0f, false));
float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f;
Vector2 ruinPos = cells[Rand.Int(cells.Count, false)].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((ruinPos - ruinSize * 0.5f).ToPoint(), ruinSize.ToPoint()));
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, false), false);
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)].Position;
}
return matchingPositions[Rand.Int(matchingPositions.Count, !useSyncedRand)].Position;
}
public void Update(float deltaTime)
{
/*
if (Submarine.MainSub != null)
{
WrappingWall.UpdateWallShift(Submarine.MainSub.WorldPosition, wrappingWalls);
}*/
if (Hull.renderer != null)
{
Hull.renderer.ScrollWater((float)deltaTime);
}
renderer.Update(deltaTime);
}
public void DrawFront(SpriteBatch spriteBatch)
{
if (renderer == null) return;
renderer.Draw(spriteBatch);
if (GameMain.DebugDraw)
{
foreach (InterestingPosition pos in positionsOfInterest)
{
Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave)
{
color = Color.DarkOrange;
}
else if (pos.PositionType == PositionType.Ruin)
{
color = Color.LightGray;
}
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X-15.0f, -pos.Position.Y-15.0f), new Vector2(30.0f, 30.0f), color, true);
}
}
}
public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundSpriteManager = null)
{
float brightness = MathHelper.Clamp(50.0f + (cam.Position.Y - Size.Y) / 2000.0f, 10.0f, 40.0f);
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (brightness / avgValue), 1.0f);
graphics.Clear(backgroundColor);
if (renderer == null) return;
renderer.DrawBackground(spriteBatch, cam, backgroundSpriteManager);
}
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 (renderer!=null)
{
renderer.Dispose();
renderer = null;
}
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, false)];
}
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,253 @@
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
{
class LevelRenderer : IDisposable
{
private static BasicEffect wallEdgeEffect, wallCenterEffect;
private static Sprite background, backgroundTop;
private static Sprite dustParticles;
private static Texture2D shaftTexture;
private static BackgroundSpriteManager backgroundSpriteManager;
Vector2 dustOffset;
private Level level;
private VertexBuffer wallVertices, bodyVertices;
//public VertexPositionTexture[] WallVertices;
//public VertexPositionColor[] BodyVertices;
public LevelRenderer(Level level)
{
if (shaftTexture == null) shaftTexture = TextureLoader.FromFile("Content/Map/iceWall.png");
if (background==null)
{
background = new Sprite("Content/Map/background2.png", Vector2.Zero);
backgroundTop = new Sprite("Content/Map/background.png", Vector2.Zero);
dustParticles = new Sprite("Content/Map/dustparticles.png", Vector2.Zero);
}
if (wallEdgeEffect == null)
{
wallEdgeEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
DiffuseColor = new Vector3(0.8f, 0.8f, 0.8f),
VertexColorEnabled = false,
TextureEnabled = true,
Texture = shaftTexture
};
wallEdgeEffect.CurrentTechnique = wallEdgeEffect.Techniques["BasicEffect_Texture"];
}
if (wallCenterEffect == null)
{
wallCenterEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = false,
TextureEnabled = true,
Texture = backgroundTop.Texture
};
wallCenterEffect.CurrentTechnique = wallCenterEffect.Techniques["BasicEffect_Texture"];
}
if (backgroundSpriteManager==null)
{
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs);
if (files.Count > 0)
backgroundSpriteManager = new BackgroundSpriteManager(files);
else
backgroundSpriteManager = new BackgroundSpriteManager("Content/BackgroundSprites/BackgroundSpritePrefabs.xml");
}
this.level = level;
}
public void PlaceSprites(int amount)
{
backgroundSpriteManager.PlaceSprites(level, amount);
}
public void Update(float deltaTime)
{
dustOffset -= Vector2.UnitY * 10.0f * deltaTime;
backgroundSpriteManager.Update(deltaTime);
}
public void SetWallVertices(VertexPositionTexture[] vertices)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length,BufferUsage.WriteOnly);
wallVertices.SetData(vertices);
}
public void SetBodyVertices(VertexPositionTexture[] vertices)
{
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(vertices);
}
public void DrawBackground(SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundCreatureManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearWrap);
Vector2 backgroundPos = cam.WorldViewCenter;
backgroundPos.Y = -backgroundPos.Y;
backgroundPos /= 20.0f;
if (backgroundPos.Y < 1024)
{
if (backgroundPos.Y < 0)
{
backgroundTop.SourceRect = new Rectangle((int)backgroundPos.X, (int)backgroundPos.Y, 1024, (int)Math.Min(-backgroundPos.Y, 1024));
backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, Math.Min(-backgroundPos.Y, GameMain.GraphicsHeight)),
Vector2.Zero, level.BackgroundColor);
}
if (backgroundPos.Y > -1024)
{
background.SourceRect = new Rectangle((int)backgroundPos.X, (int)Math.Max(backgroundPos.Y, 0), 1024, 1024);
background.DrawTiled(spriteBatch,
(backgroundPos.Y < 0) ? new Vector2(0.0f, (int)-backgroundPos.Y) : Vector2.Zero,
new Vector2(GameMain.GraphicsWidth, (int)Math.Ceiling(1024 - backgroundPos.Y)),
Vector2.Zero, level.BackgroundColor);
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.BackToFront,
BlendState.AlphaBlend,
SamplerState.LinearWrap, DepthStencilState.Default, null, null,
cam.Transform);
backgroundSpriteManager.DrawSprites(spriteBatch, cam);
if (backgroundCreatureManager!=null) backgroundCreatureManager.Draw(spriteBatch);
for (int i = 0; i < 4; i++)
{
float scale = 1.0f - i * 0.2f;
//alpha goes from 1.0 to 0.0 when scale is in the range of 0.2-0.1
float alpha = (cam.Zoom * scale) < 0.2f ? (cam.Zoom * scale - 0.1f) * 10.0f : 1.0f;
if (alpha <= 0.0f) continue;
Vector2 offset = (new Vector2(cam.WorldViewCenter.X, cam.WorldViewCenter.Y) + dustOffset) * scale;
Vector3 origin = new Vector3(cam.WorldView.Width, cam.WorldView.Height, 0.0f) * 0.5f;
dustParticles.SourceRect = new Rectangle(
(int)((offset.X - origin.X + (i * 256)) / scale),
(int)((-offset.Y - origin.Y + (i * 256)) / scale),
(int)((cam.WorldView.Width) / scale),
(int)((cam.WorldView.Height) / scale));
spriteBatch.Draw(dustParticles.Texture,
new Vector2(cam.WorldViewCenter.X, -cam.WorldViewCenter.Y),
dustParticles.SourceRect, Color.White * alpha, 0.0f,
new Vector2(cam.WorldView.Width, cam.WorldView.Height) * 0.5f / scale, scale, SpriteEffects.None, 1.0f - scale);
}
spriteBatch.End();
RenderWalls(GameMain.Instance.GraphicsDevice, cam);
}
public void Draw(SpriteBatch spriteBatch)
{
if (GameMain.DebugDraw)
{
var cells = level.GetCells(GameMain.GameScreen.Cam.WorldViewCenter, 2);
foreach (VoronoiCell cell in cells)
{
GUI.DrawRectangle(spriteBatch, new Vector2(cell.Center.X - 10.0f, -cell.Center.Y-10.0f), new Vector2(20.0f, 20.0f), Color.Cyan, true);
GUI.DrawLine(spriteBatch,
new Vector2(cell.edges[0].point1.X, -cell.edges[0].point1.Y),
new Vector2(cell.Center.X, -cell.Center.Y),
Color.Blue*0.5f);
foreach (GraphEdge edge in cell.edges)
{
GUI.DrawLine(spriteBatch, new Vector2(edge.point1.X, -edge.point1.Y),
new Vector2(edge.point2.X, -edge.point2.Y), cell.body==null ? Color.Cyan*0.5f : Color.White);
}
foreach (Vector2 point in cell.bodyVertices)
{
GUI.DrawRectangle(spriteBatch, new Vector2(point.X, -point.Y), new Vector2(10.0f, 10.0f), Color.White, true);
}
}
}
Vector2 pos = new Vector2(0.0f, -level.Size.Y);
if (GameMain.GameScreen.Cam.WorldView.Y < -pos.Y - 1024) return;
pos.X = GameMain.GameScreen.Cam.WorldView.X -1024;
int width = (int)(Math.Ceiling(GameMain.GameScreen.Cam.WorldView.Width / 1024 + 4.0f) * 1024);
GUI.DrawRectangle(spriteBatch,new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)-GameMain.GameScreen.Cam.WorldView.Y, width, (int)(GameMain.GameScreen.Cam.WorldView.Y + pos.Y) - 30),Color.Black, true);
spriteBatch.Draw(shaftTexture,
new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)pos.Y-1000, width, 1024),
new Rectangle(0, 0, width, -1024),
level.BackgroundColor, 0.0f,
Vector2.Zero,
SpriteEffects.None, 0.0f);
}
public void RenderWalls(GraphicsDevice graphicsDevice, Camera cam)
{
if (wallVertices == null) return;
wallEdgeEffect.World = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 100) * 0.5f;
wallCenterEffect.World = wallEdgeEffect.World;
//render the solid center of the wall cells
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
graphicsDevice.SetVertexBuffer(bodyVertices);
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(bodyVertices.VertexCount / 3.0f));
//render the edges of the wall cells
graphicsDevice.SetVertexBuffer(wallVertices);
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wallVertices.VertexCount / 3.0f));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (wallVertices!=null) wallVertices.Dispose();
if (bodyVertices != null) bodyVertices.Dispose();
}
}
}
@@ -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, false) < 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, false);
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, false);
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, false), 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, false);
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, false);
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, false);
int jOffset = Rand.Int(leaves2.Count, false);
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,472 @@
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;
}
}
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, false);
float verticalProbability = Rand.Range(0.4f, 0.6f, false);
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, false), Rand.Range(0.5f, 0.9f, false))
);
}
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, false) > 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, false)]);
Vector2 size = (prop.Prefab is StructurePrefab) ? ((StructurePrefab)prop.Prefab).Size : Vector2.Zero;
var shape = shapes[Rand.Int(shapes.Count, false)];
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, false), 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, false), 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, false));
}
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, false));
}
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, false)];
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, false) < 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, false),
Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom,false)), 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;
}
public void Draw(SpriteBatch spriteBatch)
{
//foreach (BTRoom room in leaves)
//{
// GUI.DrawRectangle(spriteBatch, room.Rect, Color.White);
//}
//foreach (Corridor corr in corridors)
//{
// GUI.DrawRectangle(spriteBatch, corr.Rect, Color.Blue);
//}
foreach (Line line in walls)
{
GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), Color.Red, 0.0f, 10);
}
}
}
}
@@ -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, false);
foreach (RuinStructure ruinStructure in matchingStructures)
{
if (randomNumber <= ruinStructure.commonness)
{
return ruinStructure;
}
randomNumber -= ruinStructure.commonness;
}
return null;
}
}
}
+998
View File
@@ -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,124 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
namespace Barotrauma
{
class WaterRenderer : IDisposable
{
const int DefaultBufferSize = 1500;
private Vector2 wavePos;
public VertexPositionTexture[] vertices = new VertexPositionTexture[DefaultBufferSize];
private Effect waterEffect;
private BasicEffect basicEffect;
public int PositionInBuffer = 0;
private Texture2D waterTexture;
public Texture2D WaterTexture
{
get { return waterTexture; }
}
public WaterRenderer(GraphicsDevice graphicsDevice, ContentManager content)
{
#if WINDOWS
waterEffect = content.Load<Effect>("watershader");
#endif
#if LINUX
waterEffect = content.Load<Effect>("watershader_opengl");
#endif
waterTexture = TextureLoader.FromFile("Content/waterbump.png");
waterEffect.Parameters["xWaveWidth"].SetValue(0.05f);
waterEffect.Parameters["xWaveHeight"].SetValue(0.05f);
#if WINDOWS
//waterEffect.Parameters["xTexture"].SetValue(waterTexture);
#endif
#if LINUX
waterEffect.Parameters["xWaterBumpMap"].SetValue(waterTexture);
#endif
if (basicEffect == null)
{
basicEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
basicEffect.VertexColorEnabled = false;
basicEffect.TextureEnabled = true;
}
}
public void RenderBack(SpriteBatch spriteBatch, RenderTarget2D texture, float blurAmount = 0.0f)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap, null, null, waterEffect);
waterEffect.CurrentTechnique = waterEffect.Techniques["WaterShader"];
waterEffect.Parameters["xWavePos"].SetValue(wavePos);
waterEffect.Parameters["xBlurDistance"].SetValue(blurAmount);
//waterEffect.CurrentTechnique.Passes[0].Apply();
#if WINDOWS
waterEffect.Parameters["xTexture"].SetValue(texture);
spriteBatch.Draw(waterTexture, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
#elif LINUX
spriteBatch.Draw(texture, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
#endif
spriteBatch.End();
}
public void ScrollWater(float deltaTime)
{
wavePos.X += 0.006f * deltaTime;
wavePos.Y += 0.006f * deltaTime;
}
public void Render(GraphicsDevice graphicsDevice, Camera cam, RenderTarget2D texture, Matrix transform)
{
if (vertices == null) return;
if (vertices.Length < 0) return;
basicEffect.Texture = texture;
basicEffect.View = Matrix.Identity;
basicEffect.World = transform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
basicEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
if (waterEffect != null)
{
waterEffect.Dispose();
waterEffect = null;
}
if (basicEffect != null)
{
basicEffect.Dispose();
basicEffect = null;
}
}
}
}
@@ -0,0 +1,201 @@
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
{
class WrappingWall : IDisposable
{
public const float WallWidth = 20000.0f;
private VertexBuffer wallVertices, bodyVertices;
private Vector2 midPos;
private int slot;
private Vector2 offset;
private List<VoronoiCell> cells;
public VertexBuffer WallVertices
{
get { return wallVertices; }
}
public VertexBuffer BodyVertices
{
get { return bodyVertices; }
}
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, false), Rand.Range(-variance, variance, false)*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 void SetWallVertices(VertexPositionTexture[] vertices)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(vertices);
}
public void SetBodyVertices(VertexPositionColor[] vertices)
{
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(vertices);
}
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 (wallVertices != null)
{
wallVertices.Dispose();
wallVertices = null;
}
if (bodyVertices != null)
{
bodyVertices.Dispose();
bodyVertices = null;
}
}
}
}
+677
View File
@@ -0,0 +1,677 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Barotrauma.Lights
{
/*class CachedShadow : IDisposable
{
public VertexBuffer ShadowBuffer;
public Vector2 LightPos;
public int ShadowVertexCount, PenumbraVertexCount;
public CachedShadow(VertexPositionColor[] shadowVertices, Vector2 lightPos, int shadowVertexCount, int penumbraVertexCount)
{
//var ShadowVertices = new VertexPositionColor [shadowVertices.Count()];
//shadowVertices.CopyTo(ShadowVertices, 0);
ShadowBuffer = new VertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionColor.VertexDeclaration, 6*2, BufferUsage.None);
ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length);
ShadowVertexCount = shadowVertexCount;
PenumbraVertexCount = penumbraVertexCount;
LightPos = lightPos;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
ShadowBuffer.Dispose();
}
}*/
class ConvexHullList
{
private List<ConvexHull> list;
public readonly Submarine Submarine;
public List<ConvexHull> List
{
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
}
}
public ConvexHullList(Submarine submarine)
{
Submarine = submarine;
list = new List<ConvexHull>();
}
}
class Segment
{
public SegmentPoint Start;
public SegmentPoint End;
public bool IsHorizontal;
public Segment(SegmentPoint start, SegmentPoint end)
{
Start = start;
End = end;
start.Segment = this;
end.Segment = this;
IsHorizontal = Math.Abs(start.Pos.X - end.Pos.X) > Math.Abs(start.Pos.Y - end.Pos.Y);
}
}
struct SegmentPoint
{
public Vector2 Pos;
public Vector2 WorldPos;
public Segment Segment;
public SegmentPoint(Vector2 pos)
{
Pos = pos;
WorldPos = pos;
Segment = null;
}
public override string ToString()
{
return Pos.ToString();
}
}
class ConvexHull
{
public static List<ConvexHullList> HullLists = new List<ConvexHullList>();
static BasicEffect shadowEffect;
static BasicEffect penumbraEffect;
//private Dictionary<LightSource, CachedShadow> cachedShadows;
public VertexBuffer ShadowBuffer;
Segment[] segments = new Segment[4];
SegmentPoint[] vertices = new SegmentPoint[4];
SegmentPoint[] losVertices = new SegmentPoint[4];
//private Vector2[] vertices;
//private Vector2[] losVertices;
private bool[] backFacing;
private bool[] ignoreEdge;
private VertexPositionColor[] shadowVertices;
private VertexPositionTexture[] penumbraVertices;
int shadowVertexCount;
private Entity parentEntity;
private Rectangle boundingBox;
public Entity ParentEntity
{
get { return parentEntity; }
}
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled == value) return;
enabled = value;
LastVertexChangeTime = (float)Timing.TotalTime;
}
}
/// <summary>
/// The elapsed gametime when the vertices of this hull last changed
/// </summary>
public float LastVertexChangeTime
{
get;
private set;
}
public Rectangle BoundingBox
{
get { return boundingBox; }
}
public ConvexHull(Vector2[] points, Color color, Entity parent)
{
if (shadowEffect == null)
{
shadowEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
shadowEffect.VertexColorEnabled = true;
}
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
penumbraEffect.TextureEnabled = true;
//shadowEffect.VertexColorEnabled = true;
penumbraEffect.LightingEnabled = false;
penumbraEffect.Texture = TextureLoader.FromFile("Content/Lights/penumbra.png");
}
parentEntity = parent;
//cachedShadows = new Dictionary<LightSource, CachedShadow>();
shadowVertices = new VertexPositionColor[6 * 2];
penumbraVertices = new VertexPositionTexture[6];
//vertices = points;
SetVertices(points);
//CalculateDimensions();
backFacing = new bool[4];
ignoreEdge = new bool[4];
Enabled = true;
var chList = HullLists.Find(x => x.Submarine == parent.Submarine);
if (chList == null)
{
chList = new ConvexHullList(parent.Submarine);
HullLists.Add(chList);
}
foreach (ConvexHull ch in chList.List)
{
UpdateIgnoredEdges(ch);
ch.UpdateIgnoredEdges(this);
}
chList.List.Add(this);
}
private void UpdateIgnoredEdges(ConvexHull ch)
{
if (ch == this) return;
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.X >= ch.boundingBox.X && vertices[i].Pos.X <= ch.boundingBox.Right &&
vertices[i].Pos.Y >= ch.boundingBox.Y && vertices[i].Pos.Y <= ch.boundingBox.Bottom)
{
Vector2 p = vertices[(i + 1) % vertices.Length].Pos;
if (p.X >= ch.boundingBox.X && p.X <= ch.boundingBox.Right &&
p.Y >= ch.boundingBox.Y && p.Y <= ch.boundingBox.Bottom)
{
ignoreEdge[i] = true;
}
}
}
}
private void CalculateDimensions()
{
float minX = vertices[0].Pos.X, minY = vertices[0].Pos.Y, maxX = vertices[0].Pos.X, maxY = vertices[0].Pos.Y;
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X;
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y;
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X;
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
}
boundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
public void Move(Vector2 amount)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos += amount;
losVertices[i].Pos += amount;
segments[i].Start.Pos += amount;
segments[i].End.Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime;
CalculateDimensions();
}
public void SetVertices(Vector2[] points)
{
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
LastVertexChangeTime = (float)Timing.TotalTime;
for (int i = 0; i < 4; i++)
{
vertices[i] = new SegmentPoint(points[i]);
losVertices[i] = new SegmentPoint(points[i]);
}
for (int i = 0; i < 4; i++)
{
segments[i] = new Segment(vertices[i], vertices[(i + 1) % 4]);
}
int margin = 0;
if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[1].Y))
{
losVertices[0].Pos = new Vector2(points[0].X + margin, points[0].Y);
losVertices[1].Pos = new Vector2(points[1].X + margin, points[1].Y);
losVertices[2].Pos = new Vector2(points[2].X - margin, points[2].Y);
losVertices[3].Pos = new Vector2(points[3].X - margin, points[3].Y);
}
else
{
losVertices[0].Pos = new Vector2(points[0].X, points[0].Y + margin);
losVertices[1].Pos = new Vector2(points[1].X, points[1].Y - margin);
losVertices[2].Pos = new Vector2(points[2].X, points[2].Y - margin);
losVertices[3].Pos = new Vector2(points[3].X, points[3].Y + margin);
}
CalculateDimensions();
if (parentEntity == null || ignoreEdge == null) return;
for (int i = 0; i<4; i++)
{
ignoreEdge[i] = false;
}
var chList = HullLists.Find(x => x.Submarine == parentEntity.Submarine);
if (chList != null)
{
foreach (ConvexHull ch in chList.List)
{
UpdateIgnoredEdges(ch);
}
}
}
/*private void RemoveCachedShadow(Lights.LightSource light)
{
CachedShadow shadow = null;
cachedShadows.TryGetValue(light, out shadow);
if (shadow != null)
{
shadow.Dispose();
cachedShadows.Remove(light);
}
}
private void ClearCachedShadows()
{
foreach (KeyValuePair<LightSource, CachedShadow> cachedShadow in cachedShadows)
{
cachedShadow.Key.NeedsHullUpdate = true;
cachedShadow.Value.Dispose();
}
cachedShadows.Clear();
}*/
public bool Intersects(Rectangle rect)
{
if (!Enabled) return false;
Rectangle transformedBounds = boundingBox;
if (parentEntity != null && parentEntity.Submarine != null)
{
transformedBounds.X += (int)parentEntity.Submarine.Position.X;
transformedBounds.Y += (int)parentEntity.Submarine.Position.Y;
}
return transformedBounds.Intersects(rect);
}
/// <summary>
/// Returns the segments that are facing towards viewPosition
/// </summary>
public List<Segment> GetVisibleSegments(Vector2 viewPosition)
{
List<Segment> visibleFaces = new List<Segment>();
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i]) continue;
Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
Vector2 middle = (pos1 + pos2) / 2;
Vector2 L = viewPosition - middle;
Vector2 N = new Vector2(
-(pos2.Y - pos1.Y),
pos2.X - pos1.X);
if (Vector2.Dot(N, L) > 0)
{
visibleFaces.Add(segments[i]);
}
}
return visibleFaces;
}
public void RefreshWorldPositions()
{
if (parentEntity == null || parentEntity.Submarine == null) return;
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos = vertices[i].Pos + parentEntity.Submarine.DrawPosition;
segments[i].Start.WorldPos = segments[i].Start.Pos + parentEntity.Submarine.DrawPosition;
segments[i].End.WorldPos = segments[i].End.Pos + parentEntity.Submarine.DrawPosition;
}
}
private void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true)
{
shadowVertexCount = 0;
var vertices = los ? losVertices : this.vertices;
//compute facing of each edge, using N*L
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i])
{
backFacing[i] = false;
continue;
}
Vector2 firstVertex = vertices[i].Pos;
Vector2 secondVertex = vertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
Vector2 N = new Vector2(
-(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0) == los;
}
//find beginning and ending vertices which
//belong to the shadow
int startingIndex = 0;
int endingIndex = 0;
for (int i = 0; i < 4; i++)
{
int currentEdge = i;
int nextEdge = (i + 1) % 4;
if (backFacing[currentEdge] && !backFacing[nextEdge])
endingIndex = nextEdge;
if (!backFacing[currentEdge] && backFacing[nextEdge])
startingIndex = nextEdge;
}
//nr of vertices that are in the shadow
if (endingIndex > startingIndex)
shadowVertexCount = endingIndex - startingIndex + 1;
else
shadowVertexCount = 4 + 1 - startingIndex + endingIndex;
//shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != shadowVertexCount * 2)
{
Vector3 vertexPos = new Vector3(vertices[currentIndex].Pos, 0.0f);
int i = los ? svCount : svCount + 1;
int j = los ? svCount + 1 : svCount;
//one vertex on the hull
shadowVertices[i] = new VertexPositionColor();
shadowVertices[i].Color = los ? Color.Black : Color.Transparent;
shadowVertices[i].Position = vertexPos;
//one extruded by the light direction
shadowVertices[j] = new VertexPositionColor();
shadowVertices[j].Color = shadowVertices[i].Color;
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
L2P.Normalize();
shadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000;
svCount += 2;
currentIndex = (currentIndex + 1) % 4;
}
if (los)
{
CalculatePenumbraVertices(startingIndex, endingIndex, lightSourcePos, los);
}
}
private void CalculatePenumbraVertices(int startingIndex, int endingIndex, Vector2 lightSourcePos, bool los)
{
for (int n = 0; n < 4; n += 3)
{
Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f);
penumbraVertices[n] = new VertexPositionTexture();
penumbraVertices[n].Position = penumbraStart;
penumbraVertices[n].TextureCoordinate = new Vector2(0.0f, 1.0f);
//penumbraVertices[0].te = fow ? Color.Black : Color.Transparent;
for (int i = 0; i < 2; i++)
{
penumbraVertices[n + i + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (i == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
if (n > 0) normal = -normal;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
penumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000;
if (los)
{
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
else
{
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero;
}
}
if (n > 0)
{
var temp = penumbraVertices[4];
penumbraVertices[4] = penumbraVertices[5];
penumbraVertices[5] = temp;
}
}
}
public static List<ConvexHull> GetHullsInRange(Vector2 position, float range, Submarine ParentSub)
{
List<ConvexHull> list = new List<ConvexHull>();
foreach (ConvexHullList chList in ConvexHull.HullLists)
{
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhull are both outside
if (chList.Submarine == null)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light is outside, convexhull inside a sub
else
{
if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, chList.Submarine.Borders)) continue;
lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition);
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
else
{
//light is inside, convexhull outside
if (chList.Submarine == null) continue;
//light and convexhull are both inside the same sub
if (chList.Submarine == ParentSub)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light and convexhull are inside different subs
else
{
lightPos -= (chList.Submarine.Position - ParentSub.Position);
Rectangle subBorders = chList.Submarine.Borders;
subBorders.Location += chList.Submarine.HiddenSubPosition.ToPoint() - new Point(0, chList.Submarine.Borders.Height);
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) continue;
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
}
return list;
}
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, LightSource light, Matrix transform, bool los = true)
{
if (!Enabled) return;
Vector2 lightSourcePos = light.Position;
if (parentEntity != null && parentEntity.Submarine != null)
{
if (light.ParentSub == null)
{
lightSourcePos -= parentEntity.Submarine.Position;
}
else if (light.ParentSub != parentEntity.Submarine)
{
lightSourcePos += (light.ParentSub.Position-parentEntity.Submarine.Position);
}
}
CalculateShadowVertices(lightSourcePos, los);
ShadowBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, 6 * 2, BufferUsage.None);
ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length);
graphicsDevice.SetVertexBuffer(ShadowBuffer);
shadowVertexCount = shadowVertices.Length;
DrawShadows(graphicsDevice, cam, transform, los);
}
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, Matrix transform, bool los = true)
{
if (!Enabled) return;
if (parentEntity != null && parentEntity.Submarine != null) lightSourcePos -= parentEntity.Submarine.Position;
CalculateShadowVertices(lightSourcePos, los);
DrawShadows(graphicsDevice, cam, transform, los);
}
private void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Matrix transform, bool los = true)
{
Vector3 offset = Vector3.Zero;
if (parentEntity != null && parentEntity.Submarine != null)
{
offset = new Vector3(parentEntity.Submarine.DrawPosition.X, parentEntity.Submarine.DrawPosition.Y, 0.0f);
}
if (shadowVertexCount>0)
{
shadowEffect.World = Matrix.CreateTranslation(offset) * transform;
if (los)
{
shadowEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 - 2, VertexPositionColor.VertexDeclaration);
}
else
{
shadowEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, shadowVertexCount * 2 - 2);
}
}
if (los)
{
penumbraEffect.World = shadowEffect.World;
penumbraEffect.CurrentTechnique.Passes[0].Apply();
#if WINDOWS
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVertices, 0, 2, VertexPositionTexture.VertexDeclaration);
#endif
}
}
public void Remove()
{
var chList = HullLists.Find(x => x.Submarine == parentEntity.Submarine);
if (chList != null)
{
chList.List.Remove(this);
if (chList.List.Count == 0)
{
HullLists.Remove(chList);
}
}
}
}
}
@@ -0,0 +1,392 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Barotrauma.Lights
{
class LightManager
{
private const float AmbientLightUpdateInterval = 0.2f;
private const float AmbientLightFalloff = 0.8f;
private static Entity viewTarget;
public static Entity ViewTarget
{
get { return viewTarget; }
set {
if (viewTarget == value) return;
viewTarget = value;
}
}
public Color AmbientLight;
RenderTarget2D lightMap, losTexture;
BasicEffect lightEffect;
private static Texture2D alphaClearTexture;
private List<LightSource> lights;
public bool LosEnabled = true;
public bool LightingEnabled = true;
public bool ObstructVision;
private Texture2D visionCircle;
private Dictionary<Hull, Color> hullAmbientLights;
private Dictionary<Hull, Color> smoothedHullAmbientLights;
private float ambientLightUpdateTimer;
public LightManager(GraphicsDevice graphics)
{
lights = new List<LightSource>();
AmbientLight = new Color(20, 20, 20, 255);
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
var pp = graphics.PresentationParameters;
lightMap = new RenderTarget2D(graphics,
GameMain.GraphicsWidth, GameMain.GraphicsHeight, false,
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
RenderTargetUsage.DiscardContents);
losTexture = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
if (lightEffect == null)
{
lightEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
lightEffect.VertexColorEnabled = false;
lightEffect.TextureEnabled = true;
lightEffect.Texture = LightSource.LightTexture;
}
hullAmbientLights = new Dictionary<Hull, Color>();
smoothedHullAmbientLights = new Dictionary<Hull, Color>();
if (alphaClearTexture == null)
{
alphaClearTexture = TextureLoader.FromFile("Content/Lights/alphaOne.png");
}
}
public void AddLight(LightSource light)
{
lights.Add(light);
}
public void RemoveLight(LightSource light)
{
lights.Remove(light);
}
public void OnMapLoaded()
{
foreach (LightSource light in lights)
{
light.NeedsHullCheck = true;
light.NeedsRecalculation = true;
}
}
public void Update(float deltaTime)
{
if (ambientLightUpdateTimer > 0.0f)
{
ambientLightUpdateTimer -= deltaTime;
}
else
{
CalculateAmbientLights();
ambientLightUpdateTimer = AmbientLightUpdateInterval;
}
foreach (Hull hull in hullAmbientLights.Keys)
{
if (!smoothedHullAmbientLights.ContainsKey(hull))
{
smoothedHullAmbientLights.Add(hull, Color.TransparentBlack);
}
}
foreach (Hull hull in smoothedHullAmbientLights.Keys.ToList())
{
Color targetColor = Color.TransparentBlack;
hullAmbientLights.TryGetValue(hull, out targetColor);
smoothedHullAmbientLights[hull] = Color.Lerp(smoothedHullAmbientLights[hull], targetColor, deltaTime);
}
}
public void UpdateLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Effect blur)
{
if (!LightingEnabled) return;
graphics.SetRenderTarget(lightMap);
Rectangle viewRect = cam.WorldView;
viewRect.Y -= cam.WorldView.Height;
//clear to some small ambient light
graphics.Clear(AmbientLight);
graphics.BlendState = BlendState.Additive;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, null, null, null, cam.Transform);
Matrix transform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
Vector3 offset = Vector3.Zero;// new Vector3(Submarine.MainSub.DrawPosition.X, Submarine.MainSub.DrawPosition.Y, 0.0f);
foreach (LightSource light in lights)
{
if (light.Color.A < 1 || light.Range < 1.0f || !light.CastShadows) continue;
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.Range, viewRect)) continue;
light.Draw(spriteBatch, lightEffect, transform);
}
lightEffect.World = Matrix.CreateTranslation(offset) * transform;
GameMain.ParticleManager.Draw(spriteBatch, false, Particles.ParticleBlendState.Additive);
if (Character.Controlled != null)
{
if (Character.Controlled.ClosestItem != null)
{
Character.Controlled.ClosestItem.IsHighlighted = true;
Character.Controlled.ClosestItem.Draw(spriteBatch, false, true);
Character.Controlled.ClosestItem.IsHighlighted = true;
}
else if (Character.Controlled.ClosestCharacter != null)
{
Character.Controlled.ClosestCharacter.Draw(spriteBatch);
}
}
foreach (Hull hull in smoothedHullAmbientLights.Keys)
{
if (smoothedHullAmbientLights[hull].A < 0.01f) continue;
var drawRect =
hull.Submarine == null ?
hull.Rect :
new Rectangle((int)(hull.Submarine.DrawPosition.X + hull.Rect.X), (int)(hull.Submarine.DrawPosition.Y + hull.Rect.Y), hull.Rect.Width, hull.Rect.Height);
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(hull.Rect.Width, hull.Rect.Height),
smoothedHullAmbientLights[hull] * 0.5f, true);
}
spriteBatch.End();
//clear alpha, to avoid messing stuff up later
//ClearAlphaToOne(graphics, spriteBatch);
graphics.SetRenderTarget(null);
graphics.BlendState = BlendState.AlphaBlend;
}
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if (!LosEnabled && !ObstructVision) return;
graphics.SetRenderTarget(losTexture);
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, cam.Transform);
if (ObstructVision)
{
//graphics.Clear(Color.Black);
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
diff.Y = -diff.Y;
float rotation = MathUtils.VectorToAngle(diff);
Vector2 scale = new Vector2(MathHelper.Clamp(diff.Length()/256.0f, 2.0f, 5.0f), 2.0f);
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
new Vector2(LightSource.LightTexture.Width*0.2f, LightSource.LightTexture.Height/2), scale, SpriteEffects.None, 0.0f);
}
else
{
graphics.Clear(Color.White);
}
spriteBatch.End();
//--------------------------------------
if (LosEnabled && ViewTarget != null)
{
Vector2 pos = ViewTarget.WorldPosition;
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
Matrix shadowTransform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
var convexHulls = ConvexHull.GetHullsInRange(viewTarget.Position, cam.WorldView.Width*0.75f, viewTarget.Submarine);
if (convexHulls != null)
{
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Intersects(camView)) continue;
//if (!camView.Intersects(convexHull.BoundingBox)) continue;
convexHull.DrawShadows(graphics, cam, pos, shadowTransform);
}
}
}
graphics.SetRenderTarget(null);
}
private void CalculateAmbientLights()
{
hullAmbientLights.Clear();
foreach (LightSource light in lights)
{
if (light.Color.A < 1f || light.Range < 1.0f) continue;
var newAmbientLights = AmbientLightHulls(light);
foreach (Hull hull in newAmbientLights.Keys)
{
if (hullAmbientLights.ContainsKey(hull))
{
//hull already lit by some other light source -> add the ambient lights up
hullAmbientLights[hull] = new Color(
hullAmbientLights[hull].R + newAmbientLights[hull].R,
hullAmbientLights[hull].G + newAmbientLights[hull].G,
hullAmbientLights[hull].B + newAmbientLights[hull].B,
hullAmbientLights[hull].A + newAmbientLights[hull].A);
}
else
{
hullAmbientLights.Add(hull, newAmbientLights[hull]);
}
}
}
}
/// <summary>
/// Add ambient light to the hull the lightsource is inside + all adjacent hulls connected by a gap
/// </summary>
private Dictionary<Hull, Color> AmbientLightHulls(LightSource light)
{
Dictionary<Hull, Color> hullAmbientLight = new Dictionary<Hull, Color>();
var hull = Hull.FindHull(light.WorldPosition);
if (hull == null) return hullAmbientLight;
return AmbientLightHulls(hull, hullAmbientLight, light.Color * (light.Range/2000.0f));
}
/// <summary>
/// A flood fill algorithm that adds ambient light to all hulls the starting hull is connected to
/// </summary>
private Dictionary<Hull, Color> AmbientLightHulls(Hull hull, Dictionary<Hull, Color> hullAmbientLight, Color currColor)
{
if (hullAmbientLight.ContainsKey(hull))
{
if (hullAmbientLight[hull].A > currColor.A)
return hullAmbientLight;
else
hullAmbientLight[hull] = currColor;
}
else
{
hullAmbientLight.Add(hull, currColor);
}
Color nextHullLight = currColor * AmbientLightFalloff;
//light getting too dark to notice -> no need to spread further
if (nextHullLight.A < 20) return hullAmbientLight;
//use hashset to make sure that each hull is only included once
HashSet<Hull> hulls = new HashSet<Hull>();
foreach (Gap g in hull.ConnectedGaps)
{
if (!g.IsRoomToRoom || !g.PassAmbientLight || g.Open < 0.5f) continue;
hulls.Add((g.linkedTo[0] == hull ? g.linkedTo[1] : g.linkedTo[0]) as Hull);
}
foreach (Hull h in hulls)
{
hullAmbientLight = AmbientLightHulls(h, hullAmbientLight, nextHullLight);
}
return hullAmbientLight;
}
public void DrawLightMap(SpriteBatch spriteBatch, Effect effect)
{
if (!LightingEnabled) return;
spriteBatch.Begin(SpriteSortMode.Deferred, CustomBlendStates.Multiplicative, null, null, null, effect);
spriteBatch.Draw(lightMap, Vector2.Zero, Color.White);
spriteBatch.End();
}
public void DrawLOS(SpriteBatch spriteBatch, Effect effect,bool renderingBackground)
{
if (!LosEnabled || ViewTarget == null) return;
spriteBatch.Begin(SpriteSortMode.Deferred, renderingBackground ? CustomBlendStates.LOS : CustomBlendStates.Multiplicative, null, null, null, effect);
spriteBatch.Draw(losTexture, Vector2.Zero, Color.White);
spriteBatch.End();
if (!renderingBackground) ObstructVision = false;
}
public void ClearLights()
{
lights.Clear();
}
}
class CustomBlendStates
{
static CustomBlendStates()
{
Multiplicative = new BlendState();
Multiplicative.ColorSourceBlend = Multiplicative.AlphaSourceBlend = Blend.Zero;
Multiplicative.ColorDestinationBlend = Multiplicative.AlphaDestinationBlend = Blend.SourceColor;
Multiplicative.ColorBlendFunction = Multiplicative.AlphaBlendFunction = BlendFunction.Add;
WriteToAlpha = new BlendState();
WriteToAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
MultiplyWithAlpha = new BlendState();
MultiplyWithAlpha.ColorDestinationBlend = MultiplyWithAlpha.AlphaDestinationBlend = Blend.One;
MultiplyWithAlpha.ColorSourceBlend = MultiplyWithAlpha.AlphaSourceBlend = Blend.DestinationAlpha;
LOS = new BlendState();
LOS.ColorSourceBlend = LOS.AlphaSourceBlend = Blend.Zero;
LOS.ColorDestinationBlend = LOS.AlphaDestinationBlend = Blend.InverseSourceColor;
LOS.ColorBlendFunction = LOS.AlphaBlendFunction = BlendFunction.Add;
}
public static BlendState Multiplicative { get; private set; }
public static BlendState WriteToAlpha { get; private set; }
public static BlendState MultiplyWithAlpha { get; private set; }
public static BlendState LOS { get; private set; }
}
}
+612
View File
@@ -0,0 +1,612 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Lights
{
class LightSource
{
private static Texture2D lightTexture;
private List<ConvexHullList> hullsInRange;
private Color color;
private float range;
private Sprite overrideLightTexture;
private Texture2D texture;
public Sprite LightSprite;
public Submarine ParentSub;
public bool CastShadows;
//what was the range of the light when lightvolumes were last calculated
private float prevCalculatedRange;
private Vector2 prevCalculatedPosition;
//do we need to recheck which convex hulls are within range
//(e.g. position or range of the lightsource has changed)
public bool NeedsHullCheck = true;
//do we need to recalculate the vertices of the light volume
public bool NeedsRecalculation = true;
//when were the vertices of the light volume last calculated
private float lastRecalculationTime;
private Dictionary<Submarine, Vector2> diffToSub;
private DynamicVertexBuffer lightVolumeBuffer;
private DynamicIndexBuffer lightVolumeIndexBuffer;
private int vertexCount;
private int indexCount;
private Vector2 position;
public Vector2 Position
{
get { return position; }
set
{
if (position == value) return;
position = value;
if (Vector2.Distance(prevCalculatedPosition, position) < 5.0f) return;
NeedsHullCheck = true;
NeedsRecalculation = true;
prevCalculatedPosition = position;
}
}
private float rotation;
public float Rotation
{
get { return rotation; }
set
{
if (rotation == value) return;
rotation = value;
NeedsHullCheck = true;
NeedsRecalculation = true;
}
}
public Vector2 WorldPosition
{
get { return (ParentSub == null) ? position : position + ParentSub.Position; }
}
public static Texture2D LightTexture
{
get
{
if (lightTexture == null)
{
lightTexture = TextureLoader.FromFile("Content/Lights/light.png");
}
return lightTexture;
}
}
public Color Color
{
get { return color; }
set { color = value; }
}
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
if (Math.Abs(prevCalculatedRange - range) < 10.0f) return;
NeedsHullCheck = true;
NeedsRecalculation = true;
prevCalculatedRange = range;
}
}
public LightSource (XElement element)
: this(Vector2.Zero, 100.0f, Color.White, null)
{
range = ToolBox.GetAttributeFloat(element, "range", 100.0f);
color = new Color(ToolBox.GetAttributeVector4(element, "color", Vector4.One));
CastShadows = ToolBox.GetAttributeBool(element, "castshadows", true);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
LightSprite = new Sprite(subElement);
break;
case "lighttexture":
overrideLightTexture = new Sprite(subElement);
break;
}
}
}
public LightSource(Vector2 position, float range, Color color, Submarine submarine)
{
hullsInRange = new List<ConvexHullList>();
this.ParentSub = submarine;
this.position = position;
this.range = range;
this.color = color;
CastShadows = true;
texture = LightTexture;
diffToSub = new Dictionary<Submarine, Vector2>();
GameMain.LightManager.AddLight(this);
}
/*public void DrawShadows(GraphicsDevice graphics, Camera cam, Matrix shadowTransform)
{
if (!CastShadows) return;
if (range < 1.0f || color.A < 0.01f) return;
foreach (Submarine sub in Submarine.Loaded)
{
var hulls = GetHullsInRange(sub);
if (hulls == null) continue;
foreach ( ConvexHull ch in hulls)
{
ch.DrawShadows(graphics, cam, this, shadowTransform, false);
}
}
var outsideHulls = GetHullsInRange(null);
NeedsHullUpdate = false;
if (outsideHulls == null) return;
foreach (ConvexHull ch in outsideHulls)
{
ch.DrawShadows(graphics, cam, this, shadowTransform, false);
}
}*/
/// <summary>
/// Update the contents of ConvexHullList and check if we need to recalculate vertices
/// </summary>
private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub)
{
var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub);
if (fullChList == null) return;
chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox));
NeedsHullCheck = true;
}
/// <summary>
/// Recheck which convex hulls are in range (if needed),
/// and check if we need to recalculate vertices due to changes in the convex hulls
/// </summary>
private void CheckHullsInRange()
{
List<Submarine> subs = new List<Submarine>(Submarine.Loaded);
subs.Add(null);
foreach (Submarine sub in subs)
{
//find the list of convexhulls that belong to the sub
var chList = hullsInRange.Find(x => x.Submarine == sub);
//not found -> create one
if (chList == null)
{
chList = new ConvexHullList(sub);
hullsInRange.Add(chList);
NeedsRecalculation = true;
}
if (chList.List.Any(ch => ch.LastVertexChangeTime > lastRecalculationTime))
{
NeedsRecalculation = true;
}
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhulls are both outside
if (sub == null)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, null);
}
}
//light is outside, convexhulls inside a sub
else
{
lightPos -= sub.Position;
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//only draw if the light overlaps with the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders))
{
if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear();
continue;
}
RefreshConvexHullList(chList, lightPos, sub);
}
}
else
{
//light is inside, convexhull outside
if (sub == null) continue;
//light and convexhull are both inside the same sub
if (sub == ParentSub)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, sub);
}
}
//light and convexhull are inside different subs
else
{
if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) continue;
lightPos -= (sub.Position - ParentSub.Position);
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//don't draw any shadows if the light doesn't overlap with the borders of the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders))
{
if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear();
continue;
}
//recalculate vertices if the subs have moved > 5 px relative to each other
Vector2 diff = ParentSub.WorldPosition - sub.WorldPosition;
Vector2 prevDiff;
if (!diffToSub.TryGetValue(sub, out prevDiff))
{
diffToSub.Add(sub, diff);
NeedsRecalculation = true;
}
else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f*5.0f)
{
diffToSub[sub] = diff;
NeedsRecalculation = true;
}
RefreshConvexHullList(chList, lightPos, sub);
}
}
}
}
private List<Vector2> FindRaycastHits()
{
if (!CastShadows) return null;
if (range < 1.0f || color.A < 0.01f) return null;
Vector2 drawPos = position;
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
var hulls = new List<ConvexHull>();// ConvexHull.GetHullsInRange(position, range, ParentSub);
foreach (ConvexHullList chList in hullsInRange)
{
hulls.AddRange(chList.List);
}
//find convexhull segments that are close enough and facing towards the light source
List<Segment> visibleSegments = new List<Segment>();
List<SegmentPoint> points = new List<SegmentPoint>();
foreach (ConvexHull hull in hulls)
{
hull.RefreshWorldPositions();
var visibleHullSegments = hull.GetVisibleSegments(drawPos);
visibleSegments.AddRange(visibleHullSegments);
foreach (Segment s in visibleHullSegments)
{
points.Add(s.Start);
points.Add(s.End);
}
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
var boundaryCorners = new List<SegmentPoint> {
new SegmentPoint(new Vector2(drawPos.X + range*2, drawPos.Y + range*2)),
new SegmentPoint(new Vector2(drawPos.X + range*2, drawPos.Y - range*2)),
new SegmentPoint(new Vector2(drawPos.X - range*2, drawPos.Y - range*2)),
new SegmentPoint(new Vector2(drawPos.X - range*2, drawPos.Y + range*2))
};
points.AddRange(boundaryCorners);
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Sort(compareCCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: "+drawPos+", Hull verts:\n");
foreach (SegmentPoint sp in points)
{
sb.AppendLine(sp.Pos.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
List<Vector2> output = new List<Vector2>();
//remove points that are very close to each other
for (int i = 0; i < points.Count - 1; i++)
{
if (Math.Abs(points[i].WorldPos.X - points[i + 1].WorldPos.X) < 3 &&
Math.Abs(points[i].WorldPos.Y - points[i + 1].WorldPos.Y) < 3)
{
points.RemoveAt(i + 1);
}
}
foreach (SegmentPoint p in points)
{
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 dirNormal = new Vector2(-dir.Y, dir.X)*3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
Vector2 intersection1 = RayCast(drawPos, drawPos + dir * range * 2 - dirNormal, visibleSegments);
Vector2 intersection2 = RayCast(drawPos, drawPos + dir * range * 2 + dirNormal, visibleSegments);
//hit almost the same position -> only add one vertex to output
if ((Math.Abs(intersection1.X - intersection2.X) < 5 &&
Math.Abs(intersection1.Y - intersection2.Y) < 5))
{
output.Add(intersection1);
}
else
{
output.Add(intersection1);
output.Add(intersection2);
}
}
return output;
}
private Vector2 RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
{
float closestDist = 0.0f;
Vector2? closestIntersection = null;
foreach (Segment s in segments)
{
Vector2? intersection = MathUtils.GetAxisAlignedLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, s.IsHorizontal);
if (intersection != null)
{
float dist = Vector2.Distance((Vector2)intersection, rayStart);
if (closestIntersection == null || dist < closestDist)
{
closestDist = dist;
closestIntersection = intersection;
}
}
}
return closestIntersection == null ? rayEnd : (Vector2)closestIntersection;
}
private void CalculateLightVertices(List<Vector2> rayCastHits)
{
List<VertexPositionTexture> vertices = new List<VertexPositionTexture>();
Vector2 drawPos = position;
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
Vector2 uvOffset = Vector2.Zero;
Vector2 overrideTextureDims = Vector2.One;
if (overrideLightTexture != null)
{
overrideTextureDims = new Vector2(overrideLightTexture.SourceRect.Width, overrideLightTexture.SourceRect.Height);
uvOffset = (overrideLightTexture.Origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
}
// Add a vertex for the center of the mesh
vertices.Add(new VertexPositionTexture(new Vector3(position.X, position.Y, 0),
new Vector2(0.5f, 0.5f) + uvOffset));
// Add all the other encounter points as vertices
// storing their world position as UV coordinates
foreach (Vector2 vertex in rayCastHits)
{
Vector2 rawDiff = vertex - drawPos;
Vector2 diff = rawDiff;
diff /= range*2.0f;
if (overrideLightTexture != null)
{
Vector2 originDiff = diff;
diff.X = originDiff.X * cosAngle - originDiff.Y * sinAngle;
diff.Y = originDiff.X * sinAngle + originDiff.Y * cosAngle;
diff *= (overrideTextureDims / overrideLightTexture.size) * 2.0f;
diff += uvOffset;
}
vertices.Add(new VertexPositionTexture(new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0),
new Vector2(0.5f, 0.5f) + diff));
}
// Compute the indices to form triangles
List<short> indices = new List<short>();
for (int i = 0; i < rayCastHits.Count - 1; i++)
{
indices.Add(0);
indices.Add((short)((i + 2) % vertices.Count));
indices.Add((short)((i + 1) % vertices.Count));
}
indices.Add(0);
indices.Add((short)(1));
indices.Add((short)(vertices.Count - 1));
vertexCount = vertices.Count;
indexCount = indices.Count;
//TODO: a better way to determine the size of the vertex buffer and handle changes in size?
//now we just create a buffer for 64 verts and make it larger if needed
if (lightVolumeBuffer == null)
{
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount*1.5)), BufferUsage.None);
lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), Math.Max(64*3, (int)(indexCount * 1.5)), BufferUsage.None);
}
else if (vertexCount > lightVolumeBuffer.VertexCount)
{
lightVolumeBuffer.Dispose();
lightVolumeIndexBuffer.Dispose();
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, (int)(vertexCount*1.5), BufferUsage.None);
lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), (int)(indexCount * 1.5), BufferUsage.None);
}
lightVolumeBuffer.SetData<VertexPositionTexture>(vertices.ToArray());
lightVolumeIndexBuffer.SetData<short>(indices.ToArray());
}
public void Draw(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform)
{
CheckHullsInRange();
Vector3 offset = ParentSub == null ? Vector3.Zero :
new Vector3(ParentSub.DrawPosition.X, ParentSub.DrawPosition.Y, 0.0f);
lightEffect.World = Matrix.CreateTranslation(offset) * transform;
Vector2 drawPos = position;
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
drawPos.Y = -drawPos.Y;
/*if (range > 1.0f)
{
if (overrideLightTexture == null)
{
Vector2 center = new Vector2(LightTexture.Width / 2, LightTexture.Height / 2);
float scale = range / (lightTexture.Width / 2.0f);
spriteBatch.Draw(lightTexture, drawPos, null, color * (color.A / 255.0f), 0, center, scale, SpriteEffects.None, 1);
}
else
{
overrideLightTexture.Draw(spriteBatch,
drawPos, color * (color.A / 255.0f),
overrideLightTexture.Origin, -Rotation,
new Vector2(overrideLightTexture.size.X / overrideLightTexture.SourceRect.Width, overrideLightTexture.size.Y / overrideLightTexture.SourceRect.Height));
}
}
if (LightSprite != null)
{
LightSprite.Draw(spriteBatch, drawPos, Color, LightSprite.Origin, -Rotation, 1);
}*/
if (NeedsRecalculation)
{
var verts = FindRaycastHits();
CalculateLightVertices(verts);
lastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false;
}
if (vertexCount == 0) return;
lightEffect.DiffuseColor = (new Vector3(color.R, color.G, color.B) * (color.A / 255.0f)) / 255.0f;// color.ToVector3();
if (overrideLightTexture != null)
{
lightEffect.Texture = overrideLightTexture.Texture;
}
else
{
lightEffect.Texture = LightTexture;
}
lightEffect.CurrentTechnique.Passes[0].Apply();
GameMain.Instance.GraphicsDevice.SetVertexBuffer(lightVolumeBuffer);
GameMain.Instance.GraphicsDevice.Indices = lightVolumeIndexBuffer;
GameMain.Instance.GraphicsDevice.DrawIndexedPrimitives
(
PrimitiveType.TriangleList, 0, 0, indexCount / 3
);
}
/*public void FlipX()
{
if (LightSprite != null)
{
Vector2 lightOrigin = LightSprite.Origin;
lightOrigin.X = LightSprite.SourceRect.Width - lightOrigin.X;
LightSprite.Origin = lightOrigin;
}
if (overrideLightTexture != null)
{
Vector2 lightOrigin = overrideLightTexture.Origin;
lightOrigin.X = overrideLightTexture.SourceRect.Width - lightOrigin.X;
overrideLightTexture.Origin = lightOrigin;
}
}*/
public void Remove()
{
if (LightSprite != null) LightSprite.Remove();
if (lightVolumeBuffer != null)
{
lightVolumeBuffer.Dispose();
lightVolumeBuffer = null;
}
if (lightVolumeIndexBuffer != null)
{
lightVolumeIndexBuffer.Dispose();
lightVolumeIndexBuffer = null;
}
GameMain.LightManager.RemoveLight(this);
}
}
}
+464
View File
@@ -0,0 +1,464 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
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
{
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());
}
}
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;
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (!editing || wallVertices == null) return;
Color color = (isHighlighted) ? Color.Orange : Color.Green;
if (IsSelected) color = Color.Red;
Vector2 pos = Position;
for (int i = 0; i < wallVertices.Count; i++)
{
Vector2 startPos = wallVertices[i] + pos;
startPos.Y = -startPos.Y;
Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos;
endPos.Y = -endPos.Y;
GUI.DrawLine(spriteBatch,
startPos,
endPos,
color, 0.0f, 5);
}
pos.Y = -pos.Y;
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
Rectangle drawRect = rect;
drawRect.Y = -rect.Y;
GUI.DrawRectangle(spriteBatch, drawRect, Color.Red, true);
foreach (MapEntity e in linkedTo)
{
GUI.DrawLine(spriteBatch,
new Vector2(WorldPosition.X, -WorldPosition.Y),
new Vector2(e.WorldPosition.X, -e.WorldPosition.Y),
Color.Red * 0.3f);
}
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this)
{
editingHUD = CreateEditingHUD();
}
editingHUD.Update((float)Timing.Step);
if (!PlayerInput.LeftButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) return;
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList)
{
if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) continue;
if (((Item)entity).GetComponent<DockingPort>() == null) continue;
if (linkedTo.Contains(entity))
{
linkedTo.Remove(entity);
}
else
{
linkedTo.Add(entity);
}
}
}
public override void DrawEditing(SpriteBatch spriteBatch, Camera cam)
{
if (editingHUD == null) return;
editingHUD.Draw(spriteBatch);
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
int width = 450;
int x = GameMain.GraphicsWidth / 2 - width / 2, y = 10;
editingHUD = new GUIFrame(new Rectangle(x, y, width, 100), "");
editingHUD.Padding = new Vector4(10, 10, 0, 0);
editingHUD.UserData = this;
new GUITextBlock(new Rectangle(0, 0, 100, 20), "Linked submarine", "",
Alignment.TopLeft, Alignment.TopLeft, editingHUD, false, GUI.LargeFont);
var pathBox = new GUITextBox(new Rectangle(10,30,300,20), "", editingHUD);
pathBox.Font = GUI.SmallFont;
pathBox.Text = filePath;
var reloadButton = new GUIButton(new Rectangle(320,30,80,20), "Refresh", "", editingHUD);
reloadButton.OnClicked = Reload;
reloadButton.UserData = pathBox;
reloadButton.ToolTip = "Reload the linked submarine from the specified file";
y += 20;
if (!inGame)
{
new GUITextBlock(new Rectangle(0, 0, 0, 20), "Hold space to link to a docking port",
"", Alignment.TopRight, Alignment.TopRight, editingHUD, false, GUI.SmallFont);
y += 25;
}
return editingHUD;
}
private bool Reload(GUIButton button, object obj)
{
var pathBox = obj as GUITextBox;
if (!File.Exists(pathBox.Text))
{
new GUIMessageBox("Error", "Submarine file \"" + pathBox.Text + "\" not found!");
pathBox.Flash(Color.Red);
pathBox.Text = filePath;
return false;
}
XDocument doc = Submarine.OpenFile(pathBox.Text);
if (doc == null || doc.Root == null) return false;
pathBox.Flash(Color.Green);
GenerateWallVertices(doc.Root);
saveElement = doc.Root;
saveElement.Name = "LinkedSubmarine";
filePath = pathBox.Text;
return true;
}
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 override XElement Save(XElement parentElement)
{
XElement saveElement = null;
if (sub == null)
{
if (this.saveElement == null)
{
var doc = Submarine.OpenFile(filePath);
saveElement = doc.Root;
saveElement.Name = "LinkedSubmarine";
saveElement.Add(new XAttribute("filepath", filePath));
}
else
{
saveElement = this.saveElement;
}
if (saveElement.Attribute("pos") != null) saveElement.Attribute("pos").Remove();
saveElement.Add(new XAttribute("pos", ToolBox.Vector2ToString(Position - Submarine.HiddenSubPosition)));
var linkedPort = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent<DockingPort>() != null);
if (linkedPort != null)
{
if (saveElement.Attribute("linkedto") != null) saveElement.Attribute("linkedto").Remove();
saveElement.Add(new XAttribute("linkedto", linkedPort.ID));
}
}
else
{
saveElement = new XElement("LinkedSubmarine");
sub.SaveToXElement(saveElement);
}
if (sub != null)
{
bool leaveBehind = false;
if (!sub.DockedTo.Contains(Submarine.MainSub))
{
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndPosition || Submarine.MainSub.AtStartPosition);
if (Submarine.MainSub.AtEndPosition)
{
leaveBehind = sub.AtEndPosition != Submarine.MainSub.AtEndPosition;
}
else
{
leaveBehind = sub.AtStartPosition != Submarine.MainSub.AtStartPosition;
}
}
if (leaveBehind)
{
saveElement.SetAttributeValue("location", Level.Loaded.Seed);
saveElement.SetAttributeValue("worldpos", ToolBox.Vector2ToString(sub.SubBody.Position));
}
else
{
if (saveElement.Attribute("location") != null) saveElement.Attribute("location").Remove();
if (saveElement.Attribute("worldpos") != null) saveElement.Attribute("worldpos").Remove();
}
saveElement.SetAttributeValue("pos", ToolBox.Vector2ToString(Position - Submarine.HiddenSubPosition));
}
parentElement.Add(saveElement);
return saveElement;
}
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 = pos.ToPoint();
}
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;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
class Location
{
private string name;
private Vector2 mapPosition;
private LocationType type;
private HireManager hireManager;
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 HireManager HireManager
{
get { return hireManager; }
}
public Location(Vector2 mapPosition)
{
this.type = LocationType.Random();
this.name = RandomName(type);
this.mapPosition = mapPosition;
if (type.HasHireableCharacters)
{
hireManager = new HireManager();
hireManager.GenerateCharacters(this, HireManager.MaxAvailableCharacters);
}
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, false);
return type.NameFormats[nameFormatIndex].Replace("[name]", randomName);
}
}
}
+152
View File
@@ -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, false);
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);
}
}
}
}
}
+522
View File
@@ -0,0 +1,522 @@
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
{
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 static Sprite iceTexture;
private static Texture2D iceCraters;
private static Texture2D iceCrack;
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 (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");
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, false), Rand.Range(0.0f, size, false)));
}
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, false);
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, false);
if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, false)) 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;
}
public void Update(float deltaTime, Rectangle rect, float scale = 1.0f)
{
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
Vector2 offset = -currentLocation.MapPosition;
float maxDist = 20.0f;
float closestDist = 0.0f;
highlightedLocation = null;
for (int i = 0; i < locations.Count; i++)
{
Location location = locations[i];
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
if (!rect.Contains(pos)) continue;
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
if (dist < maxDist && (highlightedLocation == null || dist < closestDist))
{
closestDist = dist;
highlightedLocation = location;
}
}
foreach (LocationConnection connection in connections)
{
if (highlightedLocation != currentLocation &&
connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation))
{
if (PlayerInput.LeftButtonClicked() &&
selectedLocation != highlightedLocation && highlightedLocation != null)
{
selectedConnection = connection;
selectedLocation = highlightedLocation;
GameMain.LobbyScreen.SelectLocation(highlightedLocation, connection);
}
}
}
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float scale = 1.0f)
{
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
Vector2 offset = -currentLocation.MapPosition;
iceTexture.DrawTiled(spriteBatch, new Vector2(rect.X, rect.Y), new Vector2(rect.Width, rect.Height), Vector2.Zero, Color.White*0.8f);
foreach (LocationConnection connection in connections)
{
Color crackColor = Color.White * Math.Max(connection.Difficulty/100.0f, 1.5f);
if (selectedLocation != currentLocation &&
(connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation)))
{
crackColor = Color.Red;
}
else if (highlightedLocation != currentLocation &&
(connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation)))
{
crackColor = Color.Red * 0.5f;
}
else if (!connection.Passed)
{
crackColor *= 0.2f;
}
for (int i = 0; i < connection.CrackSegments.Count; i++ )
{
var segment = connection.CrackSegments[i];
Vector2 start = rectCenter + (segment[0] + offset) * scale;
Vector2 end = rectCenter + (segment[1] + offset) * scale;
if (!rect.Contains(start) && !rect.Contains(end))
{
continue;
}
else
{
Vector2? intersection = MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(rect.X, rect.Y + rect.Height, rect.Width, rect.Height));
if (intersection != null)
{
if (!rect.Contains(start))
{
start = (Vector2)intersection;
}
else
{
end = (Vector2)intersection;
}
}
}
float dist = Vector2.Distance(start, end);
int width = (int)(MathHelper.Clamp(connection.Difficulty, 2.0f, 20.0f) * scale);
spriteBatch.Draw(iceCrack,
new Rectangle((int)start.X, (int)start.Y, (int)dist + 2, width),
new Rectangle(0, 0, iceCrack.Width, 60), crackColor, MathUtils.VectorToAngle(end - start),
new Vector2(0, 30), SpriteEffects.None, 0.01f);
}
}
rect.Inflate(8, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.LightGray);
for (int i = 0; i < locations.Count; i++)
{
Location location = locations[i];
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
Rectangle drawRect = location.Type.Sprite.SourceRect;
Rectangle sourceRect = drawRect;
drawRect.X = (int)pos.X - drawRect.Width/2;
drawRect.Y = (int)pos.Y - drawRect.Width/2;
if (!rect.Intersects(drawRect)) continue;
Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation))==null ? Color.White : Color.Green;
color *= (location.Discovered) ? 0.8f : 0.2f;
if (location == currentLocation) color = Color.Orange;
if (drawRect.X < rect.X)
{
sourceRect.X += rect.X - drawRect.X;
sourceRect.Width -= sourceRect.X;
drawRect.X = rect.X;
}
else if (drawRect.Right > rect.Right)
{
sourceRect.Width -= (drawRect.Right - rect.Right);
}
if (drawRect.Y < rect.Y)
{
sourceRect.Y += rect.Y - drawRect.Y;
sourceRect.Height -= sourceRect.Y;
drawRect.Y = rect.Y;
}
else if (drawRect.Bottom > rect.Bottom)
{
sourceRect.Height -= drawRect.Bottom - rect.Bottom;
}
drawRect.Width = sourceRect.Width;
drawRect.Height = sourceRect.Height;
spriteBatch.Draw(location.Type.Sprite.Texture, drawRect, sourceRect, color);
}
for (int i = 0; i < 3; i++ )
{
Location location = (i == 0) ? highlightedLocation : selectedLocation;
if (i == 2) location = currentLocation;
if (location == null) continue;
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
pos.X = (int)(pos.X + location.Type.Sprite.SourceRect.Width*0.6f);
pos.Y = (int)(pos.Y - 10);
GUI.DrawString(spriteBatch, pos, location.Name, Color.White, Color.Black * 0.8f, 3);
}
}
public void Save(XElement element)
{
XElement mapElement = new XElement("map");
mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex));
mapElement.Add(new XAttribute("seed", Seed));
mapElement.Add(new XAttribute("size", size));
List<int> discoveredLocations = new List<int>();
for (int i = 0; i < locations.Count; i++ )
{
if (locations[i].Discovered) discoveredLocations.Add(i);
}
mapElement.Add(new XAttribute("discovered", string.Join(",", discoveredLocations)));
List<int> passedConnections = new List<int>();
for (int i = 0; i < connections.Count; i++)
{
if (connections[i].Passed) passedConnections.Add(i);
}
mapElement.Add(new XAttribute("passed", string.Join(",", passedConnections)));
element.Add(mapElement);
}
}
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;
}
}
}
}
File diff suppressed because it is too large Load Diff
+239
View File
@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
[Flags]
enum MapEntityCategory
{
Structure = 1, Machine = 2, Equipment = 4, Electrical = 8, Material = 16, Misc = 32, Alien = 64
}
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 -= Submarine.MainSub.Position.ToPoint();
}
if (PlayerInput.LeftButtonReleased())
{
CreateInstance(newRect);
placePosition = Vector2.Zero;
selected = null;
}
newRect.Y = -newRect.Y;
}
if (PlayerInput.RightButtonHeld())
{
placePosition = Vector2.Zero;
selected = null;
}
}
public virtual void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{
Vector2 placeSize = Submarine.GridSize;
if (placePosition == Vector2.Zero)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
GUI.DrawLine(spriteBatch, new Vector2(position.X - GameMain.GraphicsWidth, -position.Y), new Vector2(position.X + GameMain.GraphicsWidth, -position.Y), Color.White,0,(int)(2.0f/cam.Zoom));
GUI.DrawLine(spriteBatch, new Vector2(position.X, -(position.Y - GameMain.GraphicsHeight)), new Vector2(position.X, -(position.Y + GameMain.GraphicsHeight)), Color.White, 0, (int)(2.0f / cam.Zoom));
}
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 -= Submarine.MainSub.Position.ToPoint();
}
newRect.Y = -newRect.Y;
GUI.DrawRectangle(spriteBatch, newRect, Color.DarkBlue);
}
}
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;
}
public void DrawListLine(SpriteBatch spriteBatch, Vector2 pos, Color color)
{
GUI.Font.DrawString(spriteBatch, name, pos, color);
}
}
}
+102
View File
@@ -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);
}
}
}
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma
{
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 -= Submarine.MainSub.Position.ToPoint();
var structure = new Structure(newRect, this, Submarine.MainSub);
structure.Submarine = Submarine.MainSub;
}
selected = null;
return;
}
}
if (PlayerInput.RightButtonHeld()) selected = null;
}
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
//Vector2 placeSize = size;
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;
//sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y), placeSize, Color.White);
}
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);
}
sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - GameMain.GraphicsWidth, -newRect.Y, newRect.Width + GameMain.GraphicsWidth * 2, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White);
}
}
}
File diff suppressed because it is too large Load Diff
+544
View File
@@ -0,0 +1,544 @@
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 += Position.ToPoint();
//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 += ConvertUnits.ToDisplayUnits(Body.SimPosition).ToPoint();
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);
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);
}
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 (maxDamageStructure != null)
{
SoundPlayer.PlayDamageSound(
DamageSoundType.StructureBlunt,
impact * 10.0f,
ConvertUnits.ToDisplayUnits(lastContactPoint),
MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f),
maxDamageStructure.Tags);
}
}
}
}
@@ -0,0 +1,107 @@
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;
GameMain.LightManager.LosEnabled = false;
//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);
GUI.ScreenOverlayColor = Color.TransparentBlack;
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);
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, timer/duration);
cam.Translate((cameraPos - cam.Position) * CoroutineManager.UnscaledDeltaTime*10.0f);
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);
GUI.ScreenOverlayColor = Color.TransparentBlack;
yield return CoroutineStatus.Success;
}
}
}
+838
View File
@@ -0,0 +1,838 @@
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 Microsoft.Xna.Framework.Input;
using System.Collections.ObjectModel;
using Barotrauma.Items.Components;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
public enum SpawnType { Path, Human, Enemy, Cargo };
class WayPoint : MapEntity
{
public static List<WayPoint> WayPointList = new List<WayPoint>();
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
private static Texture2D iconTexture;
private const int IconSize = 32;
private static int[] iconIndices = { 3, 0, 1, 2 };
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 (iconTexture==null)
{
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
}
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 (IsHidden()) return false;
return base.IsMouseOn(position);
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
{
if (!editing && !GameMain.DebugDraw) return;
if (IsHidden()) return;
//Rectangle drawRect =
// Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
Vector2 drawPos = Position;
if (Submarine!=null) drawPos += Submarine.DrawPosition;
drawPos.Y = -drawPos.Y;
Color clr = currentHull == null ? Color.Blue : Color.White;
if (IsSelected) clr = Color.Red;
if (isHighlighted) clr = Color.DarkRed;
int iconX = iconIndices[(int)spawnType]*IconSize % iconTexture.Width;
int iconY = (int)(Math.Floor(iconIndices[(int)spawnType]*IconSize / (float)iconTexture.Width))*IconSize;
int iconSize = ConnectedGap == null && Ladders == null ? IconSize : (int)(IconSize * 1.5f);
spriteBatch.Draw(iconTexture,
new Rectangle((int)(drawPos.X - iconSize/2), (int)(drawPos.Y - iconSize/2), iconSize, iconSize),
new Rectangle(iconX, iconY, IconSize,IconSize), clr);
//GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height), clr, true);
//GUI.SmallFont.DrawString(spriteBatch, Position.ToString(), new Vector2(Position.X, -Position.Y), Color.White);
foreach (MapEntity e in linkedTo)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
Color.Green);
}
}
private bool IsHidden()
{
if (spawnType == SpawnType.Path)
{
return (!GameMain.DebugDraw && !ShowWayPoints);
}
else
{
return (!GameMain.DebugDraw && !ShowSpawnPoints);
}
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData != this)
{
editingHUD = CreateEditingHUD();
}
editingHUD.Update((float)Timing.Step);
if (PlayerInput.LeftButtonClicked())
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity e in mapEntityList)
{
if (e.GetType() != typeof(WayPoint)) continue;
if (e == this) continue;
if (!Submarine.RectContains(e.Rect, position)) continue;
linkedTo.Add(e);
e.linkedTo.Add(this);
}
}
}
public override void DrawEditing(SpriteBatch spriteBatch, Camera cam)
{
if (editingHUD != null) editingHUD.Draw(spriteBatch);
}
private bool ChangeSpawnType(GUIButton button, object obj)
{
GUITextBlock spawnTypeText = button.Parent as GUITextBlock;
spawnType += (int)button.UserData;
if (spawnType > SpawnType.Cargo) spawnType = SpawnType.Human;
if (spawnType < SpawnType.Human) spawnType = SpawnType.Cargo;
spawnTypeText.Text = spawnType.ToString();
return true;
}
private bool EnterIDCardTags(GUITextBox textBox, string text)
{
IdCardTags = text.Split(',');
textBox.Text = text;
textBox.Color = Color.Green;
textBox.Deselect();
return true;
}
private bool EnterAssignedJob(GUITextBox textBox, string text)
{
string trimmedName = text.ToLowerInvariant().Trim();
assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == trimmedName);
if (assignedJob !=null && trimmedName!="none")
{
textBox.Color = Color.Green;
textBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
}
textBox.Deselect();
return true;
}
private bool TextBoxChanged(GUITextBox textBox, string text)
{
textBox.Color = Color.Red;
return true;
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
int width = 500;
int height = spawnType == SpawnType.Path ? 100 : 140;
int x = GameMain.GraphicsWidth / 2 - width / 2, y = 10;
editingHUD = new GUIFrame(new Rectangle(x, y, width, height), Color.Black * 0.5f);
editingHUD.Padding = new Vector4(10, 10, 0, 0);
editingHUD.UserData = this;
if (spawnType == SpawnType.Path)
{
new GUITextBlock(new Rectangle(0, 0, 100, 20), "Editing waypoint", "", editingHUD);
new GUITextBlock(new Rectangle(0, 20, 100, 20), "Hold space to link to another waypoint", "", editingHUD);
}
else
{
new GUITextBlock(new Rectangle(0, 0, 100, 20), "Editing spawnpoint", "", editingHUD);
new GUITextBlock(new Rectangle(0, 25, 100, 20), "Spawn type: ", "", editingHUD);
var spawnTypeText = new GUITextBlock(new Rectangle(0, 25, 200, 20), spawnType.ToString(), "", Alignment.Right, Alignment.TopLeft, editingHUD);
var button = new GUIButton(new Rectangle(-30,0,20,20), "-", Alignment.Right, "", spawnTypeText);
button.UserData = -1;
button.OnClicked = ChangeSpawnType;
button = new GUIButton(new Rectangle(0, 0, 20, 20), "+", Alignment.Right, "", spawnTypeText);
button.UserData = 1;
button.OnClicked = ChangeSpawnType;
y = 40 + 20;
new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD);
GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), "", editingHUD);
propertyBox.Text = string.Join(", ", idCardTags);
propertyBox.OnEnterPressed = EnterIDCardTags;
propertyBox.OnTextChanged = TextBoxChanged;
propertyBox.ToolTip = "Characters spawning at this spawnpoint will have the specified tags added to their ID card. You can, for example, use these tags to limit access to some parts of the sub.";
y = y + 30;
new GUITextBlock(new Rectangle(0, y, 100, 20), "Assigned job:", Color.Transparent, Color.White, Alignment.TopLeft, null, editingHUD);
propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), "", editingHUD);
propertyBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
propertyBox.OnEnterPressed = EnterAssignedJob;
propertyBox.OnTextChanged = TextBoxChanged;
propertyBox.ToolTip = "Only characters with the specified job will spawn at this spawnpoint.";
}
//GUI.Font.DrawString(spriteBatch, "Spawnpoint: " + spawnType.ToString() + " +/-", new Vector2(x, y + 40), Color.Black);
y = y + 30;
return editingHUD;
}
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 -= submarine.HiddenSubPosition.ToPoint();
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)];
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)
{
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;
//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, false)];
}
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 override XElement Save(XElement parentElement)
{
if (MoveWithLevel) return null;
XElement element = new XElement("WayPoint");
element.Add(new XAttribute("ID", ID),
new XAttribute("x", (int)(rect.X - Submarine.HiddenSubPosition.X)),
new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)),
new XAttribute("spawn", spawnType));
if (idCardTags.Length > 0)
{
element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags)));
}
if (assignedJob != null) element.Add(new XAttribute("job", assignedJob.Name));
if (ConnectedGap != null) element.Add(new XAttribute("gap", ConnectedGap.ID));
if (Ladders != null) element.Add(new XAttribute("ladders", Ladders.Item.ID));
parentElement.Add(element);
if (linkedTo != null)
{
int i = 0;
foreach (MapEntity e in linkedTo)
{
element.Add(new XAttribute("linkedto" + i, e.ID));
i += 1;
}
}
return element;
}
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);
}
}
}