Merge branch 'dedicated-server' (TODO: make sure I didn't break anything)
Conflicts: Barotrauma/Barotrauma.csproj Barotrauma/BarotraumaShared/Source/Characters/AI/AIController.cs Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs Barotrauma/BarotraumaShared/Source/Characters/Attack.cs Barotrauma/BarotraumaShared/Source/Characters/Character.cs Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs Barotrauma/BarotraumaShared/Source/Characters/Limb.cs Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs Barotrauma/BarotraumaShared/Source/Map/Explosion.cs
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
using System.Collections.Generic;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Entity
|
||||
{
|
||||
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
public static List<Entity> GetEntityList()
|
||||
{
|
||||
return dictionary.Values.ToList();
|
||||
}
|
||||
|
||||
public static EntitySpawner Spawner;
|
||||
|
||||
private ushort id;
|
||||
|
||||
protected AITarget aiTarget;
|
||||
|
||||
public virtual bool Removed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ushort ID
|
||||
{
|
||||
get
|
||||
{
|
||||
return id;
|
||||
}
|
||||
set
|
||||
{
|
||||
Entity thisEntity;
|
||||
if (dictionary.TryGetValue(id, out thisEntity) && thisEntity == this)
|
||||
{
|
||||
dictionary.Remove(id);
|
||||
}
|
||||
//if there's already an entity with the same ID, give it the old ID of this one
|
||||
Entity existingEntity;
|
||||
if (dictionary.TryGetValue(value, out existingEntity))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(existingEntity+" had the same ID as "+this);
|
||||
dictionary.Remove(value);
|
||||
dictionary.Add(id, existingEntity);
|
||||
existingEntity.id = id;
|
||||
}
|
||||
|
||||
id = value;
|
||||
dictionary.Add(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Vector2 SimPosition
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public virtual Vector2 Position
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public virtual Vector2 WorldPosition
|
||||
{
|
||||
get { return Submarine == null ? Position : Submarine.Position + Position; }
|
||||
}
|
||||
|
||||
public virtual Vector2 DrawPosition
|
||||
{
|
||||
get { return Submarine == null ? Position : Submarine.DrawPosition + Position; }
|
||||
}
|
||||
|
||||
public Submarine Submarine
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public AITarget AiTarget
|
||||
{
|
||||
get { return aiTarget; }
|
||||
}
|
||||
|
||||
public Entity(Submarine submarine)
|
||||
{
|
||||
this.Submarine = submarine;
|
||||
|
||||
//give an unique ID
|
||||
bool IDfound;
|
||||
id = submarine == null ? (ushort)1 : submarine.IdOffset;
|
||||
do
|
||||
{
|
||||
id += 1;
|
||||
IDfound = dictionary.ContainsKey(id);
|
||||
} while (IDfound);
|
||||
|
||||
dictionary.Add(id, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an entity based on the ID
|
||||
/// </summary>
|
||||
public static Entity FindEntityByID(ushort ID)
|
||||
{
|
||||
Entity matchingEntity;
|
||||
dictionary.TryGetValue(ID, out matchingEntity);
|
||||
|
||||
return matchingEntity;
|
||||
}
|
||||
|
||||
public static void RemoveAll()
|
||||
{
|
||||
List<Entity> list = new List<Entity>(dictionary.Values);
|
||||
foreach (Entity e in list)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.Remove();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing entity \"" + e.ToString() + "\"", exception);
|
||||
}
|
||||
}
|
||||
dictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual void Remove()
|
||||
{
|
||||
dictionary.Remove(ID);
|
||||
Removed = true;
|
||||
}
|
||||
|
||||
public static void DumpIds(int count)
|
||||
{
|
||||
List<Entity> entities = dictionary.Values.OrderByDescending(e => e.id).ToList();
|
||||
|
||||
count = Math.Min(entities.Count, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
DebugConsole.ThrowError(entities[i].id + ": " + entities[i].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class EntityGrid
|
||||
{
|
||||
private List<MapEntity>[,] entities;
|
||||
|
||||
private Rectangle limits;
|
||||
|
||||
private float cellSize;
|
||||
|
||||
public readonly Submarine Submarine;
|
||||
|
||||
public EntityGrid(Submarine submarine, float cellSize)
|
||||
{
|
||||
this.limits = submarine.Borders;
|
||||
this.Submarine = submarine;
|
||||
this.cellSize = cellSize;
|
||||
|
||||
entities = new List<MapEntity>[(int)Math.Ceiling(limits.Width / cellSize), (int)Math.Ceiling(limits.Height / cellSize)];
|
||||
for (int x = 0; x < entities.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < entities.GetLength(1); y++)
|
||||
{
|
||||
entities[x, y] = new List<MapEntity>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertEntity(MapEntity entity)
|
||||
{
|
||||
Rectangle rect = entity.Rect;
|
||||
//if (Submarine.Loaded != null) rect.Offset(-Submarine.HiddenSubPosition);
|
||||
Rectangle indices = GetIndices(rect);
|
||||
|
||||
if (indices.Width < 0 || indices.X >= entities.GetLength(0) ||
|
||||
indices.Height < 0 || indices.Y >= entities.GetLength(1))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in EntityGrid.InsertEntity: " + entity + " is outside the grid");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int x = Math.Max(indices.X, 0); x <= Math.Min(indices.Width, entities.GetLength(0)-1); x++)
|
||||
{
|
||||
for (int y = Math.Max(indices.Y,0); y <= Math.Min(indices.Height, entities.GetLength(1)-1); y++)
|
||||
{
|
||||
entities[x, y].Add(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveEntity(MapEntity entity)
|
||||
{
|
||||
for (int x = 0; x < entities.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < entities.GetLength(1); y++)
|
||||
{
|
||||
if (entities[x, y].Contains(entity)) entities[x, y].Remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int x = 0; x < entities.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < entities.GetLength(1); y++)
|
||||
{
|
||||
entities[x, y].Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<MapEntity> GetEntities(List<EntityGrid> entityGrids, Vector2 position, bool useWorldCoordinates = true)
|
||||
{
|
||||
List<MapEntity> entities = new List<MapEntity>();
|
||||
foreach (EntityGrid entityGrid in entityGrids)
|
||||
{
|
||||
Vector2 transformedPosition = position;
|
||||
if (useWorldCoordinates)
|
||||
{
|
||||
transformedPosition -= entityGrid.Submarine.Position;
|
||||
}
|
||||
|
||||
entities.AddRange(entityGrid.GetEntities(transformedPosition));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public List<MapEntity> GetEntities(Vector2 position)
|
||||
{
|
||||
if (!MathUtils.IsValid(position)) new List<MapEntity>();
|
||||
|
||||
if (Submarine != null) position -= Submarine.HiddenSubPosition;
|
||||
|
||||
Point indices = GetIndices(position);
|
||||
|
||||
if (indices.X < 0 || indices.Y < 0 || indices.X >= entities.GetLength(0) || indices.Y >= entities.GetLength(1))
|
||||
{
|
||||
return new List<MapEntity>();
|
||||
}
|
||||
|
||||
return entities[indices.X, indices.Y];
|
||||
}
|
||||
|
||||
public Rectangle GetIndices(Rectangle rect)
|
||||
{
|
||||
Rectangle indices = Rectangle.Empty;
|
||||
indices.X = (int)Math.Floor((rect.X - limits.X) / cellSize);
|
||||
indices.Y = (int)Math.Floor((limits.Y - rect.Y) / cellSize);
|
||||
|
||||
indices.Width = (int)Math.Floor((rect.Right - limits.X) / cellSize);
|
||||
indices.Height = (int)Math.Floor((limits.Y - (rect.Y - rect.Height)) / cellSize);
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
public Point GetIndices(Vector2 position)
|
||||
{
|
||||
return new Point(
|
||||
(int)Math.Floor((position.X - limits.X) / cellSize),
|
||||
(int)Math.Floor((limits.Y - position.Y) / cellSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Explosion
|
||||
{
|
||||
private Attack attack;
|
||||
|
||||
private float force;
|
||||
|
||||
public float CameraShake;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke;
|
||||
|
||||
public Explosion(float range, float force, float damage, float structureDamage)
|
||||
{
|
||||
attack = new Attack(damage, structureDamage, 0.0f, range);
|
||||
this.force = force;
|
||||
sparks = true;
|
||||
shockwave = true;
|
||||
flames = true;
|
||||
}
|
||||
|
||||
public Explosion(XElement element)
|
||||
{
|
||||
attack = new Attack(element);
|
||||
|
||||
force = ToolBox.GetAttributeFloat(element, "force", 0.0f);
|
||||
|
||||
sparks = ToolBox.GetAttributeBool(element, "sparks", true);
|
||||
shockwave = ToolBox.GetAttributeBool(element, "shockwave", true);
|
||||
flames = ToolBox.GetAttributeBool(element, "flames", true);
|
||||
smoke = ToolBox.GetAttributeBool(element, "smoke", true);
|
||||
|
||||
CameraShake = ToolBox.GetAttributeFloat(element, "camerashake", attack.Range*0.1f);
|
||||
}
|
||||
|
||||
public void Explode(Vector2 worldPosition)
|
||||
{
|
||||
Hull hull = Hull.FindHull(worldPosition);
|
||||
|
||||
ExplodeProjSpecific(worldPosition, hull);
|
||||
|
||||
float displayRange = attack.Range;
|
||||
if (displayRange < 0.1f) return;
|
||||
|
||||
float cameraDist = Vector2.Distance(GameMain.GameScreen.Cam.Position, worldPosition)/2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = CameraShake * Math.Max((displayRange - cameraDist) / displayRange, 0.0f);
|
||||
|
||||
if (attack.GetStructureDamage(1.0f) > 0.0f)
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f));
|
||||
}
|
||||
|
||||
if (force == 0.0f && attack.Stun == 0.0f && attack.GetDamage(1.0f) == 0.0f) return;
|
||||
|
||||
ApplyExplosionForces(worldPosition, attack, force);
|
||||
|
||||
if (flames && GameMain.Client == null)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) continue;
|
||||
|
||||
if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.1f) continue;
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
|
||||
|
||||
if (item.Condition <= 0.0f && GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull)
|
||||
{
|
||||
if (hull == null) return particlePos;
|
||||
|
||||
return new Vector2(
|
||||
MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right),
|
||||
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
|
||||
}
|
||||
|
||||
public static void ApplyExplosionForces(Vector2 worldPosition, Attack attack, float force)
|
||||
{
|
||||
if (attack.Range <= 0.0f) return;
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (c.Submarine != null) explosionPos -= c.Submarine.Position;
|
||||
|
||||
explosionPos = ConvertUnits.ToSimUnits(explosionPos);
|
||||
|
||||
bool wasDead = c.IsDead;
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
|
||||
|
||||
//calculate distance from the "outer surface" of the physics body
|
||||
//doesn't take the rotation of the limb into account, but should be accurate enough for this purpose
|
||||
float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius);
|
||||
dist = Math.Max(0.0f, dist - FarseerPhysics.ConvertUnits.ToDisplayUnits(limbRadius));
|
||||
|
||||
if (dist > attack.Range) continue;
|
||||
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
|
||||
//solid obstacles between the explosion and the limb reduce the effect of the explosion by 90%
|
||||
if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) distFactor *= 0.1f;
|
||||
|
||||
c.AddDamage(limb.WorldPosition, DamageType.None,
|
||||
attack.GetDamage(1.0f) / c.AnimController.Limbs.Length * distFactor,
|
||||
attack.GetBleedingDamage(1.0f) / c.AnimController.Limbs.Length * distFactor,
|
||||
attack.Stun * distFactor,
|
||||
false);
|
||||
|
||||
if (limb.WorldPosition != worldPosition && force > 0.0f)
|
||||
{
|
||||
limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.WorldPosition - worldPosition) * distFactor * force);
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasDead && c.IsDead)
|
||||
{
|
||||
foreach (LimbJoint joint in c.AnimController.LimbJoints)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability)
|
||||
{
|
||||
c.AnimController.SeverLimbJoint(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||
/// </summary>
|
||||
public static Dictionary<Structure,float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage)
|
||||
{
|
||||
List<Structure> structureList = new List<Structure>();
|
||||
float dist = 600.0f;
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
{
|
||||
Structure structure = entity as Structure;
|
||||
if (structure == null) continue;
|
||||
|
||||
if (structure.HasBody &&
|
||||
!structure.IsPlatform &&
|
||||
Vector2.Distance(structure.WorldPosition, worldPosition) < dist * 3.0f)
|
||||
{
|
||||
structureList.Add(structure);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
|
||||
foreach (Structure structure in structureList)
|
||||
{
|
||||
for (int i = 0; i < structure.SectionCount; i++)
|
||||
{
|
||||
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
|
||||
if (distFactor <= 0.0f) continue;
|
||||
|
||||
structure.AddDamage(i, damage * distFactor);
|
||||
|
||||
if (damagedStructures.ContainsKey(structure))
|
||||
{
|
||||
damagedStructures[structure] += damage * distFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
damagedStructures.Add(structure, damage * distFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return damagedStructures;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Networking;
|
||||
#if CLIENT
|
||||
using Barotrauma.Lights;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class FireSource
|
||||
{
|
||||
const float OxygenConsumption = 50.0f;
|
||||
const float GrowSpeed = 5.0f;
|
||||
|
||||
private int basicSoundIndex, largeSoundIndex;
|
||||
|
||||
private Hull hull;
|
||||
|
||||
private Vector2 position;
|
||||
private Vector2 size;
|
||||
|
||||
private Entity Submarine;
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
|
||||
position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return Submarine.Position + position; }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
set
|
||||
{
|
||||
if (value == size) return;
|
||||
|
||||
Vector2 sizeChange = value - size;
|
||||
|
||||
size = value;
|
||||
position.X -= sizeChange.X * 0.5f;
|
||||
LimitSize();
|
||||
}
|
||||
}
|
||||
|
||||
public float DamageRange
|
||||
{
|
||||
get { return (float)Math.Sqrt(size.X) * 20.0f; }
|
||||
}
|
||||
|
||||
public Hull Hull
|
||||
{
|
||||
get { return hull; }
|
||||
}
|
||||
|
||||
public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false)
|
||||
{
|
||||
hull = Hull.FindHull(worldPosition, spawningHull);
|
||||
if (hull == null) return;
|
||||
|
||||
if (!isNetworkMessage && GameMain.Client != null) return;
|
||||
|
||||
hull.AddFireSource(this);
|
||||
|
||||
Submarine = hull.Submarine;
|
||||
|
||||
this.position = worldPosition - new Vector2(-5.0f, 5.0f) - Submarine.Position;
|
||||
|
||||
#if CLIENT
|
||||
if (fireSoundBasic == null)
|
||||
{
|
||||
fireSoundBasic = Sound.Load("Content/Sounds/fire.ogg", false);
|
||||
fireSoundLarge = Sound.Load("Content/Sounds/firelarge.ogg", false);
|
||||
}
|
||||
|
||||
lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull == null ? null : hull.Submarine);
|
||||
#endif
|
||||
|
||||
size = new Vector2(10.0f, 10.0f);
|
||||
}
|
||||
|
||||
private void LimitSize()
|
||||
{
|
||||
if (hull == null) return;
|
||||
|
||||
position.X = Math.Max(hull.Rect.X, position.X);
|
||||
position.Y = Math.Min(hull.Rect.Y, position.Y);
|
||||
|
||||
size.X = Math.Min(hull.Rect.Width - (position.X - hull.Rect.X), size.X);
|
||||
size.Y = Math.Min(hull.Rect.Height - (hull.Rect.Y - position.Y), size.Y);
|
||||
}
|
||||
|
||||
public static void UpdateAll(List<FireSource> fireSources, float deltaTime)
|
||||
{
|
||||
for (int i = fireSources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
fireSources[i].Update(deltaTime);
|
||||
}
|
||||
|
||||
//combine overlapping fires
|
||||
for (int i = fireSources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
for (int j = i-1; j>=0 ; j--)
|
||||
{
|
||||
i = Math.Min(i, fireSources.Count - 1);
|
||||
j = Math.Min(j, i - 1);
|
||||
|
||||
if (!fireSources[i].CheckOverLap(fireSources[j])) continue;
|
||||
|
||||
float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X);
|
||||
|
||||
fireSources[j].size.X =
|
||||
Math.Max(fireSources[i].position.X + fireSources[i].size.X, fireSources[j].position.X + fireSources[j].size.X)
|
||||
- leftEdge;
|
||||
|
||||
fireSources[j].position.X = leftEdge;
|
||||
|
||||
fireSources[i].Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckOverLap(FireSource fireSource)
|
||||
{
|
||||
return !(position.X > fireSource.position.X + fireSource.size.X ||
|
||||
position.X + size.X < fireSource.position.X);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
//the firesource will start to shrink if oxygen percentage is below 10
|
||||
float growModifier = Math.Min((hull.OxygenPercentage / 10.0f) - 1.0f, 1.0f);
|
||||
|
||||
DamageCharacters(deltaTime);
|
||||
DamageItems(deltaTime);
|
||||
|
||||
if (hull.Volume > 0.0f) HullWaterExtinquish(deltaTime);
|
||||
|
||||
hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
|
||||
|
||||
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
|
||||
|
||||
size.X += GrowSpeed * growModifier * deltaTime;
|
||||
size.Y = MathHelper.Clamp(size.Y + GrowSpeed * growModifier * deltaTime, 10.0f, 50.0f);
|
||||
|
||||
if (size.X > 50.0f)
|
||||
{
|
||||
this.position.Y = MathHelper.Lerp(this.position.Y, hull.Rect.Y - hull.Rect.Height + size.Y, deltaTime);
|
||||
}
|
||||
|
||||
LimitSize();
|
||||
|
||||
UpdateProjSpecific(growModifier);
|
||||
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (size.X < 1.0f) Remove();
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float growModifier);
|
||||
|
||||
private void OnChangeHull(Vector2 pos, Hull particleHull)
|
||||
{
|
||||
if (particleHull == hull || particleHull==null) return;
|
||||
|
||||
//hull already has a firesource roughly at the particles position -> don't create a new one
|
||||
if (particleHull.FireSources.Find(fs => pos.X > fs.position.X - 100.0f && pos.X < fs.position.X + fs.size.X + 100.0f) != null) return;
|
||||
|
||||
new FireSource(new Vector2(pos.X, particleHull.WorldRect.Y - particleHull.Rect.Height + 5.0f));
|
||||
}
|
||||
|
||||
private void DamageCharacters(float deltaTime)
|
||||
{
|
||||
if (size.X <= 0.0f) return;
|
||||
|
||||
for (int i = 0; i < Character.CharacterList.Count; i++)
|
||||
{
|
||||
Character c = Character.CharacterList[i];
|
||||
if (c.AnimController.CurrentHull == null || c.IsDead) continue;
|
||||
|
||||
float range = DamageRange;
|
||||
if (c.Position.X < position.X - range || c.Position.X > position.X + size.X + range) continue;
|
||||
if (c.Position.Y < position.Y - size.Y || c.Position.Y > hull.Rect.Y) continue;
|
||||
|
||||
float dmg = (float)Math.Sqrt(size.X) * deltaTime / c.AnimController.Limbs.Length;
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.WearingItems.Find(w => w != null && w.WearableComponent.Item.FireProof) != null) continue;
|
||||
limb.Burnt += dmg * 10.0f;
|
||||
c.AddDamage(limb.SimPosition, DamageType.Burn, dmg, 0, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DamageItems(float deltaTime)
|
||||
{
|
||||
if (size.X <= 0.0f || GameMain.Client != null) return;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) continue;
|
||||
if (item.ParentInventory != null && item.ParentInventory.Owner is Character) return;
|
||||
|
||||
float range = (float)Math.Sqrt(size.X) * 10.0f;
|
||||
if (item.Position.X < position.X - range || item.Position.X > position.X + size.X + range) continue;
|
||||
if (item.Position.Y < position.Y - size.Y || item.Position.Y > hull.Rect.Y) continue;
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFire, deltaTime);
|
||||
if (item.Condition <= 0.0f && GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HullWaterExtinquish(float deltaTime)
|
||||
{
|
||||
//the higher the surface of the water is relative to the firesource, the faster it puts out the fire
|
||||
float extinquishAmount = (hull.Surface - (position.Y - size.Y)) * deltaTime;
|
||||
|
||||
if (extinquishAmount < 0.0f) return;
|
||||
|
||||
#if CLIENT
|
||||
float steamCount = Rand.Range(-5.0f, Math.Min(extinquishAmount * 100.0f, 10));
|
||||
for (int i = 0; i < steamCount; i++)
|
||||
{
|
||||
Vector2 spawnPos = new Vector2(
|
||||
WorldPosition.X + Rand.Range(0.0f, size.X),
|
||||
WorldPosition.Y + 10.0f);
|
||||
|
||||
Vector2 speed = new Vector2((spawnPos.X - (WorldPosition.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("steam",
|
||||
spawnPos, speed, 0.0f, hull);
|
||||
|
||||
if (particle == null) continue;
|
||||
|
||||
particle.Size *= MathHelper.Clamp(size.X / 10.0f, 0.5f, 3.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
position.X += extinquishAmount / 2.0f;
|
||||
size.X -= extinquishAmount;
|
||||
|
||||
//evaporate some of the water
|
||||
hull.Volume -= extinquishAmount;
|
||||
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (size.X < 1.0f) Remove();
|
||||
}
|
||||
|
||||
public void Extinguish(float deltaTime, float amount, Vector2 pos)
|
||||
{
|
||||
float range = 100.0f;
|
||||
|
||||
if (pos.X < WorldPosition.X - range || pos.X > WorldPosition.X + size.X + range) return;
|
||||
if (pos.Y < WorldPosition.Y - range || pos.Y > WorldPosition.Y + 500.0f) return;
|
||||
|
||||
float extinquishAmount = amount * deltaTime;
|
||||
|
||||
#if CLIENT
|
||||
float steamCount = Rand.Range(-5.0f, (float)Math.Sqrt(amount));
|
||||
for (int i = 0; i < steamCount; i++)
|
||||
{
|
||||
Vector2 spawnPos = new Vector2(pos.X + Rand.Range(-5.0f, 5.0f), Rand.Range(position.Y - size.Y, position.Y) + 10.0f);
|
||||
|
||||
Vector2 speed = new Vector2((spawnPos.X - (position.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("steam",
|
||||
spawnPos, speed, 0.0f, hull);
|
||||
|
||||
if (particle == null) continue;
|
||||
|
||||
particle.Size *= MathHelper.Clamp(size.X / 10.0f, 0.5f, 3.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
position.X += extinquishAmount / 2.0f;
|
||||
size.X -= extinquishAmount;
|
||||
|
||||
hull.Volume -= extinquishAmount;
|
||||
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (size.X < 1.0f) Remove();
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
#if CLIENT
|
||||
lightSource.Remove();
|
||||
|
||||
if (basicSoundIndex > 0)
|
||||
{
|
||||
Sounds.SoundManager.Stop(basicSoundIndex);
|
||||
basicSoundIndex = -1;
|
||||
}
|
||||
if (largeSoundIndex > 0)
|
||||
{
|
||||
Sounds.SoundManager.Stop(largeSoundIndex);
|
||||
largeSoundIndex = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
hull.RemoveFire(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,698 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.ObjectModel;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Gap : MapEntity
|
||||
{
|
||||
public static List<Gap> GapList = new List<Gap>();
|
||||
|
||||
public static bool ShowGaps = true;
|
||||
|
||||
public bool isHorizontal;
|
||||
|
||||
//private Sound waterSound;
|
||||
|
||||
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
|
||||
private float open;
|
||||
|
||||
//the force of the water flow which is exerted on physics bodies
|
||||
private Vector2 flowForce;
|
||||
|
||||
private Hull flowTargetHull;
|
||||
|
||||
private float higherSurface;
|
||||
private float lowerSurface;
|
||||
|
||||
private Vector2 lerpedFlowForce;
|
||||
|
||||
//if set to true, hull connections of this gap won't be updated when changes are being done to hulls
|
||||
public bool DisableHullRechecks;
|
||||
|
||||
//can ambient light get through the gap even if it's not open
|
||||
public bool PassAmbientLight;
|
||||
|
||||
public float Open
|
||||
{
|
||||
get { return open; }
|
||||
set { open = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public Door ConnectedDoor;
|
||||
|
||||
public Structure ConnectedWall;
|
||||
|
||||
public Vector2 LerpedFlowForce
|
||||
{
|
||||
get { return lerpedFlowForce; }
|
||||
}
|
||||
|
||||
public Hull FlowTargetHull
|
||||
{
|
||||
get { return flowTargetHull; }
|
||||
}
|
||||
|
||||
public bool IsRoomToRoom
|
||||
{
|
||||
get
|
||||
{
|
||||
return linkedTo.Count == 2;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Rect = value;
|
||||
|
||||
FindHulls();
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Gap";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool SelectableInEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ShowGaps;
|
||||
}
|
||||
}
|
||||
|
||||
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (rectangle, Submarine.MainSub)
|
||||
{ }
|
||||
|
||||
public Gap(Rectangle newRect, Submarine submarine)
|
||||
: this(newRect, newRect.Width < newRect.Height, submarine)
|
||||
{ }
|
||||
|
||||
public Gap(Rectangle newRect, bool isHorizontal, Submarine submarine)
|
||||
: base (MapEntityPrefab.list.Find(m=> m.Name == "Gap"), submarine)
|
||||
{
|
||||
rect = newRect;
|
||||
linkedTo = new ObservableCollection<MapEntity>();
|
||||
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
this.isHorizontal = isHorizontal;
|
||||
|
||||
open = 1.0f;
|
||||
|
||||
FindHulls();
|
||||
|
||||
GapList.Add(this);
|
||||
InsertToList();
|
||||
}
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
return new Gap(rect, isHorizontal, Submarine);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
FindHulls();
|
||||
}
|
||||
|
||||
public static void UpdateHulls()
|
||||
{
|
||||
foreach (Gap g in GapList)
|
||||
{
|
||||
if (g.DisableHullRechecks) continue;
|
||||
g.FindHulls();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
return (ShowGaps && Submarine.RectContains(WorldRect, position) &&
|
||||
!Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -5), position));
|
||||
}
|
||||
|
||||
private void FindHulls()
|
||||
{
|
||||
Hull[] hulls = new Hull[2];
|
||||
|
||||
linkedTo.Clear();
|
||||
|
||||
Vector2[] searchPos = new Vector2[2];
|
||||
if (isHorizontal)
|
||||
{
|
||||
searchPos[0] = new Vector2(rect.X, rect.Y - rect.Height / 2);
|
||||
searchPos[1] = new Vector2(rect.Right, rect.Y - rect.Height / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchPos[0] = new Vector2(rect.Center.X, rect.Y);
|
||||
searchPos[1] = new Vector2(rect.Center.X, rect.Y - rect.Height);
|
||||
}
|
||||
|
||||
hulls[0] = Hull.FindHullOld(searchPos[0], null, false);
|
||||
hulls[1] = Hull.FindHullOld(searchPos[1], null, false);
|
||||
|
||||
if (hulls[0] == null && hulls[1] == null) return;
|
||||
|
||||
if (hulls[0]==null && hulls[1]!=null)
|
||||
{
|
||||
Hull temp = hulls[0];
|
||||
hulls[0] = hulls[1];
|
||||
hulls[1] = temp;
|
||||
}
|
||||
|
||||
flowTargetHull = hulls[0];
|
||||
|
||||
for (int i = 0 ; i <2; i++)
|
||||
{
|
||||
if (hulls[i]==null) continue;
|
||||
linkedTo.Add(hulls[i]);
|
||||
if (!hulls[i].ConnectedGaps.Contains(this)) hulls[i].ConnectedGaps.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
if (open == 0.0f)
|
||||
{
|
||||
lerpedFlowForce = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateOxygen();
|
||||
|
||||
if (linkedTo.Count == 1)
|
||||
{
|
||||
//gap leading from a room to outside
|
||||
UpdateRoomToOut(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//gap leading from a room to another
|
||||
UpdateRoomToRoom(deltaTime);
|
||||
}
|
||||
|
||||
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime);
|
||||
|
||||
if (LerpedFlowForce.LengthSquared() > 20000.0f && flowTargetHull != null && flowTargetHull.Volume < flowTargetHull.FullVolume)
|
||||
{
|
||||
//UpdateFlowForce();
|
||||
|
||||
Vector2 pos = Position;
|
||||
if (isHorizontal)
|
||||
{
|
||||
pos.X += Math.Sign(flowForce.X);
|
||||
pos.Y = MathHelper.Clamp((higherSurface + lowerSurface) / 2.0f, rect.Y - rect.Height, rect.Y) + 10;
|
||||
|
||||
Vector2 velocity = new Vector2(
|
||||
MathHelper.Clamp(flowForce.X, -5000.0f, 5000.0f) * Rand.Range(0.5f, 0.7f),
|
||||
flowForce.Y * Rand.Range(0.5f, 0.7f));
|
||||
|
||||
#if CLIENT
|
||||
var particle = GameMain.ParticleManager.CreateParticle(
|
||||
"watersplash",
|
||||
(Submarine == null ? pos : pos + Submarine.Position) - Vector2.UnitY * Rand.Range(0.0f, 10.0f),
|
||||
velocity, 0, flowTargetHull);
|
||||
|
||||
if (particle != null)
|
||||
{
|
||||
particle.Size = particle.Size * Math.Min(Math.Abs(flowForce.X / 1000.0f), 5.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Math.Abs(flowForce.X) > 300.0f)
|
||||
{
|
||||
pos.X += Math.Sign(flowForce.X) * 10.0f;
|
||||
pos.Y = Rand.Range(lowerSurface, rect.Y - rect.Height);
|
||||
|
||||
#if CLIENT
|
||||
GameMain.ParticleManager.CreateParticle(
|
||||
"bubbles",
|
||||
Submarine == null ? pos : pos + Submarine.Position,
|
||||
flowForce / 10.0f, 0, flowTargetHull);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Sign(flowTargetHull.Rect.Y - rect.Y) != Math.Sign(lerpedFlowForce.Y)) return;
|
||||
|
||||
pos.Y += Math.Sign(flowForce.Y) * rect.Height / 2.0f;
|
||||
for (int i = 0; i < rect.Width; i += (int)Rand.Range(80, 100))
|
||||
{
|
||||
pos.X = Rand.Range(rect.X, rect.X + rect.Width);
|
||||
|
||||
Vector2 velocity = new Vector2(
|
||||
lerpedFlowForce.X * Rand.Range(0.5f, 0.7f),
|
||||
Math.Max(lerpedFlowForce.Y, -100.0f) * Rand.Range(0.5f, 0.7f));
|
||||
|
||||
#if CLIENT
|
||||
var splash = GameMain.ParticleManager.CreateParticle(
|
||||
"watersplash",
|
||||
Submarine == null ? pos : pos + Submarine.Position,
|
||||
-velocity, 0, FlowTargetHull);
|
||||
|
||||
if (splash != null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f);
|
||||
|
||||
GameMain.ParticleManager.CreateParticle(
|
||||
"bubbles",
|
||||
Submarine == null ? pos : pos + Submarine.Position,
|
||||
flowForce / 2.0f, 0, FlowTargetHull);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (flowTargetHull != null && lerpedFlowForce != Vector2.Zero)
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.AnimController.CurrentHull != flowTargetHull) continue;
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (!limb.inWater) continue;
|
||||
|
||||
float dist = Vector2.Distance(limb.WorldPosition, WorldPosition);
|
||||
if (dist > lerpedFlowForce.Length()) continue;
|
||||
|
||||
limb.body.ApplyForce(lerpedFlowForce / dist/10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void UpdateRoomToRoom(float deltaTime)
|
||||
{
|
||||
if (linkedTo.Count < 2) return;
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
Hull hull2 = (Hull)linkedTo[1];
|
||||
|
||||
Vector2 subOffset = Vector2.Zero;
|
||||
if (hull1.Submarine != Submarine)
|
||||
{
|
||||
subOffset =Submarine.Position - hull1.Submarine.Position;
|
||||
}
|
||||
else if (hull2.Submarine != Submarine)
|
||||
{
|
||||
|
||||
subOffset = hull2.Submarine.Position - Submarine.Position;
|
||||
|
||||
}
|
||||
|
||||
if (hull1.Volume == 0.0 && hull2.Volume == 0.0) return;
|
||||
|
||||
float size = (isHorizontal) ? rect.Height : rect.Width;
|
||||
|
||||
//a variable affecting the water flow through the gap
|
||||
//the larger the gap is, the faster the water flows
|
||||
float sizeModifier = size / 100.0f * open;
|
||||
|
||||
//horizontal gap (such as a regular door)
|
||||
if (isHorizontal)
|
||||
{
|
||||
higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
|
||||
float delta=0.0f;
|
||||
|
||||
//water level is above the lower boundary of the gap
|
||||
if (Math.Max(hull1.Surface+hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y +hull2.WaveY[0]) > rect.Y - size)
|
||||
{
|
||||
|
||||
int dir = (hull1.Pressure > hull2.Pressure+subOffset.Y) ? 1 : -1;
|
||||
|
||||
//water flowing from the righthand room to the lefthand room
|
||||
if (dir == -1)
|
||||
{
|
||||
if (!(hull2.Volume > 0.0f)) return;
|
||||
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
|
||||
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
|
||||
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
//make sure not to move more than what the room contains
|
||||
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.Volume, hull2.FullVolume));
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - (hull1.Volume));
|
||||
hull1.Volume += delta;
|
||||
hull2.Volume -= delta;
|
||||
if (hull1.Volume > hull1.FullVolume)
|
||||
{
|
||||
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure+subOffset.Y) / 2);
|
||||
}
|
||||
|
||||
flowForce = new Vector2(-delta, 0.0f);
|
||||
}
|
||||
else if (dir == 1)
|
||||
{
|
||||
if (!(hull1.Volume > 0.0f)) return;
|
||||
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
|
||||
|
||||
flowTargetHull = hull2;
|
||||
|
||||
//make sure not to move more than what the room contains
|
||||
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 5.0f * sizeModifier, Math.Min(hull1.Volume, hull1.FullVolume));
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull2.FullVolume + Hull.MaxCompress - (hull2.Volume));
|
||||
hull1.Volume -= delta;
|
||||
hull2.Volume += delta;
|
||||
if (hull2.Volume > hull2.FullVolume)
|
||||
{
|
||||
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2);
|
||||
}
|
||||
|
||||
flowForce = new Vector2(delta, 0.0f);
|
||||
}
|
||||
|
||||
if (delta>100.0f && subOffset == Vector2.Zero)
|
||||
{
|
||||
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
|
||||
//float avgVel = (hull2.WaveVel[1] + hull1.WaveVel[hull1.WaveY.Length - 2]) / 2.0f;
|
||||
|
||||
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
|
||||
hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
|
||||
{
|
||||
hull1.WaveVel[hull1.WaveY.Length - 1] = (avg-(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1]))*0.1f;
|
||||
hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
|
||||
}
|
||||
|
||||
if (hull2.Volume < hull2.FullVolume - Hull.MaxCompress &&
|
||||
hull2.Surface + hull2.WaveY[0] < rect.Y)
|
||||
{
|
||||
hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
|
||||
hull2.WaveVel[1] = hull2.WaveVel[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//lower room is full of water
|
||||
if ((hull2.Pressure + subOffset.Y) > hull1.Pressure)
|
||||
{
|
||||
float delta = Math.Min(hull2.Volume - hull2.FullVolume + Hull.MaxCompress / 2.0f, deltaTime * 8000.0f * sizeModifier);
|
||||
|
||||
flowForce = new Vector2(0.0f, Math.Min((hull2.Pressure + subOffset.Y) - hull1.Pressure, 500.0f));
|
||||
|
||||
delta = Math.Max(delta, 0.0f);
|
||||
hull1.Volume += delta;
|
||||
hull2.Volume -= delta;
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
if (hull1.Volume > hull1.FullVolume)
|
||||
{
|
||||
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + (hull2.Pressure + subOffset.Y)) / 2);
|
||||
}
|
||||
|
||||
}
|
||||
//there's water in the upper room, drop to lower
|
||||
else if (hull1.Volume > 0)
|
||||
{
|
||||
flowTargetHull = hull2;
|
||||
|
||||
//make sure the amount of water moved isn't more than what the room contains
|
||||
float delta = Math.Min(hull1.Volume, deltaTime * 25000f * sizeModifier);
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, (hull2.FullVolume + Math.Max(hull1.Volume - hull1.FullVolume, 0.0f)) - hull2.Volume + Hull.MaxCompress / 4.0f);
|
||||
|
||||
hull1.Volume -= delta;
|
||||
hull2.Volume += delta;
|
||||
|
||||
if (hull2.Volume > hull2.FullVolume)
|
||||
{
|
||||
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure - subOffset.Y) + hull2.Pressure) / 2);
|
||||
}
|
||||
|
||||
flowForce = new Vector2(0.0f, -delta);
|
||||
|
||||
flowForce.X = hull1.WaveY[hull1.GetWaveIndex(rect.X)] - hull1.WaveY[hull1.GetWaveIndex(rect.Right)] * 10.0f;
|
||||
|
||||
//if (water2.Volume < water2.FullVolume - Hull.MaxCompress)
|
||||
//{
|
||||
// int posX = (int)((rect.X + size / 2.0f - water1.Rect.X) / Hull.WaveWidth);
|
||||
// //water1.WaveY[posX] = -delta;
|
||||
// if (posX > -1 && posX < water2.WaveVel.Length)
|
||||
// water1.WaveVel[posX] = -delta * 0.01f;
|
||||
|
||||
// posX = (int)((rect.X + size / 2.0f - water2.Rect.X) / Hull.WaveWidth);
|
||||
// //water2.WaveY[posX] = delta;
|
||||
// if (posX > -1 && posX<water2.WaveVel.Length)
|
||||
// water2.WaveVel[posX] = delta * 0.01f;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (open > 0.0f)
|
||||
{
|
||||
if (hull1.Volume > hull1.FullVolume - Hull.MaxCompress && hull2.Volume > hull2.FullVolume - Hull.MaxCompress)
|
||||
{
|
||||
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
|
||||
hull1.LethalPressure = avgLethality;
|
||||
hull2.LethalPressure = avgLethality;
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure = 0.0f;
|
||||
hull2.LethalPressure = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRoomToOut(float deltaTime)
|
||||
{
|
||||
if (linkedTo.Count != 1) return;
|
||||
|
||||
float size = (isHorizontal) ? rect.Height : rect.Width;
|
||||
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
|
||||
//a variable affecting the water flow through the gap
|
||||
//the larger the gap is, the faster the water flows
|
||||
float sizeModifier = size * open * open;
|
||||
|
||||
float delta = Hull.MaxCompress * sizeModifier * deltaTime;
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.FullVolume + Hull.MaxCompress - hull1.Volume);
|
||||
hull1.Volume += delta;
|
||||
|
||||
if (hull1.Volume > hull1.FullVolume) hull1.Pressure += 0.5f;
|
||||
|
||||
flowTargetHull = hull1;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
//water flowing from right to left
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
flowForce = new Vector2(-delta, 0.0f);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = new Vector2(delta, 0.0f);
|
||||
}
|
||||
|
||||
higherSurface = hull1.Surface;
|
||||
lowerSurface = rect.Y;
|
||||
|
||||
if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress &&
|
||||
hull1.Surface < rect.Y)
|
||||
{
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
|
||||
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
|
||||
|
||||
hull1.WaveVel[hull1.WaveY.Length - 1] += vel;
|
||||
hull1.WaveVel[hull1.WaveY.Length - 2] += vel;
|
||||
}
|
||||
else
|
||||
{
|
||||
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 0.1f;
|
||||
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
|
||||
|
||||
hull1.WaveVel[0] += vel;
|
||||
hull1.WaveVel[1] += vel;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rect.Y > hull1.Rect.Y - hull1.Rect.Height / 2.0f)
|
||||
{
|
||||
flowForce = new Vector2(0.0f, -delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = new Vector2(0.0f, delta);
|
||||
}
|
||||
if (hull1.Volume >= hull1.FullVolume - Hull.MaxCompress)
|
||||
{
|
||||
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateOxygen()
|
||||
{
|
||||
if (linkedTo.Count < 2) return;
|
||||
Hull hull1 = (Hull)linkedTo[0];
|
||||
Hull hull2 = (Hull)linkedTo[1];
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + hull2.WaveY[0]) > rect.Y) return;
|
||||
}
|
||||
|
||||
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
|
||||
float totalVolume = (hull1.FullVolume + hull2.FullVolume);
|
||||
|
||||
float deltaOxygen = (totalOxygen * hull1.FullVolume / totalVolume) - hull1.Oxygen;
|
||||
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed, Hull.OxygenDistributionSpeed);
|
||||
|
||||
hull1.Oxygen += deltaOxygen;
|
||||
hull2.Oxygen -= deltaOxygen;
|
||||
}
|
||||
|
||||
public static Gap FindAdjacent(List<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
|
||||
{
|
||||
foreach (Gap gap in gaps)
|
||||
{
|
||||
if (gap.Open == 0.0f || gap.IsRoomToRoom) continue;
|
||||
|
||||
if (gap.ConnectedWall != null)
|
||||
{
|
||||
int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position);
|
||||
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) continue;
|
||||
}
|
||||
|
||||
if (gap.isHorizontal)
|
||||
{
|
||||
if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height &&
|
||||
Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
|
||||
{
|
||||
return gap;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right &&
|
||||
Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
|
||||
{
|
||||
return gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.ShallowRemove();
|
||||
GapList.Remove(this);
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
GapList.Remove(this);
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
FindHulls();
|
||||
}
|
||||
|
||||
public static void Load(XElement element, Submarine submarine)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
|
||||
if (element.Attribute("rect") != null)
|
||||
{
|
||||
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
|
||||
string[] rectValues = rectString.Split(',');
|
||||
|
||||
rect = new Rectangle(
|
||||
int.Parse(rectValues[0]),
|
||||
int.Parse(rectValues[1]),
|
||||
int.Parse(rectValues[2]),
|
||||
int.Parse(rectValues[3]));
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
}
|
||||
|
||||
bool isHorizontal = rect.Height > rect.Width;
|
||||
|
||||
var horizontalAttribute = element.Attribute("horizontal");
|
||||
if (horizontalAttribute!=null)
|
||||
{
|
||||
isHorizontal = horizontalAttribute.Value.ToString() == "true";
|
||||
}
|
||||
|
||||
Gap g = new Gap(rect, isHorizontal, submarine);
|
||||
g.ID = (ushort)int.Parse(element.Attribute("ID").Value);
|
||||
|
||||
g.linkedToID = new List<ushort>();
|
||||
//int i = 0;
|
||||
//while (element.Attribute("linkedto" + i) != null)
|
||||
//{
|
||||
// g.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
|
||||
// i += 1;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Lidgren.Network;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Hull : MapEntity, IPropertyObject, IServerSerializable
|
||||
{
|
||||
const float NetworkUpdateInterval = 0.5f;
|
||||
|
||||
public static List<Hull> hullList = new List<Hull>();
|
||||
private static List<EntityGrid> entityGrids = new List<EntityGrid>();
|
||||
public static List<EntityGrid> EntityGrids
|
||||
{
|
||||
get
|
||||
{
|
||||
return entityGrids;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShowHulls = true;
|
||||
|
||||
public static bool EditWater, EditFire;
|
||||
|
||||
private List<FireSource> fireSources;
|
||||
|
||||
public const float OxygenDistributionSpeed = 500.0f;
|
||||
public const float OxygenDetoriationSpeed = 0.3f;
|
||||
public const float OxygenConsumptionSpeed = 1000.0f;
|
||||
|
||||
public const int WaveWidth = 16;
|
||||
const float WaveStiffness = 0.003f;
|
||||
const float WaveSpread = 0.05f;
|
||||
const float WaveDampening = 0.01f;
|
||||
|
||||
//how much excess water the room can contain (= more than the volume of the room)
|
||||
public const float MaxCompress = 10000f;
|
||||
|
||||
public readonly Dictionary<string, ObjectProperty> properties;
|
||||
public Dictionary<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get { return properties; }
|
||||
}
|
||||
|
||||
private float lethalPressure;
|
||||
|
||||
private float surface;
|
||||
private float volume;
|
||||
private float pressure;
|
||||
|
||||
private float oxygen;
|
||||
|
||||
private bool update;
|
||||
|
||||
public bool Visible = true;
|
||||
|
||||
float[] waveY; //displacement from the surface of the water
|
||||
float[] waveVel; //velocity of the point
|
||||
|
||||
float[] leftDelta;
|
||||
float[] rightDelta;
|
||||
|
||||
private float lastSentVolume, lastSentOxygen;
|
||||
private float sendUpdateTimer;
|
||||
|
||||
public List<Gap> ConnectedGaps;
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Hull";
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Rect = value;
|
||||
|
||||
if (Submarine == null || !Submarine.Loading)
|
||||
{
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
surface = rect.Y - rect.Height + Volume / rect.Width;
|
||||
Pressure = surface;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool SelectableInEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
return ShowHulls;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsLinkable
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public float LethalPressure
|
||||
{
|
||||
get { return lethalPressure; }
|
||||
set { lethalPressure = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return new Vector2(rect.Width, rect.Height); }
|
||||
}
|
||||
|
||||
public float Surface
|
||||
{
|
||||
get { return surface; }
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get { return volume; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
volume = MathHelper.Clamp(value, 0.0f, FullVolume + MaxCompress);
|
||||
if (volume < FullVolume) Pressure = rect.Y - rect.Height + volume / rect.Width;
|
||||
if (volume > 0.0f) update = true;
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(90.0f, true)]
|
||||
public float Oxygen
|
||||
{
|
||||
get { return oxygen; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
oxygen = MathHelper.Clamp(value, 0.0f, FullVolume);
|
||||
}
|
||||
}
|
||||
|
||||
public float OxygenPercentage
|
||||
{
|
||||
get { return oxygen / FullVolume * 100.0f; }
|
||||
set { Oxygen = (value / 100.0f) * FullVolume; }
|
||||
}
|
||||
|
||||
public float FullVolume
|
||||
{
|
||||
get { return (rect.Width * rect.Height); }
|
||||
}
|
||||
|
||||
public float Pressure
|
||||
{
|
||||
get { return pressure; }
|
||||
set { pressure = value; }
|
||||
}
|
||||
|
||||
public float[] WaveY
|
||||
{
|
||||
get { return waveY; }
|
||||
}
|
||||
|
||||
public float[] WaveVel
|
||||
{
|
||||
get { return waveVel; }
|
||||
}
|
||||
|
||||
public List<FireSource> FireSources
|
||||
{
|
||||
get { return fireSources; }
|
||||
}
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (prefab, rectangle, Submarine.MainSub)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine)
|
||||
: base (prefab, submarine)
|
||||
{
|
||||
rect = rectangle;
|
||||
|
||||
OxygenPercentage = 100.0f;
|
||||
|
||||
fireSources = new List<FireSource>();
|
||||
|
||||
properties = ObjectProperty.GetProperties(this);
|
||||
|
||||
int arraySize = (rectangle.Width / WaveWidth + 1);
|
||||
waveY = new float[arraySize];
|
||||
waveVel = new float[arraySize];
|
||||
|
||||
leftDelta = new float[arraySize];
|
||||
rightDelta = new float[arraySize];
|
||||
|
||||
surface = rect.Y - rect.Height;
|
||||
|
||||
aiTarget = new AITarget(this);
|
||||
|
||||
hullList.Add(this);
|
||||
|
||||
ConnectedGaps = new List<Gap>();
|
||||
|
||||
if (submarine==null || !submarine.Loading)
|
||||
{
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
Volume = 0.0f;
|
||||
|
||||
InsertToList();
|
||||
}
|
||||
|
||||
public static Rectangle GetBorders()
|
||||
{
|
||||
if (!hullList.Any()) return Rectangle.Empty;
|
||||
|
||||
Rectangle rect = hullList[0].rect;
|
||||
|
||||
foreach (Hull hull in hullList)
|
||||
{
|
||||
if (hull.Rect.X < rect.X)
|
||||
{
|
||||
rect.Width += rect.X - hull.rect.X;
|
||||
rect.X = hull.rect.X;
|
||||
|
||||
}
|
||||
if (hull.rect.Right > rect.Right) rect.Width = hull.rect.Right - rect.X;
|
||||
|
||||
if (hull.rect.Y > rect.Y)
|
||||
{
|
||||
rect.Height += hull.rect.Y - rect.Y;
|
||||
|
||||
rect.Y = hull.rect.Y;
|
||||
}
|
||||
if (hull.rect.Y - hull.rect.Height < rect.Y - rect.Height) rect.Height = rect.Y - (hull.rect.Y - hull.rect.Height);
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
return new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), rect, Submarine);
|
||||
}
|
||||
|
||||
public static EntityGrid GenerateEntityGrid(Submarine submarine)
|
||||
{
|
||||
var newGrid = new EntityGrid(submarine, 200.0f);
|
||||
|
||||
entityGrids.Add(newGrid);
|
||||
|
||||
foreach (Hull hull in hullList)
|
||||
{
|
||||
if (hull.Submarine == submarine) newGrid.InsertEntity(hull);
|
||||
}
|
||||
return newGrid;
|
||||
}
|
||||
|
||||
public void AddToGrid(Submarine submarine)
|
||||
{
|
||||
foreach (EntityGrid grid in entityGrids)
|
||||
{
|
||||
if (grid.Submarine != submarine) continue;
|
||||
|
||||
rect.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
|
||||
|
||||
grid.InsertEntity(this);
|
||||
|
||||
rect.Location += MathUtils.ToPoint(submarine.HiddenSubPosition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetWaveIndex(Vector2 position)
|
||||
{
|
||||
return GetWaveIndex(position.X);
|
||||
}
|
||||
|
||||
public int GetWaveIndex(float xPos)
|
||||
{
|
||||
int index = (int)(xPos - rect.X) / WaveWidth;
|
||||
index = (int)MathHelper.Clamp(index, 0, waveY.Length - 1);
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
|
||||
if (Submarine==null || !Submarine.Loading)
|
||||
{
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
surface = rect.Y - rect.Height + Volume / rect.Width;
|
||||
Pressure = surface;
|
||||
}
|
||||
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.Remove();
|
||||
hullList.Remove(this);
|
||||
|
||||
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
|
||||
{
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
List<FireSource> fireSourcesToRemove = new List<FireSource>(fireSources);
|
||||
foreach (FireSource fireSource in fireSourcesToRemove)
|
||||
{
|
||||
fireSource.Remove();
|
||||
}
|
||||
fireSources.Clear();
|
||||
|
||||
#if CLIENT
|
||||
if (soundIndex > -1)
|
||||
{
|
||||
Sounds.SoundManager.Stop(soundIndex);
|
||||
soundIndex = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (entityGrids != null)
|
||||
{
|
||||
foreach (EntityGrid entityGrid in entityGrids)
|
||||
{
|
||||
entityGrid.RemoveEntity(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
hullList.Remove(this);
|
||||
|
||||
if (Submarine == null || (!Submarine.Loading && !Submarine.Unloading))
|
||||
{
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
}
|
||||
|
||||
List<FireSource> fireSourcesToRemove = new List<FireSource>(fireSources);
|
||||
foreach (FireSource fireSource in fireSourcesToRemove)
|
||||
{
|
||||
fireSource.Remove();
|
||||
}
|
||||
fireSources.Clear();
|
||||
|
||||
#if CLIENT
|
||||
if (soundIndex > -1)
|
||||
{
|
||||
Sounds.SoundManager.Stop(soundIndex);
|
||||
soundIndex = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (entityGrids != null)
|
||||
{
|
||||
foreach (EntityGrid entityGrid in entityGrids)
|
||||
{
|
||||
entityGrid.RemoveEntity(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFireSource(FireSource fireSource)
|
||||
{
|
||||
fireSources.Add(fireSource);
|
||||
|
||||
if (GameMain.Server != null) GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
Oxygen -= OxygenDetoriationSpeed * deltaTime;
|
||||
|
||||
if (EditWater)
|
||||
{
|
||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (Submarine.RectContains(WorldRect, position))
|
||||
{
|
||||
if (PlayerInput.LeftButtonHeld())
|
||||
{
|
||||
//waveY[GetWaveIndex(position.X - rect.X - Submarine.Position.X) / WaveWidth] = 100.0f;
|
||||
Volume = Volume + 1500.0f;
|
||||
}
|
||||
else if (PlayerInput.RightButtonHeld())
|
||||
{
|
||||
Volume = Volume - 1500.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EditFire)
|
||||
{
|
||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (Submarine.RectContains(WorldRect, position))
|
||||
{
|
||||
if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
new FireSource(position, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FireSource.UpdateAll(fireSources, deltaTime);
|
||||
|
||||
aiTarget.SightRange = Submarine == null ? 0.0f : Math.Max(Submarine.Velocity.Length() * 500.0f, 500.0f);
|
||||
aiTarget.SoundRange -= deltaTime * 1000.0f;
|
||||
|
||||
float strongestFlow = 0.0f;
|
||||
foreach (Gap gap in ConnectedGaps)
|
||||
{
|
||||
if (gap.IsRoomToRoom)
|
||||
{
|
||||
//only the first linked hull plays the flow sound
|
||||
if (gap.linkedTo[1] == this) continue;
|
||||
}
|
||||
|
||||
float gapFlow = gap.LerpedFlowForce.Length();
|
||||
|
||||
if (gapFlow > strongestFlow)
|
||||
{
|
||||
strongestFlow = gapFlow;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (strongestFlow > 1.0f)
|
||||
{
|
||||
soundVolume = soundVolume + ((strongestFlow < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f);
|
||||
soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f);
|
||||
|
||||
int index = (int)Math.Floor(strongestFlow / 100.0f);
|
||||
index = Math.Min(index, 2);
|
||||
|
||||
var flowSound = SoundPlayer.flowSounds[index];
|
||||
if (flowSound != currentFlowSound && soundIndex > -1)
|
||||
{
|
||||
Sounds.SoundManager.Stop(soundIndex);
|
||||
currentFlowSound = null;
|
||||
soundIndex = -1;
|
||||
}
|
||||
|
||||
currentFlowSound = flowSound;
|
||||
soundIndex = currentFlowSound.Loop(soundIndex, soundVolume, WorldPosition, Math.Min(strongestFlow*5.0f, 2000.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (soundIndex > -1)
|
||||
{
|
||||
Sounds.SoundManager.Stop(soundIndex);
|
||||
currentFlowSound = null;
|
||||
soundIndex = -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//update client hulls if the amount of water has changed by >10%
|
||||
//or if oxygen percentage has changed by 5%
|
||||
if (Math.Abs(lastSentVolume - volume) > FullVolume * 0.1f ||
|
||||
Math.Abs(lastSentOxygen - OxygenPercentage) > 5f)
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
sendUpdateTimer -= deltaTime;
|
||||
if (sendUpdateTimer < 0.0f)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
lastSentVolume = volume;
|
||||
lastSentOxygen = OxygenPercentage;
|
||||
sendUpdateTimer = NetworkUpdateInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!update)
|
||||
{
|
||||
lethalPressure = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
float surfaceY = rect.Y - rect.Height + Volume / rect.Width;
|
||||
for (int i = 0; i < waveY.Length; i++)
|
||||
{
|
||||
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
|
||||
#if CLIENT
|
||||
if (maxDelta > Rand.Range(1.0f,10.0f))
|
||||
{
|
||||
var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]);
|
||||
if (Submarine != null) particlePos += Submarine.Position;
|
||||
|
||||
GameMain.ParticleManager.CreateParticle("mist",
|
||||
particlePos,
|
||||
new Vector2(0.0f, -50.0f), 0.0f, this);
|
||||
}
|
||||
#endif
|
||||
|
||||
waveY[i] = waveY[i] + waveVel[i];
|
||||
|
||||
if (surfaceY + waveY[i] > rect.Y)
|
||||
{
|
||||
waveY[i] -= (surfaceY + waveY[i]) - rect.Y;
|
||||
waveVel[i] = waveVel[i] * -0.5f;
|
||||
}
|
||||
else if (surfaceY + waveY[i] < rect.Y - rect.Height)
|
||||
{
|
||||
waveY[i] -= (surfaceY + waveY[i]) - (rect.Y - rect.Height);
|
||||
waveVel[i] = waveVel[i] * -0.5f;
|
||||
}
|
||||
|
||||
//acceleration
|
||||
float a = -WaveStiffness * waveY[i] - waveVel[i] * WaveDampening;
|
||||
waveVel[i] = waveVel[i] + a;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
leftDelta[i] = WaveSpread * (waveY[i] - waveY[i - 1]);
|
||||
waveVel[i - 1] = waveVel[i - 1] + leftDelta[i];
|
||||
|
||||
rightDelta[i] = WaveSpread * (waveY[i] - waveY[i + 1]);
|
||||
waveVel[i + 1] = waveVel[i + 1] + rightDelta[i];
|
||||
}
|
||||
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
waveY[i - 1] = waveY[i - 1] + leftDelta[i];
|
||||
waveY[i + 1] = waveY[i + 1] + rightDelta[i];
|
||||
}
|
||||
}
|
||||
|
||||
//interpolate the position of the rendered surface towards the "target surface"
|
||||
surface = Math.Max(MathHelper.Lerp(surface, surfaceY, deltaTime*10.0f), rect.Y - rect.Height);
|
||||
|
||||
if (volume < FullVolume)
|
||||
{
|
||||
LethalPressure -= 10.0f * deltaTime;
|
||||
if (Volume == 0.0f)
|
||||
{
|
||||
//wait for the surface to be lerped back to bottom and the waves to settle until disabling update
|
||||
if (surface > rect.Y - rect.Height + 1) return;
|
||||
for (int i = 1; i < waveY.Length - 1; i++)
|
||||
{
|
||||
if (waveY[i] > 0.1f) return;
|
||||
}
|
||||
|
||||
update = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyFlowForces(float deltaTime, Item item)
|
||||
{
|
||||
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
|
||||
{
|
||||
//var pos = gap.Position - body.Position;
|
||||
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position)/1000, 1f);
|
||||
|
||||
//pos.Normalize();
|
||||
item.body.ApplyForce((gap.LerpedFlowForce/distance) * deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Extinguish(float deltaTime, float amount, Vector2 position)
|
||||
{
|
||||
for (int i = fireSources.Count - 1; i >= 0; i-- )
|
||||
{
|
||||
fireSources[i].Extinguish(deltaTime, amount, position);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFire(FireSource fire)
|
||||
{
|
||||
fireSources.Remove(fire);
|
||||
|
||||
if (GameMain.Server != null) GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
|
||||
public List<Hull> GetConnectedHulls(int? searchDepth)
|
||||
{
|
||||
return GetAdjacentHulls(new List<Hull>(), 0, searchDepth);
|
||||
|
||||
}
|
||||
|
||||
private List<Hull> GetAdjacentHulls(List<Hull> connectedHulls, int steps, int? searchDepth)
|
||||
{
|
||||
connectedHulls.Add(this);
|
||||
|
||||
if (searchDepth != null && steps >= searchDepth.Value) return connectedHulls;
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
|
||||
{
|
||||
Hull hull = g.linkedTo[i] as Hull;
|
||||
if (hull != null && !connectedHulls.Contains(hull))
|
||||
{
|
||||
hull.GetAdjacentHulls(connectedHulls, steps++, searchDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connectedHulls;
|
||||
}
|
||||
|
||||
//returns the water block which contains the point (or null if it isn't inside any)
|
||||
public static Hull FindHull(Vector2 position, Hull guess = null, bool useWorldCoordinates = true)
|
||||
{
|
||||
if (entityGrids == null) return null;
|
||||
|
||||
if (guess != null)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position)) return guess;
|
||||
}
|
||||
|
||||
var entities = EntityGrid.GetEntities(entityGrids, position, useWorldCoordinates);
|
||||
foreach (Hull hull in entities)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position)) return hull;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//returns the water block which contains the point (or null if it isn't inside any)
|
||||
public static Hull FindHullOld(Vector2 position, Hull guess = null, bool useWorldCoordinates = true)
|
||||
{
|
||||
return FindHullOld(position, hullList, guess, useWorldCoordinates);
|
||||
}
|
||||
|
||||
public static Hull FindHullOld(Vector2 position, List<Hull> hulls, Hull guess = null, bool useWorldCoordinates = true)
|
||||
{
|
||||
if (guess != null && hulls.Contains(guess))
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position)) return guess;
|
||||
}
|
||||
|
||||
foreach (Hull hull in hulls)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position)) return hull;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DetectItemVisibility(Character c=null)
|
||||
{
|
||||
if (c==null)
|
||||
{
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
it.Visible = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Hull h = c.CurrentHull;
|
||||
hullList.ForEach(j => j.Visible = false);
|
||||
List<Hull> visibleHulls;
|
||||
if (h == null || c.Submarine == null)
|
||||
{
|
||||
visibleHulls = hullList.FindAll(j => j.CanSeeOther(null, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
visibleHulls = hullList.FindAll(j => h.CanSeeOther(j, true));
|
||||
}
|
||||
visibleHulls.ForEach(j => j.Visible = true);
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.CurrentHull == null || visibleHulls.Contains(it.CurrentHull)) it.Visible = true;
|
||||
else it.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanSeeOther(Hull other,bool allowIndirect=true)
|
||||
{
|
||||
if (other == this) return true;
|
||||
|
||||
if (other != null && other.Submarine==Submarine)
|
||||
{
|
||||
bool retVal = false;
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
if (g.ConnectedWall != null && g.ConnectedWall.CastShadow) continue;
|
||||
List<Hull> otherHulls = Hull.hullList.FindAll(h => h.ConnectedGaps.Contains(g) && h!=this);
|
||||
retVal = otherHulls.Any(h => h == other);
|
||||
if (!retVal && allowIndirect) retVal = otherHulls.Any(h => h.CanSeeOther(other, false));
|
||||
if (retVal) return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
if (g.ConnectedDoor != null && !hullList.Any(h => h.ConnectedGaps.Contains(g) && h!=this)) return true;
|
||||
}
|
||||
List<MapEntity> structures = MapEntity.mapEntityList.FindAll(me => me is Structure && me.Rect.Intersects(Rect));
|
||||
return structures.Any(st => !(st as Structure).CastShadow);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer message, Client c, object[] extraData = null)
|
||||
{
|
||||
message.WriteRangedSingle(MathHelper.Clamp(volume / FullVolume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
|
||||
message.WriteRangedSingle(MathHelper.Clamp(OxygenPercentage, 0.0f, 100.0f), 0.0f, 100.0f, 8);
|
||||
|
||||
message.Write(fireSources.Count > 0);
|
||||
if (fireSources.Count > 0)
|
||||
{
|
||||
message.WriteRangedInteger(0, 16, Math.Min(fireSources.Count, 16));
|
||||
for (int i = 0; i < Math.Min(fireSources.Count, 16); i++)
|
||||
{
|
||||
var fireSource = fireSources[i];
|
||||
Vector2 normalizedPos = new Vector2(
|
||||
(fireSource.Position.X - rect.X) / rect.Width,
|
||||
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
|
||||
|
||||
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
|
||||
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
|
||||
message.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime)
|
||||
{
|
||||
Volume = message.ReadRangedSingle(0.0f, 1.5f, 8) * FullVolume;
|
||||
OxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
bool hasFireSources = message.ReadBoolean();
|
||||
int fireSourceCount = 0;
|
||||
|
||||
if (hasFireSources)
|
||||
{
|
||||
fireSourceCount = message.ReadRangedInteger(0, 16);
|
||||
for (int i = 0; i < fireSourceCount; i++)
|
||||
{
|
||||
Vector2 pos = Vector2.Zero;
|
||||
float size = 0.0f;
|
||||
pos.X = MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
|
||||
pos.Y = MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f);
|
||||
size = message.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
|
||||
pos = new Vector2(
|
||||
rect.X + rect.Width * pos.X,
|
||||
rect.Y - rect.Height + (rect.Height * pos.Y));
|
||||
size = size * rect.Width;
|
||||
|
||||
var newFire = i < fireSources.Count ? fireSources[i] : new FireSource(pos + Submarine.Position, null, true);
|
||||
newFire.Position = pos;
|
||||
newFire.Size = new Vector2(size, newFire.Size.Y);
|
||||
|
||||
//ignore if the fire wasn't added to this room (invalid position)?
|
||||
if (!fireSources.Contains(newFire))
|
||||
{
|
||||
newFire.Remove();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (fireSources.Count > fireSourceCount)
|
||||
{
|
||||
fireSources[fireSources.Count - 1].Remove();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Load(XElement element, Submarine submarine)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
|
||||
if (element.Attribute("rect") != null)
|
||||
{
|
||||
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
|
||||
string[] rectValues = rectString.Split(',');
|
||||
|
||||
rect = new Rectangle(
|
||||
int.Parse(rectValues[0]),
|
||||
int.Parse(rectValues[1]),
|
||||
int.Parse(rectValues[2]),
|
||||
int.Parse(rectValues[3]));
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
int.Parse(element.Attribute("width").Value),
|
||||
int.Parse(element.Attribute("height").Value));
|
||||
}
|
||||
|
||||
Hull h = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), rect, submarine);
|
||||
|
||||
h.volume = ToolBox.GetAttributeFloat(element, "pressure", 0.0f);
|
||||
|
||||
h.ID = (ushort)int.Parse(element.Attribute("ID").Value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
interface IDamageable
|
||||
{
|
||||
Vector2 SimPosition
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Vector2 WorldPosition
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
float Health
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
AITarget AiTarget
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound=true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Voronoi2;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Factories;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static partial class CaveGenerator
|
||||
{
|
||||
public static List<VoronoiCell> CarveCave(List<VoronoiCell> cells, Vector2 startPoint, out List<VoronoiCell> newCells)
|
||||
{
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
float siteInterval = 400.0f;
|
||||
float siteVariance = siteInterval * 0.4f;
|
||||
|
||||
Vector4 edges = new Vector4(
|
||||
cells.Min(x => x.edges.Min(e => e.point1.X)),
|
||||
cells.Min(x => x.edges.Min(e => e.point1.Y)),
|
||||
cells.Max(x => x.edges.Max(e => e.point1.X)),
|
||||
cells.Max(x => x.edges.Max(e => e.point1.Y)));
|
||||
|
||||
edges.X -= siteInterval * 2;
|
||||
edges.Y -= siteInterval * 2;
|
||||
edges.Z += siteInterval * 2;
|
||||
edges.W += siteInterval * 2;
|
||||
|
||||
Rectangle borders = new Rectangle((int)edges.X, (int)edges.Y, (int)(edges.Z - edges.X), (int)(edges.W - edges.Y));
|
||||
|
||||
for (float x = edges.X + siteInterval; x < edges.Z - siteInterval; x += siteInterval)
|
||||
{
|
||||
for (float y = edges.Y + siteInterval; y < edges.W - siteInterval; y += siteInterval)
|
||||
{
|
||||
if (Rand.Int(5, Rand.RandSync.Server) == 0) continue; //skip some positions to make the cells more irregular
|
||||
|
||||
sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, Rand.RandSync.Server));
|
||||
}
|
||||
}
|
||||
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, edges.X, edges.Y, edges.Z, edges.W);
|
||||
|
||||
List<VoronoiCell>[,] cellGrid;
|
||||
newCells = GraphEdgesToCells(graphEdges, borders, 1000, out cellGrid);
|
||||
|
||||
foreach (VoronoiCell cell in newCells)
|
||||
{
|
||||
//if the cell is at the edge of the graph, remove it
|
||||
if (cell.edges.Any(e =>
|
||||
e.point1.X == edges.X || e.point1.X == edges.Z ||
|
||||
e.point1.Y == edges.Z || e.point1.Y == edges.W))
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
continue;
|
||||
}
|
||||
|
||||
//remove cells that aren't inside any of the original "base cells"
|
||||
if (cells.Any(c => c.IsPointInside(cell.Center))) continue;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
//mark all the cells adjacent to the removed cell as edges of the cave
|
||||
var adjacent = edge.AdjacentCell(cell);
|
||||
if (adjacent != null && adjacent.CellType != CellType.Removed) adjacent.CellType = CellType.Edge;
|
||||
}
|
||||
|
||||
cell.CellType = CellType.Removed;
|
||||
}
|
||||
|
||||
newCells.RemoveAll(newCell => newCell.CellType == CellType.Removed);
|
||||
|
||||
//start carving from the edge cell closest to the startPoint
|
||||
VoronoiCell startCell = null;
|
||||
float closestDist = 0.0f;
|
||||
foreach (VoronoiCell cell in newCells)
|
||||
{
|
||||
if (cell.CellType != CellType.Edge) continue;
|
||||
|
||||
float dist = Vector2.Distance(startPoint, cell.Center);
|
||||
if (dist < closestDist || startCell == null)
|
||||
{
|
||||
startCell = cell;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
startCell.CellType = CellType.Path;
|
||||
|
||||
List<VoronoiCell> path = new List<VoronoiCell>() {startCell};
|
||||
VoronoiCell pathCell = startCell;
|
||||
for (int i = 0; i < newCells.Count / 2; i++)
|
||||
{
|
||||
var allowedNextCells = new List<VoronoiCell>();
|
||||
foreach (GraphEdge edge in pathCell.edges)
|
||||
{
|
||||
var adjacent = edge.AdjacentCell(pathCell);
|
||||
if (adjacent == null ||
|
||||
adjacent.CellType == CellType.Removed ||
|
||||
adjacent.CellType == CellType.Edge) continue;
|
||||
|
||||
allowedNextCells.Add(adjacent);
|
||||
}
|
||||
|
||||
if (allowedNextCells.Count == 0)
|
||||
{
|
||||
if (i>5) break;
|
||||
|
||||
foreach (GraphEdge edge in pathCell.edges)
|
||||
{
|
||||
var adjacent = edge.AdjacentCell(pathCell);
|
||||
if (adjacent == null ||
|
||||
adjacent.CellType == CellType.Removed) continue;
|
||||
|
||||
allowedNextCells.Add(adjacent);
|
||||
}
|
||||
|
||||
if (allowedNextCells.Count == 0) break;
|
||||
}
|
||||
|
||||
//randomly pick one of the adjacent cells as the next cell
|
||||
pathCell = allowedNextCells[Rand.Int(allowedNextCells.Count, Rand.RandSync.Server)];
|
||||
|
||||
//randomly take steps further away from the startpoint to make the cave expand further
|
||||
if (Rand.Int(4, Rand.RandSync.Server) == 0)
|
||||
{
|
||||
float furthestDist = 0.0f;
|
||||
foreach (VoronoiCell nextCell in allowedNextCells)
|
||||
{
|
||||
float dist = Vector2.Distance(startCell.Center, nextCell.Center);
|
||||
if (dist > furthestDist || furthestDist == 0.0f)
|
||||
{
|
||||
furthestDist = dist;
|
||||
pathCell = nextCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCell.CellType = CellType.Path;
|
||||
path.Add(pathCell);
|
||||
}
|
||||
|
||||
//make sure the tunnel is always wider than minPathWidth
|
||||
float minPathWidth = 100.0f;
|
||||
for (int i = 0; i < path.Count; i++)
|
||||
{
|
||||
var cell = path[i];
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
if (edge.point1 == edge.point2) continue;
|
||||
if (Vector2.Distance(edge.point1, edge.point2) > minPathWidth) continue;
|
||||
|
||||
GraphEdge adjacentEdge = cell.edges.Find(e => e != edge && (e.point1 == edge.point1 || e.point2 == edge.point1));
|
||||
|
||||
var adjacentCell = adjacentEdge.AdjacentCell(cell);
|
||||
if (i>0 && (adjacentCell.CellType == CellType.Path || adjacentCell.CellType == CellType.Edge)) continue;
|
||||
|
||||
adjacentCell.CellType = CellType.Path;
|
||||
path.Add(adjacentCell);
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GraphEdgesToCells(List<GraphEdge> graphEdges, Rectangle borders, float gridCellSize, out List<VoronoiCell>[,] cellGrid)
|
||||
{
|
||||
List<VoronoiCell> cells = new List<VoronoiCell>();
|
||||
|
||||
cellGrid = new List<VoronoiCell>[(int)Math.Ceiling(borders.Width / gridCellSize), (int)Math.Ceiling(borders.Height / gridCellSize)];
|
||||
for (int x = 0; x < borders.Width / gridCellSize; x++)
|
||||
{
|
||||
for (int y = 0; y < borders.Height / gridCellSize; y++)
|
||||
{
|
||||
cellGrid[x, y] = new List<VoronoiCell>();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GraphEdge ge in graphEdges)
|
||||
{
|
||||
if (ge.point1 == ge.point2) continue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Site site = (i == 0) ? ge.site1 : ge.site2;
|
||||
|
||||
int x = (int)(Math.Floor((site.coord.x-borders.X) / gridCellSize));
|
||||
int y = (int)(Math.Floor((site.coord.y-borders.Y) / gridCellSize));
|
||||
|
||||
x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0)-1);
|
||||
y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1)-1);
|
||||
|
||||
VoronoiCell cell = cellGrid[x,y].Find(c => c.site == site);
|
||||
|
||||
if (cell == null)
|
||||
{
|
||||
cell = new VoronoiCell(site);
|
||||
cellGrid[x, y].Add(cell);
|
||||
cells.Add(cell);
|
||||
}
|
||||
|
||||
if (ge.cell1 == null)
|
||||
{
|
||||
ge.cell1 = cell;
|
||||
}
|
||||
else
|
||||
{
|
||||
ge.cell2 = cell;
|
||||
}
|
||||
cell.edges.Add(ge);
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
|
||||
{
|
||||
if (cell == null) cell = edge.AdjacentCell(null);
|
||||
if (cell == null) return Vector2.UnitX;
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.point1, edge.point2) == -1)
|
||||
{
|
||||
var temp = edge.point1;
|
||||
edge.point1 = edge.point2;
|
||||
edge.point2 = temp;
|
||||
}
|
||||
|
||||
Vector2 normal = Vector2.Zero;
|
||||
|
||||
normal = Vector2.Normalize(edge.point2 - edge.point1);
|
||||
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.point2);
|
||||
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
|
||||
if (Vector2.Dot(normal, diffToCell) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<Vector2> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false, Vector2? gridOffset = null)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < pathNodes.Count; i++)
|
||||
{
|
||||
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
|
||||
int searchDepth = 2;
|
||||
while (searchDepth < 5)
|
||||
{
|
||||
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, searchDepth, gridOffset);
|
||||
if (cellIndex > -1)
|
||||
{
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
break;
|
||||
}
|
||||
|
||||
searchDepth++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return GeneratePath(targetCells, cells, cellGrid, gridCellSize, limits, wanderAmount, mirror);
|
||||
}
|
||||
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<VoronoiCell> targetCells, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
//how heavily the path "steers" towards the endpoint
|
||||
//lower values will cause the path to "wander" more, higher will make it head straight to the end
|
||||
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
|
||||
|
||||
List<GraphEdge> allowedEdges = new List<GraphEdge>();
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
currentCell.CellType = CellType.Path;
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
int currentTargetIndex = 1;
|
||||
|
||||
int iterationsLeft = cells.Count;
|
||||
|
||||
do
|
||||
{
|
||||
int edgeIndex = 0;
|
||||
|
||||
allowedEdges.Clear();
|
||||
foreach (GraphEdge edge in currentCell.edges)
|
||||
{
|
||||
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
|
||||
|
||||
allowedEdges.Add(edge);
|
||||
}
|
||||
|
||||
//steer towards target
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > wanderAmount || allowedEdges.Count == 0)
|
||||
{
|
||||
for (int i = 0; i < currentCell.edges.Count; i++)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(currentCell.Center, targetCells[currentTargetIndex].Center,
|
||||
currentCell.edges[i].point1, currentCell.edges[i].point2)) continue;
|
||||
edgeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//choose random edge (ignoring ones where the adjacent cell is outside limits)
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
//if (allowedEdges.Count==0)
|
||||
//{
|
||||
// edgeIndex = Rand.Int(currentCell.edges.Count, false);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server);
|
||||
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
|
||||
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
|
||||
//}
|
||||
}
|
||||
|
||||
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
|
||||
currentCell.CellType = CellType.Path;
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
iterationsLeft--;
|
||||
|
||||
if (currentCell == targetCells[currentTargetIndex])
|
||||
{
|
||||
currentTargetIndex += 1;
|
||||
if (currentTargetIndex >= targetCells.Count) break;
|
||||
}
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
|
||||
|
||||
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
public static List<Body> GeneratePolygons(List<VoronoiCell> cells, out List<Vector2[]> renderTriangles, bool setSolid=true)
|
||||
{
|
||||
renderTriangles = null;
|
||||
var bodies = new List<Body>();
|
||||
|
||||
List<Vector2> tempVertices = new List<Vector2>();
|
||||
List<Vector2> bodyPoints = new List<Vector2>();
|
||||
|
||||
for (int n = cells.Count - 1; n >= 0; n-- )
|
||||
{
|
||||
VoronoiCell cell = cells[n];
|
||||
|
||||
bodyPoints.Clear();
|
||||
tempVertices.Clear();
|
||||
foreach (GraphEdge ge in cell.edges)
|
||||
{
|
||||
if (Math.Abs(Vector2.Distance(ge.point1, ge.point2))<0.1f) continue;
|
||||
if (!tempVertices.Contains(ge.point1)) tempVertices.Add(ge.point1);
|
||||
if (!tempVertices.Contains(ge.point2)) tempVertices.Add(ge.point2);
|
||||
|
||||
VoronoiCell adjacentCell = ge.AdjacentCell(cell);
|
||||
//if (adjacentCell!=null && cells.Contains(adjacentCell)) continue;
|
||||
|
||||
if (setSolid) ge.isSolid = (adjacentCell == null || !cells.Contains(adjacentCell));
|
||||
|
||||
if (!bodyPoints.Contains(ge.point1)) bodyPoints.Add(ge.point1);
|
||||
if (!bodyPoints.Contains(ge.point2)) bodyPoints.Add(ge.point2);
|
||||
}
|
||||
|
||||
if (tempVertices.Count < 3 || bodyPoints.Count < 2)
|
||||
{
|
||||
cells.RemoveAt(n);
|
||||
continue;
|
||||
}
|
||||
|
||||
renderTriangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
|
||||
|
||||
if (bodyPoints.Count < 2) continue;
|
||||
|
||||
if (bodyPoints.Count < 3)
|
||||
{
|
||||
foreach (Vector2 vertex in tempVertices)
|
||||
{
|
||||
if (bodyPoints.Contains(vertex)) continue;
|
||||
bodyPoints.Add(vertex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bodyPoints.Count; i++)
|
||||
{
|
||||
cell.bodyVertices.Add(bodyPoints[i]);
|
||||
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
|
||||
}
|
||||
|
||||
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
|
||||
var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
|
||||
|
||||
Body cellBody = new Body(GameMain.World);
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
//don't create a triangle if any of the vertices are too close to each other
|
||||
//(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties)
|
||||
if (Vector2.Distance(triangles[i][0], triangles[i][1]) < 0.05f ||
|
||||
Vector2.Distance(triangles[i][0], triangles[i][2]) < 0.05f ||
|
||||
Vector2.Distance(triangles[i][1], triangles[i][2]) < 0.05f) continue;
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody);
|
||||
}
|
||||
|
||||
cellBody.UserData = cell;
|
||||
cellBody.SleepingAllowed = false;
|
||||
cellBody.BodyType = BodyType.Kinematic;
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
cell.body = cellBody;
|
||||
bodies.Add(cellBody);
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find the index of the cell which the point is inside
|
||||
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
|
||||
/// </summary>
|
||||
public static int FindCellIndex(Vector2 position,List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1, Vector2? offset = null)
|
||||
{
|
||||
float closestDist = 0.0f;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
|
||||
position -= gridOffset;
|
||||
|
||||
int gridPosX = (int)Math.Floor(position.X / gridCellSize);
|
||||
int gridPosY = (int)Math.Floor(position.Y / gridCellSize);
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
float dist = Vector2.Distance(cellGrid[x, y][i].Center, position);
|
||||
if (closestDist != 0.0f && dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,922 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Voronoi2;
|
||||
using Barotrauma.RuinGeneration;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Level
|
||||
{
|
||||
public const float ShaftHeight = 1000.0f;
|
||||
|
||||
public static Level Loaded
|
||||
{
|
||||
get { return loaded; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PositionType
|
||||
{
|
||||
MainPath=1, Cave=2, Ruin=4
|
||||
}
|
||||
|
||||
struct InterestingPosition
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly PositionType PositionType;
|
||||
|
||||
public InterestingPosition(Vector2 position, PositionType positionType)
|
||||
{
|
||||
Position = position;
|
||||
PositionType = positionType;
|
||||
}
|
||||
}
|
||||
|
||||
static Level loaded;
|
||||
|
||||
//how close the sub has to be to start/endposition to exit
|
||||
public const float ExitDistance = 6000.0f;
|
||||
|
||||
private string seed;
|
||||
|
||||
public const int GridCellSize = 2000;
|
||||
private List<VoronoiCell>[,] cellGrid;
|
||||
|
||||
//private WrappingWall[,] wrappingWalls;
|
||||
|
||||
//private float shaftHeight;
|
||||
|
||||
//List<Body> bodies;
|
||||
private List<VoronoiCell> cells;
|
||||
|
||||
//private VertexBuffer vertexBuffer;
|
||||
|
||||
private Vector2 startPosition, endPosition;
|
||||
|
||||
private Rectangle borders;
|
||||
|
||||
private List<Body> bodies;
|
||||
|
||||
private List<InterestingPosition> positionsOfInterest;
|
||||
|
||||
private List<Ruin> ruins;
|
||||
|
||||
private Color backgroundColor;
|
||||
|
||||
private LevelGenerationParams generationParams;
|
||||
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return new Vector2(borders.Width, borders.Height); }
|
||||
}
|
||||
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
}
|
||||
|
||||
public List<Ruin> Ruins
|
||||
{
|
||||
get { return ruins; }
|
||||
}
|
||||
|
||||
//public WrappingWall[,] WrappingWalls
|
||||
//{
|
||||
// get { return wrappingWalls; }
|
||||
//}
|
||||
|
||||
public string Seed
|
||||
{
|
||||
get { return seed; }
|
||||
}
|
||||
|
||||
public float Difficulty
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Body ShaftBody
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public LevelGenerationParams GenerationParams
|
||||
{
|
||||
get { return generationParams; }
|
||||
}
|
||||
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get { return backgroundColor; }
|
||||
}
|
||||
|
||||
public Level(string seed, float difficulty, LevelGenerationParams generationParams)
|
||||
{
|
||||
this.seed = seed;
|
||||
|
||||
this.Difficulty = difficulty;
|
||||
|
||||
this.generationParams = generationParams;
|
||||
|
||||
borders = new Rectangle(0, 0, (int)generationParams.Width, (int)generationParams.Height);
|
||||
}
|
||||
|
||||
public static Level CreateRandom(LocationConnection locationConnection)
|
||||
{
|
||||
string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
|
||||
|
||||
return new Level(seed, locationConnection.Difficulty, LevelGenerationParams.GetRandom(seed));
|
||||
}
|
||||
|
||||
public static Level CreateRandom(string seed = "")
|
||||
{
|
||||
if (seed == "")
|
||||
{
|
||||
seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
|
||||
}
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
return new Level(seed, Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), LevelGenerationParams.GetRandom(seed));
|
||||
}
|
||||
|
||||
public void Generate(bool mirror = false)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
if (loaded != null) loaded.Unload();
|
||||
loaded = this;
|
||||
|
||||
positionsOfInterest = new List<InterestingPosition>();
|
||||
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
bodies = new List<Body>();
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
#if CLIENT
|
||||
renderer = new LevelRenderer(this);
|
||||
|
||||
backgroundColor = generationParams.BackgroundColor;
|
||||
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
|
||||
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f);
|
||||
#endif
|
||||
|
||||
float minWidth = 6500.0f;
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
|
||||
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
|
||||
}
|
||||
|
||||
startPosition = new Vector2(
|
||||
Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
|
||||
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server));
|
||||
|
||||
endPosition = new Vector2(
|
||||
borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
|
||||
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server));
|
||||
|
||||
List<Vector2> pathNodes = new List<Vector2>();
|
||||
Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth);
|
||||
pathBorders.Inflate(-minWidth*2, -minWidth*2);
|
||||
|
||||
pathNodes.Add(new Vector2(startPosition.X, borders.Height));
|
||||
|
||||
Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange;
|
||||
|
||||
for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server);
|
||||
x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server);
|
||||
x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server))
|
||||
{
|
||||
pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server)));
|
||||
}
|
||||
|
||||
pathNodes.Add(new Vector2(endPosition.X, borders.Height));
|
||||
|
||||
if (pathNodes.Count <= 2)
|
||||
{
|
||||
pathNodes.Add((startPosition + endPosition) / 2);
|
||||
}
|
||||
|
||||
List<List<Vector2>> smallTunnels = new List<List<Vector2>>();
|
||||
for (int i = 0; i < generationParams.SmallTunnelCount; i++)
|
||||
{
|
||||
var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, Rand.RandSync.Server)];
|
||||
tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right);
|
||||
|
||||
float tunnelLength = Rand.Range(
|
||||
generationParams.SmallTunnelLengthRange.X,
|
||||
generationParams.SmallTunnelLengthRange.Y,
|
||||
Rand.RandSync.Server);
|
||||
|
||||
var tunnelNodes = MathUtils.GenerateJaggedLine(
|
||||
tunnelStartPos,
|
||||
new Vector2(tunnelStartPos.X, pathBorders.Bottom)+Rand.Vector(tunnelLength, Rand.RandSync.Server),
|
||||
4, 1000.0f);
|
||||
|
||||
List<Vector2> tunnel = new List<Vector2>();
|
||||
foreach (Vector2[] tunnelNode in tunnelNodes)
|
||||
{
|
||||
if (!pathBorders.Contains(tunnelNode[0])) continue;
|
||||
tunnel.Add(tunnelNode[0]);
|
||||
}
|
||||
|
||||
if (tunnel.Any()) smallTunnels.Add(tunnel);
|
||||
}
|
||||
|
||||
Vector2 siteInterval = generationParams.VoronoiSiteInterval;
|
||||
Vector2 siteVariance = generationParams.VoronoiSiteVariance;
|
||||
for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X)
|
||||
{
|
||||
for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y)
|
||||
{
|
||||
Vector2 site = new Vector2(
|
||||
x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server),
|
||||
y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server));
|
||||
|
||||
if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length())))
|
||||
{
|
||||
//add some more sites around the small tunnels to generate more small voronoi cells
|
||||
if (x < borders.Width - siteInterval.X) sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f);
|
||||
if (y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f);
|
||||
if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f);
|
||||
}
|
||||
|
||||
if (mirror) site.X = borders.Width - site.X;
|
||||
|
||||
sites.Add(site);
|
||||
}
|
||||
}
|
||||
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height);
|
||||
|
||||
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
//construct voronoi cells based on the graph edges
|
||||
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
|
||||
|
||||
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
List<VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize,
|
||||
new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror);
|
||||
|
||||
for (int i = 2; i < mainPath.Count; i += 3)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath));
|
||||
}
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>(mainPath);
|
||||
|
||||
EnlargeMainPath(pathCells, minWidth);
|
||||
|
||||
foreach (InterestingPosition positionOfInterest in positionsOfInterest)
|
||||
{
|
||||
WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null);
|
||||
wayPoint.MoveWithLevel = true;
|
||||
}
|
||||
|
||||
startPosition.X = pathCells[0].Center.X;
|
||||
|
||||
foreach (List<Vector2> tunnel in smallTunnels)
|
||||
{
|
||||
if (tunnel.Count<2) continue;
|
||||
|
||||
//find the cell which the path starts from
|
||||
int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1);
|
||||
if (startCellIndex < 0) continue;
|
||||
|
||||
//if it wasn't one of the cells in the main path, don't create a tunnel
|
||||
if (cells[startCellIndex].CellType != CellType.Path) continue;
|
||||
|
||||
var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders);
|
||||
|
||||
positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave));
|
||||
|
||||
if (tunnel.Count > 4) positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave));
|
||||
|
||||
pathCells.AddRange(newPathCells);
|
||||
}
|
||||
|
||||
Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
cells = CleanCells(pathCells);
|
||||
|
||||
pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle(
|
||||
(int)(borders.Width * 0.2f), 0,
|
||||
(int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f))));
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
if (cell.Center.Y < borders.Height / 2) continue;
|
||||
cell.edges.ForEach(e => e.OutsideLevel = true);
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in pathCells)
|
||||
{
|
||||
cell.edges.ForEach(e => e.OutsideLevel = false);
|
||||
|
||||
cell.CellType = CellType.Path;
|
||||
cells.Remove(cell);
|
||||
}
|
||||
|
||||
//generate some narrow caves
|
||||
int caveAmount = 0;// Rand.Int(3, false);
|
||||
List<VoronoiCell> usedCaveCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < caveAmount; i++)
|
||||
{
|
||||
Vector2 startPoint = Vector2.Zero;
|
||||
VoronoiCell startCell = null;
|
||||
|
||||
var caveCells = new List<VoronoiCell>();
|
||||
|
||||
int maxTries = 5, tries = 0;
|
||||
while (tries<maxTries)
|
||||
{
|
||||
startCell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)];
|
||||
|
||||
//find an edge between the cell and the already carved path
|
||||
GraphEdge startEdge =
|
||||
startCell.edges.Find(e => pathCells.Contains(e.AdjacentCell(startCell)));
|
||||
|
||||
if (startEdge != null)
|
||||
{
|
||||
startPoint = (startEdge.point1 + startEdge.point2) / 2.0f;
|
||||
startPoint += startPoint - startCell.Center;
|
||||
|
||||
//get the cells in which the cave will be carved
|
||||
caveCells = GetCells(startCell.Center, 2);
|
||||
//remove cells that have already been "carved" out
|
||||
caveCells.RemoveAll(c => c.CellType == CellType.Path);
|
||||
|
||||
//if any of the cells have already been used as a cave, continue and find some other cells
|
||||
if (usedCaveCells.Any(c => caveCells.Contains(c))) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
tries++;
|
||||
}
|
||||
|
||||
//couldn't find a place for a cave -> abort
|
||||
if (tries >= maxTries) break;
|
||||
|
||||
if (!caveCells.Any()) continue;
|
||||
|
||||
usedCaveCells.AddRange(caveCells);
|
||||
|
||||
List<VoronoiCell> caveSolidCells;
|
||||
var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells);
|
||||
|
||||
//remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones)
|
||||
caveCells.ForEach(c => cells.Remove(c));
|
||||
|
||||
cells.AddRange(caveSolidCells);
|
||||
|
||||
foreach (VoronoiCell cell in cavePathCells)
|
||||
{
|
||||
cells.Remove(cell);
|
||||
}
|
||||
|
||||
pathCells.AddRange(cavePathCells);
|
||||
|
||||
for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave));
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
cellGrid[x, y].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
||||
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
||||
|
||||
if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) continue;
|
||||
|
||||
cellGrid[x, y].Add(cell);
|
||||
}
|
||||
|
||||
ruins = new List<Ruin>();
|
||||
for (int i = 0; i<generationParams.RuinCount; i++)
|
||||
{
|
||||
GenerateRuin(mainPath);
|
||||
}
|
||||
|
||||
startPosition.Y = borders.Height;
|
||||
endPosition.Y = borders.Height;
|
||||
|
||||
List<VoronoiCell> cellsWithBody = new List<VoronoiCell>(cells);
|
||||
|
||||
List<Vector2[]> triangles;
|
||||
bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles);
|
||||
|
||||
#if CLIENT
|
||||
List<VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles);
|
||||
|
||||
renderer.SetBodyVertices(bodyVertices.ToArray());
|
||||
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells));
|
||||
|
||||
renderer.PlaceSprites(generationParams.BackgroundSpriteAmount);
|
||||
#endif
|
||||
|
||||
ShaftBody = BodyFactory.CreateEdge(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
||||
|
||||
ShaftBody.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f);
|
||||
|
||||
ShaftBody.BodyType = BodyType.Static;
|
||||
ShaftBody.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
bodies.Add(ShaftBody);
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
edge.cell1 = null;
|
||||
edge.cell2 = null;
|
||||
edge.site1 = null;
|
||||
edge.site2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
//initialize MapEntities that aren't in any sub (e.g. items inside ruins)
|
||||
MapEntity.MapLoaded(null);
|
||||
|
||||
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
Vector2 temp = startPosition;
|
||||
startPosition = endPosition;
|
||||
endPosition = temp;
|
||||
}
|
||||
|
||||
Debug.WriteLine("**********************************************************************************");
|
||||
Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
|
||||
Debug.WriteLine("Seed: "+seed);
|
||||
Debug.WriteLine("**********************************************************************************");
|
||||
}
|
||||
|
||||
|
||||
private List<VoronoiCell> CreateBottomHoles(float holeProbability, Rectangle limits)
|
||||
{
|
||||
List<VoronoiCell> toBeRemoved = new List<VoronoiCell>();
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > holeProbability) continue;
|
||||
|
||||
if (!limits.Contains(cell.Center)) continue;
|
||||
|
||||
float closestDist = 0.0f;
|
||||
WayPoint closestWayPoint = null;
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path) continue;
|
||||
|
||||
float dist =Math.Abs(cell.Center.X - wp.WorldPosition.X);
|
||||
if (closestWayPoint == null || dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestWayPoint = wp;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestWayPoint.WorldPosition.Y < cell.Center.Y) continue;
|
||||
|
||||
toBeRemoved.Add(cell);
|
||||
}
|
||||
|
||||
return toBeRemoved;
|
||||
}
|
||||
|
||||
private void EnlargeMainPath(List<VoronoiCell> pathCells, float minWidth)
|
||||
{
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
|
||||
var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, borders.Height, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
//WayPoint prevWaypoint = newWaypoint;
|
||||
|
||||
for (int i = 0; i < pathCells.Count; i++)
|
||||
{
|
||||
////clean "loops" from the path
|
||||
//for (int n = 0; n < i; n++)
|
||||
//{
|
||||
// if (pathCells[n] != pathCells[i]) continue;
|
||||
|
||||
// pathCells.RemoveRange(n + 1, i - n);
|
||||
// break;
|
||||
//}
|
||||
//if (i >= pathCells.Count) break;
|
||||
|
||||
pathCells[i].CellType = CellType.Path;
|
||||
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
wayPoints[wayPoints.Count-2].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
||||
|
||||
for (int n = 0; n < wayPoints.Count; n++)
|
||||
{
|
||||
if (wayPoints[n].Position != newWaypoint.Position) continue;
|
||||
|
||||
wayPoints[n].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[n]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//prevWaypoint = newWaypoint;
|
||||
}
|
||||
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, borders.Height, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
||||
|
||||
if (minWidth > 0.0f)
|
||||
{
|
||||
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
|
||||
foreach (VoronoiCell removedCell in removedCells)
|
||||
{
|
||||
if (removedCell.CellType == CellType.Path) continue;
|
||||
|
||||
pathCells.Add(removedCell);
|
||||
removedCell.CellType = CellType.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
Vector2 position = emptyCells[0].Center;
|
||||
|
||||
if (minDistance == 0.0f) return tooCloseCells;
|
||||
|
||||
float step = 100.0f;
|
||||
|
||||
int targetCellIndex = 1;
|
||||
|
||||
minDistance *= 0.5f;
|
||||
do
|
||||
{
|
||||
tooCloseCells.AddRange(GetTooCloseCells(position, minDistance));
|
||||
|
||||
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
|
||||
|
||||
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
|
||||
|
||||
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
var closeCells = GetCells(position, 3);
|
||||
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
bool tooClose = false;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
|
||||
if (Vector2.Distance(edge.point1, position) < minDistance ||
|
||||
Vector2.Distance(edge.point2, position) < minDistance)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell);
|
||||
}
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// remove all cells except those that are adjacent to the empty cells
|
||||
/// </summary>
|
||||
private List<VoronoiCell> CleanCells(List<VoronoiCell> emptyCells)
|
||||
{
|
||||
List<VoronoiCell> newCells = new List<VoronoiCell>();
|
||||
|
||||
foreach (VoronoiCell cell in emptyCells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
VoronoiCell adjacent = edge.AdjacentCell(cell);
|
||||
if (adjacent != null && !newCells.Contains(adjacent))
|
||||
{
|
||||
newCells.Add(adjacent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCells;
|
||||
}
|
||||
|
||||
private void GenerateRuin(List<VoronoiCell> mainPath)
|
||||
{
|
||||
|
||||
Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server));
|
||||
float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f;
|
||||
|
||||
Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].Center;
|
||||
|
||||
int iter = 0;
|
||||
|
||||
ruinPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y/2, ruinPos.Y);
|
||||
|
||||
while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f))
|
||||
{
|
||||
Vector2 weighedPathPos = ruinPos;
|
||||
iter++;
|
||||
|
||||
foreach (VoronoiCell pathCell in mainPath)
|
||||
{
|
||||
float dist = Vector2.Distance(pathCell.Center, ruinPos);
|
||||
if (dist > 10000.0f) continue;
|
||||
|
||||
Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist;
|
||||
|
||||
//if (weighedPathPos.Y + moveAmount.Y > borders.Bottom - ruinSize.X)
|
||||
//{
|
||||
// moveAmount.X = (Math.Abs(moveAmount.Y) + Math.Abs(moveAmount.X))*Math.Sign(moveAmount.X);
|
||||
// moveAmount.Y = 0.0f;
|
||||
//}
|
||||
|
||||
weighedPathPos += moveAmount;
|
||||
weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y);
|
||||
}
|
||||
|
||||
ruinPos = weighedPathPos;
|
||||
|
||||
if (iter > 10000) break;
|
||||
}
|
||||
|
||||
VoronoiCell closestPathCell = null;
|
||||
float closestDist = 0.0f;
|
||||
foreach (VoronoiCell pathCell in mainPath)
|
||||
{
|
||||
float dist = Vector2.Distance(pathCell.Center, ruinPos);
|
||||
if (closestPathCell == null || dist < closestDist)
|
||||
{
|
||||
closestPathCell = pathCell;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
var ruin = new Ruin(closestPathCell, cells, new Rectangle(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize)));
|
||||
ruins.Add(ruin);
|
||||
|
||||
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin));
|
||||
}
|
||||
|
||||
foreach (RuinShape ruinShape in ruin.RuinShapes)
|
||||
{
|
||||
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height));
|
||||
|
||||
foreach (VoronoiCell cell in tooClose)
|
||||
{
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
foreach (GraphEdge e in cell.edges)
|
||||
{
|
||||
Rectangle rect = ruinShape.Rect;
|
||||
rect.Y += rect.Height;
|
||||
if (MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
|
||||
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
||||
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
||||
|
||||
cellGrid[x, y].Remove(cell);
|
||||
cells.Remove(cell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float offsetFromWall = 10.0f)
|
||||
{
|
||||
if (!positionsOfInterest.Any()) return Size*0.5f;
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
|
||||
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
|
||||
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
Vector2 startPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true);
|
||||
|
||||
startPos += Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.Server), Rand.RandSync.Server);
|
||||
|
||||
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
|
||||
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(startPos),
|
||||
ConvertUnits.ToSimUnits(endPos),
|
||||
null, Physics.CollisionLevel) != null)
|
||||
{
|
||||
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos)*offsetFromWall;
|
||||
break;
|
||||
}
|
||||
|
||||
tries++;
|
||||
|
||||
if (tries == 10)
|
||||
{
|
||||
position = EndPosition - Vector2.UnitY * 300.0f;
|
||||
}
|
||||
|
||||
} while (tries < 10);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public Vector2 GetRandomInterestingPosition(bool useSyncedRand, PositionType positionType, bool avoidSubs)
|
||||
{
|
||||
if (!positionsOfInterest.Any()) return Size * 0.5f;
|
||||
|
||||
var matchingPositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
|
||||
|
||||
if (avoidSubs)
|
||||
{
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
float minDist = Math.Max(sub.Borders.Width, sub.Borders.Height);
|
||||
matchingPositions.RemoveAll(p => Vector2.Distance(p.Position, sub.WorldPosition) < minDist);
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingPositions.Any())
|
||||
{
|
||||
return positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
|
||||
}
|
||||
|
||||
return matchingPositions[Rand.Int(matchingPositions.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
/*
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
WrappingWall.UpdateWallShift(Submarine.MainSub.WorldPosition, wrappingWalls);
|
||||
}*/
|
||||
|
||||
#if CLIENT
|
||||
if (Hull.renderer != null)
|
||||
{
|
||||
Hull.renderer.ScrollWater((float)deltaTime);
|
||||
}
|
||||
|
||||
renderer.Update(deltaTime);
|
||||
#endif
|
||||
}
|
||||
|
||||
public List<VoronoiCell> GetCells(Vector2 pos, int searchDepth = 2)
|
||||
{
|
||||
int gridPosX = (int)Math.Floor(pos.X / GridCellSize);
|
||||
int gridPosY = (int)Math.Floor(pos.Y / GridCellSize);
|
||||
|
||||
int startX = Math.Max(gridPosX - searchDepth, 0);
|
||||
int endX = Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1);
|
||||
|
||||
int startY = Math.Max(gridPosY - searchDepth, 0);
|
||||
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
|
||||
|
||||
List<VoronoiCell> cells = new List<VoronoiCell>();
|
||||
|
||||
for (int x = startX; x <= endX; x++)
|
||||
{
|
||||
for (int y = startY; y <= endY; y++)
|
||||
{
|
||||
foreach (VoronoiCell cell in cellGrid[x, y])
|
||||
{
|
||||
cells.Add(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (wrappingWalls == null) return cells;
|
||||
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (wrappingWalls[side, n] == null) continue;
|
||||
|
||||
if (Vector2.Distance(wrappingWalls[side, n].MidPos, pos) > WrappingWall.WallWidth) continue;
|
||||
|
||||
foreach (VoronoiCell cell in wrappingWalls[side, n].Cells)
|
||||
{
|
||||
cells.Add(cell);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
private void Unload()
|
||||
{
|
||||
#if CLIENT
|
||||
if (renderer!=null)
|
||||
{
|
||||
renderer.Dispose();
|
||||
renderer = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ruins != null)
|
||||
{
|
||||
ruins.Clear();
|
||||
ruins = null;
|
||||
}
|
||||
|
||||
/*
|
||||
if (wrappingWalls!=null)
|
||||
{
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (wrappingWalls[side, i] != null) wrappingWalls[side, i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
wrappingWalls = null;
|
||||
}*/
|
||||
|
||||
|
||||
cells = null;
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
bodies.Clear();
|
||||
bodies = null;
|
||||
}
|
||||
|
||||
loaded = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LevelGenerationParams : IPropertyObject
|
||||
{
|
||||
private static List<LevelGenerationParams> presets;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float width, height;
|
||||
|
||||
private Vector2 voronoiSiteInterval;
|
||||
//how much the sites are "scattered" on x- and y-axis
|
||||
//if Vector2.Zero, the sites will just be placed in a regular grid pattern
|
||||
private Vector2 voronoiSiteVariance;
|
||||
|
||||
//how far apart the nodes of the main path can be
|
||||
//x = min interval, y = max interval
|
||||
private Vector2 mainPathNodeIntervalRange;
|
||||
|
||||
private int smallTunnelCount;
|
||||
//x = min length, y = max length
|
||||
private Vector2 smallTunnelLengthRange;
|
||||
|
||||
//how large portion of the bottom of the level should be "carved out"
|
||||
//if 0.0f, the bottom will be completely solid (making the abyss unreachable)
|
||||
//if 1.0f, the bottom will be completely open
|
||||
private float bottomHoleProbability;
|
||||
|
||||
private int ruinCount;
|
||||
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(1000, false)]
|
||||
public int BackgroundSpriteAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(100000.0f, false)]
|
||||
public float Width
|
||||
{
|
||||
get { return width; }
|
||||
set { width = Math.Max(value, 2000.0f); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(50000.0f, false)]
|
||||
public float Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = Math.Max(value, 2000.0f); }
|
||||
}
|
||||
|
||||
public Vector2 VoronoiSiteInterval
|
||||
{
|
||||
get { return voronoiSiteInterval; }
|
||||
set {
|
||||
voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100.0f, width/2);
|
||||
voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100.0f, height/2);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 VoronoiSiteVariance
|
||||
{
|
||||
get { return voronoiSiteVariance; }
|
||||
set
|
||||
{
|
||||
voronoiSiteVariance = new Vector2(
|
||||
MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
|
||||
MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MainPathNodeIntervalRange
|
||||
{
|
||||
get { return mainPathNodeIntervalRange; }
|
||||
set
|
||||
{
|
||||
mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100.0f, width / 2);
|
||||
mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, width / 2);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(5, false)]
|
||||
public int SmallTunnelCount
|
||||
{
|
||||
get { return smallTunnelCount; }
|
||||
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
public Vector2 SmallTunnelLengthRange
|
||||
{
|
||||
get { return smallTunnelLengthRange; }
|
||||
set
|
||||
{
|
||||
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100.0f, width);
|
||||
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, width);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(1, false)]
|
||||
public int RuinCount
|
||||
{
|
||||
get { return ruinCount; }
|
||||
set { ruinCount = MathHelper.Clamp(value, 0, 10); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.4f, false)]
|
||||
public float BottomHoleProbability
|
||||
{
|
||||
get { return bottomHoleProbability; }
|
||||
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public static LevelGenerationParams GetRandom(string seed)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
if (presets == null || !presets.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Level generation presets not found - using default presets");
|
||||
return new LevelGenerationParams(null);
|
||||
}
|
||||
|
||||
return presets[Rand.Range(0, presets.Count, Rand.RandSync.Server)];
|
||||
}
|
||||
|
||||
private LevelGenerationParams(XElement element)
|
||||
{
|
||||
Name = element==null ? "default" : element.Name.ToString();
|
||||
ObjectProperties = ObjectProperty.InitProperties(this, element);
|
||||
|
||||
Vector3 colorVector = ToolBox.GetAttributeVector3(element, "BackgroundColor", new Vector3(50, 46, 20));
|
||||
BackgroundColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z);
|
||||
|
||||
VoronoiSiteInterval = ToolBox.GetAttributeVector2(element, "VoronoiSiteInterval", new Vector2(3000, 3000));
|
||||
|
||||
VoronoiSiteVariance = ToolBox.GetAttributeVector2(element, "VoronoiSiteVariance", new Vector2(voronoiSiteInterval.X, voronoiSiteInterval.Y) * 0.4f);
|
||||
|
||||
MainPathNodeIntervalRange = ToolBox.GetAttributeVector2(element, "MainPathNodeIntervalRange", new Vector2(5000.0f, 10000.0f));
|
||||
|
||||
SmallTunnelLengthRange = ToolBox.GetAttributeVector2(element, "SmallTunnelLengthRange", new Vector2(5000.0f, 10000.0f));
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
presets = new List<LevelGenerationParams>();
|
||||
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.LevelGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files.Add("Content/Map/LevelGenerationParameters.xml");
|
||||
}
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
presets.Add(new LevelGenerationParams(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
/// <summary>
|
||||
/// nodes of a binary tree used for generating underwater "dungeons"
|
||||
/// </summary>
|
||||
class BTRoom : RuinShape
|
||||
{
|
||||
private BTRoom[] subRooms;
|
||||
|
||||
public BTRoom Parent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor Corridor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BTRoom[] SubRooms
|
||||
{
|
||||
get { return subRooms; }
|
||||
}
|
||||
|
||||
public BTRoom Adjacent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public BTRoom(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
}
|
||||
|
||||
public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200)
|
||||
{
|
||||
subRooms = new BTRoom[2];
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < verticalProbability &&
|
||||
rect.Width * minDivRatio >= minWidth)
|
||||
{
|
||||
SplitVertical(minDivRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitHorizontal(minDivRatio);
|
||||
}
|
||||
|
||||
subRooms[0].Parent = this;
|
||||
subRooms[1].Parent = this;
|
||||
|
||||
subRooms[0].Adjacent = subRooms[1];
|
||||
subRooms[1].Adjacent = subRooms[0];
|
||||
}
|
||||
|
||||
private void SplitHorizontal(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div)));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height));
|
||||
|
||||
}
|
||||
|
||||
private void SplitVertical(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height));
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
Walls = new List<Line>();
|
||||
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.Wall));
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
|
||||
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.Wall));
|
||||
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
|
||||
}
|
||||
|
||||
public void Scale(Vector2 scale)
|
||||
{
|
||||
rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height);
|
||||
}
|
||||
|
||||
public List<BTRoom> GetLeaves()
|
||||
{
|
||||
return GetLeaves(new List<BTRoom>());
|
||||
}
|
||||
|
||||
private List<BTRoom> GetLeaves(List<BTRoom> leaves)
|
||||
{
|
||||
if (subRooms == null)
|
||||
{
|
||||
leaves.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
subRooms[0].GetLeaves(leaves);
|
||||
subRooms[1].GetLeaves(leaves);
|
||||
}
|
||||
|
||||
return leaves;
|
||||
}
|
||||
|
||||
public void GenerateCorridors(int minWidth, int maxWidth, List<Corridor> corridors)
|
||||
{
|
||||
if (Adjacent != null && Corridor == null)
|
||||
{
|
||||
Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, Rand.RandSync.Server), corridors);
|
||||
}
|
||||
|
||||
if (subRooms != null)
|
||||
{
|
||||
subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<Corridor> corridors)
|
||||
{
|
||||
entrance.CalculateDistanceFromEntrance(1, new List<Corridor>(corridors));
|
||||
}
|
||||
|
||||
private void CalculateDistanceFromEntrance(int currentDist, List<Corridor> corridors)
|
||||
{
|
||||
if (DistanceFromEntrance == 0)
|
||||
{
|
||||
DistanceFromEntrance = currentDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
DistanceFromEntrance = Math.Min(currentDist, DistanceFromEntrance);
|
||||
}
|
||||
|
||||
currentDist++;
|
||||
|
||||
for (int i = corridors.Count - 1; i >= 0; i = Math.Min(i - 1, corridors.Count - 1))
|
||||
{
|
||||
var corridor = corridors[i];
|
||||
|
||||
if (!corridor.ConnectedRooms.Contains(this)) continue;
|
||||
|
||||
corridors.RemoveAt(i);
|
||||
|
||||
corridor.ConnectedRooms[corridor.ConnectedRooms[0] == this ? 1 : 0].CalculateDistanceFromEntrance(currentDist, corridors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
|
||||
class Corridor : RuinShape
|
||||
{
|
||||
private bool isHorizontal;
|
||||
|
||||
public Rectangle Rect
|
||||
{
|
||||
get { return rect; }
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public BTRoom[] ConnectedRooms
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
isHorizontal = rect.Width > rect.Height;
|
||||
}
|
||||
|
||||
public Corridor(BTRoom room, int width, List<Corridor> corridors)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(room.Adjacent != null);
|
||||
|
||||
ConnectedRooms = new BTRoom[2];
|
||||
ConnectedRooms[0] = room;
|
||||
ConnectedRooms[1] = room.Adjacent;
|
||||
|
||||
Rectangle room1, room2;
|
||||
|
||||
room1 = room.Rect;
|
||||
room2 = room.Adjacent.Rect;
|
||||
|
||||
isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X);
|
||||
|
||||
//use the leaves as starting points for the corridor
|
||||
if (room.SubRooms != null)
|
||||
{
|
||||
var leaves1 = room.GetLeaves();
|
||||
var leaves2 = room.Adjacent.GetLeaves();
|
||||
|
||||
var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal);
|
||||
room1 = suitableLeaves[0].Rect;
|
||||
room2 = suitableLeaves[1].Rect;
|
||||
|
||||
ConnectedRooms[0] = suitableLeaves[0];
|
||||
ConnectedRooms[1] = suitableLeaves[1];
|
||||
}
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
int left = Math.Min(room1.Right, room2.Right);
|
||||
int right = Math.Max(room1.X, room2.X);
|
||||
|
||||
int top = Math.Max(room1.Y, room2.Y);
|
||||
int bottom = Math.Min(room1.Bottom, room2.Bottom);
|
||||
|
||||
int yPos = Rand.Range(top, bottom - width, Rand.RandSync.Server);
|
||||
|
||||
rect = new Rectangle(left, yPos, right - left, width);
|
||||
}
|
||||
else if (room1.Y > room2.Bottom || room2.Y > room1.Bottom)
|
||||
{
|
||||
int left = Math.Max(room1.X, room2.X);
|
||||
int right = Math.Min(room1.Right, room2.Right);
|
||||
|
||||
int top = Math.Min(room1.Bottom, room2.Bottom);
|
||||
int bottom = Math.Max(room1.Y, room2.Y);
|
||||
|
||||
int xPos = Rand.Range(left, right - width, Rand.RandSync.Server);
|
||||
|
||||
rect = new Rectangle(xPos, top, width, bottom - top);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("wat");
|
||||
}
|
||||
|
||||
room.Corridor = this;
|
||||
room.Adjacent.Corridor = this;
|
||||
|
||||
for (int i = corridors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var corridor = corridors[i];
|
||||
|
||||
if (corridor.rect.Intersects(this.rect))
|
||||
{
|
||||
if (isHorizontal && corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Width < corridor.rect.Width)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
else if (!isHorizontal && !corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Height < corridor.rect.Height)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
corridors.Add(this);
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
|
||||
|
||||
Walls = new List<Line>();
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.CorridorWall));
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
}
|
||||
else
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find two rooms which have two face-two-face walls that we can place a corridor in between
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private BTRoom[] GetSuitableLeafRooms(List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
|
||||
{
|
||||
int iOffset = Rand.Int(leaves1.Count, Rand.RandSync.Server);
|
||||
int jOffset = Rand.Int(leaves2.Count, Rand.RandSync.Server);
|
||||
|
||||
|
||||
for (int iCount = 0; iCount < leaves1.Count; iCount++)
|
||||
{
|
||||
int i = (iCount + iOffset) % leaves1.Count;
|
||||
|
||||
for (int jCount = 0; jCount < leaves2.Count; jCount++)
|
||||
{
|
||||
int j = (jCount + jOffset) % leaves2.Count;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
//if (Math.Min(leaves1[i].Rect.Bottom, leaves2[i].Rect.Bottom) - Math.Max(leaves1[i].Rect.Y, leaves2[j].Rect.Y) < width) continue;
|
||||
|
||||
|
||||
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom-width) continue;
|
||||
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y+width) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (Math.Min(leaves1[i].Rect.Right, leaves2[i].Rect.Right) - Math.Max(leaves1[i].Rect.X, leaves2[j].Rect.X) < width) continue;
|
||||
|
||||
|
||||
if (leaves1[i].Rect.X > leaves2[j].Rect.Right-width) continue;
|
||||
if (leaves1[i].Rect.Right < leaves2[j].Rect.X+width) continue;
|
||||
}
|
||||
|
||||
|
||||
return new BTRoom[] { leaves1[i], leaves2[j] };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
abstract class RuinShape
|
||||
{
|
||||
protected Rectangle rect;
|
||||
|
||||
public Rectangle Rect
|
||||
{
|
||||
get { return rect; }
|
||||
}
|
||||
|
||||
|
||||
public int DistanceFromEntrance
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return rect.Center.ToVector2(); }
|
||||
}
|
||||
|
||||
public List<Line> Walls;
|
||||
|
||||
public virtual void CreateWalls() { }
|
||||
|
||||
public Alignment GetLineAlignment(Line line)
|
||||
{
|
||||
if (line.A.Y == line.B.Y)
|
||||
{
|
||||
if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y)
|
||||
{
|
||||
return Alignment.Bottom;
|
||||
}
|
||||
else if (line.A.Y < rect.Center.Y && line.B.Y < rect.Center.Y)
|
||||
{
|
||||
return Alignment.Top;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.A.X < rect.Center.X && line.B.X < rect.Center.X)
|
||||
{
|
||||
return Alignment.Left;
|
||||
}
|
||||
else if (line.A.X > rect.Center.X && line.B.X > rect.Center.X)
|
||||
{
|
||||
return Alignment.Right;
|
||||
}
|
||||
}
|
||||
|
||||
return Alignment.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Goes through a list of line segments and "clips off" all parts of the lines that are inside the rectangle
|
||||
/// </summary>
|
||||
public void SplitLines(Rectangle rectangle)
|
||||
{
|
||||
List<Line> newLines = new List<Line>();
|
||||
|
||||
foreach (Line line in Walls)
|
||||
{
|
||||
if (line.A.X == line.B.X) //vertical line
|
||||
{
|
||||
//line doesn't intersect the rectangle
|
||||
if (rectangle.X > line.A.X || rectangle.Right < line.A.X ||
|
||||
rectangle.Y > line.B.Y || rectangle.Bottom < line.A.Y)
|
||||
{
|
||||
newLines.Add(line);
|
||||
}
|
||||
else if (line.A.Y > rectangle.Y && line.B.Y < rectangle.Bottom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//point A is within the rectangle -> cut a portion from the top of the line
|
||||
else if (line.A.Y >= rectangle.Y && line.A.Y <= rectangle.Bottom)
|
||||
{
|
||||
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
|
||||
}
|
||||
//point B is within the rectangle -> cut a portion from the bottom of the line
|
||||
else if (line.B.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom)
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
|
||||
}
|
||||
//rect is in between the lines -> split the line into two
|
||||
else
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
|
||||
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
|
||||
}
|
||||
}
|
||||
else if (line.A.Y == line.B.Y) //horizontal line
|
||||
{
|
||||
//line doesn't intersect the rectangle
|
||||
if (rectangle.X > line.B.X || rectangle.Right < line.A.X ||
|
||||
rectangle.Y > line.A.Y || rectangle.Bottom < line.A.Y)
|
||||
{
|
||||
|
||||
newLines.Add(line);
|
||||
}
|
||||
else if (line.A.X > rectangle.X && line.B.X < rectangle.Right)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//point A is within the rectangle -> cut a portion from the left side of the line
|
||||
else if (line.A.X >= rectangle.X && line.A.X <= rectangle.Right)
|
||||
{
|
||||
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
|
||||
}
|
||||
//point B is within the rectangle -> cut a portion from the right side of the line
|
||||
else if (line.B.X >= rectangle.X && line.B.X <= rectangle.Right)
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
|
||||
}
|
||||
//rect is in between the lines -> split the line into two
|
||||
else
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
|
||||
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in StructureGenerator.SplitLines - lines must be axis aligned");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Walls = newLines;
|
||||
}
|
||||
}
|
||||
|
||||
struct Line
|
||||
{
|
||||
public readonly Vector2 A, B;
|
||||
|
||||
public readonly RuinStructureType Type;
|
||||
|
||||
public Line(Vector2 a, Vector2 b, RuinStructureType type)
|
||||
{
|
||||
Debug.Assert(a.X <= b.X);
|
||||
Debug.Assert(a.Y <= b.Y);
|
||||
|
||||
A = a;
|
||||
B = b;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
partial class Ruin
|
||||
{
|
||||
private List<BTRoom> rooms;
|
||||
private List<Corridor> corridors;
|
||||
|
||||
private List<Line> walls;
|
||||
|
||||
private List<RuinShape> allShapes;
|
||||
|
||||
public List<RuinShape> RuinShapes
|
||||
{
|
||||
get { return allShapes; }
|
||||
}
|
||||
|
||||
public List<Line> Walls
|
||||
{
|
||||
get { return walls; }
|
||||
}
|
||||
|
||||
public Rectangle Area
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Ruin(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
|
||||
{
|
||||
Area = area;
|
||||
|
||||
corridors = new List<Corridor>();
|
||||
rooms = new List<BTRoom>();
|
||||
|
||||
walls = new List<Line>();
|
||||
|
||||
allShapes = new List<RuinShape>();
|
||||
|
||||
Generate(closestPathCell, caveCells, area);
|
||||
}
|
||||
|
||||
public void Generate(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
|
||||
{
|
||||
corridors.Clear();
|
||||
rooms.Clear();
|
||||
|
||||
//area = new Rectangle(area.X, area.Y - area.Height, area.Width, area.Height);
|
||||
|
||||
int iterations = Rand.Range(3, 4, Rand.RandSync.Server);
|
||||
|
||||
float verticalProbability = Rand.Range(0.4f, 0.6f, Rand.RandSync.Server);
|
||||
|
||||
BTRoom baseRoom = new BTRoom(area);
|
||||
|
||||
rooms = new List<BTRoom> { baseRoom };
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
rooms.ForEach(l => l.Split(0.3f, verticalProbability, 300));
|
||||
|
||||
rooms = baseRoom.GetLeaves();
|
||||
}
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
leaf.Scale
|
||||
(
|
||||
new Vector2(Rand.Range(0.5f, 0.9f, Rand.RandSync.Server), Rand.Range(0.5f, 0.9f, Rand.RandSync.Server))
|
||||
);
|
||||
}
|
||||
|
||||
baseRoom.GenerateCorridors(200, 256, corridors);
|
||||
|
||||
walls = new List<Line>();
|
||||
|
||||
rooms.ForEach(leaf =>
|
||||
{
|
||||
leaf.CreateWalls();
|
||||
//walls.AddRange(leaf.Walls);
|
||||
});
|
||||
|
||||
//---------------------------
|
||||
|
||||
BTRoom entranceRoom = null;
|
||||
float shortestDistance = 0.0f;
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
float distance = Vector2.Distance(leaf.Rect.Center.ToVector2(), closestPathCell.Center);
|
||||
if (entranceRoom == null || distance < shortestDistance)
|
||||
{
|
||||
entranceRoom = leaf;
|
||||
shortestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
rooms.Remove(entranceRoom);
|
||||
|
||||
//---------------------------
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
leaf.SplitLines(corridor.Rect);
|
||||
}
|
||||
|
||||
walls.AddRange(leaf.Walls);
|
||||
}
|
||||
|
||||
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
corridor.CreateWalls();
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
corridor.SplitLines(leaf.Rect);
|
||||
}
|
||||
|
||||
foreach (Corridor corridor2 in corridors)
|
||||
{
|
||||
if (corridor == corridor2) continue;
|
||||
corridor.SplitLines(corridor2.Rect);
|
||||
}
|
||||
|
||||
|
||||
walls.AddRange(corridor.Walls);
|
||||
}
|
||||
|
||||
//leaves.Remove(entranceRoom);
|
||||
|
||||
BTRoom.CalculateDistancesFromEntrance(entranceRoom, corridors);
|
||||
|
||||
allShapes = GenerateStructures(caveCells);
|
||||
}
|
||||
|
||||
private List<RuinShape> GenerateStructures(List<VoronoiCell> caveCells)
|
||||
{
|
||||
List<RuinShape> shapes = new List<RuinShape>(rooms);
|
||||
shapes.AddRange(corridors);
|
||||
|
||||
foreach (RuinShape leaf in shapes)
|
||||
{
|
||||
RuinStructureType wallType = RuinStructureType.Wall;
|
||||
|
||||
if (!(leaf is BTRoom))
|
||||
{
|
||||
wallType = RuinStructureType.CorridorWall;
|
||||
}
|
||||
//rooms further from the entrance are more likely to have hard-to-break walls
|
||||
else if (Rand.Range(0.0f, leaf.DistanceFromEntrance, Rand.RandSync.Server) > 1.5f)
|
||||
{
|
||||
wallType = RuinStructureType.HeavyWall;
|
||||
}
|
||||
|
||||
//generate walls --------------------------------------------------------------
|
||||
foreach (Line wall in leaf.Walls)
|
||||
{
|
||||
var structurePrefab = RuinStructure.GetRandom(wallType, leaf.GetLineAlignment(wall));
|
||||
if (structurePrefab == null) continue;
|
||||
|
||||
float radius = (wall.A.X == wall.B.X) ?
|
||||
(structurePrefab.Prefab as StructurePrefab).Size.X * 0.5f :
|
||||
(structurePrefab.Prefab as StructurePrefab).Size.Y * 0.5f;
|
||||
|
||||
Rectangle rect = new Rectangle(
|
||||
(int)(wall.A.X - radius),
|
||||
(int)(wall.B.Y + radius),
|
||||
(int)((wall.B.X - wall.A.X) + radius*2.0f),
|
||||
(int)((wall.B.Y - wall.A.Y) + radius*2.0f));
|
||||
|
||||
//cut a section off from both ends of a horizontal wall to get nicer looking corners
|
||||
if (wall.A.Y == wall.B.Y)
|
||||
{
|
||||
rect.Inflate(-32, 0);
|
||||
if (rect.Width < Submarine.GridSize.X) continue;
|
||||
}
|
||||
|
||||
var structure = new Structure(rect, structurePrefab.Prefab as StructurePrefab, null);
|
||||
structure.MoveWithLevel = true;
|
||||
structure.SetCollisionCategory(Physics.CollisionLevel);
|
||||
}
|
||||
|
||||
//generate backgrounds --------------------------------------------------------------
|
||||
var background = RuinStructure.GetRandom(RuinStructureType.Back, Alignment.Center);
|
||||
if (background == null) continue;
|
||||
|
||||
Rectangle backgroundRect = new Rectangle(leaf.Rect.X, leaf.Rect.Y + leaf.Rect.Height, leaf.Rect.Width, leaf.Rect.Height);
|
||||
|
||||
new Structure(backgroundRect, (background.Prefab as StructurePrefab), null).MoveWithLevel = true;
|
||||
|
||||
}
|
||||
|
||||
//generate props --------------------------------------------------------------
|
||||
for (int i = 0; i < shapes.Count*2; i++ )
|
||||
{
|
||||
Alignment[] alignments = new Alignment[] { Alignment.Top, Alignment.Bottom, Alignment.Right, Alignment.Left, Alignment.Center };
|
||||
|
||||
var prop = RuinStructure.GetRandom(RuinStructureType.Prop, alignments[Rand.Int(alignments.Length, Rand.RandSync.Server)]);
|
||||
|
||||
Vector2 size = (prop.Prefab is StructurePrefab) ? ((StructurePrefab)prop.Prefab).Size : Vector2.Zero;
|
||||
|
||||
var shape = shapes[Rand.Int(shapes.Count, Rand.RandSync.Server)];
|
||||
|
||||
Vector2 position = shape.Rect.Center.ToVector2();
|
||||
if (prop.Alignment.HasFlag(Alignment.Top))
|
||||
{
|
||||
position = new Vector2(Rand.Range(shape.Rect.X+size.X, shape.Rect.Right - size.X, Rand.RandSync.Server), shape.Rect.Bottom - 64);
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Bottom))
|
||||
{
|
||||
position = new Vector2(Rand.Range(shape.Rect.X + size.X, shape.Rect.Right - size.X, Rand.RandSync.Server), shape.Rect.Top + 64);
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Right))
|
||||
{
|
||||
position = new Vector2(shape.Rect.Right - 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Left))
|
||||
{
|
||||
position = new Vector2(shape.Rect.X + 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
if (prop.Prefab is ItemPrefab)
|
||||
{
|
||||
var item = new Item((ItemPrefab)prop.Prefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
new Structure(new Rectangle(
|
||||
(int)(position.X - size.X/2.0f), (int)(position.Y + size.Y/2.0f),
|
||||
(int)size.X, (int)size.Y),
|
||||
prop.Prefab as StructurePrefab, null).MoveWithLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//generate doors & sensors that close them -------------------------------------------------------------
|
||||
|
||||
var sensorPrefab = ItemPrefab.list.Find(ip => ip.Name == "Alien Motion Sensor") as ItemPrefab;
|
||||
var wirePrefab = ItemPrefab.list.Find(ip => ip.Name == "Wire") as ItemPrefab;
|
||||
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
var doorPrefab = RuinStructure.GetRandom(corridor.IsHorizontal ? RuinStructureType.Door : RuinStructureType.Hatch, Alignment.Center);
|
||||
if (doorPrefab == null) continue;
|
||||
|
||||
//find all walls that are parallel to the corridor
|
||||
var suitableWalls = corridor.IsHorizontal ?
|
||||
corridor.Walls.FindAll(c => c.A.Y == c.B.Y) : corridor.Walls.FindAll(c => c.A.X == c.B.X);
|
||||
|
||||
if (!suitableWalls.Any()) continue;
|
||||
|
||||
Vector2 doorPos = corridor.Center;
|
||||
|
||||
//choose a random wall to place the door next to
|
||||
var wall = suitableWalls[Rand.Int(suitableWalls.Count, Rand.RandSync.Server)];
|
||||
if (corridor.IsHorizontal)
|
||||
{
|
||||
doorPos.X = (wall.A.X + wall.B.X) / 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f;
|
||||
}
|
||||
|
||||
var door = new Item(doorPrefab.Prefab as ItemPrefab, doorPos, null);
|
||||
door.MoveWithLevel = true;
|
||||
|
||||
door.GetComponent<Items.Components.Door>().IsOpen = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.8f;
|
||||
|
||||
if (sensorPrefab == null || wirePrefab == null) continue;
|
||||
|
||||
var sensorRoom = corridor.ConnectedRooms.FirstOrDefault(r => r != null && rooms.Contains(r));
|
||||
if (sensorRoom == null) continue;
|
||||
|
||||
var sensor = new Item(sensorPrefab, new Vector2(
|
||||
Rand.Range(sensorRoom.Rect.X, sensorRoom.Rect.Right, Rand.RandSync.Server),
|
||||
Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom, Rand.RandSync.Server)), null);
|
||||
sensor.MoveWithLevel = true;
|
||||
|
||||
var wire = new Item(wirePrefab, sensorRoom.Center, null).GetComponent<Items.Components.Wire>();
|
||||
wire.Item.MoveWithLevel = false;
|
||||
|
||||
var conn1 = door.Connections.Find(c => c.Name == "set_state");
|
||||
conn1.AddLink(0, wire);
|
||||
wire.Connect(conn1, false);
|
||||
|
||||
var conn2 = sensor.Connections.Find(c => c.Name == "state_out");
|
||||
conn2.AddLink(0, wire);
|
||||
wire.Connect(conn2, false);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
[Flags]
|
||||
enum RuinStructureType
|
||||
{
|
||||
Wall = 1, CorridorWall = 2, Prop = 4, Back = 8, Door=16, Hatch=32, HeavyWall=64
|
||||
}
|
||||
|
||||
class RuinStructure
|
||||
{
|
||||
const string ConfigFile = "Content/Map/RuinConfig.xml";
|
||||
|
||||
private static List<RuinStructure> list;
|
||||
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
public readonly Alignment Alignment;
|
||||
|
||||
public readonly RuinStructureType Type;
|
||||
|
||||
private int commonness;
|
||||
|
||||
private RuinStructure(XElement element)
|
||||
{
|
||||
string prefab = ToolBox.GetAttributeString(element, "prefab", "").ToLowerInvariant();
|
||||
Prefab = MapEntityPrefab.list.Find(s => s.Name.ToLowerInvariant() == prefab);
|
||||
|
||||
if (Prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Loading ruin structure failed - structure prefab \""+prefab+" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
string alignmentStr = ToolBox.GetAttributeString(element,"alignment","Bottom");
|
||||
if (!Enum.TryParse<Alignment>(alignmentStr, true, out Alignment))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ruin structure \""+prefab+"\" - "+alignmentStr+" is not a valid alignment");
|
||||
}
|
||||
|
||||
|
||||
string typeStr = ToolBox.GetAttributeString(element,"type","");
|
||||
if (!Enum.TryParse<RuinStructureType>(typeStr,true, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ruin structure \"" + prefab + "\" - " + typeStr + " is not a valid type");
|
||||
return;
|
||||
}
|
||||
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
list = new List<RuinStructure>();
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(ConfigFile);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
new RuinStructure(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static RuinStructure GetRandom(RuinStructureType type, Alignment alignment)
|
||||
{
|
||||
if (list==null)
|
||||
{
|
||||
DebugConsole.Log("Loading ruin structures...");
|
||||
Load();
|
||||
}
|
||||
|
||||
var matchingStructures = list.FindAll(rs => rs.Type.HasFlag(type) && rs.Alignment.HasFlag(alignment));
|
||||
|
||||
if (!matchingStructures.Any()) return null;
|
||||
|
||||
int totalCommonness = matchingStructures.Sum(m => m.commonness);
|
||||
|
||||
int randomNumber = Rand.Int(totalCommonness + 1, Rand.RandSync.Server);
|
||||
|
||||
foreach (RuinStructure ruinStructure in matchingStructures)
|
||||
{
|
||||
if (randomNumber <= ruinStructure.commonness)
|
||||
{
|
||||
return ruinStructure;
|
||||
}
|
||||
|
||||
randomNumber -= ruinStructure.commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,998 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 11:30 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
|
||||
* Bell Laboratories.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code was originally written by Stephan Fortune in C code. I, Shane O'Sullivan,
|
||||
* have since modified it, encapsulating it in a C++ class and, fixing memory leaks and
|
||||
* adding accessors to the Voronoi Edges.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Java Version by Zhenyu Pan
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of Voronoi.
|
||||
/// </summary>
|
||||
public class Voronoi
|
||||
{
|
||||
// ************* Private members ******************
|
||||
double borderMinX, borderMaxX, borderMinY, borderMaxY;
|
||||
int siteidx;
|
||||
double xmin, xmax, ymin, ymax, deltax, deltay;
|
||||
int nvertices;
|
||||
int nedges;
|
||||
int nsites;
|
||||
Site[] sites;
|
||||
Site bottomsite;
|
||||
int sqrt_nsites;
|
||||
double minDistanceBetweenSites;
|
||||
int PQcount;
|
||||
int PQmin;
|
||||
int PQhashsize;
|
||||
Halfedge[] PQhash;
|
||||
|
||||
const int LE = 0;
|
||||
const int RE = 1;
|
||||
|
||||
int ELhashsize;
|
||||
Halfedge[] ELhash;
|
||||
Halfedge ELleftend, ELrightend;
|
||||
List<GraphEdge> allEdges;
|
||||
|
||||
|
||||
// ************* Public methods ******************
|
||||
// ******************************************
|
||||
|
||||
// constructor
|
||||
public Voronoi ( double minDistanceBetweenSites )
|
||||
{
|
||||
siteidx = 0;
|
||||
sites = null;
|
||||
|
||||
allEdges = null;
|
||||
this.minDistanceBetweenSites = minDistanceBetweenSites;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param xValuesIn Array of X values for each site.
|
||||
* @param yValuesIn Array of Y values for each site. Must be identical length to yValuesIn
|
||||
* @param minX The minimum X of the bounding box around the voronoi
|
||||
* @param maxX The maximum X of the bounding box around the voronoi
|
||||
* @param minY The minimum Y of the bounding box around the voronoi
|
||||
* @param maxY The maximum Y of the bounding box around the voronoi
|
||||
* @return
|
||||
*/
|
||||
// تستدعى هذه العملية لإنشاء مخطط فورونوي
|
||||
public List<GraphEdge> generateVoronoi ( double[] xValuesIn, double[] yValuesIn, double minX, double maxX, double minY, double maxY )
|
||||
{
|
||||
sort(xValuesIn, yValuesIn, xValuesIn.Length);
|
||||
|
||||
// Check bounding box inputs - if mins are bigger than maxes, swap them
|
||||
double temp = 0;
|
||||
if ( minX > maxX )
|
||||
{
|
||||
temp = minX;
|
||||
minX = maxX;
|
||||
maxX = temp;
|
||||
}
|
||||
if ( minY > maxY )
|
||||
{
|
||||
temp = minY;
|
||||
minY = maxY;
|
||||
maxY = temp;
|
||||
}
|
||||
|
||||
borderMinX = minX;
|
||||
borderMinY = minY;
|
||||
borderMaxX = maxX;
|
||||
borderMaxY = maxY;
|
||||
|
||||
siteidx = 0;
|
||||
voronoi_bd ();
|
||||
return allEdges;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************
|
||||
* Private methods - implementation details
|
||||
********************************************************/
|
||||
|
||||
private void sort ( double[] xValuesIn, double[] yValuesIn, int count )
|
||||
{
|
||||
sites = null;
|
||||
allEdges = new List<GraphEdge>();
|
||||
|
||||
nsites = count;
|
||||
nvertices = 0;
|
||||
nedges = 0;
|
||||
|
||||
double sn = (double)nsites + 4;
|
||||
sqrt_nsites = (int) Math.Sqrt ( sn );
|
||||
|
||||
// Copy the inputs so we don't modify the originals
|
||||
double[] xValues = new double[count];
|
||||
double[] yValues = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
xValues[i] = xValuesIn[i];
|
||||
yValues[i] = yValuesIn[i];
|
||||
}
|
||||
sortNode ( xValues, yValues, count );
|
||||
}
|
||||
|
||||
private void qsort ( Site[] sites )
|
||||
{
|
||||
List<Site> listSites = new List<Site>( sites.Length );
|
||||
for ( int i = 0; i < sites.Length; i++ )
|
||||
{
|
||||
listSites.Add ( sites[i] );
|
||||
}
|
||||
|
||||
listSites.Sort ( new SiteSorterYX () );
|
||||
|
||||
// Copy back into the array
|
||||
for (int i=0; i < sites.Length; i++)
|
||||
{
|
||||
sites[i] = listSites[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNode ( double[] xValues, double[] yValues, int numPoints )
|
||||
{
|
||||
nsites = numPoints;
|
||||
sites = new Site[nsites];
|
||||
xmin = xValues[0];
|
||||
ymin = yValues[0];
|
||||
xmax = xValues[0];
|
||||
ymax = yValues[0];
|
||||
|
||||
for ( int i = 0; i < nsites; i++ )
|
||||
{
|
||||
sites[i] = new Site();
|
||||
sites[i].coord.setPoint ( xValues[i], yValues[i] );
|
||||
sites[i].sitenbr = i;
|
||||
|
||||
if ( xValues[i] < xmin )
|
||||
xmin = xValues[i];
|
||||
else if ( xValues[i] > xmax )
|
||||
xmax = xValues[i];
|
||||
|
||||
if ( yValues[i] < ymin )
|
||||
ymin = yValues[i];
|
||||
else if ( yValues[i] > ymax )
|
||||
ymax = yValues[i];
|
||||
}
|
||||
|
||||
qsort ( sites );
|
||||
deltax = xmax - xmin;
|
||||
deltay = ymax - ymin;
|
||||
}
|
||||
|
||||
private Site nextone ()
|
||||
{
|
||||
Site s;
|
||||
if ( siteidx < nsites )
|
||||
{
|
||||
s = sites[siteidx];
|
||||
siteidx++;
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Edge bisect ( Site s1, Site s2 )
|
||||
{
|
||||
double dx, dy, adx, ady;
|
||||
Edge newedge;
|
||||
|
||||
newedge = new Edge();
|
||||
|
||||
newedge.reg[0] = s1;
|
||||
newedge.reg[1] = s2;
|
||||
|
||||
newedge.ep [0] = null;
|
||||
newedge.ep[1] = null;
|
||||
|
||||
dx = s2.coord.x - s1.coord.x;
|
||||
dy = s2.coord.y - s1.coord.y;
|
||||
|
||||
adx = dx > 0 ? dx : -dx;
|
||||
ady = dy > 0 ? dy : -dy;
|
||||
newedge.c = (double)(s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy* dy) * 0.5);
|
||||
|
||||
if ( adx > ady )
|
||||
{
|
||||
newedge.a = 1.0;
|
||||
newedge.b = dy / dx;
|
||||
newedge.c /= dx;
|
||||
}
|
||||
else
|
||||
{
|
||||
newedge.a = dx / dy;
|
||||
newedge.b = 1.0;
|
||||
newedge.c /= dy;
|
||||
}
|
||||
|
||||
newedge.edgenbr = nedges;
|
||||
nedges++;
|
||||
|
||||
return newedge;
|
||||
}
|
||||
|
||||
private void makevertex ( Site v )
|
||||
{
|
||||
v.sitenbr = nvertices;
|
||||
nvertices++;
|
||||
}
|
||||
|
||||
private bool PQinitialize ()
|
||||
{
|
||||
PQcount = 0;
|
||||
PQmin = 0;
|
||||
PQhashsize = 4 * sqrt_nsites;
|
||||
PQhash = new Halfedge[ PQhashsize ];
|
||||
|
||||
for ( int i = 0; i < PQhashsize; i++ )
|
||||
{
|
||||
PQhash [i] = new Halfedge();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int PQbucket ( Halfedge he )
|
||||
{
|
||||
int bucket;
|
||||
|
||||
bucket = (int) ((he.ystar - ymin) / deltay * PQhashsize);
|
||||
if ( bucket < 0 )
|
||||
bucket = 0;
|
||||
if ( bucket >= PQhashsize )
|
||||
bucket = PQhashsize - 1;
|
||||
if ( bucket < PQmin )
|
||||
PQmin = bucket;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
// push the HalfEdge into the ordered linked list of vertices
|
||||
private void PQinsert ( Halfedge he, Site v, double offset )
|
||||
{
|
||||
Halfedge last, next;
|
||||
|
||||
he.vertex = v;
|
||||
he.ystar = (double)(v.coord.y + offset);
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
|
||||
while
|
||||
(
|
||||
(next = last.PQnext) != null
|
||||
&&
|
||||
(he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))
|
||||
)
|
||||
{
|
||||
last = next;
|
||||
}
|
||||
|
||||
he.PQnext = last.PQnext;
|
||||
last.PQnext = he;
|
||||
PQcount++;
|
||||
}
|
||||
|
||||
// remove the HalfEdge from the list of vertices
|
||||
private void PQdelete ( Halfedge he )
|
||||
{
|
||||
Halfedge last;
|
||||
|
||||
if (he.vertex != null)
|
||||
{
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
while ( last.PQnext != he )
|
||||
{
|
||||
last = last.PQnext;
|
||||
}
|
||||
|
||||
last.PQnext = he.PQnext;
|
||||
PQcount--;
|
||||
he.vertex = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PQempty ()
|
||||
{
|
||||
return ( PQcount == 0 );
|
||||
}
|
||||
|
||||
private Point PQ_min ()
|
||||
{
|
||||
Point answer = new Point ();
|
||||
|
||||
while ( PQhash[PQmin].PQnext == null )
|
||||
{
|
||||
PQmin++;
|
||||
}
|
||||
|
||||
answer.x = PQhash[PQmin].PQnext.vertex.coord.x;
|
||||
answer.y = PQhash[PQmin].PQnext.ystar;
|
||||
return answer;
|
||||
}
|
||||
|
||||
private Halfedge PQextractmin ()
|
||||
{
|
||||
Halfedge curr;
|
||||
|
||||
curr = PQhash[PQmin].PQnext;
|
||||
PQhash[PQmin].PQnext = curr.PQnext;
|
||||
PQcount--;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
private Halfedge HEcreate(Edge e, int pm)
|
||||
{
|
||||
Halfedge answer = new Halfedge();
|
||||
answer.ELedge = e;
|
||||
answer.ELpm = pm;
|
||||
answer.PQnext = null;
|
||||
answer.vertex = null;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private bool ELinitialize()
|
||||
{
|
||||
ELhashsize = 2 * sqrt_nsites;
|
||||
ELhash = new Halfedge[ELhashsize];
|
||||
|
||||
for (int i = 0; i < ELhashsize; i++)
|
||||
{
|
||||
ELhash[i] = null;
|
||||
}
|
||||
|
||||
ELleftend = HEcreate ( null, 0 );
|
||||
ELrightend = HEcreate ( null, 0 );
|
||||
ELleftend.ELleft = null;
|
||||
ELleftend.ELright = ELrightend;
|
||||
ELrightend.ELleft = ELleftend;
|
||||
ELrightend.ELright = null;
|
||||
ELhash[0] = ELleftend;
|
||||
ELhash[ELhashsize - 1] = ELrightend;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Halfedge ELright( Halfedge he )
|
||||
{
|
||||
return he.ELright;
|
||||
}
|
||||
|
||||
private Halfedge ELleft( Halfedge he )
|
||||
{
|
||||
return he.ELleft;
|
||||
}
|
||||
|
||||
private Site leftreg( Halfedge he )
|
||||
{
|
||||
if (he.ELedge == null)
|
||||
{
|
||||
return bottomsite;
|
||||
}
|
||||
return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]);
|
||||
}
|
||||
|
||||
private void ELinsert( Halfedge lb, Halfedge newHe )
|
||||
{
|
||||
newHe.ELleft = lb;
|
||||
newHe.ELright = lb.ELright;
|
||||
(lb.ELright).ELleft = newHe;
|
||||
lb.ELright = newHe;
|
||||
}
|
||||
|
||||
/*
|
||||
* This delete routine can't reclaim node, since pointers from hash table
|
||||
* may be present.
|
||||
*/
|
||||
private void ELdelete( Halfedge he )
|
||||
{
|
||||
(he.ELleft).ELright = he.ELright;
|
||||
(he.ELright).ELleft = he.ELleft;
|
||||
he.deleted = true;
|
||||
}
|
||||
|
||||
/* Get entry from hash table, pruning any deleted nodes */
|
||||
private Halfedge ELgethash( int b )
|
||||
{
|
||||
Halfedge he;
|
||||
if (b < 0 || b >= ELhashsize)
|
||||
return null;
|
||||
|
||||
he = ELhash[b];
|
||||
if (he == null || !he.deleted )
|
||||
return he;
|
||||
|
||||
/* Hash table points to deleted half edge. Patch as necessary. */
|
||||
ELhash[b] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Halfedge ELleftbnd( Point p )
|
||||
{
|
||||
int bucket;
|
||||
Halfedge he;
|
||||
|
||||
/* Use hash table to get close to desired halfedge */
|
||||
// use the hash function to find the place in the hash map that this
|
||||
// HalfEdge should be
|
||||
bucket = (int) ((p.x - xmin) / deltax * ELhashsize);
|
||||
|
||||
// make sure that the bucket position is within the range of the hash
|
||||
// array
|
||||
if ( bucket < 0 ) bucket = 0;
|
||||
if ( bucket >= ELhashsize ) bucket = ELhashsize - 1;
|
||||
|
||||
he = ELgethash ( bucket );
|
||||
|
||||
// if the HE isn't found, search backwards and forwards in the hash map
|
||||
// for the first non-null entry
|
||||
if ( he == null )
|
||||
{
|
||||
for ( int i = 1; i < ELhashsize; i++ )
|
||||
{
|
||||
if ( (he = ELgethash ( bucket - i ) ) != null )
|
||||
break;
|
||||
if ( (he = ELgethash ( bucket + i ) ) != null )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now search linear list of halfedges for the correct one */
|
||||
if ( he == ELleftend || ( he != ELrightend && right_of (he, p) ) )
|
||||
{
|
||||
// keep going right on the list until either the end is reached, or
|
||||
// you find the 1st edge which the point isn't to the right of
|
||||
do
|
||||
{
|
||||
he = he.ELright;
|
||||
}
|
||||
while ( he != ELrightend && right_of(he, p) );
|
||||
he = he.ELleft;
|
||||
}
|
||||
else
|
||||
// if the point is to the left of the HalfEdge, then search left for
|
||||
// the HE just to the left of the point
|
||||
{
|
||||
do
|
||||
{
|
||||
he = he.ELleft;
|
||||
}
|
||||
while ( he != ELleftend && !right_of(he, p) );
|
||||
}
|
||||
|
||||
/* Update hash table and reference counts */
|
||||
if ( bucket > 0 && bucket < ELhashsize - 1)
|
||||
{
|
||||
ELhash[bucket] = he;
|
||||
}
|
||||
|
||||
return he;
|
||||
}
|
||||
|
||||
private void pushGraphEdge( Site leftSite, Site rightSite, Vector2 point1, Vector2 point2 )
|
||||
{
|
||||
GraphEdge newEdge = new GraphEdge(point1, point2);
|
||||
allEdges.Add ( newEdge );
|
||||
|
||||
newEdge.site1 = leftSite;
|
||||
newEdge.site2 = rightSite;
|
||||
}
|
||||
|
||||
private void clip_line( Edge e )
|
||||
{
|
||||
double pxmin, pxmax, pymin, pymax;
|
||||
Site s1, s2;
|
||||
|
||||
double x1 = e.reg[0].coord.x;
|
||||
double y1 = e.reg[0].coord.y;
|
||||
double x2 = e.reg[1].coord.x;
|
||||
double y2 = e.reg[1].coord.y;
|
||||
double x = x2- x1;
|
||||
double y = y2 - y1;
|
||||
|
||||
// if the distance between the two points this line was created from is
|
||||
// less than the square root of 2 عن جد؟, then ignore it
|
||||
if ( Math.Sqrt ( (x*x) + (y*y) ) < minDistanceBetweenSites )
|
||||
{
|
||||
return;
|
||||
}
|
||||
pxmin = borderMinX;
|
||||
pymin = borderMinY;
|
||||
pxmax = borderMaxX;
|
||||
pymax = borderMaxY;
|
||||
|
||||
if ( e.a == 1.0 && e.b >= 0.0 )
|
||||
{
|
||||
s1 = e.ep[1];
|
||||
s2 = e.ep[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
s1 = e.ep[0];
|
||||
s2 = e.ep[1];
|
||||
}
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
y1 = pymin;
|
||||
|
||||
if ( s1 != null && s1.coord.y > pymin )
|
||||
y1 = s1.coord.y;
|
||||
if ( y1 > pymax )
|
||||
y1 = pymax;
|
||||
x1 = e.c - e.b * y1;
|
||||
y2 = pymax;
|
||||
|
||||
if ( s2 != null && s2.coord.y < pymax )
|
||||
y2 = s2.coord.y;
|
||||
if ( y2 < pymin )
|
||||
y2 = pymin;
|
||||
x2 = e.c - e.b * y2;
|
||||
if ( ( (x1 > pxmax) & (x2 > pxmax) ) | ( (x1 < pxmin) & (x2 < pxmin) ) )
|
||||
return;
|
||||
|
||||
if ( x1 > pxmax )
|
||||
{
|
||||
x1 = pxmax;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x1 < pxmin )
|
||||
{
|
||||
x1 = pxmin;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x2 > pxmax )
|
||||
{
|
||||
x2 = pxmax;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
if ( x2 < pxmin )
|
||||
{
|
||||
x2 = pxmin;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = pxmin;
|
||||
if ( s1 != null && s1.coord.x > pxmin )
|
||||
x1 = s1.coord.x;
|
||||
if ( x1 > pxmax )
|
||||
x1 = pxmax;
|
||||
y1 = e.c - e.a * x1;
|
||||
|
||||
x2 = pxmax;
|
||||
if ( s2 != null && s2.coord.x < pxmax )
|
||||
x2 = s2.coord.x;
|
||||
if ( x2 < pxmin )
|
||||
x2 = pxmin;
|
||||
y2 = e.c - e.a * x2;
|
||||
|
||||
if (((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin)))
|
||||
return;
|
||||
|
||||
if ( y1 > pymax )
|
||||
{
|
||||
y1 = pymax;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y1 < pymin )
|
||||
{
|
||||
y1 = pymin;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y2 > pymax )
|
||||
{
|
||||
y2 = pymax;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
if ( y2 < pymin )
|
||||
{
|
||||
y2 = pymin;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
}
|
||||
|
||||
pushGraphEdge(e.reg[0], e.reg[1], new Vector2((float)x1, (float)y1), new Vector2((float)x2, (float)y2));
|
||||
}
|
||||
|
||||
private void endpoint( Edge e, int lr, Site s )
|
||||
{
|
||||
e.ep[lr] = s;
|
||||
if ( e.ep[RE - lr] == null )
|
||||
return;
|
||||
clip_line ( e );
|
||||
}
|
||||
|
||||
/* returns true if p is to right of halfedge e */
|
||||
private bool right_of(Halfedge el, Point p)
|
||||
{
|
||||
Edge e;
|
||||
Site topsite;
|
||||
bool right_of_site;
|
||||
bool above, fast;
|
||||
double dxp, dyp, dxs, t1, t2, t3, yl;
|
||||
|
||||
e = el.ELedge;
|
||||
topsite = e.reg[1];
|
||||
|
||||
if ( p.x > topsite.coord.x )
|
||||
right_of_site = true;
|
||||
else
|
||||
right_of_site = false;
|
||||
|
||||
if ( right_of_site && el.ELpm == LE )
|
||||
return true;
|
||||
if (!right_of_site && el.ELpm == RE )
|
||||
return false;
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
dxp = p.x - topsite.coord.x;
|
||||
dyp = p.y - topsite.coord.y;
|
||||
fast = false;
|
||||
|
||||
if ( (!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0)) )
|
||||
{
|
||||
above = dyp >= e.b * dxp;
|
||||
fast = above;
|
||||
}
|
||||
else
|
||||
{
|
||||
above = p.x + p.y * e.b > e.c;
|
||||
if ( e.b < 0.0 )
|
||||
above = !above;
|
||||
if ( !above )
|
||||
fast = true;
|
||||
}
|
||||
if ( !fast )
|
||||
{
|
||||
dxs = topsite.coord.x - ( e.reg[0] ).coord.x;
|
||||
above = e.b * (dxp * dxp - dyp * dyp)
|
||||
< dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b);
|
||||
|
||||
if ( e.b < 0 )
|
||||
above = !above;
|
||||
}
|
||||
}
|
||||
else // e.b == 1.0
|
||||
{
|
||||
yl = e.c - e.a * p.x;
|
||||
t1 = p.y - yl;
|
||||
t2 = p.x - topsite.coord.x;
|
||||
t3 = yl - topsite.coord.y;
|
||||
above = t1 * t1 > t2 * t2 + t3 * t3;
|
||||
}
|
||||
return ( el.ELpm == LE ? above : !above );
|
||||
}
|
||||
|
||||
private Site rightreg(Halfedge he)
|
||||
{
|
||||
if (he.ELedge == (Edge) null)
|
||||
// if this halfedge has no edge, return the bottom site (whatever
|
||||
// that is)
|
||||
{
|
||||
return (bottomsite);
|
||||
}
|
||||
|
||||
// if the ELpm field is zero, return the site 0 that this edge bisects,
|
||||
// otherwise return site number 1
|
||||
return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]);
|
||||
}
|
||||
|
||||
private double dist( Site s, Site t )
|
||||
{
|
||||
double dx, dy;
|
||||
dx = s.coord.x - t.coord.x;
|
||||
dy = s.coord.y - t.coord.y;
|
||||
return Math.Sqrt ( dx * dx + dy * dy );
|
||||
}
|
||||
|
||||
// create a new site where the HalfEdges el1 and el2 intersect - note that
|
||||
// the Point in the argument list is not used, don't know why it's there
|
||||
private Site intersect( Halfedge el1, Halfedge el2 )
|
||||
{
|
||||
Edge e1, e2, e;
|
||||
Halfedge el;
|
||||
double d, xint, yint;
|
||||
bool right_of_site;
|
||||
Site v; // vertex
|
||||
|
||||
e1 = el1.ELedge;
|
||||
e2 = el2.ELedge;
|
||||
|
||||
if ( e1 == null || e2 == null )
|
||||
return null;
|
||||
|
||||
// if the two edges bisect the same parent, return null
|
||||
if ( e1.reg[1] == e2.reg[1] )
|
||||
return null;
|
||||
|
||||
d = e1.a * e2.b - e1.b * e2.a;
|
||||
if ( -1.0e-10 < d && d < 1.0e-10 )
|
||||
return null;
|
||||
|
||||
xint = ( e1.c * e2.b - e2.c * e1.b ) / d;
|
||||
yint = ( e2.c * e1.a - e1.c * e2.a ) / d;
|
||||
|
||||
if ( (e1.reg[1].coord.y < e2.reg[1].coord.y)
|
||||
|| (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x) )
|
||||
{
|
||||
el = el1;
|
||||
e = e1;
|
||||
}
|
||||
else
|
||||
{
|
||||
el = el2;
|
||||
e = e2;
|
||||
}
|
||||
|
||||
right_of_site = xint >= e.reg[1].coord.x;
|
||||
if ((right_of_site && el.ELpm == LE)
|
||||
|| (!right_of_site && el.ELpm == RE))
|
||||
return null;
|
||||
|
||||
// create a new site at the point of intersection - this is a new vector
|
||||
// event waiting to happen
|
||||
v = new Site();
|
||||
v.coord.x = xint;
|
||||
v.coord.y = yint;
|
||||
return v;
|
||||
}
|
||||
|
||||
/*
|
||||
* implicit parameters: nsites, sqrt_nsites, xmin, xmax, ymin, ymax, deltax,
|
||||
* deltay (can all be estimates). Performance suffers if they are wrong;
|
||||
* better to make nsites, deltax, and deltay too big than too small. (?)
|
||||
*/
|
||||
private bool voronoi_bd()
|
||||
{
|
||||
Site newsite, bot, top, temp, p;
|
||||
Site v;
|
||||
Point newintstar = null;
|
||||
int pm;
|
||||
Halfedge lbnd, rbnd, llbnd, rrbnd, bisector;
|
||||
Edge e;
|
||||
|
||||
PQinitialize();
|
||||
ELinitialize();
|
||||
|
||||
bottomsite = nextone();
|
||||
newsite = nextone();
|
||||
while (true)
|
||||
{
|
||||
if (!PQempty())
|
||||
{
|
||||
newintstar = PQ_min();
|
||||
}
|
||||
// if the lowest site has a smaller y value than the lowest vector
|
||||
// intersection,
|
||||
// process the site otherwise process the vector intersection
|
||||
|
||||
if (newsite != null && (PQempty()
|
||||
|| newsite.coord.y < newintstar.y
|
||||
|| (newsite.coord.y == newintstar.y
|
||||
&& newsite.coord.x < newintstar.x)))
|
||||
{
|
||||
/* new site is smallest -this is a site event */
|
||||
// get the first HalfEdge to the LEFT of the new site
|
||||
lbnd = ELleftbnd((newsite.coord));
|
||||
// get the first HalfEdge to the RIGHT of the new site
|
||||
rbnd = ELright(lbnd);
|
||||
// if this halfedge has no edge,bot =bottom site (whatever that
|
||||
// is)
|
||||
bot = rightreg(lbnd);
|
||||
// create a new edge that bisects
|
||||
e = bisect(bot, newsite);
|
||||
|
||||
// create a new HalfEdge, setting its ELpm field to 0
|
||||
bisector = HEcreate(e, LE);
|
||||
// insert this new bisector edge between the left and right
|
||||
// vectors in a linked list
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if the new bisector intersects with the left edge,
|
||||
// remove the left edge's vertex, and put in the new one
|
||||
if ((p = intersect(lbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(lbnd);
|
||||
PQinsert(lbnd, p, dist(p, newsite));
|
||||
}
|
||||
lbnd = bisector;
|
||||
// create a new HalfEdge, setting its ELpm field to 1
|
||||
bisector = HEcreate(e, RE);
|
||||
// insert the new HE to the right of the original bisector
|
||||
// earlier in the IF stmt
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if this new bisector intersects with the new HalfEdge
|
||||
if ((p = intersect(bisector, rbnd)) != null)
|
||||
{
|
||||
// push the HE into the ordered linked list of vertices
|
||||
PQinsert(bisector, p, dist(p, newsite));
|
||||
}
|
||||
newsite = nextone();
|
||||
} else if (!PQempty())
|
||||
/* intersection is smallest - this is a vector event */
|
||||
{
|
||||
// pop the HalfEdge with the lowest vector off the ordered list
|
||||
// of vectors
|
||||
lbnd = PQextractmin();
|
||||
// get the HalfEdge to the left of the above HE
|
||||
llbnd = ELleft(lbnd);
|
||||
// get the HalfEdge to the right of the above HE
|
||||
rbnd = ELright(lbnd);
|
||||
// get the HalfEdge to the right of the HE to the right of the
|
||||
// lowest HE
|
||||
rrbnd = ELright(rbnd);
|
||||
// get the Site to the left of the left HE which it bisects
|
||||
bot = leftreg(lbnd);
|
||||
// get the Site to the right of the right HE which it bisects
|
||||
top = rightreg(rbnd);
|
||||
|
||||
v = lbnd.vertex; // get the vertex that caused this event
|
||||
makevertex(v); // set the vertex number - couldn't do this
|
||||
// earlier since we didn't know when it would be processed
|
||||
endpoint(lbnd.ELedge, lbnd.ELpm, v);
|
||||
// set the endpoint of
|
||||
// the left HalfEdge to be this vector
|
||||
endpoint(rbnd.ELedge, rbnd.ELpm, v);
|
||||
// set the endpoint of the right HalfEdge to
|
||||
// be this vector
|
||||
ELdelete(lbnd); // mark the lowest HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
PQdelete(rbnd);
|
||||
// remove all vertex events to do with the right HE
|
||||
ELdelete(rbnd); // mark the right HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
pm = LE; // set the pm variable to zero
|
||||
|
||||
if (bot.coord.y > top.coord.y)
|
||||
// if the site to the left of the event is higher than the
|
||||
// Site
|
||||
{ // to the right of it, then swap them and set the 'pm'
|
||||
// variable to 1
|
||||
temp = bot;
|
||||
bot = top;
|
||||
top = temp;
|
||||
pm = RE;
|
||||
}
|
||||
e = bisect(bot, top); // create an Edge (or line)
|
||||
// that is between the two Sites. This creates the formula of
|
||||
// the line, and assigns a line number to it
|
||||
bisector = HEcreate(e, pm); // create a HE from the Edge 'e',
|
||||
// and make it point to that edge
|
||||
// with its ELedge field
|
||||
ELinsert(llbnd, bisector); // insert the new bisector to the
|
||||
// right of the left HE
|
||||
endpoint(e, RE - pm, v); // set one endpoint to the new edge
|
||||
// to be the vector point 'v'.
|
||||
// If the site to the left of this bisector is higher than the
|
||||
// right Site, then this endpoint
|
||||
// is put in position 0; otherwise in pos 1
|
||||
|
||||
// if left HE and the new bisector intersect, then delete
|
||||
// the left HE, and reinsert it
|
||||
if ((p = intersect(llbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(llbnd);
|
||||
PQinsert(llbnd, p, dist(p, bot));
|
||||
}
|
||||
|
||||
// if right HE and the new bisector intersect, then
|
||||
// reinsert it
|
||||
if ((p = intersect(bisector, rrbnd)) != null)
|
||||
{
|
||||
PQinsert(bisector, p, dist(p, bot));
|
||||
}
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (lbnd = ELright(ELleftend); lbnd != ELrightend; lbnd = ELright(lbnd))
|
||||
{
|
||||
e = lbnd.ELedge;
|
||||
clip_line(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
double[] xVal = new double[sites.Count];
|
||||
double[] yVal = new double[sites.Count];
|
||||
for (int i = 0; i < sites.Count; i++)
|
||||
{
|
||||
xVal[i] = sites[i].X;
|
||||
yVal[i] = sites[i].Y;
|
||||
}
|
||||
return generateVoronoi(xVal, yVal, minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, int width, int height)
|
||||
{
|
||||
double[] xVal = new double[sites.Count];
|
||||
double[] yVal = new double[sites.Count];
|
||||
for (int i = 0; i < sites.Count; i++)
|
||||
{
|
||||
xVal[i] = sites[i].X;
|
||||
yVal[i] = sites[i].Y;
|
||||
}
|
||||
return generateVoronoi(xVal, yVal, 0, width, 0, height);
|
||||
}
|
||||
|
||||
} // Voronoi Class End
|
||||
} // namespace Voronoi2 End
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 09:29 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2011 James Humphreys. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY James Humphreys ``AS IS\" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of James Humphreys.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
public class Point
|
||||
{
|
||||
public double x, y;
|
||||
|
||||
public void setPoint ( double x, double y )
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// use for sites and vertecies
|
||||
public class Site
|
||||
{
|
||||
public Point coord;
|
||||
public int sitenbr;
|
||||
|
||||
public void SetPoint(Vector2 point)
|
||||
{
|
||||
coord.setPoint(point.X, point.Y);
|
||||
}
|
||||
|
||||
public Site ()
|
||||
{
|
||||
coord = new Point();
|
||||
}
|
||||
}
|
||||
|
||||
public class Edge
|
||||
{
|
||||
public double a = 0, b = 0, c = 0;
|
||||
public Site[] ep;
|
||||
public Site[] reg;
|
||||
public int edgenbr;
|
||||
|
||||
public Edge ()
|
||||
{
|
||||
ep = new Site[2];
|
||||
reg = new Site[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Halfedge
|
||||
{
|
||||
public Halfedge ELleft, ELright;
|
||||
public Edge ELedge;
|
||||
public bool deleted;
|
||||
public int ELpm;
|
||||
public Site vertex;
|
||||
public double ystar;
|
||||
public Halfedge PQnext;
|
||||
|
||||
public Halfedge ()
|
||||
{
|
||||
PQnext = null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CellType
|
||||
{
|
||||
Solid, Empty, Edge, Path, Removed
|
||||
}
|
||||
|
||||
public class VoronoiCell
|
||||
{
|
||||
public List<GraphEdge> edges;
|
||||
public Site site;
|
||||
|
||||
public List<Vector2> bodyVertices;
|
||||
|
||||
public Body body;
|
||||
|
||||
public CellType CellType;
|
||||
|
||||
public Vector2 Translation;
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return new Vector2((float)site.coord.x, (float)site.coord.y)+Translation; }
|
||||
}
|
||||
|
||||
public VoronoiCell(Vector2[] vertices)
|
||||
{
|
||||
edges = new List<GraphEdge>();
|
||||
bodyVertices = new List<Vector2>();
|
||||
|
||||
Vector2 midPoint = Vector2.Zero;
|
||||
foreach (Vector2 vertex in vertices)
|
||||
{
|
||||
midPoint += vertex;
|
||||
}
|
||||
midPoint /= vertices.Length;
|
||||
|
||||
|
||||
for (int i = 1; i < vertices.Length; i++ )
|
||||
{
|
||||
GraphEdge ge = new GraphEdge(vertices[i-1], vertices[i]);
|
||||
|
||||
System.Diagnostics.Debug.Assert(ge.point1 != ge.point2);
|
||||
|
||||
edges.Add(ge);
|
||||
}
|
||||
|
||||
GraphEdge lastEdge = new GraphEdge(vertices[0], vertices[vertices.Length-1]);
|
||||
|
||||
edges.Add(lastEdge);
|
||||
|
||||
site = new Site();
|
||||
site.SetPoint(midPoint);
|
||||
}
|
||||
|
||||
public VoronoiCell(Site site)
|
||||
{
|
||||
edges = new List<GraphEdge>();
|
||||
bodyVertices = new List<Vector2>();
|
||||
//bodies = new List<Body>();
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
public bool IsPointInside(Vector2 point)
|
||||
{
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (MathUtils.LinesIntersect(point, Center, edge.point1, edge.point2)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class GraphEdge
|
||||
{
|
||||
public Vector2 point1, point2;
|
||||
public Site site1, site2;
|
||||
public VoronoiCell cell1, cell2;
|
||||
|
||||
public bool isSolid;
|
||||
|
||||
public bool OutsideLevel;
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return (point1 + point2) / 2.0f; }
|
||||
}
|
||||
|
||||
public GraphEdge(Vector2 point1, Vector2 point2)
|
||||
{
|
||||
this.point1 = point1;
|
||||
this.point2 = point2;
|
||||
}
|
||||
|
||||
public VoronoiCell AdjacentCell(VoronoiCell cell)
|
||||
{
|
||||
if (cell1==cell)
|
||||
{
|
||||
return cell2;
|
||||
}
|
||||
else if (cell2==cell)
|
||||
{
|
||||
return cell1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the normal of the edge that points outwards from the specified cell
|
||||
/// </summary>
|
||||
public Vector2 GetNormal(VoronoiCell cell)
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(point1 - point2);
|
||||
|
||||
Vector2 normal = new Vector2(dir.Y, -dir.X);
|
||||
|
||||
if (cell != null && Vector2.Dot(normal, Vector2.Normalize(Center - cell.Center)) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
|
||||
// للترتيب
|
||||
public class SiteSorterYX : IComparer<Site>
|
||||
{
|
||||
public int Compare ( Site p1, Site p2 )
|
||||
{
|
||||
Point s1 = p1.coord;
|
||||
Point s2 = p2.coord;
|
||||
if ( s1.y < s2.y ) return -1;
|
||||
if ( s1.y > s2.y ) return 1;
|
||||
if ( s1.x < s2.x ) return -1;
|
||||
if ( s1.x > s2.x ) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WrappingWall : IDisposable
|
||||
{
|
||||
public const float WallWidth = 20000.0f;
|
||||
|
||||
private Vector2 midPos;
|
||||
private int slot;
|
||||
|
||||
private Vector2 offset;
|
||||
|
||||
private List<VoronoiCell> cells;
|
||||
|
||||
public Vector2 Offset
|
||||
{
|
||||
get { return offset; }
|
||||
}
|
||||
|
||||
public List<VoronoiCell> Cells
|
||||
{
|
||||
get { return cells; }
|
||||
}
|
||||
|
||||
public Vector2 MidPos
|
||||
{
|
||||
get { return midPos; }
|
||||
}
|
||||
|
||||
public WrappingWall(List<VoronoiCell> pathCells, List<VoronoiCell> mapCells, Rectangle ignoredArea, int dir = -1)
|
||||
{
|
||||
cells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell edgeCell = null;
|
||||
foreach (VoronoiCell cell in mapCells)
|
||||
{
|
||||
if (ignoredArea.Contains(cell.Center)) continue;
|
||||
if (Math.Sign(cell.Center.X - ignoredArea.Center.X) != Math.Sign(dir)) continue;
|
||||
if (edgeCell == null || cell.Center.Y < edgeCell.Center.Y)
|
||||
{
|
||||
edgeCell = cell;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 wallSectionSize = new Vector2(2000.0f, 2000.0f);
|
||||
Vector2 startPos = (dir < 0) ?
|
||||
edgeCell.Center + Vector2.UnitX * WallWidth * dir :
|
||||
edgeCell.Center + WallWidth * Vector2.UnitX * (dir - 1);
|
||||
|
||||
midPos = startPos + Vector2.UnitX * WallWidth/2;
|
||||
|
||||
List<Vector2> bottomVertices = new List<Vector2>();
|
||||
|
||||
for (float x = 0; x <= WallWidth; x += wallSectionSize.X)
|
||||
{
|
||||
Vector2 center = new Vector2(startPos.X + x, edgeCell.Center.Y);
|
||||
float distFromCenter = Math.Abs(x - WallWidth / 2);
|
||||
float distFromEdge = WallWidth / 2 - distFromCenter;
|
||||
float normalizedDist = distFromEdge / (WallWidth / 2);
|
||||
|
||||
float variance = 1000.0f * normalizedDist;
|
||||
bottomVertices.Add(center + new Vector2(Rand.Range(-variance, variance, Rand.RandSync.Server), Rand.Range(-variance, variance, Rand.RandSync.Server) *2.0f));
|
||||
}
|
||||
|
||||
for (int i = 1; i < bottomVertices.Count; i++)
|
||||
{
|
||||
Vector2[] vertices = new Vector2[4];
|
||||
vertices[0] = bottomVertices[i];
|
||||
vertices[1] = bottomVertices[i - 1];
|
||||
vertices[2] = vertices[1] + Vector2.UnitY * wallSectionSize.Y;
|
||||
vertices[3] = vertices[0] + Vector2.UnitY * wallSectionSize.Y;
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices);
|
||||
wallCell.edges[0].cell1 = wallCell;
|
||||
wallCell.edges[1].cell1 = wallCell;
|
||||
wallCell.edges[2].cell1 = wallCell;
|
||||
wallCell.edges[3].cell1 = wallCell;
|
||||
|
||||
wallCell.edges[0].isSolid = true;
|
||||
wallCell.edges[2].isSolid = true;
|
||||
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
wallCell.edges[1].cell2 = cells[i - 2];
|
||||
cells[i - 2].edges[3].cell2 = wallCell;
|
||||
}
|
||||
|
||||
cells.Add(wallCell);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateWallShift(Vector2 pos, WrappingWall[,] walls)
|
||||
{
|
||||
if (pos.X < walls[0, 1].midPos.X && walls[0,0].midPos.X > pos.X)
|
||||
{
|
||||
walls[0, 0].Shift(-2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[0, 0] = walls[0, 1];
|
||||
walls[0, 1] = temp;
|
||||
}
|
||||
else if (pos.X > walls[0, 0].midPos.X && walls[0,1].midPos.X < pos.X && walls[0,1].slot<0)
|
||||
{
|
||||
walls[0, 1].Shift(2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[0, 0] = walls[0, 1];
|
||||
walls[0, 1] = temp;
|
||||
}
|
||||
else if (pos.X > walls[1, 1].midPos.X && walls[1,0].midPos.X < pos.X)
|
||||
{
|
||||
walls[1, 0].Shift(2);
|
||||
|
||||
var temp = walls[1, 0];
|
||||
walls[1, 0] = walls[1, 1];
|
||||
walls[1, 1] = temp;
|
||||
}
|
||||
else if (pos.X < walls[1, 0].midPos.X && walls[1, 1].midPos.X > pos.X && walls[1, 1].slot > 0)
|
||||
{
|
||||
walls[1, 1].Shift(-2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[1, 0] = walls[1, 1];
|
||||
walls[1, 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void Shift(int amount)
|
||||
{
|
||||
slot += amount;
|
||||
|
||||
Vector2 moveAmount = Vector2.UnitX * WallWidth * amount;
|
||||
Vector2 simMoveAmount = ConvertUnits.ToSimUnits(moveAmount);
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
cell.body.SetTransform(cell.body.Position + simMoveAmount, 0.0f);
|
||||
cell.Translation += moveAmount;
|
||||
}
|
||||
|
||||
midPos += moveAmount;
|
||||
offset += moveAmount;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
#if CLIENT
|
||||
if (wallVertices != null)
|
||||
{
|
||||
wallVertices.Dispose();
|
||||
wallVertices = null;
|
||||
}
|
||||
if (bodyVertices != null)
|
||||
{
|
||||
bodyVertices.Dispose();
|
||||
bodyVertices = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LinkedSubmarinePrefab : MapEntityPrefab
|
||||
{
|
||||
public readonly Submarine mainSub;
|
||||
|
||||
public LinkedSubmarinePrefab(Submarine submarine)
|
||||
{
|
||||
this.mainSub = submarine;
|
||||
}
|
||||
|
||||
protected override void CreateInstance(Rectangle rect)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub != null);
|
||||
|
||||
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2());
|
||||
}
|
||||
}
|
||||
|
||||
partial class LinkedSubmarine : MapEntity
|
||||
{
|
||||
private List<Vector2> wallVertices;
|
||||
|
||||
private string filePath;
|
||||
|
||||
private bool loadSub;
|
||||
private Submarine sub;
|
||||
|
||||
public Submarine Sub
|
||||
{
|
||||
get
|
||||
{
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
private XElement saveElement;
|
||||
|
||||
public override bool IsLinkable
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public LinkedSubmarine(Submarine submarine)
|
||||
: base(null, submarine)
|
||||
{
|
||||
linkedTo = new System.Collections.ObjectModel.ObservableCollection<MapEntity>();
|
||||
linkedToID = new List<ushort>();
|
||||
|
||||
InsertToList();
|
||||
}
|
||||
|
||||
public static LinkedSubmarine CreateDummy(Submarine mainSub, Submarine linkedSub)
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
|
||||
sl.sub = linkedSub;
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
public static LinkedSubmarine CreateDummy(Submarine mainSub, string filePath, Vector2 position)
|
||||
{
|
||||
XDocument doc = Submarine.OpenFile(filePath);
|
||||
if (doc == null || doc.Root == null) return null;
|
||||
|
||||
LinkedSubmarine sl = CreateDummy(mainSub, doc.Root, position);
|
||||
sl.filePath = filePath;
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
public static LinkedSubmarine CreateDummy(Submarine mainSub, XElement element, Vector2 position)
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
|
||||
sl.GenerateWallVertices(element);
|
||||
|
||||
sl.Rect = new Rectangle(
|
||||
(int)sl.wallVertices.Min(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Max(v => v.Y + position.Y),
|
||||
(int)sl.wallVertices.Max(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Min(v => v.Y + position.Y));
|
||||
|
||||
sl.rect = new Rectangle((int)position.X, (int)position.Y, 1, 1);
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
public override bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
return Vector2.Distance(position, WorldPosition) < 50.0f;
|
||||
}
|
||||
|
||||
private void GenerateWallVertices(XElement rootElement)
|
||||
{
|
||||
List<Vector2> points = new List<Vector2>();
|
||||
|
||||
var wallPrefabs =
|
||||
MapEntityPrefab.list.FindAll(mp => (mp is StructurePrefab) && ((StructurePrefab)mp).HasBody);
|
||||
|
||||
foreach (XElement element in rootElement.Elements())
|
||||
{
|
||||
if (element.Name != "Structure") continue;
|
||||
|
||||
string name = ToolBox.GetAttributeString(element, "name", "");
|
||||
if (!wallPrefabs.Any(wp => wp.Name == name)) continue;
|
||||
|
||||
var rect = ToolBox.GetAttributeVector4(element, "rect", Vector4.Zero);
|
||||
|
||||
points.Add(new Vector2(rect.X, rect.Y));
|
||||
points.Add(new Vector2(rect.X + rect.Z, rect.Y));
|
||||
points.Add(new Vector2(rect.X, rect.Y - rect.W));
|
||||
points.Add(new Vector2(rect.X + rect.Z, rect.Y - rect.W));
|
||||
}
|
||||
|
||||
wallVertices = MathUtils.GiftWrap(points);
|
||||
}
|
||||
|
||||
public static void Load(XElement element, Submarine submarine)
|
||||
{
|
||||
Vector2 pos = ToolBox.GetAttributeVector2(element, "pos", Vector2.Zero);
|
||||
|
||||
LinkedSubmarine linkedSub = null;
|
||||
|
||||
if (Screen.Selected == GameMain.EditMapScreen)
|
||||
{
|
||||
//string filePath = ToolBox.GetAttributeString(element, "filepath", "");
|
||||
|
||||
linkedSub = CreateDummy(submarine, element, pos);
|
||||
linkedSub.saveElement = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
linkedSub = new LinkedSubmarine(submarine);
|
||||
linkedSub.saveElement = element;
|
||||
|
||||
string levelSeed = ToolBox.GetAttributeString(element, "location", "");
|
||||
if (!string.IsNullOrWhiteSpace(levelSeed) && GameMain.GameSession.Level != null && GameMain.GameSession.Level.Seed != levelSeed)
|
||||
{
|
||||
linkedSub.loadSub = false;
|
||||
return;
|
||||
}
|
||||
|
||||
linkedSub.loadSub = true;
|
||||
|
||||
linkedSub.rect.Location = MathUtils.ToPoint(pos);
|
||||
}
|
||||
|
||||
linkedSub.filePath = ToolBox.GetAttributeString(element, "filepath", "");
|
||||
|
||||
string linkedToString = ToolBox.GetAttributeString(element, "linkedto", "");
|
||||
if (linkedToString != "")
|
||||
{
|
||||
string[] linkedToIds = linkedToString.Split(',');
|
||||
for (int i = 0; i < linkedToIds.Length; i++)
|
||||
{
|
||||
linkedSub.linkedToID.Add((ushort)int.Parse(linkedToIds[i]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (!loadSub) return;
|
||||
|
||||
sub = Submarine.Load(saveElement, false);
|
||||
|
||||
Vector2 worldPos = ToolBox.GetAttributeVector2(saveElement, "worldpos", Vector2.Zero);
|
||||
if (worldPos != Vector2.Zero)
|
||||
{
|
||||
sub.SetPosition(worldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
sub.SetPosition(WorldPosition);
|
||||
}
|
||||
|
||||
|
||||
DockingPort linkedPort = null;
|
||||
DockingPort myPort = null;
|
||||
|
||||
MapEntity linkedItem = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent<DockingPort>() != null);
|
||||
if (linkedItem == null)
|
||||
{
|
||||
linkedPort = DockingPort.list.Find(dp => dp.DockingTarget != null && dp.DockingTarget.Item.Submarine == sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
linkedPort = ((Item)linkedItem).GetComponent<DockingPort>();
|
||||
}
|
||||
|
||||
if (linkedPort == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float closestDistance = 0.0f;
|
||||
foreach (DockingPort port in DockingPort.list)
|
||||
{
|
||||
if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) continue;
|
||||
|
||||
float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition);
|
||||
if (myPort == null || dist < closestDistance)
|
||||
{
|
||||
myPort = port;
|
||||
closestDistance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (myPort != null)
|
||||
{
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - sub.WorldPosition;
|
||||
Vector2 offset = (myPort.IsHorizontal ?
|
||||
Vector2.UnitX * Math.Sign(linkedPort.Item.WorldPosition.X - myPort.Item.WorldPosition.X) :
|
||||
Vector2.UnitY * Math.Sign(linkedPort.Item.WorldPosition.Y - myPort.Item.WorldPosition.Y));
|
||||
offset *= myPort.DockedDistance;
|
||||
|
||||
sub.SetPosition(
|
||||
(linkedPort.Item.WorldPosition - portDiff)
|
||||
- offset);
|
||||
|
||||
|
||||
myPort.Dock(linkedPort);
|
||||
myPort.Lock(true);
|
||||
}
|
||||
|
||||
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition);
|
||||
sub.Submarine = Submarine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Location
|
||||
{
|
||||
private string name;
|
||||
|
||||
private Vector2 mapPosition;
|
||||
|
||||
private LocationType type;
|
||||
|
||||
public List<LocationConnection> Connections;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public Vector2 MapPosition
|
||||
{
|
||||
get { return mapPosition; }
|
||||
}
|
||||
|
||||
public bool Discovered;
|
||||
|
||||
public LocationType Type
|
||||
{
|
||||
get { return type; }
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition)
|
||||
{
|
||||
this.type = LocationType.Random();
|
||||
|
||||
this.name = RandomName(type);
|
||||
|
||||
this.mapPosition = mapPosition;
|
||||
|
||||
#if CLIENT
|
||||
if (type.HasHireableCharacters)
|
||||
{
|
||||
hireManager = new HireManager();
|
||||
hireManager.GenerateCharacters(this, HireManager.MaxAvailableCharacters);
|
||||
}
|
||||
#endif
|
||||
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
public static Location CreateRandom(Vector2 position)
|
||||
{
|
||||
return new Location(position);
|
||||
}
|
||||
|
||||
private string RandomName(LocationType type)
|
||||
{
|
||||
string randomName = ToolBox.GetRandomLine("Content/Map/locationNames.txt");
|
||||
int nameFormatIndex = Rand.Int(type.NameFormats.Count, Rand.RandSync.Server);
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", randomName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LocationType
|
||||
{
|
||||
private static List<LocationType> list = new List<LocationType>();
|
||||
//sum of the commonness-values of each location type
|
||||
private static int totalWeight;
|
||||
|
||||
private string name;
|
||||
|
||||
private int commonness;
|
||||
|
||||
private List<string> nameFormats;
|
||||
|
||||
private Sprite symbolSprite;
|
||||
|
||||
private Sprite backGround;
|
||||
|
||||
//<name, commonness>
|
||||
private List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private float totalHireableWeight;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public List<string> NameFormats
|
||||
{
|
||||
get { return nameFormats; }
|
||||
}
|
||||
|
||||
public bool HasHireableCharacters
|
||||
{
|
||||
get { return hireableJobs.Any(); }
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return symbolSprite; }
|
||||
}
|
||||
|
||||
public Sprite Background
|
||||
{
|
||||
get { return backGround; }
|
||||
}
|
||||
|
||||
private LocationType(XElement element)
|
||||
{
|
||||
name = element.Name.ToString();
|
||||
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
totalWeight += commonness;
|
||||
|
||||
nameFormats = new List<string>();
|
||||
foreach (XAttribute nameFormat in element.Element("nameformats").Attributes())
|
||||
{
|
||||
nameFormats.Add(nameFormat.Value);
|
||||
}
|
||||
|
||||
hireableJobs = new List<Tuple<JobPrefab, float>>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "hireable") continue;
|
||||
|
||||
string jobName = ToolBox.GetAttributeString(subElement, "name", "");
|
||||
|
||||
JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName.ToLowerInvariant());
|
||||
if (jobPrefab==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid job name ("+jobName+") in location type "+name);
|
||||
}
|
||||
|
||||
float jobCommonness = ToolBox.GetAttributeFloat(subElement, "commonness", 1.0f);
|
||||
totalHireableWeight += jobCommonness;
|
||||
|
||||
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
|
||||
|
||||
hireableJobs.Add(hireableJob);
|
||||
}
|
||||
|
||||
string spritePath = ToolBox.GetAttributeString(element, "symbol", "Content/Map/beaconSymbol.png");
|
||||
symbolSprite = new Sprite(spritePath, new Vector2(0.5f, 0.5f));
|
||||
|
||||
string backgroundPath = ToolBox.GetAttributeString(element, "background", "");
|
||||
backGround = new Sprite(backgroundPath, Vector2.Zero);
|
||||
}
|
||||
|
||||
public JobPrefab GetRandomHireable()
|
||||
{
|
||||
float randFloat = Rand.Range(0.0f, totalHireableWeight);
|
||||
|
||||
foreach (Tuple<JobPrefab, float> hireable in hireableJobs)
|
||||
{
|
||||
if (randFloat < hireable.Item2) return hireable.Item1;
|
||||
randFloat -= hireable.Item2;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static LocationType Random(string seed = "")
|
||||
{
|
||||
Debug.Assert(list.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(seed))
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
}
|
||||
|
||||
int randInt = Rand.Int(totalWeight, Rand.RandSync.Server);
|
||||
|
||||
foreach (LocationType type in list)
|
||||
{
|
||||
if (randInt < type.commonness) return type;
|
||||
randInt -= type.commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
var locationTypeFiles = GameMain.SelectedPackage.GetFilesOfType(ContentType.LocationTypes);
|
||||
|
||||
foreach (string file in locationTypeFiles)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
|
||||
if (doc==null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
LocationType locationType = new LocationType(element);
|
||||
list.Add(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Map
|
||||
{
|
||||
Vector2 difficultyIncrease = new Vector2(5.0f, 10.0f);
|
||||
Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f);
|
||||
|
||||
private List<Level> levels;
|
||||
|
||||
private List<Location> locations;
|
||||
|
||||
private List<LocationConnection> connections;
|
||||
|
||||
private string seed;
|
||||
private int size;
|
||||
|
||||
private Location currentLocation;
|
||||
private Location selectedLocation;
|
||||
|
||||
private Location highlightedLocation;
|
||||
|
||||
private LocationConnection selectedConnection;
|
||||
|
||||
public Location CurrentLocation
|
||||
{
|
||||
get { return currentLocation; }
|
||||
}
|
||||
|
||||
public int CurrentLocationIndex
|
||||
{
|
||||
get { return locations.IndexOf(currentLocation); }
|
||||
}
|
||||
|
||||
public Location SelectedLocation
|
||||
{
|
||||
get { return selectedLocation; }
|
||||
}
|
||||
|
||||
public LocationConnection SelectedConnection
|
||||
{
|
||||
get { return selectedConnection; }
|
||||
}
|
||||
|
||||
public string Seed
|
||||
{
|
||||
get { return seed; }
|
||||
}
|
||||
|
||||
public static Map Load(XElement element)
|
||||
{
|
||||
string mapSeed = ToolBox.GetAttributeString(element, "seed", "a");
|
||||
|
||||
int size = ToolBox.GetAttributeInt(element, "size", 500);
|
||||
Map map = new Map(mapSeed, size);
|
||||
|
||||
map.SetLocation(ToolBox.GetAttributeInt(element, "currentlocation", 0));
|
||||
|
||||
string discoveredStr = ToolBox.GetAttributeString(element, "discovered", "");
|
||||
|
||||
string[] discoveredStrs = discoveredStr.Split(',');
|
||||
for (int i = 0; i < discoveredStrs.Length; i++)
|
||||
{
|
||||
int index = -1;
|
||||
if (int.TryParse(discoveredStrs[i], out index)) map.locations[index].Discovered = true;
|
||||
}
|
||||
|
||||
string passedStr = ToolBox.GetAttributeString(element, "passed", "");
|
||||
string[] passedStrs = passedStr.Split(',');
|
||||
for (int i = 0; i < passedStrs.Length; i++)
|
||||
{
|
||||
int index = -1;
|
||||
if (int.TryParse(passedStrs[i], out index)) map.connections[index].Passed = true;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public Map(string seed, int size)
|
||||
{
|
||||
this.seed = seed;
|
||||
|
||||
this.size = size;
|
||||
|
||||
levels = new List<Level>();
|
||||
|
||||
locations = new List<Location>();
|
||||
|
||||
connections = new List<LocationConnection>();
|
||||
|
||||
#if CLIENT
|
||||
if (iceTexture == null) iceTexture = new Sprite("Content/Map/iceSurface.png", Vector2.Zero);
|
||||
if (iceCraters == null) iceCraters = TextureLoader.FromFile("Content/Map/iceCraters.png");
|
||||
if (iceCrack == null) iceCrack = TextureLoader.FromFile("Content/Map/iceCrack.png");
|
||||
#endif
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(this.seed));
|
||||
|
||||
GenerateLocations();
|
||||
|
||||
currentLocation = locations[locations.Count / 2];
|
||||
currentLocation.Discovered = true;
|
||||
GenerateDifficulties(currentLocation, new List<LocationConnection>(connections), 10.0f);
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
connection.Level = Level.CreateRandom(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLocations()
|
||||
{
|
||||
Voronoi voronoi = new Voronoi(0.5f);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
sites.Add(new Vector2(Rand.Range(0.0f, size, Rand.RandSync.Server), Rand.Range(0.0f, size, Rand.RandSync.Server)));
|
||||
}
|
||||
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
|
||||
|
||||
sites.Clear();
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.point1 == edge.point2) continue;
|
||||
|
||||
//remove points from the edge of the map
|
||||
if (edge.point1.X == 0 || edge.point1.X == size) continue;
|
||||
if (edge.point1.Y == 0 || edge.point1.Y == size) continue;
|
||||
if (edge.point2.X == 0 || edge.point2.X == size) continue;
|
||||
if (edge.point2.Y == 0 || edge.point2.Y == size) continue;
|
||||
|
||||
Location[] newLocations = new Location[2];
|
||||
newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2);
|
||||
newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2));
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (newLocations[i] != null) continue;
|
||||
|
||||
Vector2[] points = new Vector2[] { edge.point1, edge.point2 };
|
||||
|
||||
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
|
||||
|
||||
newLocations[i] = Location.CreateRandom(position);
|
||||
locations.Add(newLocations[i]);
|
||||
}
|
||||
//int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode());
|
||||
connections.Add(new LocationConnection(newLocations[0], newLocations[1]));
|
||||
}
|
||||
|
||||
float minDistance = 50.0f;
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
LocationConnection connection = connections[i];
|
||||
|
||||
if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
locations.Remove(connection.Locations[0]);
|
||||
connections.Remove(connection);
|
||||
|
||||
foreach (LocationConnection connection2 in connections)
|
||||
{
|
||||
if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1];
|
||||
if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
connection.Locations[0].Connections.Add(connection);
|
||||
connection.Locations[1].Connections.Add(connection);
|
||||
}
|
||||
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
i = Math.Min(i, connections.Count - 1);
|
||||
|
||||
LocationConnection connection = connections[i];
|
||||
|
||||
for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--)
|
||||
{
|
||||
if (connection.Locations.Contains(connections[n].Locations[0])
|
||||
&& connection.Locations.Contains(connections[n].Locations[1]))
|
||||
{
|
||||
connections.RemoveAt(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
Vector2 start = connection.Locations[0].MapPosition;
|
||||
Vector2 end = connection.Locations[1].MapPosition;
|
||||
int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f));
|
||||
connection.CrackSegments = MathUtils.GenerateJaggedLine(start, end, generations, 5.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void GenerateDifficulties(Location start, List<LocationConnection> locations, float currDifficulty)
|
||||
{
|
||||
//start.Difficulty = currDifficulty;
|
||||
currDifficulty += Rand.Range(difficultyIncrease.X, difficultyIncrease.Y, Rand.RandSync.Server);
|
||||
if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, Rand.RandSync.Server)) currDifficulty = 10.0f;
|
||||
|
||||
foreach (LocationConnection connection in start.Connections)
|
||||
{
|
||||
if (!locations.Contains(connection)) continue;
|
||||
|
||||
Location nextLocation = connection.OtherLocation(start);
|
||||
locations.Remove(connection);
|
||||
|
||||
connection.Difficulty = currDifficulty;
|
||||
|
||||
GenerateDifficulties(nextLocation, locations, currDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToNextLocation()
|
||||
{
|
||||
selectedConnection.Passed = true;
|
||||
|
||||
currentLocation = selectedLocation;
|
||||
currentLocation.Discovered = true;
|
||||
selectedLocation = null;
|
||||
}
|
||||
|
||||
public void SetLocation(int index)
|
||||
{
|
||||
if (index < 0 || index >= locations.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Location index out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
currentLocation = locations[index];
|
||||
currentLocation.Discovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LocationConnection
|
||||
{
|
||||
private Location[] locations;
|
||||
private Level level;
|
||||
|
||||
public float Difficulty;
|
||||
|
||||
public List<Vector2[]> CrackSegments;
|
||||
|
||||
public bool Passed;
|
||||
|
||||
private int missionsCompleted;
|
||||
|
||||
private Mission mission;
|
||||
public Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mission == null || mission.Completed)
|
||||
{
|
||||
if (mission != null && mission.Completed) missionsCompleted++;
|
||||
|
||||
long seed = (long)locations[0].MapPosition.X + (long)locations[0].MapPosition.Y * 100;
|
||||
seed += (long)locations[1].MapPosition.X * 10000 + (long)locations[1].MapPosition.Y * 1000000;
|
||||
|
||||
MTRandom rand = new MTRandom((int)((seed + missionsCompleted) % int.MaxValue));
|
||||
|
||||
if (rand.NextDouble() < 0.3f) return null;
|
||||
|
||||
mission = Mission.LoadRandom(locations, rand, "", true);
|
||||
}
|
||||
|
||||
return mission;
|
||||
}
|
||||
}
|
||||
|
||||
public Location[] Locations
|
||||
{
|
||||
get { return locations; }
|
||||
}
|
||||
|
||||
public Level Level
|
||||
{
|
||||
get { return level; }
|
||||
set { level = value; }
|
||||
}
|
||||
|
||||
public LocationConnection(Location location1, Location location2)
|
||||
{
|
||||
locations = new Location[] { location1, location2 };
|
||||
|
||||
missionsCompleted = 0;
|
||||
}
|
||||
|
||||
public Location OtherLocation(Location location)
|
||||
{
|
||||
if (locations[0] == location)
|
||||
{
|
||||
return locations[1];
|
||||
}
|
||||
else if (locations[1] == location)
|
||||
{
|
||||
return locations[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
//using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.ObjectModel;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class MapEntity : Entity
|
||||
{
|
||||
public static List<MapEntity> mapEntityList = new List<MapEntity>();
|
||||
|
||||
private MapEntityPrefab prefab;
|
||||
|
||||
protected List<ushort> linkedToID;
|
||||
|
||||
//observable collection because some entities may need to be notified when the collection is modified
|
||||
public ObservableCollection<MapEntity> linkedTo;
|
||||
|
||||
//protected float soundRange;
|
||||
//protected float sightRange;
|
||||
|
||||
public bool MoveWithLevel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//the position and dimensions of the entity
|
||||
protected Rectangle rect;
|
||||
|
||||
//is the mouse inside the rect
|
||||
protected bool isHighlighted;
|
||||
|
||||
public bool IsHighlighted
|
||||
{
|
||||
get { return isHighlighted; }
|
||||
set { isHighlighted = value; }
|
||||
}
|
||||
|
||||
private static bool resizing;
|
||||
private int resizeDirX, resizeDirY;
|
||||
|
||||
public virtual Rectangle Rect {
|
||||
get { return rect; }
|
||||
set { rect = value; }
|
||||
}
|
||||
|
||||
public Rectangle WorldRect
|
||||
{
|
||||
get { return Submarine == null ? rect : new Rectangle((int)(Submarine.Position.X + rect.X), (int)(Submarine.Position.Y + rect.Y), rect.Width, rect.Height); }
|
||||
}
|
||||
|
||||
public virtual Sprite Sprite
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public virtual bool DrawBelowWater
|
||||
{
|
||||
get
|
||||
{
|
||||
return Sprite != null && Sprite.Depth > 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool DrawOverWater
|
||||
{
|
||||
get
|
||||
{
|
||||
return !DrawBelowWater;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool DrawDamageEffect
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsLinkable
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual bool SelectableInEditor
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override Vector2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 rectPos = new Vector2(
|
||||
rect.X + rect.Width / 2.0f,
|
||||
rect.Y - rect.Height / 2.0f);
|
||||
|
||||
//if (MoveWithLevel) rectPos += Level.Loaded.Position;
|
||||
return rectPos;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 SimPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return ConvertUnits.ToSimUnits(Position);
|
||||
}
|
||||
}
|
||||
|
||||
public float SoundRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (aiTarget == null) return 0.0f;
|
||||
return aiTarget.SoundRange;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (aiTarget == null) return;
|
||||
aiTarget.SoundRange = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float SightRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (aiTarget == null) return 0.0f;
|
||||
return aiTarget.SightRange;
|
||||
}
|
||||
set { aiTarget.SightRange = value; }
|
||||
}
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get { return ""; }
|
||||
}
|
||||
|
||||
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
}
|
||||
|
||||
public virtual bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
return (Submarine.RectContains(WorldRect, position));
|
||||
}
|
||||
|
||||
public virtual MapEntity Clone()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static List<MapEntity> Clone(List<MapEntity> entitiesToClone)
|
||||
{
|
||||
List<MapEntity> clones = new List<MapEntity>();
|
||||
foreach (MapEntity e in entitiesToClone)
|
||||
{
|
||||
Debug.Assert(e != null);
|
||||
clones.Add(e.Clone());
|
||||
Debug.Assert(clones.Last() != null);
|
||||
}
|
||||
|
||||
Debug.Assert(clones.Count == entitiesToClone.Count);
|
||||
|
||||
//clone links between the entities
|
||||
for (int i = 0; i < clones.Count; i++)
|
||||
{
|
||||
if (entitiesToClone[i].linkedTo == null) continue;
|
||||
foreach (MapEntity linked in entitiesToClone[i].linkedTo)
|
||||
{
|
||||
if (!entitiesToClone.Contains(linked)) continue;
|
||||
|
||||
clones[i].linkedTo.Add(clones[entitiesToClone.IndexOf(linked)]);
|
||||
}
|
||||
}
|
||||
|
||||
//connect clone wires to the clone items
|
||||
for (int i = 0; i < clones.Count; i++)
|
||||
{
|
||||
var cloneItem = clones[i] as Item;
|
||||
if (cloneItem == null) continue;
|
||||
|
||||
var cloneWire = cloneItem.GetComponent<Wire>();
|
||||
if (cloneWire == null) continue;
|
||||
|
||||
var originalWire = ((Item)entitiesToClone[i]).GetComponent<Wire>();
|
||||
|
||||
cloneWire.SetNodes(originalWire.GetNodes());
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (originalWire.Connections[n] == null) continue;
|
||||
|
||||
var connectedItem = originalWire.Connections[n].Item;
|
||||
if (connectedItem == null) continue;
|
||||
|
||||
//index of the item the wire is connected to
|
||||
int itemIndex = entitiesToClone.IndexOf(connectedItem);
|
||||
//index of the connection in the connectionpanel of the target item
|
||||
int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]);
|
||||
|
||||
(clones[itemIndex] as Item).GetComponent<ConnectionPanel>().Connections[connectionIndex].TryAddLink(cloneWire);
|
||||
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return clones;
|
||||
}
|
||||
|
||||
protected void InsertToList()
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (Sprite==null)
|
||||
{
|
||||
mapEntityList.Add(this);
|
||||
return;
|
||||
}
|
||||
|
||||
while (i<mapEntityList.Count)
|
||||
{
|
||||
i++;
|
||||
|
||||
Sprite existingSprite = mapEntityList[i-1].Sprite;
|
||||
if (existingSprite == null) continue;
|
||||
#if CLIENT
|
||||
if (existingSprite.Texture == this.Sprite.Texture) break;
|
||||
#endif
|
||||
}
|
||||
|
||||
mapEntityList.Insert(i, this);
|
||||
}
|
||||
|
||||
public virtual bool IsVisible(Rectangle worldView)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the entity from the entity list without removing links to other entities
|
||||
/// </summary>
|
||||
public virtual void ShallowRemove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
mapEntityList.Remove(this);
|
||||
|
||||
if (aiTarget != null) aiTarget.Remove();
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
mapEntityList.Remove(this);
|
||||
|
||||
#if CLIENT
|
||||
if (selectedList.Contains(this))
|
||||
{
|
||||
selectedList = selectedList.FindAll(e => e != this);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (aiTarget != null) aiTarget.Remove();
|
||||
|
||||
if (linkedTo != null)
|
||||
{
|
||||
for (int i = linkedTo.Count - 1; i >= 0; i-- )
|
||||
{
|
||||
linkedTo[i].RemoveLinked(this);
|
||||
}
|
||||
linkedTo.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call Update() on every object in Entity.list
|
||||
/// </summary>
|
||||
public static void UpdateAll(float deltaTime, Camera cam)
|
||||
{
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
gap.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
item.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
Spawner?.Update();
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime, Camera cam) { }
|
||||
|
||||
public virtual void FlipX()
|
||||
{
|
||||
if (Submarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't flip MapEntity \""+Name+"\", submarine==null");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 relative = WorldPosition - Submarine.WorldPosition;
|
||||
relative.Y = 0.0f;
|
||||
|
||||
Move(-relative * 2.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the linkedTo-lists of the entities based on the linkedToID-lists
|
||||
/// Has to be done after all the entities have been loaded (an entity can't
|
||||
/// be linked to some other entity that hasn't been loaded yet)
|
||||
/// </summary>
|
||||
public static void MapLoaded(Submarine sub)
|
||||
{
|
||||
foreach (MapEntity e in mapEntityList)
|
||||
{
|
||||
if (e.Submarine != sub) continue;
|
||||
if (e.linkedToID == null) continue;
|
||||
if (e.linkedToID.Count == 0) continue;
|
||||
|
||||
e.linkedTo.Clear();
|
||||
|
||||
foreach (ushort i in e.linkedToID)
|
||||
{
|
||||
MapEntity linked = FindEntityByID(i) as MapEntity;
|
||||
|
||||
if (linked != null) e.linkedTo.Add(linked);
|
||||
}
|
||||
}
|
||||
|
||||
List<LinkedSubmarine> linkedSubs = new List<LinkedSubmarine>();
|
||||
|
||||
for (int i = 0; i<mapEntityList.Count; i++)
|
||||
{
|
||||
if (mapEntityList[i].Submarine != sub) continue;
|
||||
|
||||
if (mapEntityList[i] is LinkedSubmarine)
|
||||
{
|
||||
linkedSubs.Add((LinkedSubmarine)mapEntityList[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
mapEntityList[i].OnMapLoaded();
|
||||
}
|
||||
|
||||
Item.UpdateHulls();
|
||||
Gap.UpdateHulls();
|
||||
|
||||
foreach (LinkedSubmarine linkedSub in linkedSubs)
|
||||
{
|
||||
linkedSub.OnMapLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnMapLoaded() { }
|
||||
|
||||
|
||||
public void RemoveLinked(MapEntity e)
|
||||
{
|
||||
if (linkedTo == null) return;
|
||||
if (linkedTo.Contains(e)) linkedTo.Remove(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
enum MapEntityCategory
|
||||
{
|
||||
Structure = 1, Machine = 2, Equipment = 4, Electrical = 8, Material = 16, Misc = 32, Alien = 64
|
||||
}
|
||||
|
||||
partial class MapEntityPrefab
|
||||
{
|
||||
public static List<MapEntityPrefab> list = new List<MapEntityPrefab>();
|
||||
|
||||
protected string name;
|
||||
|
||||
public List<string> tags;
|
||||
|
||||
protected bool isLinkable;
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
//the position where the structure is being placed (needed when stretching the structure)
|
||||
protected static Vector2 placePosition;
|
||||
|
||||
protected ConstructorInfo constructor;
|
||||
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
public bool resizeHorizontal { get; protected set; }
|
||||
public bool resizeVertical { get; protected set; }
|
||||
|
||||
//which prefab has been selected for placing
|
||||
protected static MapEntityPrefab selected;
|
||||
|
||||
protected int price;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public static MapEntityPrefab Selected
|
||||
{
|
||||
get { return selected; }
|
||||
set { selected = value; }
|
||||
}
|
||||
|
||||
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public virtual bool IsLinkable
|
||||
{
|
||||
get { return isLinkable; }
|
||||
}
|
||||
|
||||
public bool ResizeHorizontal
|
||||
{
|
||||
get { return resizeHorizontal; }
|
||||
}
|
||||
|
||||
public bool ResizeVertical
|
||||
{
|
||||
get { return resizeVertical; }
|
||||
}
|
||||
|
||||
public MapEntityCategory Category
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public int Price
|
||||
{
|
||||
get { return price; }
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
MapEntityPrefab ep = new MapEntityPrefab();
|
||||
ep.name = "Hull";
|
||||
ep.Description = "Hulls determine which parts are considered to be \"inside the sub\". Generally every room should be enclosed by a hull.";
|
||||
ep.constructor = typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
|
||||
ep.resizeHorizontal = true;
|
||||
ep.resizeVertical = true;
|
||||
list.Add(ep);
|
||||
|
||||
ep = new MapEntityPrefab();
|
||||
ep.name = "Gap";
|
||||
ep.Description = "Gaps allow water and air to flow between two hulls. ";
|
||||
ep.constructor = typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
|
||||
ep.resizeHorizontal = true;
|
||||
ep.resizeVertical = true;
|
||||
list.Add(ep);
|
||||
|
||||
ep = new MapEntityPrefab();
|
||||
ep.name = "Waypoint";
|
||||
ep.constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
|
||||
list.Add(ep);
|
||||
|
||||
ep = new MapEntityPrefab();
|
||||
ep.name = "Spawnpoint";
|
||||
ep.constructor = typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) });
|
||||
list.Add(ep);
|
||||
|
||||
//ep = new MapEntityPrefab();
|
||||
//ep.name = "Linked Submarine";
|
||||
//ep.Category = 0;
|
||||
//list.Add(ep);
|
||||
|
||||
}
|
||||
|
||||
public MapEntityPrefab()
|
||||
{
|
||||
Category = MapEntityCategory.Structure;
|
||||
}
|
||||
|
||||
public virtual void UpdatePlacing(Camera cam)
|
||||
{
|
||||
Vector2 placeSize = Submarine.GridSize;
|
||||
|
||||
if (placePosition == Vector2.Zero)
|
||||
{
|
||||
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||
|
||||
if (PlayerInput.LeftButtonHeld()) placePosition = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||
|
||||
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
|
||||
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
|
||||
|
||||
Rectangle newRect = Submarine.AbsRect(placePosition, placeSize);
|
||||
newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X);
|
||||
newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y);
|
||||
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
|
||||
}
|
||||
|
||||
if (PlayerInput.LeftButtonReleased())
|
||||
{
|
||||
CreateInstance(newRect);
|
||||
placePosition = Vector2.Zero;
|
||||
selected = null;
|
||||
}
|
||||
|
||||
newRect.Y = -newRect.Y;
|
||||
}
|
||||
|
||||
if (PlayerInput.RightButtonHeld())
|
||||
{
|
||||
placePosition = Vector2.Zero;
|
||||
selected = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CreateInstance(Rectangle rect)
|
||||
{
|
||||
object[] lobject = new object[] { this, rect };
|
||||
constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
public static bool SelectPrefab(object selection)
|
||||
{
|
||||
if ((selected = selection as MapEntityPrefab) != null)
|
||||
{
|
||||
placePosition = Vector2.Zero;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
|
||||
public static object GetSelected()
|
||||
{
|
||||
return (object)selected;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Md5Hash
|
||||
{
|
||||
private string hash;
|
||||
private string shortHash;
|
||||
|
||||
public string Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public string ShortHash
|
||||
{
|
||||
get
|
||||
{
|
||||
return shortHash;
|
||||
}
|
||||
}
|
||||
|
||||
public Md5Hash(string md5Hash)
|
||||
{
|
||||
this.hash = md5Hash;
|
||||
|
||||
shortHash = GetShortHash(md5Hash);
|
||||
}
|
||||
|
||||
public Md5Hash(byte[] bytes)
|
||||
{
|
||||
hash = CalculateHash(bytes);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
public Md5Hash(FileStream fileStream)
|
||||
{
|
||||
hash = CalculateHash(fileStream);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
public Md5Hash(XDocument doc)
|
||||
{
|
||||
if (doc == null) return;
|
||||
|
||||
string docString = Regex.Replace(doc.ToString(), @"\s+", "");
|
||||
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(docString);
|
||||
|
||||
hash = CalculateHash(inputBytes);
|
||||
|
||||
shortHash = GetShortHash(hash);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
private string CalculateHash(FileStream stream)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] byteHash = md5.ComputeHash(stream);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string CalculateHash(byte[] bytes)
|
||||
{
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] byteHash = md5.ComputeHash(bytes);
|
||||
// step 2, convert byte array to hex string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < byteHash.Length; i++)
|
||||
{
|
||||
sb.Append(byteHash[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetShortHash(string fullHash)
|
||||
{
|
||||
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,871 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WallSection
|
||||
{
|
||||
public Rectangle rect;
|
||||
public float damage;
|
||||
public Gap gap;
|
||||
|
||||
public int GapID;
|
||||
|
||||
//public float lastSentDamage;
|
||||
|
||||
public WallSection(Rectangle rect)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
|
||||
|
||||
this.rect = rect;
|
||||
damage = 0.0f;
|
||||
}
|
||||
|
||||
public WallSection(Rectangle rect, float damage)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0);
|
||||
|
||||
this.rect = rect;
|
||||
this.damage = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
partial class Structure : MapEntity, IDamageable, IServerSerializable
|
||||
{
|
||||
public static int wallSectionSize = 96;
|
||||
public static List<Structure> WallList = new List<Structure>();
|
||||
|
||||
StructurePrefab prefab;
|
||||
|
||||
//farseer physics bodies, separated by gaps
|
||||
List<Body> bodies;
|
||||
|
||||
//sections of the wall that are supposed to be rendered
|
||||
public WallSection[] sections {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
bool isHorizontal;
|
||||
|
||||
public SpriteEffects SpriteEffects = SpriteEffects.None;
|
||||
|
||||
public bool resizeHorizontal
|
||||
{
|
||||
get { return prefab.resizeHorizontal; }
|
||||
}
|
||||
|
||||
public bool resizeVertical
|
||||
{
|
||||
get { return prefab.resizeVertical; }
|
||||
}
|
||||
|
||||
private bool flippedX;
|
||||
|
||||
public override Sprite Sprite
|
||||
{
|
||||
get { return prefab.sprite; }
|
||||
}
|
||||
|
||||
public bool IsPlatform
|
||||
{
|
||||
get { return prefab.IsPlatform; }
|
||||
}
|
||||
|
||||
public Direction StairDirection
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return prefab.Name; }
|
||||
}
|
||||
|
||||
public bool HasBody
|
||||
{
|
||||
get { return prefab.HasBody; }
|
||||
}
|
||||
|
||||
public bool CastShadow
|
||||
{
|
||||
get { return prefab.CastShadow; }
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public int SectionCount
|
||||
{
|
||||
get { return sections.Length; }
|
||||
}
|
||||
|
||||
public float Health
|
||||
{
|
||||
get { return prefab.MaxHealth; }
|
||||
}
|
||||
|
||||
public override bool DrawBelowWater
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.DrawBelowWater || prefab.BackgroundSprite != null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool DrawOverWater
|
||||
{
|
||||
get
|
||||
{
|
||||
return !DrawDamageEffect;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool DrawDamageEffect
|
||||
{
|
||||
get
|
||||
{
|
||||
return prefab.HasBody;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> Tags
|
||||
{
|
||||
get { return prefab.tags; }
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
Rectangle oldRect = Rect;
|
||||
base.Rect = value;
|
||||
if (prefab.HasBody) CreateSections();
|
||||
else
|
||||
{
|
||||
foreach (WallSection sec in sections)
|
||||
{
|
||||
Rectangle secRect = sec.rect;
|
||||
secRect.X -= oldRect.X; secRect.Y -= oldRect.Y;
|
||||
secRect.X *= value.Width; secRect.X /= oldRect.Width;
|
||||
secRect.Y *= value.Height; secRect.Y /= oldRect.Height;
|
||||
secRect.Width *= value.Width; secRect.Width /= oldRect.Width;
|
||||
secRect.Height *= value.Height; secRect.Height /= oldRect.Height;
|
||||
secRect.X += value.X; secRect.Y += value.Y;
|
||||
sec.rect = secRect;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
{
|
||||
Rectangle r = sections[i].rect;
|
||||
r.X += (int)amount.X;
|
||||
r.Y += (int)amount.Y;
|
||||
sections[i].rect = r;
|
||||
}
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
|
||||
foreach (Body b in bodies)
|
||||
{
|
||||
b.SetTransform(b.Position + simAmount, b.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls!=null)
|
||||
{
|
||||
convexHulls.ForEach(x => x.Move(amount));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine)
|
||||
: base(sp, submarine)
|
||||
{
|
||||
if (rectangle.Width == 0 || rectangle.Height == 0) return;
|
||||
System.Diagnostics.Debug.Assert(rectangle.Width > 0 && rectangle.Height > 0);
|
||||
|
||||
rect = rectangle;
|
||||
prefab = sp;
|
||||
|
||||
isHorizontal = (rect.Width>rect.Height);
|
||||
|
||||
StairDirection = prefab.StairDirection;
|
||||
|
||||
if (prefab.HasBody)
|
||||
{
|
||||
bodies = new List<Body>();
|
||||
//gaps = new List<Gap>();
|
||||
|
||||
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
1.5f);
|
||||
newBody.BodyType = BodyType.Static;
|
||||
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
|
||||
newBody.Friction = 0.5f;
|
||||
|
||||
newBody.OnCollision += OnWallCollision;
|
||||
|
||||
newBody.UserData = this;
|
||||
|
||||
newBody.CollisionCategories = (prefab.IsPlatform) ? Physics.CollisionPlatform : Physics.CollisionWall;
|
||||
|
||||
bodies.Add(newBody);
|
||||
|
||||
WallList.Add(this);
|
||||
|
||||
CreateSections();
|
||||
}
|
||||
else
|
||||
{
|
||||
sections = new WallSection[1];
|
||||
sections[0] = new WallSection(rect);
|
||||
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
CreateStairBodies();
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (prefab.CastShadow)
|
||||
{
|
||||
GenerateConvexHull();
|
||||
}
|
||||
#endif
|
||||
|
||||
InsertToList();
|
||||
}
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
return new Structure(rect, prefab, Submarine);
|
||||
}
|
||||
|
||||
private void CreateStairBodies()
|
||||
{
|
||||
bodies = new List<Body>();
|
||||
|
||||
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width * Math.Sqrt(2.0) + Submarine.GridSize.X * 3.0f),
|
||||
ConvertUnits.ToSimUnits(10),
|
||||
1.5f);
|
||||
|
||||
newBody.BodyType = BodyType.Static;
|
||||
Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + rect.Width / 2.0f);
|
||||
stairPos += new Vector2(
|
||||
(StairDirection == Direction.Right) ? -Submarine.GridSize.X * 1.5f : Submarine.GridSize.X * 1.5f,
|
||||
-Submarine.GridSize.Y * 2.0f);
|
||||
|
||||
newBody.Position = ConvertUnits.ToSimUnits(stairPos);
|
||||
newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
|
||||
newBody.Friction = 0.8f;
|
||||
|
||||
newBody.CollisionCategories = Physics.CollisionStairs;
|
||||
|
||||
newBody.UserData = this;
|
||||
bodies.Add(newBody);
|
||||
}
|
||||
|
||||
private void CreateSections()
|
||||
{
|
||||
int xsections = 1, ysections = 1;
|
||||
int width = rect.Width, height = rect.Height;
|
||||
|
||||
if (!HasBody)
|
||||
{
|
||||
if (flippedX && isHorizontal)
|
||||
{
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width);
|
||||
width = prefab.sprite.SourceRect.Width;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
xsections = 1;
|
||||
ysections = 1;
|
||||
}
|
||||
sections = new WallSection[xsections];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
xsections = (int)Math.Ceiling((float)rect.Width / wallSectionSize);
|
||||
sections = new WallSection[xsections];
|
||||
width = (int)wallSectionSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
ysections = (int)Math.Ceiling((float)rect.Height / wallSectionSize);
|
||||
sections = new WallSection[ysections];
|
||||
height = (int)wallSectionSize;
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < xsections; x++)
|
||||
{
|
||||
for (int y = 0; y < ysections; y++)
|
||||
{
|
||||
if (flippedX)
|
||||
{
|
||||
Rectangle sectionRect = new Rectangle(rect.Right - (x + 1) * width, rect.Y - y * height, width, height);
|
||||
|
||||
int over = Math.Max(rect.X - sectionRect.X, 0);
|
||||
|
||||
sectionRect.X += over;
|
||||
sectionRect.Width -= over;
|
||||
|
||||
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
|
||||
|
||||
sections[xsections - 1 - x + y] = new WallSection(sectionRect);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Rectangle sectionRect = new Rectangle(rect.X + x * width, rect.Y - y * height, width, height);
|
||||
sectionRect.Width -= (int)Math.Max(sectionRect.Right - rect.Right, 0.0f);
|
||||
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height) - (sectionRect.Y - sectionRect.Height), 0.0f);
|
||||
|
||||
sections[x + y] = new WallSection(sectionRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
|
||||
{
|
||||
if (isHorizontal)
|
||||
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
|
||||
mergedSections.Sum(x => x.rect.Width), mergedSections.First().rect.Height);
|
||||
else
|
||||
{
|
||||
return new Rectangle(mergedSections.Min(x => x.rect.Left), mergedSections.Max(x => x.rect.Top),
|
||||
mergedSections.First().rect.Width, mergedSections.Sum(x => x.rect.Height));
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2[] CalculateExtremes(Rectangle sectionRect)
|
||||
{
|
||||
Vector2[] corners = new Vector2[4];
|
||||
corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height);
|
||||
corners[1] = new Vector2(sectionRect.X, sectionRect.Y);
|
||||
corners[2] = new Vector2(sectionRect.Right, sectionRect.Y);
|
||||
corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height);
|
||||
|
||||
return corners;
|
||||
}
|
||||
|
||||
public override bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
if (StairDirection == Direction.None)
|
||||
{
|
||||
return base.IsMouseOn(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!base.IsMouseOn(position)) return false;
|
||||
|
||||
if (StairDirection == Direction.Left)
|
||||
{
|
||||
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 40.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathUtils.LineToPointDistance(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 40.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.ShallowRemove();
|
||||
|
||||
if (WallList.Contains(this)) WallList.Remove(this);
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
foreach (Body b in bodies)
|
||||
GameMain.World.RemoveBody(b);
|
||||
}
|
||||
|
||||
if (sections != null)
|
||||
{
|
||||
foreach (WallSection s in sections)
|
||||
{
|
||||
if (s.gap != null)
|
||||
{
|
||||
s.gap.Remove();
|
||||
s.gap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
if (WallList.Contains(this)) WallList.Remove(this);
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
foreach (Body b in bodies)
|
||||
GameMain.World.RemoveBody(b);
|
||||
}
|
||||
|
||||
if (sections != null)
|
||||
{
|
||||
foreach (WallSection s in sections)
|
||||
{
|
||||
if (s.gap != null)
|
||||
{
|
||||
s.gap.Remove();
|
||||
s.gap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (convexHulls != null) convexHulls.ForEach(x => x.Remove());
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool IsVisible(Rectangle WorldView)
|
||||
{
|
||||
Rectangle worldRect = WorldRect;
|
||||
|
||||
if (worldRect.X > WorldView.Right || worldRect.Right < WorldView.X) return false;
|
||||
if (worldRect.Y < WorldView.Y - WorldView.Height || worldRect.Y - worldRect.Height > WorldView.Y) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
if (prefab.IsPlatform)
|
||||
{
|
||||
Limb limb;
|
||||
if ((limb = f2.Body.UserData as Limb) != null)
|
||||
{
|
||||
if (limb.character.AnimController.IgnorePlatforms) return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (f2.Body.UserData is Limb)
|
||||
{
|
||||
var character = ((Limb)f2.Body.UserData).character;
|
||||
if (character.DisableImpactDamageTimer > 0.0f || ((Limb)f2.Body.UserData).Mass < 100.0f) return true;
|
||||
}
|
||||
|
||||
if (!prefab.IsPlatform && prefab.StairDirection == Direction.None)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
|
||||
|
||||
int section = FindSectionIndex(pos);
|
||||
if (section > 0)
|
||||
{
|
||||
Vector2 normal = contact.Manifold.LocalNormal;
|
||||
|
||||
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal)*f2.Body.Mass*0.1f;
|
||||
|
||||
if (impact < 10.0f) return true;
|
||||
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound(DamageSoundType.StructureBlunt, impact,
|
||||
new Vector2(
|
||||
sections[section].rect.X + sections[section].rect.Width / 2,
|
||||
sections[section].rect.Y - sections[section].rect.Height / 2));
|
||||
#endif
|
||||
|
||||
AddDamage(section, impact);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public WallSection GetSection(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return null;
|
||||
|
||||
return sections[sectionIndex];
|
||||
|
||||
}
|
||||
|
||||
public bool SectionBodyDisabled(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
|
||||
|
||||
return (sections[sectionIndex].damage>=prefab.MaxHealth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sections that are leaking have a gap placed on them
|
||||
/// </summary>
|
||||
public bool SectionIsLeaking(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
|
||||
|
||||
return (sections[sectionIndex].damage >= prefab.MaxHealth*0.5f);
|
||||
}
|
||||
|
||||
public int SectionLength(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return 0;
|
||||
|
||||
return (isHorizontal ? sections[sectionIndex].rect.Width : sections[sectionIndex].rect.Height);
|
||||
}
|
||||
|
||||
public void AddDamage(int sectionIndex, float damage)
|
||||
{
|
||||
if (!prefab.HasBody || prefab.IsPlatform) return;
|
||||
|
||||
if (sectionIndex < 0 || sectionIndex > sections.Length - 1) return;
|
||||
|
||||
var section = sections[sectionIndex];
|
||||
|
||||
#if CLIENT
|
||||
float particleAmount = Math.Min(Health - section.damage, damage) * Rand.Range(0.01f, 1.0f);
|
||||
|
||||
particleAmount = Math.Min(particleAmount + Rand.Range(-5,1), 100);
|
||||
for (int i = 0; i < particleAmount; i++)
|
||||
{
|
||||
Vector2 particlePos = new Vector2(
|
||||
Rand.Range(section.rect.X, section.rect.Right),
|
||||
Rand.Range(section.rect.Y - section.rect.Height, section.rect.Y));
|
||||
|
||||
if (Submarine != null) particlePos += Submarine.DrawPosition;
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)));
|
||||
if (particle == null) break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.Client == null) SetDamage(sectionIndex, section.damage + damage);
|
||||
}
|
||||
|
||||
public int FindSectionIndex(Vector2 displayPos)
|
||||
{
|
||||
if (!sections.Any()) return -1;
|
||||
|
||||
//if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize
|
||||
//and we need to adjust the position accordingly
|
||||
if (sections[0].rect.Width < wallSectionSize)
|
||||
{
|
||||
displayPos.X += wallSectionSize - sections[0].rect.Width;
|
||||
}
|
||||
|
||||
int index = (isHorizontal) ?
|
||||
(int)Math.Floor((displayPos.X - rect.X) / wallSectionSize) :
|
||||
(int)Math.Floor((rect.Y - displayPos.Y) / wallSectionSize);
|
||||
|
||||
if (index < 0 || index > sections.Length - 1) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
public float SectionDamage(int sectionIndex)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return 0.0f;
|
||||
|
||||
return sections[sectionIndex].damage;
|
||||
}
|
||||
|
||||
public Vector2 SectionPosition(int sectionIndex, bool world = false)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= sections.Length) return Vector2.Zero;
|
||||
|
||||
Vector2 sectionPos = new Vector2(
|
||||
sections[sectionIndex].rect.X + sections[sectionIndex].rect.Width / 2.0f,
|
||||
sections[sectionIndex].rect.Y - sections[sectionIndex].rect.Height / 2.0f);
|
||||
|
||||
if (world && Submarine != null) sectionPos += Submarine.Position;
|
||||
|
||||
return sectionPos;
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode) return new AttackResult(0.0f, 0.0f);
|
||||
if (!prefab.HasBody || prefab.IsPlatform) return new AttackResult(0.0f, 0.0f);
|
||||
|
||||
Vector2 transformedPos = worldPosition;
|
||||
if (Submarine != null) transformedPos -= Submarine.Position;
|
||||
|
||||
int i = FindSectionIndex(transformedPos);
|
||||
if (i == -1) return new AttackResult(0.0f, 0.0f);
|
||||
|
||||
float damageAmount = attack.GetStructureDamage(deltaTime);
|
||||
|
||||
AddDamage(i, damageAmount);
|
||||
|
||||
#if CLIENT
|
||||
GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f);
|
||||
|
||||
if (playSound && !SectionBodyDisabled(i))
|
||||
{
|
||||
DamageSoundType damageSoundType = (attack.DamageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash;
|
||||
SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition);
|
||||
}
|
||||
#endif
|
||||
|
||||
return new AttackResult(damageAmount, 0.0f);
|
||||
}
|
||||
|
||||
private void SetDamage(int sectionIndex, float damage)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode) return;
|
||||
if (!prefab.HasBody) return;
|
||||
|
||||
if (!MathUtils.IsValid(damage)) return;
|
||||
|
||||
if (GameMain.Server != null && damage != sections[sectionIndex].damage)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
|
||||
if (damage < prefab.MaxHealth*0.5f)
|
||||
{
|
||||
if (sections[sectionIndex].gap != null)
|
||||
{
|
||||
//remove existing gap if damage is below 50%
|
||||
sections[sectionIndex].gap.Remove();
|
||||
sections[sectionIndex].gap = null;
|
||||
#if CLIENT
|
||||
if(CastShadow) GenerateConvexHull();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sections[sectionIndex].gap == null)
|
||||
{
|
||||
Rectangle gapRect = sections[sectionIndex].rect;
|
||||
gapRect.X -= 10;
|
||||
gapRect.Y += 10;
|
||||
gapRect.Width += 20;
|
||||
gapRect.Height += 20;
|
||||
sections[sectionIndex].gap = new Gap(gapRect, !isHorizontal, Submarine);
|
||||
sections[sectionIndex].gap.ConnectedWall = this;
|
||||
#if CLIENT
|
||||
if(CastShadow) GenerateConvexHull();
|
||||
#endif
|
||||
}
|
||||
|
||||
sections[sectionIndex].gap.Open = (damage / prefab.MaxHealth - 0.5f) * 2.0f;
|
||||
}
|
||||
|
||||
bool hadHole = SectionBodyDisabled(sectionIndex);
|
||||
sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.MaxHealth);
|
||||
|
||||
bool hasHole = SectionBodyDisabled(sectionIndex);
|
||||
|
||||
if (hadHole == hasHole) return;
|
||||
//if (hasHole) Explosion.ApplyExplosionForces(sections[sectionIndex].gap.WorldPosition, 500.0f, 5.0f, 0.0f, 0.0f);
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
public void SetCollisionCategory(Category collisionCategory)
|
||||
{
|
||||
if (bodies == null) return;
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
body.CollisionCategories = collisionCategory;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSections()
|
||||
{
|
||||
foreach (Body b in bodies)
|
||||
{
|
||||
GameMain.World.RemoveBody(b);
|
||||
}
|
||||
bodies.Clear();
|
||||
|
||||
bool hasHoles = false;
|
||||
var mergedSections = new List<WallSection>();
|
||||
for (int i = 0; i < sections.Length; i++ )
|
||||
{
|
||||
// if there is a gap and we have sections to merge, do it.
|
||||
if (SectionBodyDisabled(i))
|
||||
{
|
||||
hasHoles = true;
|
||||
|
||||
if (!mergedSections.Any()) continue;
|
||||
var mergedRect = GenerateMergedRect(mergedSections);
|
||||
mergedSections.Clear();
|
||||
CreateRectBody(mergedRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
mergedSections.Add(sections[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// take care of any leftover pieces
|
||||
if (mergedSections.Count > 0)
|
||||
{
|
||||
var mergedRect = GenerateMergedRect(mergedSections);
|
||||
CreateRectBody(mergedRect);
|
||||
}
|
||||
|
||||
//if the section has holes (or is just one big hole with no bodies),
|
||||
//we need a sensor for repairtools to be able to target the structure
|
||||
if (hasHoles || !bodies.Any()) CreateRectBody(rect).IsSensor = true;
|
||||
}
|
||||
|
||||
private Body CreateRectBody(Rectangle rect)
|
||||
{
|
||||
Body newBody = BodyFactory.CreateRectangle(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
1.5f);
|
||||
newBody.BodyType = BodyType.Static;
|
||||
newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f));
|
||||
newBody.Friction = 0.5f;
|
||||
|
||||
newBody.OnCollision += OnWallCollision;
|
||||
|
||||
newBody.CollisionCategories = Physics.CollisionWall;
|
||||
|
||||
newBody.UserData = this;
|
||||
|
||||
bodies.Add(newBody);
|
||||
|
||||
return newBody;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
{
|
||||
msg.WriteRangedSingle(sections[i].damage / Health, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
{
|
||||
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health;
|
||||
|
||||
SetDamage(i, damage);
|
||||
}
|
||||
}
|
||||
public override void FlipX()
|
||||
{
|
||||
base.FlipX();
|
||||
|
||||
flippedX = !flippedX;
|
||||
|
||||
if (prefab.CanSpriteFlipX)
|
||||
{
|
||||
SpriteEffects ^= SpriteEffects.FlipHorizontally;
|
||||
}
|
||||
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
StairDirection = StairDirection == Direction.Left ? Direction.Right : Direction.Left;
|
||||
bodies.ForEach(b => GameMain.World.RemoveBody(b));
|
||||
|
||||
CreateStairBodies();
|
||||
}
|
||||
|
||||
CreateSections();
|
||||
}
|
||||
|
||||
public static void Load(XElement element, Submarine submarine)
|
||||
{
|
||||
string rectString = ToolBox.GetAttributeString(element, "rect", "0,0,0,0");
|
||||
string[] rectValues = rectString.Split(',');
|
||||
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(rectValues[0]),
|
||||
int.Parse(rectValues[1]),
|
||||
int.Parse(rectValues[2]),
|
||||
int.Parse(rectValues[3]));
|
||||
|
||||
string name = element.Attribute("name").Value;
|
||||
|
||||
Structure s = null;
|
||||
|
||||
foreach (MapEntityPrefab ep in MapEntityPrefab.list)
|
||||
{
|
||||
if (ep.Name == name)
|
||||
{
|
||||
s = new Structure(rect, (StructurePrefab)ep, submarine);
|
||||
s.Submarine = submarine;
|
||||
s.ID = (ushort)int.Parse(element.Attribute("ID").Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Structure prefab " + name + " not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "section":
|
||||
int index = ToolBox.GetAttributeInt(subElement, "i", -1);
|
||||
if (index == -1) continue;
|
||||
|
||||
s.sections[index].damage =
|
||||
ToolBox.GetAttributeFloat(subElement, "damage", 0.0f);
|
||||
|
||||
s.sections[index].GapID = ToolBox.GetAttributeInt(subElement, "gap", -1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
foreach (WallSection s in sections)
|
||||
{
|
||||
if (s.GapID == -1) continue;
|
||||
|
||||
s.gap = FindEntityByID((ushort)s.GapID) as Gap;
|
||||
if (s.gap != null) s.gap.ConnectedWall = this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class StructurePrefab : MapEntityPrefab
|
||||
{
|
||||
//public static List<StructurePrefab> list = new List<StructurePrefab>();
|
||||
|
||||
//does the structure have a physics body
|
||||
private bool hasBody;
|
||||
|
||||
private bool castShadow;
|
||||
|
||||
private bool isPlatform;
|
||||
private Direction stairDirection;
|
||||
private bool canSpriteFlipX;
|
||||
|
||||
private float maxHealth;
|
||||
|
||||
//default size
|
||||
private Vector2 size;
|
||||
|
||||
public bool HasBody
|
||||
{
|
||||
get { return hasBody; }
|
||||
}
|
||||
|
||||
public bool IsPlatform
|
||||
{
|
||||
get { return isPlatform; }
|
||||
}
|
||||
|
||||
public float MaxHealth
|
||||
{
|
||||
get { return maxHealth; }
|
||||
}
|
||||
|
||||
public bool CastShadow
|
||||
{
|
||||
get { return castShadow; }
|
||||
}
|
||||
|
||||
public Direction StairDirection
|
||||
{
|
||||
get { return stairDirection; }
|
||||
}
|
||||
|
||||
public bool CanSpriteFlipX
|
||||
{
|
||||
get { return canSpriteFlipX; }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
}
|
||||
|
||||
public Sprite BackgroundSprite
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static void LoadAll(List<string> filePaths)
|
||||
{
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement el in doc.Root.Elements())
|
||||
{
|
||||
StructurePrefab sp = Load(el);
|
||||
|
||||
list.Add(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static StructurePrefab Load(XElement element)
|
||||
{
|
||||
StructurePrefab sp = new StructurePrefab();
|
||||
sp.name = element.Name.ToString();
|
||||
|
||||
sp.tags = new List<string>();
|
||||
sp.tags.AddRange(ToolBox.GetAttributeString(element, "tags", "").Split(','));
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "sprite":
|
||||
sp.sprite = new Sprite(subElement);
|
||||
|
||||
if (ToolBox.GetAttributeBool(subElement, "fliphorizontal", false))
|
||||
sp.sprite.effects = SpriteEffects.FlipHorizontally;
|
||||
if (ToolBox.GetAttributeBool(subElement, "flipvertical", false))
|
||||
sp.sprite.effects = SpriteEffects.FlipVertically;
|
||||
|
||||
sp.canSpriteFlipX = ToolBox.GetAttributeBool(subElement, "canflipx", true);
|
||||
|
||||
break;
|
||||
case "backgroundsprite":
|
||||
sp.BackgroundSprite = new Sprite(subElement);
|
||||
|
||||
if (ToolBox.GetAttributeBool(subElement, "fliphorizontal", false))
|
||||
sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally;
|
||||
if (ToolBox.GetAttributeBool(subElement, "flipvertical", false))
|
||||
sp.BackgroundSprite.effects = SpriteEffects.FlipVertically;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MapEntityCategory category;
|
||||
|
||||
if (!Enum.TryParse(ToolBox.GetAttributeString(element, "category", "Structure"), true, out category))
|
||||
{
|
||||
category = MapEntityCategory.Structure;
|
||||
}
|
||||
|
||||
sp.Category = category;
|
||||
|
||||
sp.Description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
sp.size = Vector2.Zero;
|
||||
sp.size.X = ToolBox.GetAttributeFloat(element, "width", 0.0f);
|
||||
sp.size.Y = ToolBox.GetAttributeFloat(element, "height", 0.0f);
|
||||
|
||||
string spriteColorStr = ToolBox.GetAttributeString(element, "spritecolor", "1.0,1.0,1.0,1.0");
|
||||
sp.SpriteColor = new Color(ToolBox.ParseToVector4(spriteColorStr));
|
||||
|
||||
sp.maxHealth = ToolBox.GetAttributeFloat(element, "health", 100.0f);
|
||||
|
||||
sp.resizeHorizontal = ToolBox.GetAttributeBool(element, "resizehorizontal", false);
|
||||
sp.resizeVertical = ToolBox.GetAttributeBool(element, "resizevertical", false);
|
||||
|
||||
sp.isPlatform = ToolBox.GetAttributeBool(element, "platform", false);
|
||||
sp.stairDirection = (Direction)Enum.Parse(typeof(Direction), ToolBox.GetAttributeString(element, "stairdirection", "None"), true);
|
||||
|
||||
sp.castShadow = ToolBox.GetAttributeBool(element, "castshadow", false);
|
||||
|
||||
sp.hasBody = ToolBox.GetAttributeBool(element, "body", false);
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
public override void UpdatePlacing(Camera cam)
|
||||
{
|
||||
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
|
||||
|
||||
if (placePosition == Vector2.Zero)
|
||||
{
|
||||
if (PlayerInput.LeftButtonHeld())
|
||||
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||
|
||||
newRect.X = (int)position.X;
|
||||
newRect.Y = (int)position.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 placeSize = size;
|
||||
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
|
||||
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
|
||||
|
||||
newRect = Submarine.AbsRect(placePosition, placeSize);
|
||||
|
||||
if (PlayerInput.LeftButtonReleased())
|
||||
{
|
||||
//don't allow resizing width/height to zero
|
||||
if ((!resizeHorizontal || placeSize.X != 0.0f) && (!resizeVertical || placeSize.Y != 0.0f))
|
||||
{
|
||||
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
|
||||
|
||||
var structure = new Structure(newRect, this, Submarine.MainSub);
|
||||
structure.Submarine = Submarine.MainSub;
|
||||
}
|
||||
|
||||
selected = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerInput.RightButtonHeld()) selected = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,548 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Common.Decomposition;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Factories;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SubmarineBody
|
||||
{
|
||||
public const float DamageDepth = -30000.0f;
|
||||
//private const float PressureDamageMultiplier = 0.001f;
|
||||
|
||||
private const float DamageMultiplier = 50.0f;
|
||||
|
||||
private const float Friction = 0.2f, Restitution = 0.0f;
|
||||
|
||||
public List<Vector2> HullVertices
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float depthDamageTimer;
|
||||
|
||||
private readonly Submarine submarine;
|
||||
|
||||
public readonly PhysicsBody Body;
|
||||
|
||||
private List<PosInfo> memPos = new List<PosInfo>();
|
||||
|
||||
public Rectangle Borders
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 Velocity
|
||||
{
|
||||
get { return Body.LinearVelocity; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
Body.LinearVelocity = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(Body.SimPosition); }
|
||||
}
|
||||
|
||||
public List<PosInfo> MemPos
|
||||
{
|
||||
get { return memPos; }
|
||||
}
|
||||
|
||||
public bool AtDamageDepth
|
||||
{
|
||||
get { return Position.Y < DamageDepth; }
|
||||
}
|
||||
|
||||
public SubmarineBody(Submarine sub)
|
||||
{
|
||||
this.submarine = sub;
|
||||
|
||||
Body farseerBody = null;
|
||||
|
||||
if (!Hull.hullList.Any())
|
||||
{
|
||||
Body = new PhysicsBody(1,1,1,1);
|
||||
farseerBody = Body.FarseerBody;
|
||||
DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Vector2> convexHull = GenerateConvexHull();
|
||||
HullVertices = convexHull;
|
||||
|
||||
for (int i = 0; i < convexHull.Count; i++)
|
||||
{
|
||||
convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
|
||||
}
|
||||
|
||||
convexHull.Reverse();
|
||||
|
||||
//get farseer 'vertices' from vectors
|
||||
Vertices shapevertices = new Vertices(convexHull);
|
||||
|
||||
AABB hullAABB = shapevertices.GetAABB();
|
||||
|
||||
Borders = new Rectangle(
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.LowerBound.X),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.UpperBound.Y),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.X * 2.0f),
|
||||
(int)ConvertUnits.ToDisplayUnits(hullAABB.Extents.Y * 2.0f));
|
||||
|
||||
farseerBody = BodyFactory.CreateBody(GameMain.World, this);
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine) continue;
|
||||
|
||||
Rectangle rect = wall.Rect;
|
||||
|
||||
FixtureFactory.AttachRectangle(
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
50.0f,
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
|
||||
farseerBody, this);
|
||||
}
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != submarine) continue;
|
||||
|
||||
Rectangle rect = hull.Rect;
|
||||
FixtureFactory.AttachRectangle(
|
||||
ConvertUnits.ToSimUnits(rect.Width),
|
||||
ConvertUnits.ToSimUnits(rect.Height),
|
||||
5.0f,
|
||||
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
|
||||
farseerBody, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
farseerBody.BodyType = BodyType.Dynamic;
|
||||
farseerBody.CollisionCategories = Physics.CollisionWall;
|
||||
farseerBody.CollidesWith =
|
||||
Physics.CollisionItem |
|
||||
Physics.CollisionLevel |
|
||||
Physics.CollisionCharacter |
|
||||
Physics.CollisionProjectile |
|
||||
Physics.CollisionWall;
|
||||
|
||||
farseerBody.Restitution = Restitution;
|
||||
farseerBody.Friction = Friction;
|
||||
farseerBody.FixedRotation = true;
|
||||
//mass = Body.Mass;
|
||||
farseerBody.Awake = true;
|
||||
farseerBody.SleepingAllowed = false;
|
||||
farseerBody.IgnoreGravity = true;
|
||||
farseerBody.OnCollision += OnCollision;
|
||||
farseerBody.UserData = submarine;
|
||||
|
||||
Body = new PhysicsBody(farseerBody);
|
||||
}
|
||||
|
||||
|
||||
private List<Vector2> GenerateConvexHull()
|
||||
{
|
||||
List<Structure> subWalls = Structure.WallList.FindAll(wall => wall.Submarine == submarine);
|
||||
|
||||
if (subWalls.Count == 0)
|
||||
{
|
||||
return new List<Vector2> { new Vector2(-1.0f, 1.0f), new Vector2(1.0f, 1.0f), new Vector2(0.0f, -1.0f) };
|
||||
}
|
||||
|
||||
List<Vector2> points = new List<Vector2>();
|
||||
|
||||
foreach (Structure wall in subWalls)
|
||||
{
|
||||
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y));
|
||||
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y));
|
||||
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y - wall.Rect.Height));
|
||||
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y - wall.Rect.Height));
|
||||
}
|
||||
|
||||
List<Vector2> hullPoints = MathUtils.GiftWrap(points);
|
||||
|
||||
return hullPoints;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
if (memPos.Count == 0) return;
|
||||
|
||||
Vector2 newVelocity = Body.LinearVelocity;
|
||||
Vector2 newPosition = Body.SimPosition;
|
||||
|
||||
Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition);
|
||||
Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition);
|
||||
|
||||
List<Submarine> subsToMove = new List<Submarine>() { this.submarine };
|
||||
subsToMove.AddRange(submarine.DockedTo);
|
||||
|
||||
foreach (Submarine dockedSub in submarine.DockedTo)
|
||||
{
|
||||
//clear the position buffer of the docked sub to prevent unnecessary position corrections
|
||||
dockedSub.SubBody.memPos.Clear();
|
||||
}
|
||||
|
||||
Submarine closestSub = null;
|
||||
if (Character.Controlled == null)
|
||||
{
|
||||
closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
closestSub = Character.Controlled.Submarine;
|
||||
}
|
||||
|
||||
bool displace = moveAmount.Length() > 100.0f;
|
||||
|
||||
foreach (Submarine sub in subsToMove)
|
||||
{
|
||||
sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
|
||||
sub.PhysicsBody.LinearVelocity = newVelocity;
|
||||
|
||||
if (displace) sub.SubBody.DisplaceCharacters(moveAmount);
|
||||
}
|
||||
|
||||
if (closestSub != null && subsToMove.Contains(closestSub))
|
||||
{
|
||||
GameMain.GameScreen.Cam.Position += moveAmount;
|
||||
if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount;
|
||||
|
||||
if (Character.Controlled!=null) Character.Controlled.CursorPosition += moveAmount;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if outside left or right edge of the level
|
||||
if (Position.X < 0 || Position.X > Level.Loaded.Size.X)
|
||||
{
|
||||
Rectangle worldBorders = Borders;
|
||||
worldBorders.Location += MathUtils.ToPoint(Position);
|
||||
|
||||
//push the sub back below the upper "barrier" of the level
|
||||
if (worldBorders.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
Body.LinearVelocity = new Vector2(
|
||||
Body.LinearVelocity.X,
|
||||
Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y)));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------
|
||||
|
||||
Vector2 totalForce = CalculateBuoyancy();
|
||||
|
||||
if (Body.LinearVelocity.LengthSquared() > 0.000001f)
|
||||
{
|
||||
float dragCoefficient = 0.01f;
|
||||
|
||||
float speedLength = (Body.LinearVelocity == Vector2.Zero) ? 0.0f : Body.LinearVelocity.Length();
|
||||
float drag = speedLength * speedLength * dragCoefficient * Body.Mass;
|
||||
|
||||
totalForce += -Vector2.Normalize(Body.LinearVelocity) * drag;
|
||||
}
|
||||
|
||||
ApplyForce(totalForce);
|
||||
|
||||
UpdateDepthDamage(deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves away any character that is inside the bounding box of the sub (but not inside the sub)
|
||||
/// </summary>
|
||||
/// <param name="subTranslation">The translation that was applied to the sub before doing the displacement
|
||||
/// (used for determining where to push the characters)</param>
|
||||
private void DisplaceCharacters(Vector2 subTranslation)
|
||||
{
|
||||
Rectangle worldBorders = Borders;
|
||||
worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(Body.SimPosition));
|
||||
|
||||
Vector2 translateDir = Vector2.Normalize(subTranslation);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) continue;
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
//if the character isn't inside the bounding box, continue
|
||||
if (!Submarine.RectContains(worldBorders, limb.WorldPosition)) continue;
|
||||
|
||||
//cast a line from the position of the character to the same direction as the translation of the sub
|
||||
//and see where it intersects with the bounding box
|
||||
Vector2? intersection = MathUtils.GetLineRectangleIntersection(limb.WorldPosition,
|
||||
limb.WorldPosition + translateDir*100000.0f, worldBorders);
|
||||
|
||||
//should never be null when casting a line out from inside the bounding box
|
||||
Debug.Assert(intersection != null);
|
||||
|
||||
//"+ translatedir" in order to move the character slightly away from the wall
|
||||
c.AnimController.SetPosition(ConvertUnits.ToSimUnits(c.WorldPosition + ((Vector2)intersection - limb.WorldPosition)) + translateDir);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 CalculateBuoyancy()
|
||||
{
|
||||
float waterVolume = 0.0f;
|
||||
float volume = 0.0f;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != submarine) continue;
|
||||
|
||||
waterVolume += hull.Volume;
|
||||
volume += hull.FullVolume;
|
||||
}
|
||||
|
||||
float waterPercentage = volume==0.0f ? 0.0f : waterVolume / volume;
|
||||
|
||||
float neutralPercentage = 0.07f;
|
||||
|
||||
float buoyancy = neutralPercentage - waterPercentage;
|
||||
|
||||
if (buoyancy > 0.0f) buoyancy *= 2.0f;
|
||||
|
||||
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
|
||||
}
|
||||
|
||||
public void ApplyForce(Vector2 force)
|
||||
{
|
||||
Body.ApplyForce(force);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
Body.SetTransform(ConvertUnits.ToSimUnits(position), 0.0f);
|
||||
}
|
||||
|
||||
private void UpdateDepthDamage(float deltaTime)
|
||||
{
|
||||
if (Position.Y > DamageDepth) return;
|
||||
|
||||
float depth = DamageDepth - Position.Y;
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
|
||||
if (depthDamageTimer > 0.0f) return;
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine) continue;
|
||||
|
||||
if (wall.Health < depth * 0.01f)
|
||||
{
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, depth * 0.01f);
|
||||
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(depth * 0.001f, 50.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depthDamageTimer = 10.0f;
|
||||
}
|
||||
|
||||
public bool OnCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
Limb limb = f2.Body.UserData as Limb;
|
||||
if (limb!= null)
|
||||
{
|
||||
|
||||
bool collision = HandleLimbCollision(contact, limb);
|
||||
|
||||
if (collision && limb.Mass > 100.0f)
|
||||
{
|
||||
Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition);
|
||||
|
||||
float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f;
|
||||
|
||||
ApplyImpact(impact * Math.Min(limb.Mass / 200.0f, 1), -normal, contact);
|
||||
}
|
||||
|
||||
return collision;
|
||||
}
|
||||
|
||||
VoronoiCell cell = f2.Body.UserData as VoronoiCell;
|
||||
if (cell != null)
|
||||
{
|
||||
var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center);
|
||||
|
||||
float wallImpact = Vector2.Dot(Velocity, -collisionNormal);
|
||||
|
||||
ApplyImpact(wallImpact, -collisionNormal, contact);
|
||||
|
||||
Vector2 n;
|
||||
FixedArray2<Vector2> particlePos;
|
||||
contact.GetWorldManifold(out n, out particlePos);
|
||||
|
||||
#if CLIENT
|
||||
int particleAmount = (int)(wallImpact*10.0f);
|
||||
for (int i = 0; i < particleAmount; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("iceshards",
|
||||
ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
|
||||
Rand.Vector(Rand.Range(50.0f,500.0f)) + Velocity);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Submarine sub = f2.Body.UserData as Submarine;
|
||||
if (sub != null)
|
||||
{
|
||||
Debug.Assert(sub != submarine);
|
||||
|
||||
Vector2 normal;
|
||||
FixedArray2<Vector2> points;
|
||||
contact.GetWorldManifold(out normal, out points);
|
||||
if (contact.FixtureA.Body == sub.SubBody.Body.FarseerBody)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
float massRatio = sub.SubBody.Body.Mass / (sub.SubBody.Body.Mass + Body.Mass);
|
||||
|
||||
ApplyImpact((Vector2.Dot(Velocity - sub.Velocity, normal) / 2.0f)*massRatio, normal, contact);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleLimbCollision(Contact contact, Limb limb)
|
||||
{
|
||||
if (limb.character.Submarine != null) return false;
|
||||
|
||||
Vector2 normal2;
|
||||
FixedArray2<Vector2> points;
|
||||
contact.GetWorldManifold(out normal2, out points);
|
||||
|
||||
Vector2 normalizedVel = limb.character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
|
||||
Vector2.Zero : Vector2.Normalize(limb.character.AnimController.Collider.LinearVelocity);
|
||||
|
||||
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - normal2);
|
||||
|
||||
Hull newHull = Hull.FindHull(targetPos, null);
|
||||
|
||||
if (newHull == null)
|
||||
{
|
||||
targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
|
||||
|
||||
newHull = Hull.FindHull(targetPos, null);
|
||||
|
||||
if (newHull == null) return true;
|
||||
}
|
||||
|
||||
var gaps = newHull.ConnectedGaps;
|
||||
|
||||
targetPos = limb.character.WorldPosition;
|
||||
|
||||
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f);
|
||||
|
||||
if (adjacentGap==null) return true;
|
||||
|
||||
var ragdoll = limb.character.AnimController;
|
||||
ragdoll.FindHull(newHull.WorldPosition, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyImpact(float impact, Vector2 direction, Contact contact)
|
||||
{
|
||||
if (impact < 3.0f) return;
|
||||
|
||||
Vector2 tempNormal;
|
||||
|
||||
FarseerPhysics.Common.FixedArray2<Vector2> worldPoints;
|
||||
contact.GetWorldManifold(out tempNormal, out worldPoints);
|
||||
|
||||
Vector2 lastContactPoint = worldPoints[0];
|
||||
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
|
||||
}
|
||||
|
||||
Vector2 impulse = direction * impact * 0.5f;
|
||||
|
||||
float length = impulse.Length();
|
||||
if (length > 5.0f) impulse = (impulse / length) * 5.0f;
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != submarine) continue;
|
||||
|
||||
if (impact > 2.0f) c.SetStun((impact - 2.0f) * 0.1f);
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * impulse);
|
||||
}
|
||||
|
||||
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != submarine || item.CurrentHull == null ||
|
||||
item.body == null || !item.body.Enabled) continue;
|
||||
|
||||
item.body.ApplyLinearImpulse(item.body.Mass * impulse);
|
||||
}
|
||||
|
||||
var damagedStructures = Explosion.RangedStructureDamage(ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, impact * DamageMultiplier);
|
||||
|
||||
//play a damage sound for the structure that took the most damage
|
||||
float maxDamage = 0.0f;
|
||||
Structure maxDamageStructure = null;
|
||||
foreach (KeyValuePair<Structure,float> structureDamage in damagedStructures)
|
||||
{
|
||||
if (maxDamageStructure == null || structureDamage.Value > maxDamage)
|
||||
{
|
||||
maxDamage = structureDamage.Value;
|
||||
maxDamageStructure = structureDamage.Key;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (maxDamageStructure != null)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound(
|
||||
DamageSoundType.StructureBlunt,
|
||||
impact * 10.0f,
|
||||
ConvertUnits.ToDisplayUnits(lastContactPoint),
|
||||
MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f),
|
||||
maxDamageStructure.Tags);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TransitionCinematic
|
||||
{
|
||||
public bool Running
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float duration;
|
||||
|
||||
public TransitionCinematic(Submarine submarine, Camera cam, float duration)
|
||||
: this(new List<Submarine>() { submarine }, cam, duration)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TransitionCinematic(List<Submarine> submarines, Camera cam, float duration)
|
||||
{
|
||||
if (!submarines.Any(s => s != null)) return;
|
||||
|
||||
Vector2 targetPos = new Vector2(
|
||||
submarines.Sum(s => s.Position.X),
|
||||
submarines.Sum(s => s.Position.Y)) / submarines.Count;
|
||||
|
||||
if (submarines.First().AtEndPosition)
|
||||
{
|
||||
targetPos = Level.Loaded.EndPosition + Vector2.UnitY * 500.0f;
|
||||
}
|
||||
else if (submarines.First().AtStartPosition)
|
||||
{
|
||||
targetPos = Level.Loaded.StartPosition + Vector2.UnitY * 500.0f;
|
||||
}
|
||||
|
||||
this.duration = duration;
|
||||
|
||||
Running = true;
|
||||
CoroutineManager.StartCoroutine(UpdateTransitionCinematic(submarines, cam, targetPos));
|
||||
}
|
||||
|
||||
private IEnumerable<object> UpdateTransitionCinematic(List<Submarine> subs, Camera cam, Vector2 targetPos)
|
||||
{
|
||||
if (!subs.Any()) yield return CoroutineStatus.Success;
|
||||
|
||||
Character.Controlled = null;
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
#endif
|
||||
|
||||
//Vector2 diff = targetPos - sub.Position;
|
||||
float targetSpeed = 10.0f;
|
||||
|
||||
Level.Loaded.ShaftBody.Enabled = false;
|
||||
|
||||
cam.TargetPos = Vector2.Zero;
|
||||
float timer = 0.0f;
|
||||
|
||||
while (timer < duration)
|
||||
{
|
||||
if (Screen.Selected != GameMain.GameScreen)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
#if CLIENT
|
||||
GUI.ScreenOverlayColor = Color.TransparentBlack;
|
||||
#endif
|
||||
|
||||
Running = false;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
cam.Zoom = Math.Max(0.2f, cam.Zoom - CoroutineManager.UnscaledDeltaTime * 0.1f);
|
||||
|
||||
Vector2 cameraPos = subs.First().Position + Submarine.MainSub.HiddenSubPosition;
|
||||
cameraPos.Y = Math.Min(cameraPos.Y, ConvertUnits.ToDisplayUnits(Level.Loaded.ShaftBody.Position.Y) - cam.WorldView.Height / 2.0f);
|
||||
cam.Translate((cameraPos - cam.Position) * CoroutineManager.UnscaledDeltaTime * 10.0f);
|
||||
#if CLIENT
|
||||
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, timer/duration);
|
||||
#endif
|
||||
|
||||
foreach (Submarine sub in subs)
|
||||
{
|
||||
if (sub.Position == targetPos) continue;
|
||||
sub.ApplyForce((Vector2.Normalize(targetPos - sub.Position) * targetSpeed - sub.Velocity) * 500.0f);
|
||||
}
|
||||
|
||||
timer += CoroutineManager.UnscaledDeltaTime;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Running = false;
|
||||
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
#if CLIENT
|
||||
GUI.ScreenOverlayColor = Color.TransparentBlack;
|
||||
#endif
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
//using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.ObjectModel;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics.Dynamics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum SpawnType { Path, Human, Enemy, Cargo };
|
||||
partial class WayPoint : MapEntity
|
||||
{
|
||||
public static List<WayPoint> WayPointList = new List<WayPoint>();
|
||||
|
||||
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
|
||||
|
||||
protected SpawnType spawnType;
|
||||
|
||||
//characters spawning at the waypoint will be given an ID card with these tags
|
||||
private string[] idCardTags;
|
||||
|
||||
//only characters with this job will be spawned at the waypoint
|
||||
private JobPrefab assignedJob;
|
||||
|
||||
private Hull currentHull;
|
||||
|
||||
private ushort ladderId;
|
||||
public Ladder Ladders;
|
||||
|
||||
private ushort gapId;
|
||||
public Gap ConnectedGap
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Hull CurrentHull
|
||||
{
|
||||
get { return currentHull; }
|
||||
}
|
||||
|
||||
public SpawnType SpawnType
|
||||
{
|
||||
get { return spawnType; }
|
||||
set { spawnType = value; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return spawnType == SpawnType.Path ? "WayPoint" : "SpawnPoint";
|
||||
}
|
||||
}
|
||||
|
||||
public string[] IdCardTags
|
||||
{
|
||||
get { return idCardTags; }
|
||||
private set
|
||||
{
|
||||
idCardTags = value;
|
||||
for (int i = 0; i<idCardTags.Length; i++)
|
||||
{
|
||||
idCardTags[i] = idCardTags[i].Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WayPoint(Vector2 position, SpawnType spawnType, Submarine submarine, Gap gap = null)
|
||||
: this(new Rectangle((int)position.X-3, (int)position.Y+3, 6, 6), submarine)
|
||||
{
|
||||
this.spawnType = spawnType;
|
||||
ConnectedGap = gap;
|
||||
}
|
||||
|
||||
public WayPoint(MapEntityPrefab prefab, Rectangle rectangle)
|
||||
: this (rectangle, Submarine.MainSub)
|
||||
{
|
||||
if (prefab.Name.Contains("Spawn"))
|
||||
{
|
||||
spawnType = SpawnType.Human;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnType = SpawnType.Path;
|
||||
}
|
||||
}
|
||||
|
||||
public WayPoint(Rectangle newRect, Submarine submarine)
|
||||
: base (null, submarine)
|
||||
{
|
||||
rect = newRect;
|
||||
linkedTo = new ObservableCollection<MapEntity>();
|
||||
idCardTags = new string[0];
|
||||
|
||||
#if CLIENT
|
||||
if (iconTexture==null)
|
||||
{
|
||||
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
|
||||
}
|
||||
#endif
|
||||
|
||||
InsertToList();
|
||||
WayPointList.Add(this);
|
||||
|
||||
currentHull = Hull.FindHull(WorldPosition);
|
||||
}
|
||||
|
||||
public override MapEntity Clone()
|
||||
{
|
||||
var clone = new WayPoint(rect, Submarine);
|
||||
clone.idCardTags = idCardTags;
|
||||
clone.spawnType = spawnType;
|
||||
clone.assignedJob = assignedJob;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public override bool IsMouseOn(Vector2 position)
|
||||
{
|
||||
#if CLIENT
|
||||
if (IsHidden()) return false;
|
||||
#endif
|
||||
|
||||
return base.IsMouseOn(position);
|
||||
}
|
||||
|
||||
public static void GenerateSubWaypoints(Submarine submarine)
|
||||
{
|
||||
if (!Hull.hullList.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path);
|
||||
foreach (WayPoint wayPoint in existingWaypoints)
|
||||
{
|
||||
wayPoint.Remove();
|
||||
}
|
||||
|
||||
float minDist = 150.0f;
|
||||
float heightFromFloor = 110.0f;
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Rect.Height < 150) continue;
|
||||
|
||||
WayPoint prevWaypoint = null;
|
||||
|
||||
if (hull.Rect.Width<minDist*3.0f)
|
||||
{
|
||||
new WayPoint(
|
||||
new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (float x = hull.Rect.X + minDist; x <= hull.Rect.Right - minDist; x += minDist)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
|
||||
|
||||
if (prevWaypoint != null) wayPoint.ConnectTo(prevWaypoint);
|
||||
|
||||
prevWaypoint = wayPoint;
|
||||
}
|
||||
}
|
||||
|
||||
float outSideWaypointInterval = 200.0f;
|
||||
int outsideWaypointDist = 100;
|
||||
|
||||
Rectangle borders = Hull.GetBorders();
|
||||
|
||||
borders.X -= outsideWaypointDist;
|
||||
borders.Y += outsideWaypointDist;
|
||||
|
||||
borders.Width += outsideWaypointDist * 2;
|
||||
borders.Height += outsideWaypointDist * 2;
|
||||
|
||||
borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition);
|
||||
|
||||
if (borders.Width <= outSideWaypointInterval*2)
|
||||
{
|
||||
borders.Inflate(outSideWaypointInterval*2 - borders.Width, 0);
|
||||
}
|
||||
|
||||
if (borders.Height <= outSideWaypointInterval * 2)
|
||||
{
|
||||
int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height;
|
||||
borders.Y += inflateAmount / 2;
|
||||
|
||||
borders.Height += inflateAmount;
|
||||
}
|
||||
|
||||
WayPoint[,] cornerWaypoint = new WayPoint[2,2];
|
||||
|
||||
for (int i = 0; i<2; i++)
|
||||
{
|
||||
for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval)
|
||||
{
|
||||
var wayPoint = new WayPoint(
|
||||
new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
if (x == borders.X + outSideWaypointInterval)
|
||||
{
|
||||
cornerWaypoint[i, 0] = wayPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count-2]);
|
||||
}
|
||||
}
|
||||
|
||||
cornerWaypoint[i, 1] = WayPoint.WayPointList[WayPointList.Count - 1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
WayPoint wayPoint = null;
|
||||
for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval)
|
||||
{
|
||||
wayPoint = new WayPoint(
|
||||
new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition,
|
||||
SpawnType.Path, submarine);
|
||||
|
||||
if (y == borders.Y - borders.Height)
|
||||
{
|
||||
wayPoint.ConnectTo(cornerWaypoint[1, i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]);
|
||||
}
|
||||
}
|
||||
|
||||
wayPoint.ConnectTo(cornerWaypoint[0, i]);
|
||||
}
|
||||
|
||||
List<Structure> stairList = new List<Structure>();
|
||||
foreach (MapEntity me in mapEntityList)
|
||||
{
|
||||
Structure stairs = me as Structure;
|
||||
if (stairs == null) continue;
|
||||
|
||||
if (stairs.StairDirection != Direction.None) stairList.Add(stairs);
|
||||
}
|
||||
|
||||
foreach (Structure stairs in stairList)
|
||||
{
|
||||
WayPoint[] stairPoints = new WayPoint[3];
|
||||
|
||||
stairPoints[0] = new WayPoint(
|
||||
new Vector2(stairs.Rect.X - 32.0f,
|
||||
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine);
|
||||
|
||||
stairPoints[1] = new WayPoint(
|
||||
new Vector2(stairs.Rect.Right + 32.0f,
|
||||
stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine);
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = stairPoints[i].FindClosest(dir, true, new Vector2(-30.0f,30f));
|
||||
if (closest == null) continue;
|
||||
stairPoints[i].ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
|
||||
stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position)/2, SpawnType.Path, submarine);
|
||||
stairPoints[0].ConnectTo(stairPoints[2]);
|
||||
stairPoints[2].ConnectTo(stairPoints[1]);
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var ladders = item.GetComponent<Items.Components.Ladder>();
|
||||
if (ladders == null) continue;
|
||||
|
||||
WayPoint[] ladderPoints = new WayPoint[2];
|
||||
|
||||
ladderPoints[0] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine);
|
||||
ladderPoints[1] = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y-1.0f), SpawnType.Path, submarine);
|
||||
|
||||
WayPoint prevPoint = ladderPoints[0];
|
||||
Vector2 prevPos = prevPoint.SimPosition;
|
||||
|
||||
List<Body> ignoredBodies = new List<Body>();
|
||||
|
||||
while (prevPoint != ladderPoints[1])
|
||||
{
|
||||
var pickedBody = Submarine.PickBody(prevPos, ladderPoints[1].SimPosition, ignoredBodies);
|
||||
|
||||
if (pickedBody == null) break;
|
||||
|
||||
ignoredBodies.Add(pickedBody);
|
||||
|
||||
if (pickedBody.UserData is Item)
|
||||
{
|
||||
var door = ((Item)pickedBody.UserData).GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
WayPoint newPoint = new WayPoint(door.Item.Position, SpawnType.Path, submarine);
|
||||
newPoint.Ladders = ladders;
|
||||
newPoint.ConnectedGap = door.LinkedGap;
|
||||
|
||||
newPoint.ConnectTo(prevPoint);
|
||||
|
||||
prevPoint = newPoint;
|
||||
|
||||
prevPos = ConvertUnits.ToSimUnits(door.Item.Position - Vector2.UnitY * door.Item.Rect.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
prevPos = Submarine.LastPickedPosition;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prevPos = Submarine.LastPickedPosition;
|
||||
}
|
||||
}
|
||||
|
||||
prevPoint.ConnectTo(ladderPoints[1]);
|
||||
|
||||
|
||||
|
||||
//for (float y = ladderPoints[0].Position.Y+100.0f; y < ladderPoints[1].Position.Y; y+=100.0f )
|
||||
//{
|
||||
// var midPoint = new WayPoint(new Vector2(item.Rect.Center.X, y), SpawnType.Path, Submarine.Loaded);
|
||||
// midPoint.Ladders = ladders;
|
||||
|
||||
// midPoint.ConnectTo(prevPoint);
|
||||
// prevPoint = midPoint;
|
||||
//}
|
||||
//ladderPoints[1].ConnectTo(prevPoint);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ladderPoints[i].Ladders = ladders;
|
||||
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = ladderPoints[i].FindClosest(dir, true, new Vector2(-150.0f, 10f));
|
||||
if (closest == null) continue;
|
||||
ladderPoints[i].ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
|
||||
//ladderPoints[0].ConnectTo(ladderPoints[1]);
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (!gap.isHorizontal) continue;
|
||||
|
||||
//too small to walk through
|
||||
if (gap.Rect.Height < 150.0f) continue;
|
||||
|
||||
var wayPoint = new WayPoint(
|
||||
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor), SpawnType.Path, submarine, gap);
|
||||
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
float tolerance = gap.IsRoomToRoom ? 50.0f : outSideWaypointInterval / 2.0f;
|
||||
|
||||
WayPoint closest = wayPoint.FindClosest(
|
||||
dir, true, new Vector2(-tolerance, tolerance),
|
||||
gap.ConnectedDoor == null ? null : gap.ConnectedDoor.Body.FarseerBody);
|
||||
|
||||
if (closest != null)
|
||||
{
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.isHorizontal || gap.IsRoomToRoom) continue;
|
||||
|
||||
//too small to walk through
|
||||
if (gap.Rect.Width < 100.0f) continue;
|
||||
|
||||
var wayPoint = new WayPoint(
|
||||
new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height/2), SpawnType.Path, submarine, gap);
|
||||
|
||||
for (int dir = -1; dir <= 1; dir += 2)
|
||||
{
|
||||
WayPoint closest = wayPoint.FindClosest(dir, false, new Vector2(-outSideWaypointInterval, outSideWaypointInterval) / 2.0f);
|
||||
if (closest == null) continue;
|
||||
wayPoint.ConnectTo(closest);
|
||||
}
|
||||
}
|
||||
|
||||
var orphans = WayPointList.FindAll(w => w.spawnType == SpawnType.Path && !w.linkedTo.Any());
|
||||
|
||||
foreach (WayPoint wp in orphans)
|
||||
{
|
||||
wp.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null)
|
||||
{
|
||||
if (dir != -1 && dir != 1) return null;
|
||||
|
||||
float closestDist = 0.0f;
|
||||
WayPoint closest = null;
|
||||
|
||||
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path || wp == this) continue;
|
||||
|
||||
float diff = 0.0f;
|
||||
if (horizontalSearch)
|
||||
{
|
||||
if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) continue;
|
||||
|
||||
diff = wp.Position.X - Position.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) continue;
|
||||
|
||||
diff = wp.Position.Y - Position.Y;
|
||||
}
|
||||
|
||||
if (Math.Sign(diff) != dir) continue;
|
||||
|
||||
float dist = Vector2.Distance(wp.Position, Position);
|
||||
if (closest == null || dist < closestDist)
|
||||
{
|
||||
var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true);
|
||||
if (body != null && body != ignoredBody && !(body.UserData is Submarine))
|
||||
{
|
||||
if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
|
||||
}
|
||||
|
||||
closestDist = dist;
|
||||
closest = wp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
private void ConnectTo(WayPoint wayPoint2)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(this != wayPoint2);
|
||||
|
||||
if (!linkedTo.Contains(wayPoint2)) linkedTo.Add(wayPoint2);
|
||||
if (!wayPoint2.linkedTo.Contains(this)) wayPoint2.linkedTo.Add(this);
|
||||
}
|
||||
|
||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, bool useSyncedRand = false)
|
||||
{
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
|
||||
foreach (WayPoint wp in WayPointList)
|
||||
{
|
||||
if (sub != null && wp.Submarine != sub) continue;
|
||||
if (wp.spawnType != spawnType) continue;
|
||||
if (assignedJob != null && wp.assignedJob != assignedJob.Prefab) continue;
|
||||
|
||||
wayPoints.Add(wp);
|
||||
}
|
||||
|
||||
if (!wayPoints.Any()) return null;
|
||||
|
||||
return wayPoints[Rand.Int(wayPoints.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))];
|
||||
}
|
||||
|
||||
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine, bool tryAssignWayPoint = true)
|
||||
{
|
||||
List<WayPoint> subWayPoints = WayPointList.FindAll(wp => wp.Submarine == submarine);
|
||||
|
||||
List<WayPoint> unassignedWayPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human);
|
||||
|
||||
WayPoint[] assignedWayPoints = new WayPoint[crew.Count];
|
||||
|
||||
for (int i = 0; i < crew.Count; i++ )
|
||||
{
|
||||
//try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job
|
||||
for (int n = 0; n < unassignedWayPoints.Count; n++)
|
||||
{
|
||||
if (crew[i].Job.Prefab != unassignedWayPoints[n].assignedJob) continue;
|
||||
assignedWayPoints[i] = unassignedWayPoints[n];
|
||||
unassignedWayPoints.RemoveAt(n);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//go through the crewmembers that don't have a spawnpoint yet (if any)
|
||||
for (int i = 0; i < crew.Count; i++)
|
||||
{
|
||||
if (assignedWayPoints[i] != null) continue;
|
||||
|
||||
//try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else
|
||||
foreach (WayPoint wp in subWayPoints)
|
||||
{
|
||||
if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) continue;
|
||||
|
||||
assignedWayPoints[i] = wp;
|
||||
break;
|
||||
}
|
||||
|
||||
if (assignedWayPoints[i] != null) continue;
|
||||
|
||||
if (tryAssignWayPoint)
|
||||
{
|
||||
//try to assign a spawnpoint that isn't meant for any specific job
|
||||
var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.assignedJob == null);
|
||||
|
||||
if (nonJobSpecificPoints.Any())
|
||||
{
|
||||
assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)];
|
||||
}
|
||||
}
|
||||
|
||||
if (assignedWayPoints[i] != null) continue;
|
||||
|
||||
//everything else failed -> just give a random spawnpoint
|
||||
assignedWayPoints[i] = GetRandom(SpawnType.Human);
|
||||
}
|
||||
|
||||
for (int i = 0; i < assignedWayPoints.Length; i++ )
|
||||
{
|
||||
if (assignedWayPoints[i]==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!");
|
||||
assignedWayPoints[i] = WayPointList[0];
|
||||
}
|
||||
}
|
||||
|
||||
return assignedWayPoints;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
currentHull = Hull.FindHull(WorldPosition, currentHull);
|
||||
|
||||
if (gapId > 0) ConnectedGap = FindEntityByID(gapId) as Gap;
|
||||
|
||||
if (ladderId > 0)
|
||||
{
|
||||
var ladderItem = FindEntityByID(ladderId) as Item;
|
||||
|
||||
if (ladderItem != null) Ladders = ladderItem.GetComponent<Ladder>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Load(XElement element, Submarine submarine)
|
||||
{
|
||||
Rectangle rect = new Rectangle(
|
||||
int.Parse(element.Attribute("x").Value),
|
||||
int.Parse(element.Attribute("y").Value),
|
||||
(int)Submarine.GridSize.X, (int)Submarine.GridSize.Y);
|
||||
|
||||
WayPoint w = new WayPoint(rect, submarine);
|
||||
|
||||
w.ID = (ushort)int.Parse(element.Attribute("ID").Value);
|
||||
|
||||
Enum.TryParse<SpawnType>(ToolBox.GetAttributeString(element, "spawn", "Path"), out w.spawnType);
|
||||
|
||||
string idCardTagString = ToolBox.GetAttributeString(element, "idcardtags", "");
|
||||
if (!string.IsNullOrWhiteSpace(idCardTagString))
|
||||
{
|
||||
w.IdCardTags = idCardTagString.Split(',');
|
||||
}
|
||||
|
||||
string jobName = ToolBox.GetAttributeString(element, "job", "").ToLowerInvariant();
|
||||
if (!string.IsNullOrWhiteSpace(jobName))
|
||||
{
|
||||
w.assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == jobName);
|
||||
}
|
||||
|
||||
w.ladderId = (ushort)ToolBox.GetAttributeInt(element, "ladders", 0);
|
||||
w.gapId = (ushort)ToolBox.GetAttributeInt(element, "gap", 0);
|
||||
|
||||
w.linkedToID = new List<ushort>();
|
||||
int i = 0;
|
||||
while (element.Attribute("linkedto" + i) != null)
|
||||
{
|
||||
w.linkedToID.Add((ushort)int.Parse(element.Attribute("linkedto" + i).Value));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ShallowRemove()
|
||||
{
|
||||
base.ShallowRemove();
|
||||
|
||||
WayPointList.Remove(this);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
WayPointList.Remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user