This commit is contained in:
Regalis
2015-07-31 21:05:55 +03:00
parent 23d847a4ac
commit 85b0cda4ca
181 changed files with 4455 additions and 4073 deletions

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Subsurface.Networking;
namespace Subsurface
{
class Entity
{
private static Dictionary<int, Entity> dictionary = new Dictionary<int, Entity>();
private int id;
protected AITarget aiTarget;
//protected float soundRange;
//protected float sightRange;
public int 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 Entity()
{
//give an unique ID
bool IDfound;
id = 1;//Rand.Int(int.MaxValue);
do
{
id += 1;
IDfound = dictionary.ContainsKey(id);
} while (IDfound);
dictionary.Add(id, this);
}
public virtual void FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data) { }
public virtual void ReadNetworkData(NetworkEventType type, NetIncomingMessage message) { }
/// <summary>
/// Find an entity based on the ID
/// </summary>
public static Entity FindEntityByID(int 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)
{
e.Remove();
}
dictionary.Clear();
}
public virtual void Remove()
{
dictionary.Remove(ID);
}
}
}

View File

@@ -0,0 +1,140 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Subsurface.Lights;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Subsurface
{
class Explosion
{
private Vector2 position;
private float range;
private float damage;
private float structureDamage;
private float stun;
private float force;
private LightSource light;
public float CameraShake;
public Explosion(Vector2 position, float range, float damage, float structureDamage, float stun = 0.0f, float force = 0.0f)
{
this.position = position;
this.range = Math.Max(range, 1.0f);
this.damage = damage;
this.structureDamage = structureDamage;
this.stun = stun;
this.force = force;
CameraShake = range*10.0f;
}
public Explosion(XElement element)
{
range = Math.Max(ToolBox.GetAttributeFloat(element, "range", 1.0f), 1.0f);
damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f);
structureDamage = ToolBox.GetAttributeFloat(element, "structuredamage", 0.0f);
stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f);
force = ToolBox.GetAttributeFloat(element, "force", 0.0f);
}
public void Explode()
{
Explode(position);
}
public void Explode(Vector2 simPosition)
{
for (int i = 0; i < range * 10; i++)
{
Game1.ParticleManager.CreateParticle("explosionfire", simPosition,
Rand.Vector(Rand.Range(3.0f, 4.0f)), 0.0f);
}
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
float displayRange = ConvertUnits.ToDisplayUnits(range);
light = new LightSource(displayPosition, displayRange, Color.LightYellow);
CoroutineManager.StartCoroutine(DimLight());
float cameraDist = Vector2.Distance(Game1.GameScreen.Cam.Position, displayPosition)/2.0f;
Game1.GameScreen.Cam.Shake = CameraShake * Math.Max((displayRange - cameraDist)/displayRange, 0.0f);
if (structureDamage > 0.0f)
{
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.Position, displayPosition) < dist*3.0f)
{
structureList.Add(structure);
}
}
foreach (Structure structure in structureList)
{
for (int i = 0; i < structure.SectionCount; i++)
{
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i), displayPosition) / displayRange);
if (distFactor > 0.0f) structure.AddDamage(i, structureDamage*distFactor);
}
}
}
foreach (Character c in Character.CharacterList)
{
float dist = Vector2.Distance(c.SimPosition, simPosition);
if (dist > range) continue;
float distFactor = 1.0f - dist / range;
foreach (Limb limb in c.AnimController.limbs)
{
distFactor = 1.0f - Vector2.Distance(limb.SimPosition, simPosition)/range;
c.AddDamage(limb.SimPosition, DamageType.None, damage / c.AnimController.limbs.Length * distFactor, 0.0f, stun * distFactor);
if (force>0.0f)
{
limb.body.ApplyLinearImpulse(Vector2.Normalize(limb.SimPosition - simPosition) * distFactor * force);
}
}
}
}
private IEnumerable<Status> DimLight()
{
float currBrightness= 1.0f;
float startRange = light.Range;
while (light.Color.A > 0.0f)
{
light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, currBrightness);
light.Range = startRange * currBrightness;
currBrightness -= 0.1f;
yield return Status.Running;
}
light.Remove();
yield return Status.Success;
}
}
}

View File

@@ -0,0 +1,588 @@
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;
namespace Subsurface
{
class Gap : MapEntity
{
public bool isHorizontal;
//private Sound waterSound;
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
float open;
//the force of the water flow which is exerted on physics bodies
Vector2 flowForce;
Hull flowTargetHull;
float higherSurface;
float lowerSurface;
private int soundIndex;
float soundVolume;
public float Open
{
get { return open; }
set { open = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
public Vector2 FlowForce
{
get { return flowForce*soundVolume; }
}
public Hull FlowTargetHull
{
get { return flowTargetHull; }
}
public Gap(Rectangle newRect)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
//waterSound = new Sound("waterstream", 0.0f);
flowForce = Vector2.Zero;
isHorizontal = (rect.Width < rect.Height);
open = 1.0f;
FindHulls();
mapEntityList.Add(this);
}
public Gap(Rectangle newRect, bool isHorizontal)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
flowForce = Vector2.Zero;
this.isHorizontal = isHorizontal;
open = 1.0f;
FindHulls();
mapEntityList.Add(this);
}
public static void UpdateHulls()
{
foreach (MapEntity entity in mapEntityList)
{
Gap g = entity as Gap;
if (g != null) g.FindHulls();
}
}
public override bool Contains(Vector2 position)
{
return (Submarine.RectContains(rect, position) &&
!Submarine.RectContains(new Rectangle(rect.X + 4, rect.Y - 4, rect.Width - 8, rect.Height - 8), position));
}
private void FindHulls()
{
Hull[] hulls = new Hull[2];
linkedTo.Clear();
foreach (Hull h in Hull.hullList)
{
if (!Submarine.RectsOverlap(h.Rect, rect, false)) continue;
//if the gap is inside the hull completely, ignore it
if (rect.X > h.Rect.X && rect.X + rect.Width < h.Rect.X+h.Rect.Width &&
rect.Y < h.Rect.Y && rect.Y - rect.Height > h.Rect.Y - h.Rect.Height) continue;
for (int i = 0; i < 2; i++ )
{
if (hulls[i] != null) continue;
hulls[i] = h;
break;
}
if (hulls[1] != null) break;
}
if (hulls[0] == null && hulls[1] == null) return;
if (hulls[0]!=null && hulls[1]!=null)
{
if ((isHorizontal && hulls[0].Rect.X > hulls[1].Rect.X) || (!isHorizontal && hulls[0].Rect.Y < hulls[1].Rect.Y))
{
//make sure that hull1 is the lefthand room if the gap is horizontal,
//or that hull1 is the upper hull if the gap is vertical
Hull temp = hulls[0];
hulls[0] = hulls[1];
hulls[1] = temp;
}
}
linkedTo.Add(hulls[0]);
if (hulls[1] != null) linkedTo.Add(hulls[1]);
//if (hull1 != null && hull2 != null)
//{
// if (isHorizontal)
// {
// //make sure that water1 is the lefthand room
// //or that water2 is null if the gap doesn't lead to another room
// if (hull1.Rect.X < hull2.Rect.X)
// {
// linkedTo.Add(hull1);
// linkedTo.Add(hull2);
// }
// else
// {
// linkedTo.Add(hull2);
// linkedTo.Add(hull1);
// }
// }
// else
// {
// //make sure that water1 is the room on the top
// //or that water2 is null if the gap doesn't lead to another room
// if (hull1.Rect.Y > hull2.Rect.Y)
// {
// linkedTo.Add(hull1);
// linkedTo.Add(hull2);
// }
// else
// {
// linkedTo.Add(hull2);
// linkedTo.Add(hull1);
// }
// }
//}
//else
//{
// linkedTo.Add(hull1);
//}
}
public override void Draw(SpriteBatch sb, bool editing)
{
//if (linkedTo[0] != null)
// GUI.DrawLine(sb, new Vector2(Position.X, Position.Y),
// new Vector2(linkedTo[0].Position.X, linkedTo[0].Position.Y), Color.Blue);
//if (linkedTo.Count > 1 && linkedTo[1] != null)
// GUI.DrawLine(sb, new Vector2(Position.X, Position.Y),
// new Vector2(linkedTo[1].Position.X, linkedTo[1].Position.Y), Color.Blue);
//GUI.DrawLine(sb, new Vector2(Position.X, -Position.Y), new Vector2(Position.X, -Position.Y)+new Vector2(flowForce.X, -flowForce.Y), Color.LightBlue);
if (!editing) return;
Color clr = (open == 0.0f) ? Color.Red : Color.Cyan;
GUI.DrawRectangle(sb, new Rectangle(rect.X, -rect.Y, rect.Width, rect.Height), clr);
if (isSelected)
{
GUI.DrawRectangle(sb,
new Vector2(rect.X - 5, -rect.Y - 5),
new Vector2(rect.Width + 10, rect.Height + 10),
Color.Red);
}
//HUD.DrawLine(sb, new Vector2(position.X, -position.Y),
// isHorizontal ? new Vector2(position.X, -position.Y + size) : new Vector2(position.X + size, -position.Y),
// clr);
}
public override void Update(Camera cam, float deltaTime)
{
soundVolume = soundVolume + ((flowForce.Length() < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f);
soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f);
int index = (int)Math.Floor(flowForce.Length() / 100.0f);
index = Math.Min(index,2);
soundIndex = AmbientSoundManager.flowSounds[index].Loop(soundIndex, soundVolume, Position, 2000.0f);
//soundVolume = Math.Max(0.0f, soundVolume-deltaTime);
//Sound.UpdatePosition(soundIndex, Position, 2000.0f);
flowForce = Vector2.Zero;
if (open == 0.0f) 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);
}
if (FlowForce.Length() > 150.0f && flowTargetHull!=null && flowTargetHull.Volume < flowTargetHull.FullVolume)
{
//UpdateFlowForce();
Vector2 pos = SimPosition;
if (isHorizontal)
{
pos.Y = ConvertUnits.ToSimUnits(MathHelper.Clamp(lowerSurface, rect.Y-rect.Height, rect.Y));
Game1.ParticleManager.CreateParticle("watersplash",
new Vector2(pos.X, pos.Y - Rand.Range(0.0f, 0.1f)),
new Vector2(flowForce.X * Rand.Range(0.005f, 0.007f), flowForce.Y * Rand.Range(0.005f, 0.007f)));
pos.Y = ConvertUnits.ToSimUnits(Rand.Range(lowerSurface, rect.Y - rect.Height));
Game1.ParticleManager.CreateParticle("bubbles", pos, flowForce / 200.0f);
}
else
{
pos.Y += Math.Sign(flowForce.Y) * ConvertUnits.ToSimUnits(rect.Height / 2.0f);
for (int i = 0; i < rect.Width; i += (int)Rand.Range(80, 100))
{
pos.X = ConvertUnits.ToSimUnits(Rand.Range(rect.X, rect.X+rect.Width));
Subsurface.Particles.Particle splash = Game1.ParticleManager.CreateParticle("watersplash", pos,
new Vector2(flowForce.X * Rand.Range(0.005f, 0.008f), flowForce.Y * Rand.Range(0.005f, 0.008f)));
if (splash!=null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f);
Game1.ParticleManager.CreateParticle("bubbles", pos, flowForce / 200.0f);
}
}
}
}
void UpdateRoomToRoom(float deltaTime)
{
if (linkedTo.Count < 2) return;
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
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.Min(hull1.Surface,hull2.Surface);
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+hull2.WaveY[0]) > rect.Y - size)
{
int dir = (hull1.Pressure > hull2.Pressure) ? 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 - hull1.Pressure) * 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) / 2);
flowForce = new Vector2(-delta, 0.0f);
}
else if (dir == 1)
{
if (!(hull1.Volume > 0.0f)) return;
lowerSurface = hull2.Surface - hull2.WaveY[1];
flowTargetHull = hull2;
//make sure not to move more than what the room contains
delta = Math.Min((hull1.Pressure - hull2.Pressure) * 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 + hull2.Pressure) / 2);
flowForce = new Vector2(delta, 0.0f);
}
if (delta>100.0f)
{
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 > 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 - hull1.Pressure, 500.0f));
delta = Math.Max(delta, 0.0f);
hull1.Volume += delta;
hull2.Volume -= delta;
flowTargetHull = hull1;
//delta = (water2.Pressure - water1.Pressure) * 0.1f;
//if (delta > 0.1f)
//{
// int posX = (int)((rect.X + size / 2.0f - water1.Rect.X) / Hull.WaveWidth);
// //water1.WaveY[posX] = delta;
// water1.WaveVel[posX] = delta * 0.01f;
//}
if (hull1.Volume > hull1.FullVolume)
{
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure) / 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 * 10000f * 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 + hull2.Pressure) / 2);
}
flowForce = new Vector2(0.0f,-delta);
//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 && hull2.Volume>hull2.FullVolume)
{
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;
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)
{
float vel = (rect.Y + hull1.Surface) * 0.03f;
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
{
hull1.WaveVel[hull1.WaveY.Length - 1] += vel;
hull1.WaveVel[hull1.WaveY.Length - 2] += vel;
}
else
{
hull1.WaveVel[0] += vel;
hull1.WaveVel[1] += vel;
}
}
}
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);
}
}
}
private void UpdateOxygen()
{
if (linkedTo.Count < 2) return;
Hull hull1 = (Hull)linkedTo[0];
Hull hull2 = (Hull)linkedTo[1];
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
float totalVolume = (hull1.FullVolume + hull2.FullVolume);
hull1.Oxygen += Math.Sign(totalOxygen * hull1.FullVolume / (totalVolume) - hull1.Oxygen) * Hull.OxygenDistributionSpeed;
hull2.Oxygen += Math.Sign(totalOxygen * hull2.FullVolume / (totalVolume) - hull2.Oxygen) * Hull.OxygenDistributionSpeed;
}
public override void Remove()
{
base.Remove();
if (soundIndex > -1) Sounds.SoundManager.Stop(soundIndex);
}
public override XElement Save(XDocument doc)
{
XElement element = new XElement("Gap");
element.Add(new XAttribute("ID", ID),
new XAttribute("x", rect.X),
new XAttribute("y", rect.Y),
new XAttribute("width", rect.Width),
new XAttribute("height", rect.Height));
//if (linkedTo != null)
//{
// int i = 0;
// foreach (Entity e in linkedTo)
// {
// if (e == null) continue;
// element.Add(new XAttribute("linkedto" + i, e.ID));
// i += 1;
// }
//}
doc.Root.Add(element);
return element;
}
public static void Load(XElement element)
{
Rectangle 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));
Gap g = new Gap(rect);
g.ID = int.Parse(element.Attribute("ID").Value);
g.linkedToID = new List<int>();
//int i = 0;
//while (element.Attribute("linkedto" + i) != null)
//{
// g.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
// i += 1;
//}
}
}
}

View File

@@ -0,0 +1,436 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Subsurface
{
class Hull : MapEntity
{
public static List<Hull> hullList = new List<Hull>();
public static bool EditWater;
public static WaterRenderer renderer;
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, PropertyDescriptor> properties;
private float lethalPressure;
private float surface;
private float volume;
private float pressure;
private float oxygen;
private bool update;
float[] waveY; //displacement from the surface of the water
float[] waveVel; //velocity of the point
float[] leftDelta;
float[] rightDelta;
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
{
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 { 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 Hull(Rectangle rectangle)
{
rect = rectangle;
OxygenPercentage = Rand.Range(90.0f, 100.0f, false);
properties = TypeDescriptor.GetProperties(GetType())
.Cast<PropertyDescriptor>()
.ToDictionary(pr => pr.Name);
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);
aiTarget.SightRange = (rect.Width + rect.Height)*10.0f;
hullList.Add(this);
Item.UpdateHulls();
Gap.UpdateHulls();
Volume = 0.0f;
//add to list of entities as well
mapEntityList.Add(this);
}
public override bool Contains(Vector2 position)
{
return (Submarine.RectContains(rect, position) &&
!Submarine.RectContains(new Rectangle(rect.X + 8, rect.Y - 8, rect.Width - 16, rect.Height - 16), position));
}
public int GetWaveIndex(Vector2 position)
{
int index = (int)(position.X - rect.X) / WaveWidth;
index = 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;
Item.UpdateHulls();
Gap.UpdateHulls();
}
public override void Remove()
{
base.Remove();
Item.UpdateHulls();
Gap.UpdateHulls();
//renderer.Dispose();
hullList.Remove(this);
}
public override void Update(Camera cam, float deltaTime)
{
Oxygen -= OxygenDetoriationSpeed * deltaTime;
if (EditWater)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(rect, position))
{
if (PlayerInput.LeftButtonDown())
{
waveY[(int)(position.X - rect.X) / WaveWidth] = 100.0f;
Volume = Volume + 1500.0f;
}
else if (PlayerInput.GetMouseState.RightButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
{
Volume = Volume - 1500.0f;
}
}
}
if (!update) 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 (maxDelta > Rand.Range(0.2f,10.0f))
{
Game1.ParticleManager.CreateParticle("mist",
ConvertUnits.ToSimUnits(new Vector2(rect.X + WaveWidth * i,surface + waveY[i])),
new Vector2(0.0f, -0.5f));
}
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];
}
}
if (volume<FullVolume)
{
LethalPressure -= 0.5f;
if (Volume == 0.0f)
{
for (int i = 1; i < waveY.Length - 1; i++)
{
if (waveY[i] > 0.1f) return;
}
update = false;
}
}
else
{
LethalPressure += 1.0f;
}
}
public override void Draw(SpriteBatch spriteBatch, bool editing)
{
if (!editing && !Game1.DebugDraw) return;
GUI.DrawRectangle(spriteBatch,
new Vector2(rect.X, -rect.Y),
new Vector2(rect.Width, rect.Height),
isHighlighted ? Color.Green : Color.Blue);
GUI.DrawRectangle(spriteBatch,
new Rectangle(rect.X, -rect.Y, rect.Width, rect.Height),
Color.Red*((100.0f-OxygenPercentage)/400.0f), true);
spriteBatch.DrawString(GUI.Font, "Pressure: " + ((int)pressure - rect.Y).ToString() +
" - Lethality: " + lethalPressure +
" - Oxygen: "+((int)OxygenPercentage), new Vector2(rect.X+10, -rect.Y+10), Color.Black);
spriteBatch.DrawString(GUI.Font, volume +" / "+ FullVolume, new Vector2(rect.X+10, -rect.Y+30), Color.Black);
if (isSelected && editing)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(rect.X - 5, -rect.Y - 5),
new Vector2(rect.Width + 10, rect.Height + 10),
Color.Red);
}
}
public void Render(GraphicsDevice graphicsDevice, Camera cam)
{
if (renderer.positionInBuffer > renderer.vertices.Length - 6) return;
//calculate where the surface should be based on the water volume
float top = rect.Y;
float bottom = rect.Y - rect.Height;
float surfaceY = bottom + Volume / rect.Width;
//interpolate the position of the rendered surface towards the "target surface"
surface = surface + (surfaceY - surface) / 10.0f;
Matrix transform =
cam.Transform
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) return;
if (!update)
{
// create the four corners of our triangle.
Vector3 p1 = new Vector3(rect.X, top, 0.0f);
Vector3 p2 = new Vector3(rect.X + rect.Width, top, 0.0f);
Vector3 p3 = new Vector3(p2.X, bottom, 0.0f);
Vector3 p4 = new Vector3(p1.X, bottom, 0.0f);
renderer.vertices[renderer.positionInBuffer] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
renderer.vertices[renderer.positionInBuffer + 1] = new WaterVertex(p2, new Vector2(p2.X, -p2.Y), transform);
renderer.vertices[renderer.positionInBuffer + 2] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
renderer.vertices[renderer.positionInBuffer + 3] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
renderer.vertices[renderer.positionInBuffer + 4] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
renderer.vertices[renderer.positionInBuffer + 5] = new WaterVertex(p4, new Vector2(p4.X, -p4.Y), transform);
renderer.positionInBuffer += 6;
return;
}
int x = rect.X;
int start = (int)Math.Floor((float)(cam.WorldView.X - x) / WaveWidth);
start = Math.Max(start, 0);
int end = (waveY.Length - 1)
- (int)Math.Floor((float)((x + rect.Width) - (cam.WorldView.X + cam.WorldView.Width)) / WaveWidth);
end = Math.Min(end, waveY.Length - 1);
x += start * WaveWidth;
for (int i = start; i < end; i++)
{
if (renderer.positionInBuffer > renderer.vertices.Length - 6) return;
Vector3 p1 = new Vector3(x, top, 0.0f);
Vector3 p4 = new Vector3(p1.X, surface + waveY[i], 0.0f);
//skip adjacent "water rects" if the surface of the water is roughly at the same position
int width = WaveWidth;
while (i<end-1 && Math.Abs(waveY[i + 1] - waveY[i]) < 1.0f)
{
width += WaveWidth;
i++;
}
Vector3 p2 = new Vector3(x + width, top, 0.0f);
Vector3 p3 = new Vector3(p2.X, surface + waveY[i+1], 0.0f);
renderer.vertices[renderer.positionInBuffer] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
renderer.vertices[renderer.positionInBuffer + 1] = new WaterVertex(p2, new Vector2(p2.X, -p2.Y), transform);
renderer.vertices[renderer.positionInBuffer + 2] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
renderer.vertices[renderer.positionInBuffer + 3] = new WaterVertex(p1, new Vector2(p1.X, -p1.Y), transform);
renderer.vertices[renderer.positionInBuffer + 4] = new WaterVertex(p3, new Vector2(p3.X, -p3.Y), transform);
renderer.vertices[renderer.positionInBuffer + 5] = new WaterVertex(p4, new Vector2(p4.X, -p4.Y), transform);
renderer.positionInBuffer += 6;
x += width;
}
}
//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)
{
if (guess != null && hullList.Contains(guess))
{
if (Submarine.RectContains(guess.rect, position)) return guess;
}
foreach (Hull w in hullList)
{
if (Submarine.RectContains(w.rect, position)) return w;
}
return null;
}
public override XElement Save(XDocument doc)
{
XElement element = new XElement("Hull");
element.Add(new XAttribute("ID", ID),
new XAttribute("x", rect.X),
new XAttribute("y", rect.Y),
new XAttribute("width", rect.Width),
new XAttribute("height", rect.Height),
new XAttribute("water", volume));
doc.Root.Add(element);
return element;
}
public static void Load(XElement element)
{
Rectangle 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(rect);
h.volume = ToolBox.GetAttributeFloat(element, "pressure", 0.0f);
h.ID = int.Parse(element.Attribute("ID").Value);
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Xna.Framework;
namespace Subsurface
{
interface IDamageable
{
Vector2 SimPosition
{
get;
}
float Health
{
get;
}
AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound=true);
}
}

View File

@@ -0,0 +1,959 @@
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.Collections.Generic;
using System.Diagnostics;
using Voronoi2;
namespace Subsurface
{
class Level
{
public static Level Loaded
{
get { return loaded; }
}
static Level loaded;
private static Texture2D shaftTexture;
//how close the sub has to be to start/endposition to exit
const float ExitDistance = 3000.0f;
private string seed;
private int siteInterval;
public const int GridCellWidth = 2000;
private List<VoronoiCell>[,] cellGrid;
private float shaftHeight;
//List<Body> bodies;
private List<VoronoiCell> cells;
private static BasicEffect basicEffect;
private VertexPositionTexture[] vertices;
private VertexBuffer vertexBuffer;
private Vector2 startPosition;
private Vector2 endPosition;
private Rectangle borders;
private List<Body> bodies;
private List<Vector2> positionsOfInterest;
public Vector2 StartPosition
{
get { return startPosition; }
}
public bool AtStartPosition
{
get;
private set;
}
public Vector2 EndPosition
{
get { return endPosition; }
}
public bool AtEndPosition
{
get;
private set;
}
public Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(cells[0].body.Position); }
}
public List<Vector2> PositionsOfInterest
{
get { return positionsOfInterest; }
}
public string Seed
{
get { return seed; }
}
public float Difficulty
{
get;
private set;
}
public Level(string seed, float difficulty, int width, int height, int siteInterval)
{
if (shaftTexture == null) shaftTexture = Game1.TextureLoader.FromFile("Content/Map/shaft.png");
if (basicEffect==null)
{
basicEffect = new BasicEffect(Game1.CurrGraphicsDevice);
basicEffect.VertexColorEnabled = false;
basicEffect.TextureEnabled = true;
basicEffect.Texture = Game1.TextureLoader.FromFile("Content/Map/iceSurface.png");
}
this.seed = seed;
this.siteInterval = siteInterval;
this.Difficulty = difficulty;
positionsOfInterest = new List<Vector2>();
borders = new Rectangle(0, 0, width, height);
}
public static Level CreateRandom(LocationConnection locationConnection)
{
int seed = locationConnection.Locations[0].GetHashCode() | locationConnection.Locations[1].GetHashCode();
return new Level(seed.ToString(), locationConnection.Difficulty, 100000, 40000, 2000);
}
public static Level CreateRandom(string seed = "")
{
if (seed == "")
{
seed = Rand.Range(0, int.MaxValue, false).ToString();
}
return new Level(seed, Rand.Range(30.0f,80.0f,false), 100000, 40000, 2000);
}
public void Generate(float minWidth, bool mirror=false)
{
Stopwatch sw = new Stopwatch();
sw.Start();
if (loaded != null)
{
loaded.Unload();
}
loaded = this;
Voronoi voronoi = new Voronoi(1.0);
List<Vector2> sites = new List<Vector2>();
bodies = new List<Body>();
Random rand = new Random(seed.GetHashCode());
float siteVariance = siteInterval * 0.8f;
for (int x = siteInterval / 2; x < borders.Width; x += siteInterval)
{
for (int y = siteInterval / 2; y < borders.Height; y += siteInterval)
{
Vector2 site = new Vector2(
x + (float)(rand.NextDouble() - 0.5) * siteVariance,
y + (float)(rand.NextDouble() - 0.5) * siteVariance);
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();
cellGrid = new List<VoronoiCell>[borders.Width / GridCellWidth, borders.Height / GridCellWidth];
for (int x = 0; x < borders.Width / GridCellWidth; x++)
{
for (int y = 0; y < borders.Height / GridCellWidth; y++)
{
cellGrid[x, y] = new List<VoronoiCell>();
}
}
//construct voronoi cells based on the graph edges
cells = new List<VoronoiCell>();
foreach (GraphEdge ge in graphEdges)
{
for (int i = 0; i < 2; i++)
{
Site site = (i == 0) ? ge.site1 : ge.site2;
VoronoiCell cell = cellGrid[
(int)Math.Floor(site.coord.x / GridCellWidth),
(int)Math.Floor(site.coord.y / GridCellWidth)].Find(c => c.site == site);
if (cell == null)
{
cell = new VoronoiCell(site);
cellGrid[(int)Math.Floor(cell.Center.X / GridCellWidth), (int)Math.Floor(cell.Center.Y / GridCellWidth)].Add(cell);
cells.Add(cell);
}
if (ge.cell1 == null)
{
ge.cell1 = cell;
}
else
{
ge.cell2 = cell;
}
cell.edges.Add(ge);
}
}
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
//generate a path from the left edge of the map to right edge
Rectangle pathBorders = new Rectangle(
borders.X + (int)minWidth * 2, borders.Y + (int)minWidth * 2,
borders.Right - (int)minWidth * 4, borders.Y + borders.Height - (int)minWidth * 4);
List<Vector2> pathNodes = new List<Vector2>();
startPosition = new Vector2((int)minWidth * 2, rand.Next((int)minWidth * 2, borders.Height - (int)minWidth * 2));
endPosition = new Vector2(borders.Width - (int)minWidth * 2, rand.Next((int)minWidth * 2, borders.Height - (int)minWidth * 2));
pathNodes.Add(new Vector2(startPosition.X, borders.Height));
pathNodes.Add(startPosition);
pathNodes.Add(endPosition);
pathNodes.Add(new Vector2(endPosition.X, borders.Height));
if (mirror)
{
pathNodes.Reverse();
}
List<VoronoiCell> pathCells = GeneratePath(rand,
pathNodes, cells, pathBorders, minWidth, 0.3f, mirror, true);
//place some enemy spawnpoints at random points in the path
for (int i = 0; i <3 ; i++ )
{
Vector2 position = pathCells[rand.Next((int)(pathCells.Count * 0.5f), pathCells.Count - 2)].Center;
WayPoint wayPoint = new WayPoint(new Rectangle((int)position.X, (int)position.Y, 10, 10));
wayPoint.MoveWithLevel = true;
wayPoint.SpawnType = SpawnType.Enemy;
}
startPosition = pathCells[0].Center;
endPosition = pathCells[pathCells.Count - 1].Center;
//generate a couple of random paths
for (int i = 0; i <= rand.Next() % 3; i++)
{
//pathBorders = new Rectangle(
//borders.X + siteInterval * 2, borders.Y - siteInterval * 2,
//borders.Right - siteInterval * 2, borders.Y + borders.Height - siteInterval * 2);
Vector2 start = pathCells[rand.Next(1, pathCells.Count - 2)].Center;
float x = pathBorders.X + (float)rand.NextDouble() * (pathBorders.Right - pathBorders.X);
float y = pathBorders.Y + (float)rand.NextDouble() * (pathBorders.Bottom - pathBorders.Y);
if (mirror) x = borders.Width - x;
Vector2 end = new Vector2(x, y);
var newPathCells = GeneratePath(rand, new List<Vector2> { start, end }, cells, pathBorders, 0.0f, 0.8f, mirror);
for (int n = 0; n < newPathCells.Count-5; n += 3)
{
positionsOfInterest.Add(newPathCells[n].Center);
}
pathCells.AddRange(newPathCells);
}
Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
//for (int i = 0; i < 2; i++ )
//{
// Vector2 tunnelStart = (i == 0) ? startPosition : endPosition;
// pathCells.AddRange
// (
// GeneratePath(rand, tunnelStart, new Vector2(tunnelStart.X, borders.Height), cells, pathBorders, minWidth, 0.1f, mirror)
// );
//}
cells = CleanCells(pathCells);
foreach (VoronoiCell cell in pathCells)
{
cells.Remove(cell);
}
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)
{
cellGrid[(int)Math.Floor(cell.Center.X / GridCellWidth), (int)Math.Floor(cell.Center.Y / GridCellWidth)].Add(cell);
}
startPosition.Y = borders.Height;
endPosition.Y = borders.Height;
//for (int i = 0; i < 2; i++)
//{
// Vector2 tunnelStart = (i == 0) ? startPosition : endPosition;
// for (int n = -1; n < 2; n += 2)
// {
// int cellIndex = FindCellIndex(new Vector2(tunnelStart.X + minWidth * 0.5f * n, tunnelStart.Y), 3);
// foreach (GraphEdge ge in cells[cellIndex].edges)
// {
// if (ge.point1.Y > cells[cellIndex].Center.Y) ge.point1.Y = borders.Height + shaftHeight;
// if (ge.point2.Y > cells[cellIndex].Center.Y) ge.point2.Y = borders.Height + shaftHeight;
// }
// }
//}
//startPosition.Y += shaftHeight;
//endPosition.Y += shaftHeight;
GeneratePolygons(cells, pathCells);
foreach (VoronoiCell cell in cells)
{
foreach (GraphEdge edge in cell.edges)
{
edge.cell1 = null;
edge.cell2 = null;
edge.site1 = null;
edge.site2 = null;
}
}
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
vertexBuffer = new VertexBuffer(Game1.CurrGraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);
if (mirror)
{
Vector2 temp = startPosition;
startPosition = endPosition;
endPosition = temp;
}
Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
}
private List<VoronoiCell> GeneratePath(Random rand, List<Vector2> points, List<VoronoiCell> cells, Microsoft.Xna.Framework.Rectangle limits, float minWidth, float wanderAmount = 0.3f, bool mirror=false, bool placeWaypoints=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<VoronoiCell> pathCells = new List<VoronoiCell>();
VoronoiCell[] targetCells = new VoronoiCell[points.Count];
for (int i = 0; i <targetCells.Length; i++)
{
targetCells[i]= cells[FindCellIndex(points[i])];
}
VoronoiCell currentCell = targetCells[0];
pathCells.Add(currentCell);
int currentTargetIndex = 1;
do
{
int edgeIndex = 0;
//steer towards target
if (rand.NextDouble() > wanderAmount)
{
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
{
List<GraphEdge> allowedEdges = new List<GraphEdge>();
foreach (GraphEdge edge in currentCell.edges)
{
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
allowedEdges.Add(edge);
}
if (allowedEdges.Count==0)
{
edgeIndex = 0;
}
else
{
edgeIndex = rand.Next() % allowedEdges.Count;
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
}
}
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
pathCells.Add(currentCell);
if (currentCell==targetCells[currentTargetIndex])
{
currentTargetIndex += 1;
if (currentTargetIndex>=targetCells.Length) break;
}
} while (currentCell != targetCells[targetCells.Length-1]);
if (placeWaypoints)
{
WayPoint newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, (int)(borders.Height + shaftHeight), 10, 10));
newWaypoint.MoveWithLevel = true;
WayPoint prevWaypoint = newWaypoint;
for (int i = 0; i < pathCells.Count; i++)
{
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10));
newWaypoint.MoveWithLevel = true;
if (prevWaypoint != null)
{
prevWaypoint.linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(prevWaypoint);
}
prevWaypoint = newWaypoint;
}
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, (int)(borders.Height + shaftHeight), 10, 10));
newWaypoint.MoveWithLevel = true;
if (prevWaypoint != null)
{
prevWaypoint.linkedTo.Add(newWaypoint);
newWaypoint.linkedTo.Add(prevWaypoint);
}
}
Debug.WriteLine("genpath: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
foreach (VoronoiCell removedCell in removedCells)
{
if (pathCells.Contains(removedCell)) continue;
pathCells.Add(removedCell);
}
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
sw2.Restart();
return pathCells;
}
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
{
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0) continue;
Vector2 cornerPos = position + new Vector2(x * minDistance, y * minDistance);
int cellIndex = FindCellIndex(cornerPos);
if (cellIndex == -1) continue;
if (!tooCloseCells.Contains(cells[cellIndex]))
{
tooCloseCells.Add(cells[cellIndex]);
}
}
}
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;
}
/// <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 (!newCells.Contains(adjacent)) newCells.Add(adjacent);
}
}
return newCells;
}
/// <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>
private int FindCellIndex(Vector2 position, int searchDepth = 1)
{
float closestDist = 0.0f;
VoronoiCell closestCell = null;
int gridPosX = (int)Math.Floor(position.X / GridCellWidth);
int gridPosY = (int)Math.Floor(position.Y / GridCellWidth);
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);
}
private void GeneratePolygons(List<VoronoiCell> cells, List<VoronoiCell> emptyCells)
{
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
//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 (ge.point1 == ge.point2) 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 (!emptyCells.Contains(adjacentCell)) continue;
ge.isSolid = true;
if (!bodyPoints.Contains(ge.point1)) bodyPoints.Add(ge.point1);
if (!bodyPoints.Contains(ge.point2)) bodyPoints.Add(ge.point2);
}
if (tempVertices.Count < 3 || bodyPoints.Count < 2)
{
cells.RemoveAt(n);
continue;
}
var triangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
for (int i = 0; i < triangles.Count; i++)
{
foreach (Vector2 vertex in triangles[i])
{
verticeList.Add(new VertexPositionTexture(new Vector3(vertex, 0.0f), vertex/1000.0f));
}
}
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]);
}
triangles = MathUtils.TriangulateConvexHull(bodyPoints, cell.Center);
Body edgeBody = new Body(Game1.World);
for (int i = 0; i < triangles.Count; i++)
{
if (triangles[i][0].Y == triangles[i][1].Y && triangles[i][0].Y == triangles[i][2].Y) continue;
if (triangles[i][0].X == triangles[i][1].X && triangles[i][0].X == triangles[i][2].X) continue;
Vertices bodyVertices = new Vertices(triangles[i]);
FixtureFactory.AttachPolygon(bodyVertices, 5.0f, edgeBody);
}
edgeBody.UserData = cell;
edgeBody.SleepingAllowed = false;
edgeBody.BodyType = BodyType.Kinematic;
edgeBody.CollisionCategories = Physics.CollisionWall | Physics.CollisionLevel;
cell.body = edgeBody;
bodies.Add(edgeBody);
}
for (int i = 0; i < 2; i++ )
{
Body shaftBody = BodyFactory.CreateRectangle(Game1.World, 100.0f, 10.0f, 5.0f);
shaftBody.BodyType = BodyType.Kinematic;
shaftBody.CollisionCategories = Physics.CollisionWall | Physics.CollisionLevel;
shaftBody.SetTransform(ConvertUnits.ToSimUnits((i==0) ? startPosition : endPosition), 0.0f);
shaftBody.SleepingAllowed = false;
bodies.Add(shaftBody);
}
vertices = verticeList.ToArray();
}
public void SetPosition(Vector2 pos)
{
Vector2 amount = pos - Position;
Vector2 simAmount = ConvertUnits.ToSimUnits(amount);
//foreach (VoronoiCell cell in cells)
//{
// if (cell.body == null) continue;
// cell.body.SleepingAllowed = false;
// cell.body.SetTransform(cell.body.Position + simAmount, cell.body.Rotation);
//}
int i = 0;
foreach (Body body in bodies)
{
System.Diagnostics.Debug.WriteLine(i);
i++;
body.SetTransform(body.Position + simAmount, body.Rotation);
}
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
{
Item item = mapEntity as Item;
if (item == null)
{
//if (!mapEntity.MoveWithLevel) continue;
//mapEntity.Move(amount);
}
else if (item.body != null)
{
if (item.CurrentHull != null) continue;
item.SetTransform(item.SimPosition+amount, item.body.Rotation);
}
}
}
Vector2 prevVelocity;
public void Move(Vector2 amount)
{
Vector2 velocity = amount;
Vector2 simVelocity = ConvertUnits.ToSimUnits(amount / (float)Physics.step);
foreach (Body body in bodies)
{
body.LinearVelocity = simVelocity;
}
foreach (Character character in Character.CharacterList)
{
foreach (Limb limb in character.AnimController.limbs)
{
if (character.AnimController.CurrentHull != null) continue;
limb.body.LinearVelocity += simVelocity;
}
}
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
{
Item item = mapEntity as Item;
if (item == null)
{
//if (!mapEntity.MoveWithLevel) continue;
//mapEntity.Move(velocity);
}
else if (item.body!=null)
{
if (item.CurrentHull != null) continue;
item.body.LinearVelocity += simVelocity;
}
}
AtStartPosition = Vector2.Distance(startPosition, -Position) < ExitDistance;
AtEndPosition = Vector2.Distance(endPosition, -Position) < ExitDistance;
prevVelocity = simVelocity;
}
public static void AfterWorldStep()
{
if (loaded == null) return;
loaded.ResetBodyVelocities();
}
private void ResetBodyVelocities()
{
foreach (Character character in Character.CharacterList)
{
if (character.AnimController.CurrentHull != null) continue;
foreach (Limb limb in character.AnimController.limbs)
{
limb.body.LinearVelocity -= prevVelocity;
}
}
foreach (Item item in Item.itemList)
{
if (item.body == null || item.CurrentHull != null) continue;
item.body.LinearVelocity -= prevVelocity;
}
}
public void DebugCheckPos()
{
Vector2 avgPos = Vector2.Zero;
foreach (VoronoiCell cell in cells)
{
if (cell.body == null) continue;
System.Diagnostics.Debug.WriteLine(cell.body.Position);
avgPos += cell.body.Position;
}
System.Diagnostics.Debug.WriteLine("avgpos: " + avgPos / cells.Count);
System.Diagnostics.Debug.WriteLine("pos: " + Position);
}
Vector2 observerPosition;
public void SetObserverPosition(Vector2 position)
{
//observerPosition = position - this.Position;
//int gridPosX = (int)Math.Floor(observerPosition.X / GridCellWidth);
//int gridPosY = (int)Math.Floor(observerPosition.Y / GridCellWidth);
//int searchOffset = 2;
//int startX = Math.Max(gridPosX - searchOffset, 0);
//int endX = Math.Min(gridPosX + searchOffset, cellGrid.GetLength(0) - 1);
//int startY = Math.Max(gridPosY - searchOffset, 0);
//int endY = Math.Min(gridPosY + searchOffset, cellGrid.GetLength(1) - 1);
//for (int x = 0; x < cellGrid.GetLength(0); x++)
//{
// for (int y = 0; y < cellGrid.GetLength(1); y++)
// {
// for (int i = 0; i < cellGrid[x, y].Count; i++)
// {
// //foreach (Body b in cellGrid[x, y][i].bodies)
// //{
// if (cellGrid[x, y][i].body == null) continue;
// cellGrid[x, y][i].body.Enabled = true;// (x >= startX && x <= endX && y >= startY && y <= endY);
// //}
// }
// }
//}
}
public void Draw(SpriteBatch spriteBatch)
{
Vector2 pos = endPosition;
pos.X += Position.X;
pos.Y = -pos.Y - Position.Y;
int shaftWidth = 10000;
spriteBatch.Draw(shaftTexture,
new Rectangle((int)(pos.X - shaftWidth / 2), (int)pos.Y, shaftWidth, 512),
new Rectangle(0, 0, shaftWidth, 256),
Color.White, 0.0f,
Vector2.Zero,
SpriteEffects.None, 0.0f);
pos = startPosition;
pos.X += Position.X;
pos.Y = -pos.Y - Position.Y;
spriteBatch.Draw(shaftTexture,
new Rectangle((int)(pos.X - shaftWidth/2), (int)pos.Y, shaftWidth, 512),
new Rectangle(0, 0, shaftWidth, 256),
Color.White, 0.0f,
Vector2.Zero,
SpriteEffects.None, 0.0f);
//List<Vector2[]> edges = GetCellEdges(observerPosition, 1, false);
//foreach (VoronoiCell cell in cells)
//{
// for (int i = 0; i < cell.bodyVertices.Count - 1; i++)
// {
// Vector2 start = cell.bodyVertices[i];
// start.X += Position.X;
// start.Y = -start.Y - Position.Y;
// start.X += Rand.Range(-10.0f, 10.0f);
// Vector2 end = cell.bodyVertices[i + 1];
// end.X += Position.X;
// end.Y = -end.Y - Position.Y;
// end.X += Rand.Range(-10.0f, 10.0f);
// GUI.DrawLine(spriteBatch, start, end, (cell.body != null && cell.body.Enabled) ? Color.Red : Color.Red);
// }
//}
}
public List<Vector2[]> GetCellEdges(Vector2 refPos, int searchDepth = 2, bool onlySolid = true)
{
int gridPosX = (int)Math.Floor(refPos.X / GridCellWidth);
int gridPosY = (int)Math.Floor(refPos.Y / GridCellWidth);
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<Vector2[]> edges = new List<Vector2[]>();
for (int x = startX; x < endX; x++)
{
for (int y = startY; y < endY; y++)
{
foreach (VoronoiCell cell in cellGrid[x, y])
{
for (int i = 0; i < cell.edges.Count; i++)
{
if (onlySolid && !cell.edges[i].isSolid) continue;
Vector2 start = cell.edges[i].point1 + Position;
start.Y = -start.Y;
Vector2 end = cell.edges[i].point2 + Position;
end.Y = -end.Y;
edges.Add(new Vector2[] { start, end });
//GUI.DrawLine(spriteBatch, start, end, (cell.body != null && cell.body.Enabled) ? Color.Green : Color.Red);
}
}
}
}
return edges;
}
public void Render(GraphicsDevice graphicsDevice, Camera cam)
{
if (vertices == null) return;
if (vertices.Length <= 0) return;
basicEffect.World = Matrix.CreateTranslation(new Vector3(Position, 0.0f)) * cam.ShaderTransform
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
basicEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, (int)Math.Floor(vertices.Length / 3.0f));
}
private void Unload()
{
//position = Vector2.Zero;
//foreach (VoronoiCell cell in cells)
//{
// //foreach (Body b in cell.bodies)
// //{
// Game1.world.RemoveBody(cell.body);
// //}
//}
//bodies = null;
vertices = null;
cells = null;
bodies.Clear();
bodies = null;
vertexBuffer.Dispose();
vertexBuffer = null;
}
}
}

View File

@@ -0,0 +1,988 @@
/*
* 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 ();
allEdges.Add ( newEdge );
newEdge.point1 = point1;
newEdge.point2 = point2;
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, 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

View File

@@ -0,0 +1,181 @@
/*
* 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 Subsurface;
using System;
using System.Collections.Generic;
namespace Voronoi2
{
public class Point
{
public double x, y;
public Point ()
{
}
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 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 class VoronoiCell
{
public List<GraphEdge> edges;
public Site site;
public List<Vector2> bodyVertices;
public Body body;
public Vector2 Center
{
get { return new Vector2((float)site.coord.x, (float)site.coord.y); }
}
public VoronoiCell(Site site)
{
edges = new List<GraphEdge>();
bodyVertices = new List<Vector2>();
//bodies = new List<Body>();
this.site = site;
}
}
public class GraphEdge
{
public Vector2 point1, point2;
public Site site1, site2;
public VoronoiCell cell1, cell2;
public bool isSolid;
public VoronoiCell AdjacentCell(VoronoiCell cell)
{
if (cell1==cell)
{
return cell2;
}
else if (cell2==cell)
{
return cell1;
}
else
{
return null;
}
}
}
// للترتيب
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;
}
}
}

View File

@@ -0,0 +1,243 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
namespace Subsurface.Lights
{
class ConvexHull
{
public static List<ConvexHull> list = new List<ConvexHull>();
static BasicEffect losEffect;
static BasicEffect shadowEffect;
private VertexPositionColor[] vertices;
private short[] indices;
int primitiveCount;
bool[] backFacing;
VertexPositionColor[] shadowVertices;
public bool Enabled
{
get;
set;
}
public ConvexHull(Vector2[] points, Color color)
{
int vertexCount = points.Length;
vertices = new VertexPositionColor[vertexCount + 1];
Vector2 center = Vector2.Zero;
for (int i = 0; i < vertexCount; i++)
{
vertices[i] = new VertexPositionColor(new Vector3(points[i], 0), color);
center += points[i];
}
center /= points.Length;
vertices[vertexCount] = new VertexPositionColor(new Vector3(center, 0), color);
primitiveCount = points.Length;
indices = new short[primitiveCount * 3];
for (int i = 0; i < primitiveCount; i++)
{
indices[3 * i] = (short)i;
indices[3 * i + 1] = (short)((i + 1) % vertexCount);
indices[3 * i + 2] = (short)vertexCount;
}
backFacing = new bool[vertexCount];
Enabled = true;
list.Add(this);
}
public void Move(Vector2 amount)
{
for (int i = 0; i < vertices.Count(); i++)
{
vertices[i].Position = new Vector3(vertices[i].Position.X + amount.X, vertices[i].Position.Y + amount.Y, vertices[i].Position.Z);
}
}
public void SetVertices(Vector2[] points)
{
int vertexCount = points.Length;
vertices = new VertexPositionColor[vertexCount + 1];
for (int i = 0; i < vertexCount; i++)
{
vertices[i] = new VertexPositionColor(new Vector3(points[i], 0), Color.Black);
}
}
//public void Draw(GameTime gameTime)
//{
// device.RasterizerState = RasterizerState.CullNone;
// device.BlendState = BlendState.Opaque;
// drawingEffect.World = Matrix.CreateTranslation(position.X, position.Y, 0);
// foreach (EffectPass pass in drawingEffect.CurrentTechnique.Passes)
// {
// pass.Apply();
// device.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, primitiveCount);
// }
//}
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, bool los = true)
{
if (!Enabled) return;
if (losEffect == null)
{
losEffect = new BasicEffect(graphicsDevice);
losEffect.VertexColorEnabled = true;
}
if (shadowEffect==null)
{
shadowEffect = new BasicEffect(graphicsDevice);
shadowEffect.TextureEnabled = true;
//shadowEffect.VertexColorEnabled = true;
shadowEffect.LightingEnabled = false;
shadowEffect.Texture = Game1.TextureLoader.FromFile("Content/lights/penumbra.png");
}
//compute facing of each edge, using N*L
for (int i = 0; i < primitiveCount; i++)
{
Vector2 firstVertex = new Vector2(vertices[i].Position.X, vertices[i].Position.Y);
int secondIndex = (i + 1) % primitiveCount;
Vector2 secondVertex = new Vector2(vertices[secondIndex].Position.X, vertices[secondIndex].Position.Y);
Vector2 middle = (firstVertex + secondVertex) / 2;
Vector2 L = lightSourcePos - middle;
Vector2 N = new Vector2();
N.X = -(secondVertex.Y - firstVertex.Y);
N.Y = secondVertex.X - firstVertex.X;
backFacing[i] = (Vector2.Dot(N, L) < 0);
}
//find beginning and ending vertices which
//belong to the shadow
int startingIndex = 0;
int endingIndex = 0;
for (int i = 0; i < primitiveCount; i++)
{
int currentEdge = i;
int nextEdge = (i + 1) % primitiveCount;
if (backFacing[currentEdge] && !backFacing[nextEdge])
endingIndex = nextEdge;
if (!backFacing[currentEdge] && backFacing[nextEdge])
startingIndex = nextEdge;
}
VertexPositionTexture[] penumbraVertices = new VertexPositionTexture[6];
if (los)
{
for (int n = 0; n < 4; n+=3)
{
Vector3 penumbraStart = (n == 0) ? vertices[startingIndex].Position : vertices[endingIndex].Position;
penumbraVertices[n] = new VertexPositionTexture();
penumbraVertices[n].Position = penumbraStart;
penumbraVertices[n].TextureCoordinate = new Vector2(0.0f, 1.0f);
//penumbraVertices[0].te = fow ? Color.Black : Color.Transparent;
for (int i = 0; i < 2; i++ )
{
penumbraVertices[n + i + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (i == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f)*0.05f;
if (n > 0) normal = -normal;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
penumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000;
if (los)
{
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
else
{
penumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero;
}
}
if (n > 0)
{
var temp = penumbraVertices[4];
penumbraVertices[4] = penumbraVertices[5];
penumbraVertices[5] = temp;
}
}
}
int shadowVertexCount;
//nr of vertices that are in the shadow
if (endingIndex > startingIndex)
shadowVertexCount = endingIndex - startingIndex + 1;
else
shadowVertexCount = primitiveCount + 1 - startingIndex + endingIndex;
shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != shadowVertexCount * 2)
{
Vector3 vertexPos = vertices[currentIndex].Position;
//one vertex on the hull
shadowVertices[svCount] = new VertexPositionColor();
shadowVertices[svCount].Color = los ? Color.Black : Color.Transparent;
shadowVertices[svCount].Position = vertexPos;
//one extruded by the light direction
shadowVertices[svCount + 1] = new VertexPositionColor();
shadowVertices[svCount + 1].Color = los ? Color.Black : Color.Transparent;
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
L2P.Normalize();
shadowVertices[svCount + 1].Position = new Vector3(lightSourcePos, 0) + L2P * 9000;
svCount += 2;
currentIndex = (currentIndex + 1) % primitiveCount;
}
losEffect.World = cam.ShaderTransform
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
losEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 - 2);
if (los)
{
shadowEffect.World = cam.ShaderTransform
* Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1) * 0.5f;
shadowEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, penumbraVertices, 0, 2, VertexPositionTexture.VertexDeclaration);
}
}
public void Remove()
{
list.Remove(this);
}
}
}

View File

@@ -0,0 +1,66 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Subsurface.Lights
{
class LightSource
{
private static Texture2D lightTexture;
private Color color;
private float range;
private Texture2D texture;
public Vector2 Position;
public Color Color
{
get { return color; }
set { color = value; }
}
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
}
}
public LightSource(Vector2 position, float range, Color color)
{
Position = position;
this.range = range;
this.color = color;
if (lightTexture == null)
{
lightTexture = Game1.TextureLoader.FromFile("Content/Lights/light.png");
}
texture = lightTexture;
Game1.LightManager.AddLight(this);
}
public void Draw(SpriteBatch spriteBatch)
{
Vector2 center = new Vector2(lightTexture.Width / 2, lightTexture.Height / 2);
float scale = range / ((float)lightTexture.Width / 2.0f);
spriteBatch.Draw(lightTexture, new Vector2(Position.X, -Position.Y), null, color, 0, center, scale, SpriteEffects.None, 1);
}
public void Remove()
{
Game1.LightManager.RemoveLight(this);
}
}
}

View File

@@ -0,0 +1,139 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Subsurface.Lights
{
class LightManager
{
public static Vector2 ViewPos;
public Color AmbientLight;
RenderTarget2D lightMap;
private static Texture2D alphaClearTexture;
private List<LightSource> lights;
public bool LosEnabled = true;
public bool LightingEnabled = true;
public RenderTarget2D LightMap
{
get { return lightMap; }
}
public LightManager(GraphicsDevice graphics)
{
lights = new List<LightSource>();
AmbientLight = new Color(80, 80, 80, 255);
var pp = graphics.PresentationParameters;
lightMap = new RenderTarget2D(graphics, Game1.GraphicsWidth, Game1.GraphicsHeight, false,
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
RenderTargetUsage.DiscardContents);
if (alphaClearTexture==null)
{
alphaClearTexture = Game1.TextureLoader.FromFile("Content/Lights/alphaOne.png");
}
}
public void AddLight(LightSource light)
{
lights.Add(light);
}
public void RemoveLight(LightSource light)
{
lights.Remove(light);
}
public void DrawLOS(GraphicsDevice graphics, Camera cam, Vector2 pos)
{
if (!LosEnabled) return;
foreach (ConvexHull convexHull in ConvexHull.list)
{
convexHull.DrawShadows(graphics, cam, pos);
}
}
public void DrawLightmap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam)
{
graphics.SetRenderTarget(lightMap);
Rectangle viewRect = cam.WorldView;
viewRect.Y -= cam.WorldView.Height;
//clear to some small ambient light
graphics.Clear(AmbientLight);
foreach (LightSource light in lights)
{
if (light.Color.A < 0.01f || light.Range < 0.01f) continue;
//clear alpha to 1
ClearAlphaToOne(graphics, spriteBatch);
if (!MathUtils.CircleIntersectsRectangle(light.Position, light.Range, viewRect)) continue;
//draw all shadows
//write only to the alpha channel, which sets alpha to 0
graphics.RasterizerState = RasterizerState.CullNone;
graphics.BlendState = CustomBlendStates.WriteToAlpha;
foreach (ConvexHull ch in ConvexHull.list)
{
//draw shadow
ch.DrawShadows(graphics, cam, light.Position, false);
}
//draw the light shape
//where Alpha is 0, nothing will be written
spriteBatch.Begin(SpriteSortMode.Immediate, CustomBlendStates.MultiplyWithAlpha, null, null, null, null, cam.Transform);
light.Draw(spriteBatch);
spriteBatch.End();
}
//clear alpha, to avoid messing stuff up later
ClearAlphaToOne(graphics, spriteBatch);
graphics.SetRenderTarget(null);
}
private void ClearAlphaToOne(GraphicsDevice graphics, SpriteBatch spriteBatch)
{
spriteBatch.Begin(SpriteSortMode.Immediate, CustomBlendStates.WriteToAlpha);
spriteBatch.Draw(alphaClearTexture, new Rectangle(0, 0,graphics.Viewport.Width, graphics.Viewport.Height), Color.White);
spriteBatch.End();
}
}
class CustomBlendStates
{
static CustomBlendStates()
{
Multiplicative = new BlendState();
Multiplicative.ColorSourceBlend = Multiplicative.AlphaSourceBlend = Blend.Zero;
Multiplicative.ColorDestinationBlend = Multiplicative.AlphaDestinationBlend = Blend.SourceColor;
Multiplicative.ColorBlendFunction = Multiplicative.AlphaBlendFunction = BlendFunction.Add;
WriteToAlpha = new BlendState();
WriteToAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
MultiplyWithAlpha = new BlendState();
MultiplyWithAlpha.ColorDestinationBlend = MultiplyWithAlpha.AlphaDestinationBlend = Blend.One;
MultiplyWithAlpha.ColorSourceBlend = MultiplyWithAlpha.AlphaSourceBlend = Blend.DestinationAlpha;
}
public static BlendState Multiplicative { get; private set; }
public static BlendState WriteToAlpha { get; private set; }
public static BlendState MultiplyWithAlpha { get; private set; }
}
}

View File

@@ -0,0 +1,73 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Subsurface
{
class Location
{
private string name;
private Vector2 mapPosition;
private LocationType type;
private HireManager hireManager;
public List<LocationConnection> Connections;
public string Name
{
get { return name; }
}
public Vector2 MapPosition
{
get { return mapPosition; }
}
public bool Discovered;
public LocationType Type
{
get { return type; }
}
public HireManager HireManager
{
get { return hireManager; }
}
public Location(Vector2 mapPosition)
{
this.type = LocationType.Random();
this.name = RandomName(type);
this.mapPosition = mapPosition;
if (type.HasHireableCharacters)
{
hireManager = new HireManager();
hireManager.GenerateCharacters(Character.HumanConfigFile, 10);
}
Connections = new List<LocationConnection>();
}
public static Location CreateRandom(Vector2 position)
{
return new Location(position);
}
private string RandomName(LocationType type)
{
string name = ToolBox.GetRandomLine("Content/Map/locationNames.txt");
int nameFormatIndex = Rand.Int(type.NameFormats.Count, false);
return type.NameFormats[nameFormatIndex].Replace("[name]", name);
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Subsurface
{
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 sprite;
public bool HasHireableCharacters
{
get;
private set;
}
public string Name
{
get { return name; }
}
public List<string> NameFormats
{
get { return nameFormats; }
}
public Sprite Sprite
{
get { return sprite; }
}
private LocationType(XElement element)
{
name = element.Name.ToString();
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
totalWeight += commonness;
HasHireableCharacters = ToolBox.GetAttributeBool(element, "hireablecharacters", false);
nameFormats = new List<string>();
foreach (XAttribute nameFormat in element.Element("nameformats").Attributes())
{
nameFormats.Add(nameFormat.Value.ToString());
}
string spritePath = ToolBox.GetAttributeString(element, "symbol", "Content/Map/beaconSymbol.png");
sprite = new Sprite(spritePath, new Microsoft.Xna.Framework.Vector2(0.5f, 0.5f));
//sprite.Origin = ;
}
public static LocationType Random()
{
Debug.Assert(list.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
int randInt = Rand.Int(totalWeight, false);
foreach (LocationType type in list)
{
if (randInt < type.commonness) return type;
randInt -= type.commonness;
}
return null;
}
public static void Init(string file)
{
XDocument doc = ToolBox.TryLoadXml(file);
if (doc==null)
{
return;
}
foreach (XElement element in doc.Root.Elements())
{
LocationType locationType = new LocationType(element);
list.Add(locationType);
}
}
}
}

View File

@@ -0,0 +1,441 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Voronoi2;
namespace Subsurface
{
class Map
{
Vector2 difficultyIncrease = new Vector2(5.0f,10.0f);
Vector2 difficultyCutoff = new Vector2(80.0f, 100.0f);
private List<Level> levels;
private List<Location> locations;
private List<LocationConnection> connections;
private string seed;
private int size;
private static Sprite iceTexture;
private static Texture2D iceCraters;
private static Texture2D iceCrack;
private Location currentLocation;
private Location selectedLocation;
private 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 Map(string seed, int size)
{
this.seed = seed;
this.size = size;
levels = new List<Level>();
locations = new List<Location>();
connections = new List<LocationConnection>();
if (iceTexture==null) iceTexture = new Sprite("Content/Map/iceSurface.png", Vector2.Zero);
if (iceCraters == null) iceCraters = Game1.TextureLoader.FromFile("Content/Map/iceCraters.png");
if (iceCrack == null) iceCrack = Game1.TextureLoader.FromFile("Content/Map/iceCrack.png");
Rand.SetSyncedSeed(this.seed.GetHashCode());
GenerateLocations();
currentLocation = locations[locations.Count / 2];
GenerateDifficulties(currentLocation, new List<LocationConnection> (connections), 10.0f);
foreach (LocationConnection connection in connections)
{
connection.Level = Level.CreateRandom(connection);
}
}
private void GenerateLocations()
{
Voronoi voronoi = new Voronoi(0.5f);
List<Vector2> sites = new List<Vector2>();
for (int i = 0; i < 50; i++)
{
sites.Add(new Vector2(Rand.Range(0.0f, size, false), Rand.Range(0.0f, size, false)));
}
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
sites.Clear();
foreach (GraphEdge edge in edges)
{
if (edge.point1 == edge.point2) continue;
//remove points from the edge of the map
if (edge.point1.X == 0 || edge.point1.X == size) continue;
if (edge.point1.Y == 0 || edge.point1.Y == size) continue;
if (edge.point2.X == 0 || edge.point2.X == size) continue;
if (edge.point2.Y == 0 || edge.point2.Y == size) continue;
Location[] newLocations = new Location[2];
newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2);
newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2));
for (int i = 0; i < 2; i++)
{
if (newLocations[i] != null) continue;
Vector2[] points = new Vector2[] { edge.point1, edge.point2 };
int positionIndex = Rand.Int(1,false);
Vector2 position = points[positionIndex];
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
newLocations[i] = Location.CreateRandom(position);
locations.Add(newLocations[i]);
}
int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode());
connections.Add(new LocationConnection(newLocations[0], newLocations[1]));
}
float minDistance = 50.0f;
for (int i = connections.Count - 1; i >= 0; i--)
{
LocationConnection connection = connections[i];
if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance)
{
continue;
}
locations.Remove(connection.Locations[0]);
connections.Remove(connection);
foreach (LocationConnection connection2 in connections)
{
if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1];
if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1];
}
}
foreach (LocationConnection connection in connections)
{
connection.Locations[0].Connections.Add(connection);
connection.Locations[1].Connections.Add(connection);
}
for (int i = connections.Count - 1; i >= 0; i--)
{
i = Math.Min(i, connections.Count - 1);
LocationConnection connection = connections[i];
for (int n = Math.Min(i - 1,connections.Count - 1); n >= 0; n--)
{
if (connection.Locations.Contains(connections[n].Locations[0])
&& connection.Locations.Contains(connections[n].Locations[1]))
{
connections.RemoveAt(n);
}
}
}
foreach (LocationConnection connection in connections)
{
Vector2 start = connection.Locations[0].MapPosition;
Vector2 end = connection.Locations[1].MapPosition;
int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f));
connection.CrackSegments = GenerateCrack(start, end, generations);
}
}
private void GenerateDifficulties(Location start, List<LocationConnection> locations, float currDifficulty)
{
//start.Difficulty = currDifficulty;
currDifficulty += Rand.Range(difficultyIncrease.X, difficultyIncrease.Y, false);
if (currDifficulty > Rand.Range(difficultyCutoff.X, difficultyCutoff.Y, false)) currDifficulty = 10.0f;
foreach (LocationConnection connection in start.Connections)
{
if (!locations.Contains(connection)) continue;
Location nextLocation = connection.OtherLocation(start);
locations.Remove(connection);
connection.Difficulty = currDifficulty;
GenerateDifficulties(nextLocation, locations, currDifficulty);
}
}
private List<Vector2[]> GenerateCrack(Vector2 start, Vector2 end, int generations)
{
List<Vector2[]> segments = new List<Vector2[]>();
segments.Add(new Vector2[] {start, end});
float offsetAmount = 5.0f;
for (int n = 0; n < generations; n++)
{
for (int i = 0; i < segments.Count; i++)
{
Vector2 startSegment = segments[i][0];
Vector2 endSegment = segments[i][1];
segments.RemoveAt(i);
Vector2 midPoint = (startSegment + endSegment) / 2.0f;
Vector2 normal = Vector2.Normalize(endSegment - startSegment);
normal = new Vector2(-normal.Y, normal.X);
midPoint += normal * Rand.Range(-offsetAmount, offsetAmount, false);
segments.Insert(i, new Vector2[] { startSegment, midPoint });
segments.Insert(i+1, new Vector2[] { midPoint, endSegment });
i++;
}
}
return segments;
}
public void MoveToNextLocation()
{
currentLocation = selectedLocation;
selectedLocation = null;
}
public void SetLocation(int index)
{
if (index < 0 || index >= locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
currentLocation = locations[index];
}
private Location highlightedLocation;
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float scale = 1.0f)
{
//GUI.DrawRectangle(spriteBatch, rect, Color.DarkBlue, true);
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
Vector2 offset = -currentLocation.MapPosition;
iceTexture.DrawTiled(spriteBatch, new Vector2(rect.X, rect.Y), new Vector2(rect.Width, rect.Height), offset, Color.White);
//spriteBatch.Draw(iceTexture, offset, rect, null, null, 0f, null, Color.White, SpriteEffects.None, 0.0f);
//Vector2 scale = new Vector2((float)rect.Width/ size, (float)rect.Height/size);
float maxDist = 20.0f;
float closestDist = 0.0f;
highlightedLocation = null;
for (int i = 0; i < locations.Count;i++ )
{
Location location = locations[i];
Vector2 pos = rectCenter + (location.MapPosition+offset) * scale;
if (!rect.Contains(pos)) continue;
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
if (dist < maxDist && (highlightedLocation == null || dist < closestDist))
{
closestDist = dist;
highlightedLocation = location;
}
}
foreach (LocationConnection connection in connections)
{
Color crackColor = Color.White * Math.Max(connection.Difficulty/100.0f, 0.5f);
if (highlightedLocation != currentLocation &&
connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation))
{
crackColor = Color.Red;
if (PlayerInput.LeftButtonClicked()&&
selectedLocation != highlightedLocation && highlightedLocation != null)
{
selectedConnection = connection;
selectedLocation = highlightedLocation;
Game1.LobbyScreen.SelectLocation(highlightedLocation, connection);
}
}
if (selectedLocation != currentLocation &&
(connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation)))
{
crackColor = Color.Red;
}
foreach (Vector2[] segment in connection.CrackSegments)
{
Vector2 start = rectCenter + (segment[0] + offset) * scale;
Vector2 end = rectCenter + (segment[1] + offset) * scale;
if (!rect.Contains(start) || !rect.Contains(end)) continue;
float dist = Vector2.Distance(start, end);
spriteBatch.Draw(iceCrack,
new Rectangle((int)start.X, (int)start.Y, (int)dist+2, 30),
new Rectangle(0, 0, iceCrack.Width, 60), crackColor, MathUtils.VectorToAngle(end -start),
new Vector2(0, 30), SpriteEffects.None, 0.01f);
}
}
for (int i = 0; i < locations.Count; i++)
{
Location location = locations[i];
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
if (!rect.Contains(pos)) continue;
Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation))==null ? Color.White : Color.Green;
color *= (location.Discovered) ? 0.8f : 0.4f;
if (location == currentLocation) color = Color.Orange;
location.Type.Sprite.Draw(spriteBatch, pos, color, 0.0f, scale/3.0f);
//int imgIndex = i % 16;
//int xCell = imgIndex % 4;
//int yCell = (int)Math.Floor(imgIndex / 4.0f);
//spriteBatch.Draw(iceCraters, pos,
// new Rectangle(xCell * 64, yCell * 64, 64, 64),
// Color.White, i,
// new Vector2(32, 32), 0.5f*scale, SpriteEffects.None, 0.0f);
}
for (int i = 0; i < 3; i++ )
{
Location location = (i == 0) ? highlightedLocation : selectedLocation;
if (i == 2) location = currentLocation;
if (location == null) continue;
Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
if (highlightedLocation == location)
{
spriteBatch.DrawString(GUI.Font, location.Name, pos + new Vector2(0, 50), Color.DarkRed, 0.0f, GUI.Font.MeasureString(location.Name)/2.0f, 1.0f, SpriteEffects.None, 0.0f);
}
}
}
}
class LocationConnection
{
private Location[] locations;
private Level level;
public float Difficulty;
public List<Vector2[]> CrackSegments;
private int questsCompleted;
private Quest quest;
public Quest Quest
{
get
{
if (quest==null || quest.Completed)
{
if (quest !=null && quest.Completed) questsCompleted++;
int seed = (int)locations[0].MapPosition.X + (int)locations[0].MapPosition.Y * 100;
seed += (int)locations[1].MapPosition.X*10000 + (int)locations[1].MapPosition.Y * 1000000;
Random rand = new Random(seed + questsCompleted);
if (rand.NextDouble() < 0.3f) return null;
quest = Quest.LoadRandom(locations, rand);
}
return quest;
}
}
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 };
questsCompleted = 0;
}
public Location OtherLocation(Location location)
{
if (locations[0] == location)
{
return locations[1];
}
else if (locations[1] == location)
{
return locations[0];
}
else
{
return null;
}
}
}
}

View File

@@ -0,0 +1,471 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.ObjectModel;
namespace Subsurface
{
class MapEntity : Entity
{
public static List<MapEntity> mapEntityList = new List<MapEntity>();
//which entities have been selected for editing
protected static List<MapEntity> selectedList = new List<MapEntity>();
protected static GUIComponent editingHUD;
protected static Vector2 selectionPos = Vector2.Zero;
protected static Vector2 selectionSize = Vector2.Zero;
protected static Vector2 startMovingPos = Vector2.Zero;
protected List<int> 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;
//is the mouse inside the rect
protected bool isHighlighted;
protected bool isSelected;
private static bool disableSelect;
public static bool DisableSelect
{
get { return disableSelect; }
set {
disableSelect = value;
if (disableSelect==true)
{
startMovingPos = Vector2.Zero;
selectionSize = Vector2.Zero;
selectionPos = Vector2.Zero;
}
}
}
public bool MoveWithLevel
{
get;
set;
}
//the position and dimensions of the entity
protected Rectangle rect;
public virtual Rectangle Rect {
get { return rect; }
set { rect = value; }
}
public virtual Sprite sprite
{
get { return null; }
}
public virtual bool IsLinkable
{
get { return false; }
}
public virtual 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 { aiTarget.SoundRange = value; }
}
public float SightRange
{
get
{
if (aiTarget == null) return 0.0f;
return aiTarget.SightRange;
}
set { aiTarget.SightRange = value; }
}
public bool IsHighlighted {
get { return isHighlighted; }
set { isHighlighted = value; }
}
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; }
}
public virtual string Name
{
get { return ""; }
}
public virtual void Move(Vector2 amount)
{
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
}
public virtual bool Contains(Vector2 position)
{
return (Submarine.RectContains(rect, position));
}
public virtual void Draw(SpriteBatch spriteBatch, bool editing) {}
public override void Remove()
{
base.Remove();
mapEntityList.Remove(this);
if (aiTarget != null) aiTarget.Remove();
if (linkedTo != null)
{
foreach (MapEntity e in linkedTo)
{
e.RemoveLinked(this);
}
linkedTo.Clear();
}
}
/// <summary>
/// Call Update() on every object in Entity.list
/// </summary>
public static void UpdateAll(Camera cam, float deltaTime)
{
foreach (Item item in Item.itemList)
{
item.Updated = false;
}
for (int i = 0; i < mapEntityList.Count; i++)
{
mapEntityList[i].Update(cam, deltaTime);
}
}
public virtual void Update(Camera cam, float deltaTime) { }
/// <summary>
/// Update the selection logic in editmap-screen
/// </summary>
public static void UpdateSelecting(Camera cam)
{
if (GUIComponent.MouseOn != null) return;
if (DisableSelect)
{
DisableSelect = false;
return;
}
foreach (MapEntity e in mapEntityList)
{
e.isHighlighted = false;
e.isSelected = false;
}
if (MapEntityPrefab.Selected != null)
{
selectionPos = Vector2.Zero;
selectedList.Clear();
return;
}
if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.Delete))
{
foreach (MapEntity e in selectedList) e.Remove();
selectedList.Clear();
}
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
position = cam.ScreenToWorld(position);
MapEntity highLightedEntity = null;
foreach (MapEntity e in mapEntityList)
{
if (highLightedEntity == null || e.sprite == null ||
(highLightedEntity.sprite!=null && e.sprite.Depth < highLightedEntity.sprite.Depth))
{
if (e.Contains(position)) highLightedEntity = e;
}
e.isSelected = false;
}
if (highLightedEntity != null)
highLightedEntity.isHighlighted = true;
foreach (MapEntity e in selectedList)
{
e.isSelected = true;
}
//started moving selected entities
if (startMovingPos != Vector2.Zero)
{
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
{
//mouse released -> move the entities to the new position of the mouse
Vector2 moveAmount = position - startMovingPos;
moveAmount = Submarine.VectorToWorldGrid(moveAmount);
if (moveAmount != Vector2.Zero)
{
foreach (MapEntity e in selectedList)
e.Move(moveAmount);
}
startMovingPos = Vector2.Zero;
}
}
//started dragging a "selection rectangle"
else if (selectionPos != Vector2.Zero)
{
selectionSize.X = position.X - selectionPos.X;
selectionSize.Y = selectionPos.Y - position.Y;
List<MapEntity> newSelection = new List<MapEntity>();// FindSelectedEntities(selectionPos, selectionSize);
if (Math.Abs(selectionSize.X) > Submarine.GridSize.X || Math.Abs(selectionSize.Y) > Submarine.GridSize.Y)
{
newSelection = FindSelectedEntities(selectionPos, selectionSize);
}
else
{
if (highLightedEntity != null) newSelection.Add(highLightedEntity);
}
foreach (MapEntity e in newSelection)
{
e.isHighlighted = true;
}
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
{
if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.LeftControl) ||
PlayerInput.GetKeyboardState.IsKeyDown(Keys.RightControl))
{
foreach (MapEntity e in newSelection)
{
bool alreadySelected = false;
foreach (MapEntity e2 in selectedList)
{
if (e.ID == e2.ID) alreadySelected = true;
}
if (alreadySelected)
selectedList.Remove(e);
else
selectedList.Add(e);
}
}
else
{
selectedList = newSelection;
}
selectionPos = Vector2.Zero;
Debug.WriteLine("zero");
selectionSize = Vector2.Zero;
}
}
//default, not doing anything specific yet
else
{
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed &&
PlayerInput.GetKeyboardState.IsKeyUp(Keys.Space))
{
//if clicking a selected entity, start moving it
foreach (MapEntity e in selectedList)
{
if (e.Contains(position)) startMovingPos = position;
}
selectionPos = position;
Debug.WriteLine("pos");
}
}
}
/// <summary>
/// Draw the "selection rectangle" and outlines of entities that are being dragged (if any)
/// </summary>
public static void DrawSelecting(SpriteBatch spriteBatch, Camera cam)
{
if (GUIComponent.MouseOn != null) return;
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
position = cam.ScreenToWorld(position);
if (startMovingPos != Vector2.Zero)
{
Vector2 moveAmount = position - startMovingPos;
moveAmount = Submarine.VectorToWorldGrid(moveAmount);
moveAmount.Y = -moveAmount.Y;
//started moving the selected entities
if (moveAmount != Vector2.Zero)
{
foreach (MapEntity e in selectedList)
GUI.DrawRectangle(spriteBatch,
new Vector2(e.rect.X, -e.rect.Y) + moveAmount,
new Vector2(e.rect.Width, e.rect.Height),
Color.DarkRed);
//stop dragging the "selection rectangle"
selectionPos = Vector2.Zero;
}
}
if (selectionPos != null && selectionPos != Vector2.Zero)
{
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed);
}
}
/// <summary>
/// Call DrawEditing() if only one entity is selected
/// </summary>
public static void Edit(SpriteBatch spriteBatch, Camera cam)
{
if (selectedList.Count == 1)
{
selectedList[0].DrawEditing(spriteBatch, cam);
}
else
{
editingHUD = null;
}
}
public static void SelectEntity(MapEntity entity)
{
foreach (MapEntity e in selectedList)
{
e.isSelected = false;
}
selectedList.Clear();
entity.isSelected = true;
selectedList.Add(entity);
}
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) {}
public static List<MapEntity> FindMapEntities(Vector2 pos)
{
List<MapEntity> foundEntities = new List<MapEntity>();
foreach (MapEntity e in mapEntityList)
{
if (Submarine.RectContains(e.rect, pos)) foundEntities.Add(e);
}
return foundEntities;
}
public static MapEntity FindMapEntity(Vector2 pos)
{
foreach (MapEntity e in mapEntityList)
{
if (Submarine.RectContains(e.rect, pos)) return e;
}
return null;
}
/// <summary>
/// Find entities whose rect intersects with the "selection rect"
/// </summary>
public static List<MapEntity> FindSelectedEntities(Vector2 pos, Vector2 size)
{
List<MapEntity> foundEntities = new List<MapEntity>();
Rectangle selectionRect = Submarine.AbsRect(pos, size);
foreach (MapEntity e in mapEntityList)
{
if (Submarine.RectsOverlap(selectionRect, e.rect))
foundEntities.Add(e);
}
return foundEntities;
}
public virtual XElement Save(XDocument doc)
{
DebugConsole.ThrowError("Saving entity " + GetType() + " failed.");
return null;
}
/// <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 LinkAll()
{
foreach (MapEntity e in mapEntityList)
{
if (e.linkedToID == null) continue;
if (e.linkedToID.Count == 0) continue;
e.linkedTo.Clear();
foreach (int i in e.linkedToID)
{
MapEntity linked = FindEntityByID(i) as MapEntity;
if (linked != null)
e.linkedTo.Add(linked);
}
}
}
public void RemoveLinked(MapEntity e)
{
if (linkedTo == null) return;
if (linkedTo.Contains(e)) linkedTo.Remove(e);
}
}
}

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Subsurface
{
class MapEntityPrefab
{
public static List<MapEntityPrefab> list = new List<MapEntityPrefab>();
protected string name;
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
protected bool resizeHorizontal;
protected bool resizeVertical;
//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; }
}
public virtual bool IsLinkable
{
get { return isLinkable; }
}
public bool ResizeHorizontal
{
get { return resizeHorizontal; }
}
public bool ResizeVertical
{
get { return resizeVertical; }
}
public int Price
{
get { return price; }
}
public static void Init()
{
MapEntityPrefab ep = new MapEntityPrefab();
ep.name = "hull";
ep.constructor = typeof(Hull).GetConstructor(new Type[] { typeof(Rectangle) });
ep.resizeHorizontal = true;
ep.resizeVertical = true;
list.Add(ep);
ep = new MapEntityPrefab();
ep.name = "gap";
ep.constructor = typeof(Gap).GetConstructor(new Type[] { 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(Rectangle) });
list.Add(ep);
}
public virtual void UpdatePlacing(SpriteBatch spriteBatch, Camera cam)
{
Vector2 placeSize = Submarine.GridSize;
if (placePosition == Vector2.Zero)
{
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed)
placePosition = Submarine.MouseToWorldGrid(cam);
}
else
{
Vector2 position = Submarine.MouseToWorldGrid(cam);
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 (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
{
object[] lobject = new object[] { newRect };
constructor.Invoke(lobject);
placePosition = Vector2.Zero;
selected = null;
}
newRect.Y = -newRect.Y;
GUI.DrawRectangle(spriteBatch, newRect, Color.DarkBlue);
}
if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed)
{
placePosition = Vector2.Zero;
selected = null;
}
}
public static bool SelectPrefab(object selection)
{
if ((selected = selection as MapEntityPrefab) != null)
{
placePosition = Vector2.Zero;
return true;
}
else
{
return false;
}
}
//a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected
public static object GetSelected()
{
return (object)selected;
}
public void DrawListLine(SpriteBatch spriteBatch, Vector2 pos, Color color)
{
spriteBatch.DrawString(GUI.Font, name, pos, color);
}
}
}

View File

@@ -0,0 +1,95 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Subsurface
{
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)
{
string docString = Regex.Replace(doc.ToString(), @"\s+", "");
byte[] inputBytes = Encoding.ASCII.GetBytes(docString);
hash = CalculateHash(inputBytes);
shortHash = GetShortHash(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();
}
private string GetShortHash(string fullHash)
{
return fullHash;
}
}
}

View File

@@ -0,0 +1,630 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 Subsurface.Networking;
using Subsurface.Lights;
namespace Subsurface
{
class WallSection
{
public Rectangle rect;
public float damage;
public Gap gap;
public bool isHighLighted;
public WallSection(Rectangle rect)
{
this.rect = rect;
damage = 0.0f;
}
public WallSection(Rectangle rect, float damage)
{
this.rect = rect;
this.damage = 0.0f;
}
}
class Structure : MapEntity, IDamageable
{
static int wallSectionSize = 100;
public static List<Structure> wallList = new List<Structure>();
ConvexHull convexHull;
StructurePrefab prefab;
//farseer physics bodies, separated by gaps
List<Body> bodies;
//sections of the wall that are supposed to be rendered
private WallSection[] sections;
bool isHorizontal;
public override Sprite sprite
{
get { return prefab.sprite; }
}
public bool IsPlatform
{
get { return prefab.IsPlatform; }
}
public Direction StairDirection
{
get { return prefab.StairDirection; }
}
public override string Name
{
get { return "structure"; }
}
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 void Move(Vector2 amount)
{
base.Move(amount);
for (int i = 0; i < sections.Count(); 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 (convexHull!=null)
{
convexHull.Move(amount);
}
//if (gaps != null)
//{
// foreach (Gap g in gaps)
// {
// g.Move(amount);
// //g.position.X += amount.X;
// //g.position.Y -= amount.Y;
// }
//}
}
public Structure(Rectangle rectangle, StructurePrefab sp)
{
if (rectangle.Width == 0 || rectangle.Height == 0) return;
rect = rectangle;
prefab = sp;
isHorizontal = (rect.Width>rect.Height);
if (prefab.HasBody)
{
bodies = new List<Body>();
//gaps = new List<Gap>();
Body newBody = BodyFactory.CreateRectangle(Game1.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);
int xsections = 1;
int ysections = 1;
int width, height;
if (isHorizontal)
{
xsections = (int)Math.Ceiling((float)rect.Width / wallSectionSize);
sections = new WallSection[xsections];
width = (int)wallSectionSize;
height = rect.Height;
}
else
{
ysections = (int)Math.Ceiling((float)rect.Height / wallSectionSize);
sections = new WallSection[ysections];
width = rect.Width;
height = (int)wallSectionSize;
}
for (int x = 0; x < xsections; x++ )
{
for (int y = 0; y < ysections; y++)
{
Rectangle sectionRect = new Rectangle(rect.X + x * width, rect.Y - y * height, width, height);
sectionRect.Width -= (int)Math.Max((sectionRect.X + sectionRect.Width) - (rect.X + rect.Width), 0.0f);
sectionRect.Height -= (int)Math.Max((rect.Y - rect.Height)-(sectionRect.Y - sectionRect.Height), 0.0f);
sections[x+y] = new WallSection(sectionRect);
}
}
}
else
{
sections = new WallSection[1];
sections[0] = new WallSection(rect);
if (StairDirection!=Direction.None)
{
bodies = new List<Body>();
Body newBody = BodyFactory.CreateRectangle(Game1.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);
//newBody = BodyFactory.CreateRectangle(Game1.World,
// ConvertUnits.ToSimUnits(Submarine.GridSize.X*2),
// ConvertUnits.ToSimUnits(10.0f),
// 1.5f);
//newBody.BodyType = BodyType.Static;
////newBody.IsSensor = true;
//newBody.Position = ConvertUnits.ToSimUnits(
// new Vector2(Position.X + (rect.Width/2 + Submarine.GridSize.X) * ((StairDirection == Direction.Right) ? -1.0f : 1.0f), rect.Y + 5.0f));
////newBody.Rotation = (StairDirection == Direction.Right) ? MathHelper.PiOver4 : -MathHelper.PiOver4;
////newBody.Friction = 0.8f;
//newBody.CollisionCategories = Physics.CollisionStairs;
//newBody.UserData = this;
//bodies.Add(newBody);
}
}
if (prefab.CastShadow)
{
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(rect.X, rect.Y - rect.Height);
corners[1] = new Vector2(rect.X, rect.Y);
corners[2] = new Vector2(rect.Right, rect.Y);
corners[3] = new Vector2(rect.Right, rect.Y - rect.Height);
convexHull = new ConvexHull(corners, Color.Black);
}
mapEntityList.Add(this);
}
public override void Remove()
{
base.Remove();
if (wallList.Contains(this)) wallList.Remove(this);
if (bodies != null)
{
foreach (Body b in bodies)
Game1.World.RemoveBody(b);
}
if (convexHull != null) convexHull.Remove();
}
public override void Draw(SpriteBatch spriteBatch, bool editing)
{
if (prefab.sprite == null) return;
Color color = (isHighlighted) ? Color.Green : Color.White;
if (isSelected && editing) color = Color.Red;
prefab.sprite.DrawTiled(spriteBatch, new Vector2(rect.X, -rect.Y), new Vector2(rect.Width, rect.Height), Vector2.Zero, color);
foreach (WallSection s in sections)
{
if (s.isHighLighted)
GUI.DrawRectangle(spriteBatch,
new Rectangle((int)s.rect.X, (int)-s.rect.Y, (int)s.rect.Width, (int)s.rect.Height),
new Color((s.damage / prefab.MaxHealth), 1.0f - (s.damage / prefab.MaxHealth), 0.0f, 1.0f), true);
s.isHighLighted = false;
if (s.damage == 0.0f) continue;
GUI.DrawRectangle(spriteBatch,
new Rectangle((int)s.rect.X, (int)-s.rect.Y, (int)s.rect.Width, (int)s.rect.Height),
Color.Black * (s.damage / prefab.MaxHealth), true);
}
}
private bool OnWallCollision(Fixture f1, Fixture f2, Contact contact)
{
//Structure structure = f1.Body.UserData as Structure;
//if (f2.Body.UserData as Item != null)
//{
// if (prefab.IsPlatform || prefab.StairDirection != Direction.None) return false;
//}
if (prefab.IsPlatform)
{
Limb limb;
if ((limb = f2.Body.UserData as Limb) != null)
{
if (limb.character.AnimController.IgnorePlatforms) return false;
}
}
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;
AmbientSoundManager.PlayDamageSound(DamageSoundType.StructureBlunt, impact,
new Vector2(
sections[section].rect.X + sections[section].rect.Width / 2,
sections[section].rect.Y - sections[section].rect.Height / 2));
AddDamage(section, impact);
}
}
return true;
}
public void HighLightSection(int sectionIndex)
{
sections[sectionIndex].isHighLighted = true;
}
public bool SectionHasHole(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return false;
return (sections[sectionIndex].damage>=prefab.MaxHealth);
}
public void AddDamage(int sectionIndex, float damage)
{
if (!prefab.HasBody || prefab.IsPlatform) return;
if (Game1.Client==null)
SetDamage(sectionIndex, sections[sectionIndex].damage + damage);
}
public int FindSectionIndex(Vector2 pos)
{
int index = (isHorizontal) ?
(int)Math.Floor((pos.X - rect.X) / wallSectionSize) :
(int)Math.Floor((rect.Y - pos.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)
{
if (sectionIndex < 0 || sectionIndex >= sections.Length) return Vector2.Zero;
return new Vector2(
sections[sectionIndex].rect.X + sections[sectionIndex].rect.Width / 2.0f,
sections[sectionIndex].rect.Y - sections[sectionIndex].rect.Height / 2.0f);
}
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound = false)
{
if (!prefab.HasBody || prefab.IsPlatform) return new AttackResult(0.0f, 0.0f);
int i = FindSectionIndex(ConvertUnits.ToDisplayUnits(position));
if (i == -1) return new AttackResult(0.0f, 0.0f);
Game1.ParticleManager.CreateParticle("dustcloud", ConvertUnits.ToSimUnits(SectionPosition(i)), 0.0f, 0.0f);
if (playSound && !SectionHasHole(i))
{
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash;
AmbientSoundManager.PlayDamageSound(damageSoundType, amount, position);
}
AddDamage(i, amount);
return new AttackResult(amount, 0.0f);
}
private void SetDamage(int sectionIndex, float damage)
{
if (!prefab.HasBody) return;
if (damage != sections[sectionIndex].damage)
new NetworkEvent(ID, false);
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;
}
}
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);
}
}
if (sections[sectionIndex].gap != null)
sections[sectionIndex].gap.Open = (float)Math.Pow(((damage / prefab.MaxHealth)-0.5)*2.0, 2.0);
bool hadHole = SectionHasHole(sectionIndex);
sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, prefab.MaxHealth);
bool hasHole = SectionHasHole(sectionIndex);
if (hadHole != hasHole) UpdateSections();
}
private void UpdateSections()
{
foreach (Body b in bodies)
{
Game1.World.RemoveBody(b);
}
bodies.Clear();
int x = sections[0].rect.X;
int y = sections[0].rect.Y;
int width = sections[0].rect.Width;
int height = sections[0].rect.Height;
bool hasHoles = false;
for (int i = 1; i < sections.Length; i++)
{
bool hasHole = SectionHasHole(i);
if (hasHole) hasHoles = true;
if (hasHole || i == sections.Length - 1)
{
if (width > 0 && height > 0)
{
CreateRectBody(new Rectangle(x, y, width, height));
}
if (isHorizontal)
{
x = sections[i].rect.X+ sections[i].rect.Width;
width = 0;
}
else
{
y = sections[i].rect.Y - sections[i].rect.Height;
height = 0;
}
}
else
{
if (isHorizontal)
{
width += sections[i].rect.Width;
}
else
{
height += sections[i].rect.Height;
}
}
}
if (hasHoles)
{
CreateRectBody(rect).IsSensor = true;
}
}
private Body CreateRectBody(Rectangle rect)
{
Body newBody = BodyFactory.CreateRectangle(Game1.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 override XElement Save(XDocument doc)
{
XElement element = new XElement("Structure");
element.Add(new XAttribute("name", prefab.Name),
new XAttribute("ID", ID),
new XAttribute("rect", rect.X + "," + rect.Y+","+rect.Width+","+rect.Height));
for (int i = 0; i < sections.Count(); i++)
{
if (sections[i].damage == 0.0f) continue;
element.Add(new XElement("section",
new XAttribute("i", i),
new XAttribute("damage", sections[i].damage)));
}
doc.Root.Add(element);
return element;
}
public static void Load(XElement element)
{
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;
Debug.WriteLine(name+" - "+rect);
Structure s = null;
foreach (MapEntityPrefab ep in MapEntityPrefab.list)
{
if (ep.Name == name)
{
s = new Structure(rect, (StructurePrefab)ep);
s.ID = 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":
if (subElement.Attribute("i") == null) continue;
s.sections[int.Parse(subElement.Attribute("i").Value)].damage =
ToolBox.GetAttributeFloat(subElement, "damage", 0.0f);
break;
}
}
}
public override void FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data)
{
for (int i = 0; i < sections.Length; i++ )
{
message.Write(sections[i].damage);
}
}
public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message)
{
for (int i = 0; i < sections.Length; i++)
{
float damage = message.ReadFloat();
if (damage != sections[i].damage) SetDamage(i, damage);
}
}
}
}

View File

@@ -0,0 +1,161 @@
using System;
using System.Diagnostics;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
namespace Subsurface
{
class StructurePrefab : MapEntityPrefab
{
//public static List<StructurePrefab> list = new List<StructurePrefab>();
//does the structure have a physics body
bool hasBody;
bool castShadow;
bool isPlatform;
Direction stairDirection;
float maxHealth;
//default size
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 static void LoadAll(List<string> filePaths)
{
foreach (string filePath in filePaths)
{
XDocument doc = ToolBox.TryLoadXml(filePath);
if (doc == null) return;
foreach (XElement el in doc.Root.Elements())
{
StructurePrefab sp = Load(el);
Debug.WriteLine(sp.name);
list.Add(sp);
}
}
}
public static StructurePrefab Load(XElement el)
{
StructurePrefab sp = new StructurePrefab();
sp.name = el.Name.ToString();
Vector4 sourceVector = ToolBox.GetAttributeVector4(el, "sourcerect", new Vector4(0,0,1,1));
Rectangle sourceRect = new Rectangle(
(int)sourceVector.X,
(int)sourceVector.Y,
(int)sourceVector.Z,
(int)sourceVector.W);
if (el.Attribute("sprite") != null)
{
sp.sprite = new Sprite(el.Attribute("sprite").Value, sourceRect, Vector2.Zero);
sp.sprite.Depth = ToolBox.GetAttributeFloat(el, "depth", 0.0f);
if (ToolBox.GetAttributeBool(el, "fliphorizontal", false)) sp.sprite.effects = SpriteEffects.FlipHorizontally;
if (ToolBox.GetAttributeBool(el, "flipvertical", false)) sp.sprite.effects = SpriteEffects.FlipVertically;
}
sp.size = Vector2.Zero;
sp.size.X = ToolBox.GetAttributeFloat(el, "width", 0.0f);
sp.size.Y = ToolBox.GetAttributeFloat(el, "height", 0.0f);
sp.maxHealth = ToolBox.GetAttributeFloat(el, "health", 100.0f);
sp.resizeHorizontal = ToolBox.GetAttributeBool(el, "resizehorizontal", false);
sp.resizeVertical = ToolBox.GetAttributeBool(el, "resizevertical", false);
sp.isPlatform = ToolBox.GetAttributeBool(el, "platform", false);
sp.stairDirection = (Direction)Enum.Parse(typeof(Direction), ToolBox.GetAttributeString(el, "stairdirection", "None"));
sp.castShadow = ToolBox.GetAttributeBool(el, "castshadow", false);
sp.hasBody = ToolBox.GetAttributeBool(el, "body", false);
return sp;
}
public override void UpdatePlacing(SpriteBatch spriteBatch, Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam);
//Vector2 placeSize = size;
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
if (placePosition == Vector2.Zero)
{
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed)
placePosition = Submarine.MouseToWorldGrid(cam);
newRect.X = (int)position.X;
newRect.Y = (int)position.Y;
//sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y), placeSize, Color.White);
}
else
{
Vector2 placeSize = size;
if (resizeHorizontal) placeSize.X = position.X - placePosition.X;
if (resizeVertical) placeSize.Y = placePosition.Y - position.Y;
newRect = Submarine.AbsRect(placePosition, placeSize);
//newRect.Width = (int)Math.Max(newRect.Width, Map.gridSize.X);
//newRect.Height = (int)Math.Max(newRect.Height, Map.gridSize.Y);
if (PlayerInput.GetMouseState.LeftButton == ButtonState.Released)
{
new Structure(newRect, this);
selected = null;
return;
}
//position = placePosition;
}
sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - Game1.GraphicsWidth, -newRect.Y, newRect.Width + Game1.GraphicsWidth*2, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - Game1.GraphicsHeight, newRect.Width, newRect.Height + Game1.GraphicsHeight*2), Color.White);
if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null;
}
}
}

View File

@@ -0,0 +1,891 @@
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Subsurface.Items.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Voronoi2;
namespace Subsurface
{
public enum Direction : byte
{
None = 0, Left = 1, Right = 2
}
class Submarine : Entity
{
public static List<Submarine> SavedSubmarines = new List<Submarine>();
public static readonly Vector2 GridSize = new Vector2(16.0f, 16.0f);
private static Submarine loaded;
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
static string SaveFolder;
Md5Hash hash;
Vector2 speed;
Vector2 targetPosition;
private Rectangle borders;
private Body hullBody;
private string filePath;
private string name;
private double lastNetworkUpdate;
//properties ----------------------------------------------------
public string Name
{
get { return name; }
}
public static Vector2 LastPickedPosition
{
get { return lastPickedPosition; }
}
public static float LastPickedFraction
{
get { return lastPickedFraction; }
}
public List<Vector2> HullVertices
{
get;
private set;
}
public Md5Hash Hash
{
get
{
if (hash != null) return hash;
XDocument doc = OpenDoc(filePath);
hash = new Md5Hash(doc);
return hash;
}
}
public static Submarine Loaded
{
get { return loaded; }
}
public static Rectangle Borders
{
get
{
return (loaded==null) ? Rectangle.Empty : loaded.borders;
}
}
public Vector2 Center
{
get { return new Vector2(borders.X+borders.Width/2, borders.Y - borders.Height/2); }
}
public Vector2 Position
{
get { return (Level.Loaded == null) ? Vector2.Zero : -Level.Loaded.Position; }
}
public Vector2 Speed
{
get { return speed; }
}
public string FilePath
{
get { return filePath; }
}
//constructors & generation ----------------------------------------------------
public Submarine(string filePath, string hash = "")
{
this.filePath = filePath;
try
{
name = System.IO.Path.GetFileNameWithoutExtension(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading map " + filePath + "!", e);
}
if (hash != "")
{
this.hash = new Md5Hash(hash);
}
else
{
//XDocument doc = OpenDoc(filePath);
//string md5Hash = ToolBox.GetAttributeString(doc.Root, "md5hash", "");
//if (md5Hash == "" || md5Hash.Length < 16)
//{
// DebugConsole.ThrowError("Couldn't find a valid MD5 hash in the map file");
//}
//this.mapHash = new MapHash(md5Hash);
}
base.Remove();
ID = -1;
}
private List<Vector2> GenerateConvexHull()
{
List<Vector2> points = new List<Vector2>();
Vector2 leftMost = Vector2.Zero;
foreach (Structure wall in Structure.wallList)
{
for (int x = -1; x <= 1; x += 2)
{
for (int y = -1; y <= 1; y += 2)
{
Vector2 corner = new Vector2(wall.Rect.X + wall.Rect.Width / 2.0f, wall.Rect.Y - wall.Rect.Height / 2.0f);
corner.X += x * wall.Rect.Width / 2.0f;
corner.Y += y * wall.Rect.Height / 2.0f;
if (points.Contains(corner)) continue;
points.Add(corner);
if (leftMost == Vector2.Zero || corner.X < leftMost.X) leftMost = corner;
}
}
}
List<Vector2> hullPoints = new List<Vector2>();
Vector2 currPoint = leftMost;
Vector2 endPoint;
do
{
hullPoints.Add(currPoint);
endPoint = points[0];
for (int i = 1; i < points.Count; i++)
{
if ((currPoint == endPoint)
|| (Orientation(currPoint, endPoint, points[i]) == -1))
{
endPoint = points[i];
}
}
currPoint = endPoint;
}
while (endPoint != hullPoints[0]);
return hullPoints;
}
private static int Orientation(Vector2 p1, Vector2 p2, Vector2 p)
{
// Determinant
float Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
//drawing ----------------------------------------------------
public static void Draw(SpriteBatch spriteBatch, bool editing = false)
{
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++ )
{
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
}
}
public static void DrawFront(SpriteBatch spriteBatch, bool editing = false)
{
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++)
{
if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth < 0.5f)
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
}
if (loaded == null) return;
//foreach (HullBody hb in loaded.hullBodies)
//{
// spriteBatch.Draw(
// hb.shapeTexture,
// ConvertUnits.ToDisplayUnits(new Vector2(hb.body.Position.X, -hb.body.Position.Y)),
// null,
// Color.White,
// -hb.body.Rotation,
// new Vector2(hb.shapeTexture.Width / 2, hb.shapeTexture.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
//}
}
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false)
{
for (int i = 0; i < MapEntity.mapEntityList.Count(); i++)
{
if (MapEntity.mapEntityList[i].sprite == null || MapEntity.mapEntityList[i].sprite.Depth >= 0.5f)
MapEntity.mapEntityList[i].Draw(spriteBatch, editing);
}
}
//math/physics stuff ----------------------------------------------------
public static Vector2 MouseToWorldGrid(Camera cam)
{
Vector2 position = new Vector2(PlayerInput.GetMouseState.X, PlayerInput.GetMouseState.Y);
position = cam.ScreenToWorld(position);
return VectorToWorldGrid(position);
}
public static Vector2 VectorToWorldGrid(Vector2 position)
{
position.X = (float)Math.Floor(Convert.ToDouble(position.X / GridSize.X)) * GridSize.X;
position.Y = (float)Math.Ceiling(Convert.ToDouble(position.Y / GridSize.Y)) * GridSize.Y;
return position;
}
public static Rectangle AbsRect(Vector2 pos, Vector2 size)
{
if (size.X < 0.0f)
{
pos.X += size.X;
size.X = -size.X;
}
if (size.Y < 0.0f)
{
pos.Y -= size.Y;
size.Y = -size.Y;
}
return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);
}
public static bool RectContains(Rectangle rect, Vector2 pos)
{
return (pos.X > rect.X && pos.X < rect.X + rect.Width
&& pos.Y < rect.Y && pos.Y > rect.Y - rect.Height);
}
public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true)
{
if (inclusive)
{
return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
}
else
{
return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
}
}
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null)
{
float closestFraction = 1.0f;
Body closestBody = null;
Game1.World.RayCast((fixture, point, normal, fraction) =>
{
if (fixture == null || fixture.CollisionCategories == Category.None) return -1;
if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1;
Structure structure = fixture.Body.UserData as Structure;
if (structure != null && (structure.IsPlatform || !structure.HasBody)) return -1;
if (fraction < closestFraction)
{
closestFraction = fraction;
if (fixture.Body!=null) closestBody = fixture.Body;
}
return fraction;
}
, rayStart, rayEnd);
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
return closestBody;
}
/// <summary>
/// check visibility between two points (in sim units)
/// </summary>
/// <returns>a physics body that was between the points (or null)</returns>
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd)
{
Body closestBody = null;
float closestFraction = 1.0f;
if (Vector2.Distance(rayStart, rayEnd) < 0.01f)
{
closestFraction = 0.01f;
return null;
}
Game1.World.RayCast((fixture, point, normal, fraction) =>
{
if (fixture == null || fixture.CollisionCategories != Physics.CollisionWall) return -1;
Structure structure = fixture.Body.UserData as Structure;
if (structure != null)
{
if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1;
int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point));
if (sectionIndex > -1 && structure.SectionHasHole(sectionIndex)) return -1;
}
if (fraction < closestFraction)
{
closestBody = fixture.Body;
closestFraction = fraction;
}
return closestFraction;
}
, rayStart, rayEnd);
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
return closestBody;
}
public static Body PickBody(Vector2 point)
{
Body foundBody = null;
AABB aabb = new AABB(point, point);
Game1.World.QueryAABB(p =>
{
foundBody = p.Body;
return true;
}, ref aabb);
return foundBody;
}
public static bool InsideWall(Vector2 point)
{
Body foundBody = PickBody(point);
if (foundBody==null) return false;
Structure wall = foundBody.UserData as Structure;
if (wall == null || wall.IsPlatform) return false;
return true;
}
//movement ----------------------------------------------------
float collisionRigidness = 1.0f;
public void Update(float deltaTime)
{
Vector2 translateAmount = speed * deltaTime;
translateAmount += ConvertUnits.ToDisplayUnits(hullBody.Position) * collisionRigidness;
if (targetPosition != Vector2.Zero && Vector2.Distance(targetPosition, Position) > 50.0f)
{
translateAmount += (targetPosition - Position) * 0.01f;
}
else
{
targetPosition = Vector2.Zero;
}
Translate(translateAmount);
ApplyForce(CalculateBuoyancy());
float dragCoefficient = 0.00001f;
float speedLength = speed.Length();
float drag = speedLength * speedLength * dragCoefficient * mass;
if (speed != Vector2.Zero)
{
ApplyForce(-Vector2.Normalize(speed) * drag);
}
//hullBodies[0].body.LinearVelocity = -hullBodies[0].body.Position;
//hullBody.SetTransform(Vector2.Zero , 0.0f);
hullBody.LinearVelocity = -hullBody.Position/(float)Physics.step;
if (collidingCell == null)
{
collisionRigidness = MathHelper.Lerp(collisionRigidness, 1.0f, 0.1f);
return;
}
foreach (GraphEdge ge in collidingCell.edges)
{
Body body = PickBody(
ConvertUnits.ToSimUnits(ge.point1+ Game1.GameSession.Level.Position),
ConvertUnits.ToSimUnits(ge.point2 + Game1.GameSession.Level.Position), new List<Body>(){collidingCell.body});
if (body == null || body.UserData == null) continue;
Structure structure = body.UserData as Structure;
if (structure == null) continue;
structure.AddDamage(lastPickedPosition, DamageType.Blunt, 50.0f, 0.0f, 0.0f, true);
}
//hullBodies[0].body.SetTransform(Vector2.Zero, 0.0f);
//position = hullBodies[0].body.Position;
//Level.Loaded.Move(-ConvertUnits.ToDisplayUnits(position - prevPosition));
//prevPosition = hullBodies[0].body.Position;
}
private Vector2 CalculateBuoyancy()
{
float waterVolume = 0.0f;
float volume = 0.0f;
foreach (Hull hull in Hull.hullList)
{
waterVolume += hull.Volume;
volume += hull.FullVolume;
}
float waterPercentage = waterVolume / volume;
float neutralPercentage = 0.07f;
float buoyancy = neutralPercentage-waterPercentage;
buoyancy *= mass * 30.0f;
return new Vector2(0.0f, buoyancy);
}
public void SetPosition(Vector2 position)
{
//hullBodies[0].body.SetTransform(position, 0.0f);
Level.Loaded.SetPosition(-position);
//prevPosition = position;
}
private void Translate(Vector2 amount)
{
if (amount == Vector2.Zero) return;
Level.Loaded.Move(-amount);
}
float mass = 10000.0f;
public void ApplyForce(Vector2 force)
{
speed += force/mass;
}
//public void Move(Vector2 amount)
//{
// speed = Vector2.Lerp(speed, amount, 0.05f);
//}
VoronoiCell collidingCell;
public bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
System.Diagnostics.Debug.WriteLine("colliding");
VoronoiCell cell = f2.Body.UserData as VoronoiCell;
if (cell==null) return true;
Vector2 normal = -contact.Manifold.LocalNormal;
Vector2 simSpeed = ConvertUnits.ToSimUnits(speed);
float impact = -Vector2.Dot(simSpeed, normal);
Vector2 u = Vector2.Dot(simSpeed, normal)*normal;
Vector2 w = simSpeed - u;
System.Diagnostics.Debug.WriteLine("IMPACT:"+impact);
if (impact < 4.0f)
{
speed = ConvertUnits.ToDisplayUnits(w * 0.9f - u * 0.2f);
return true;
}
else
{
speed = ConvertUnits.ToDisplayUnits(w * 0.9f + u * 0.5f);
}
if (contact.Manifold.PointCount >= 1)
{
FixedArray2<Vector2> worldPoints;
contact.GetWorldManifold(out normal, out worldPoints);
Game1.GameScreen.Cam.Shake = impact;
}
collisionRigidness = 0.8f;
collidingCell = cell;
return true;
}
public void OnSeparation(Fixture f1, Fixture f2)
{
collidingCell = null;
}
public override void FillNetworkData(Networking.NetworkEventType type, NetOutgoingMessage message, object data)
{
message.Write(NetTime.Now);
message.Write(Position.X);
message.Write(Position.Y);
message.Write(speed.X);
message.Write(speed.Y);
}
public override void ReadNetworkData(Networking.NetworkEventType type, NetIncomingMessage message)
{
double sendingTime;
Vector2 newTargetPosition, newSpeed;
try
{
sendingTime = message.ReadDouble();
if (sendingTime <= lastNetworkUpdate) return;
newTargetPosition = new Vector2(message.ReadFloat(), message.ReadFloat());
newSpeed = new Vector2(message.ReadFloat(), message.ReadFloat());
}
catch
{
return;
}
//newTargetPosition = newTargetPosition + newSpeed * (float)(NetTime.Now - sendingTime);
targetPosition = newTargetPosition;
speed = newSpeed;
lastNetworkUpdate = sendingTime;
}
//saving/loading ----------------------------------------------------
public void Save()
{
SaveAs(filePath);
}
public void SaveAs(string filePath)
{
//if (filePath=="")
//{
// DebugConsole.ThrowError("No save file selected");
// return;
//}
XDocument doc = new XDocument(new XElement("Submarine"));
doc.Root.Add(new XAttribute("name", name));
foreach (MapEntity e in MapEntity.mapEntityList)
{
e.Save(doc);
}
hash = new Md5Hash(doc);
doc.Root.Add(new XAttribute("md5hash", hash.Hash));
try
{
SaveUtil.CompressStringToFile(filePath, doc.ToString());
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving submarine ''" + filePath + "'' failed!", e);
}
//doc.Save(filePath);
}
public static void SaveCurrent(string savePath)
{
if (loaded==null)
{
loaded = new Submarine(savePath);
// return;
}
loaded.SaveAs(savePath);
}
public static void Preload(string folder)
{
SaveFolder = folder;
//string[] mapFilePaths;
Unload();
SavedSubmarines.Clear();
if (!Directory.Exists(SaveFolder))
{
try
{
Directory.CreateDirectory(SaveFolder);
}
catch
{
DebugConsole.ThrowError("Directory ''"+SaveFolder+"'' not found and creating the directory failed.");
return;
}
}
string[] filePaths;
try
{
filePaths = Directory.GetFiles(SaveFolder);
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open directory ''" + SaveFolder + "''!", e);
return;
}
foreach (string path in filePaths)
{
//Map savedMap = new Map(mapPath);
SavedSubmarines.Add(new Submarine(path));
}
}
private XDocument OpenDoc(string file)
{
XDocument doc = null;
string extension = "";
try
{
extension = System.IO.Path.GetExtension(file);
}
catch
{
DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)");
return null;
}
if (extension == ".gz")
{
Stream stream = SaveUtil.DecompressFiletoStream(file);
if (stream == null)
{
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
return null;
}
try
{
stream.Position = 0;
doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file);
stream.Close();
stream.Dispose();
}
catch
{
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
return null;
}
}
else if (extension == ".xml")
{
try
{
doc = XDocument.Load(file);
}
catch
{
DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!");
return null;
}
}
else
{
DebugConsole.ThrowError("Couldn't load submarine ''" + file + "! (Unrecognized file extension)");
return null;
}
return doc;
}
public void Load()
{
Unload();
//string file = filePath;
XDocument doc = OpenDoc(filePath);
if (doc == null) return;
foreach (XElement element in doc.Root.Elements())
{
string typeName = element.Name.ToString();
Type t;
try
{
// Get the type of a specified class.
t = Type.GetType("Subsurface." + typeName + ", Subsurface", true, true);
if (t == null)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.");
continue;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type ''" + typeName + "''.", e);
continue;
}
try
{
MethodInfo loadMethod = t.GetMethod("Load");
loadMethod.Invoke(t, new object[] { element });
}
catch (Exception e)
{
DebugConsole.ThrowError("Could not find the method ''Load'' in " + t + ".", e);
}
}
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));
var triangulatedVertices = Triangulate.ConvexPartition(shapevertices, TriangulationAlgorithm.Bayazit);
hullBody = BodyFactory.CreateCompoundPolygon(Game1.World, triangulatedVertices, 5.0f);
hullBody.BodyType = BodyType.Dynamic;
hullBody.CollisionCategories = Physics.CollisionMisc;
hullBody.CollidesWith = Physics.CollisionLevel;
hullBody.FixedRotation = true;
hullBody.Awake = true;
hullBody.SleepingAllowed = false;
hullBody.GravityScale = 0.0f;
hullBody.OnCollision += OnCollision;
hullBody.OnSeparation += OnSeparation;
MapEntity.LinkAll();
foreach (Item item in Item.itemList)
{
System.Diagnostics.Debug.WriteLine(item.ID);
foreach (ItemComponent ic in item.components)
{
ic.OnMapLoaded();
}
}
ID = int.MaxValue-10;
loaded = this;
}
public static Submarine Load(string file)
{
Unload();
Submarine sub = new Submarine(file);
sub.Load();
//Entity.dictionary.Add(int.MaxValue, sub);
return sub;
}
public static void Unload()
{
if (loaded == null) return;
loaded.Remove();
loaded.Clear();
loaded = null;
}
private void Clear()
{
if (Game1.GameScreen.Cam != null) Game1.GameScreen.Cam.TargetPos = Vector2.Zero;
Entity.RemoveAll();
PhysicsBody.list.Clear();
Ragdoll.list.Clear();
Game1.World.Clear();
}
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Subsurface
{
struct WaterVertex
{
public Vector3 position;
private Vector2 texCoord;
public WaterVertex(Vector3 position, Vector2 texCoord, Matrix transform)
{
this.position = position;
this.texCoord = Vector2.Transform(texCoord, transform);
}
public WaterVertex(Vector3 position, Vector2 texCoord)
{
this.position = position;
this.texCoord = texCoord;
}
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
);
public void TransformTexCoord(Matrix transform)
{
texCoord = Vector2.Transform(texCoord, transform);
}
}
class WaterRenderer : IDisposable
{
const int DefaultBufferSize = 1500;
Effect effect;
public Vector2 wavePos;
public WaterVertex[] vertices = new WaterVertex[DefaultBufferSize];
private VertexBuffer vertexBuffer;
public int positionInBuffer = 0;
public WaterRenderer(GraphicsDevice graphicsDevice)
{
//vertexBuffer = new VertexBuffer(graphicsDevice, WaterVertex.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
//vertexBuffer.SetData(vertices);
//effect = Game1.game.Content.Load<Effect>("effects");
byte[] bytecode = File.ReadAllBytes("Content/effects.mgfx");
effect = new Effect(graphicsDevice, bytecode);
//Texture2D waterBumpMap = Game1.textureLoader.FromFile("Content/waterbump.jpg");
//effect.Parameters["xBump"].SetValue(waterBumpMap);
//effect.Parameters["xWaveLength"].SetValue(0.5f);
//effect.Parameters["xWaveHeight"].SetValue(0.03f);
effect.Parameters["xProjection"].SetValue(Matrix.CreateOrthographic(Game1.GraphicsWidth, Game1.GraphicsHeight, -1, 1));
effect.Parameters["xColor"].SetValue(new Vector4(0.75f, 0.8f, 0.9f, 1.0f));
effect.Parameters["xBlurDistance"].SetValue(0.0005f);
effect.Parameters["xWaterBumpMap"].SetValue(Game1.TextureLoader.FromFile("Content/waterbump.jpg"));
effect.Parameters["xWaveWidth"].SetValue(0.1f);
effect.Parameters["xWaveHeight"].SetValue(0.1f);
vertexBuffer = new VertexBuffer(graphicsDevice, WaterVertex.VertexDeclaration, DefaultBufferSize, BufferUsage.WriteOnly);
}
public void RenderBack(GraphicsDevice graphicsDevice, RenderTarget2D texture, Matrix transform)
{
WaterVertex[] verts = new WaterVertex[6];
// create the four corners of our triangle.
Vector3 p1 = new Vector3(-graphicsDevice.Viewport.Width / 2.0f, graphicsDevice.Viewport.Height / 2.0f, 0.0f);
Vector3 p2 = new Vector3(-p1.X, p1.Y, 0.0f);
Vector3 p3 = new Vector3(p2.X, -p1.Y, 0.0f);
Vector3 p4 = new Vector3(p1.X, -p1.Y, 0.0f);
verts[0] = new WaterVertex(p1, new Vector2(0, 0));
verts[1] = new WaterVertex(p2, new Vector2(1, 0));
verts[2] = new WaterVertex(p3, new Vector2(1, 1));
verts[3] = new WaterVertex(p1, new Vector2(0, 0));
verts[4] = new WaterVertex(p3, new Vector2(1, 1));
verts[5] = new WaterVertex(p4, new Vector2(0, 1));
vertexBuffer.SetData(verts);
wavePos.X += 0.0001f;
wavePos.Y += 0.0001f;
effect.Parameters["xWavePos"].SetValue(wavePos);
effect.CurrentTechnique = effect.Techniques["WaterShader"];
effect.Parameters["xTexture"].SetValue(texture);
effect.Parameters["xView"].SetValue(Matrix.Identity);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, verts, 0, verts.Length / 3, WaterVertex.VertexDeclaration);
}
}
public void Render(GraphicsDevice graphicsDevice, Camera cam, RenderTarget2D texture, Matrix transform)
{
if (vertices == null) return;
if (vertices.Length < 0) return;
vertexBuffer.SetData(vertices);
effect.Parameters["xBumpPos"].SetValue(cam.Position / Game1.GraphicsWidth / cam.Zoom);
effect.CurrentTechnique = effect.Techniques["EmptyShader"];
effect.Parameters["xTexture"].SetValue(texture);
effect.Parameters["xView"].SetValue(transform);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3, WaterVertex.VertexDeclaration);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (vertexBuffer != null)
{
vertexBuffer.Dispose();
vertexBuffer = null;
}
if (effect != null)
{
effect.Dispose();
effect = null;
}
}
}
}
}

View File

@@ -0,0 +1,332 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.ObjectModel;
namespace Subsurface
{
public enum SpawnType { None, Human, Enemy, Cargo };
class WayPoint : MapEntity
{
public static List<WayPoint> WayPointList = new List<WayPoint>();
private 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;
public SpawnType SpawnType
{
get { return spawnType; }
set { spawnType = value; }
}
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(Rectangle newRect)
{
rect = newRect;
linkedTo = new ObservableCollection<MapEntity>();
idCardTags = new string[0];
mapEntityList.Add(this);
WayPointList.Add(this);
}
public override void Draw(SpriteBatch spriteBatch, bool editing)
{
if (!editing && !Game1.DebugDraw) return;
Point pos = new Point((int)Position.X, (int)Position.Y);
Color clr = (isSelected) ? Color.Red : Color.LightGreen;
GUI.DrawRectangle(spriteBatch, new Rectangle(pos.X - rect.Width / 2, -pos.Y - rect.Height / 2, rect.Width, rect.Height), clr, true);
foreach (MapEntity e in linkedTo)
{
GUI.DrawLine(spriteBatch,
new Vector2(pos.X, -pos.Y),
new Vector2(e.Position.X + e.Rect.Width / 2, -e.Position.Y + e.Rect.Height / 2),
Color.Green);
}
}
public override void DrawEditing(SpriteBatch spriteBatch, Camera cam)
{
if (editingHUD==null)
{
editingHUD = CreateEditingHUD();
}
editingHUD.Update((float)Physics.step);
editingHUD.Draw(spriteBatch);
if (!PlayerInput.LeftButtonClicked()) return;
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity e in mapEntityList)
{
if (e.GetType()!=typeof(WayPoint)) continue;
if (e == this) continue;
if (!Submarine.RectContains(e.Rect, position)) continue;
linkedTo.Add(e);
e.linkedTo.Add(this);
}
}
private bool ChangeSpawnType(GUIButton button, object obj)
{
GUITextBlock spawnTypeText = button.Parent as GUITextBlock;
spawnType += (int)button.UserData;
if (spawnType > SpawnType.Cargo) spawnType = SpawnType.None;
if (spawnType < SpawnType.None) spawnType = SpawnType.Enemy;
spawnTypeText.Text = spawnType.ToString();
return true;
}
private bool EnterIDCardTags(GUITextBox textBox, string text)
{
IdCardTags = text.Split(',');
textBox.Text = text;
textBox.Color = Color.White;
return true;
}
private bool EnterAssignedJob(GUITextBox textBox, string text)
{
string trimmedName = text.ToLower().Trim();
assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLower() == trimmedName);
if (assignedJob !=null && trimmedName!="none")
{
textBox.Color = Color.White;
textBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
}
return true;
}
private bool TextBoxChanged(GUITextBox textBox, string text)
{
textBox.Color = Color.Red;
return true;
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
int width = 500;
int x = Game1.GraphicsWidth / 2 - width / 2, y = 10;
editingHUD = new GUIFrame(new Rectangle(x, y, width, 150), Color.Black * 0.5f);
editingHUD.Padding = new Vector4(10, 10, 0, 0);
editingHUD.UserData = this;
new GUITextBlock(new Rectangle(0, 0, 100, 20), "Editing waypoint", GUI.style, editingHUD);
new GUITextBlock(new Rectangle(0, 20, 100, 20), "Hold space to link to another entity", GUI.style, editingHUD);
new GUITextBlock(new Rectangle(0, 40, 100, 20), "Spawnpoint: ", GUI.style, editingHUD);
var spawnTypeText = new GUITextBlock(new Rectangle(0, 40, 200, 20), spawnType.ToString(), GUI.style, Alignment.Right, Alignment.TopLeft, editingHUD);
var button = new GUIButton(new Rectangle(-30,0,20,20), "-", Alignment.Right, GUI.style, spawnTypeText);
button.UserData = -1;
button.OnClicked = ChangeSpawnType;
button = new GUIButton(new Rectangle(0, 0, 20, 20), "+", Alignment.Right, GUI.style, spawnTypeText);
button.UserData = 1;
button.OnClicked = ChangeSpawnType;
//spriteBatch.DrawString(GUI.font, "Spawnpoint: " + spawnType.ToString() + " +/-", new Vector2(x, y + 40), Color.Black);
y = 40+20;
new GUITextBlock(new Rectangle(0, y, 100, 20), "ID Card tags:", Color.Transparent, Color.Black, Alignment.TopLeft, null, editingHUD);
GUITextBox propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), GUI.style, editingHUD);
propertyBox.Text = string.Join(", ", idCardTags);
propertyBox.OnEnter = EnterIDCardTags;
propertyBox.OnTextChanged = TextBoxChanged;
y = y + 30;
new GUITextBlock(new Rectangle(0, y, 100, 20), "Assigned job:", Color.Transparent, Color.Black, Alignment.TopLeft, null, editingHUD);
propertyBox = new GUITextBox(new Rectangle(100, y, 200, 20), GUI.style, editingHUD);
propertyBox.Text = (assignedJob == null) ? "None" : assignedJob.Name;
propertyBox.OnEnter = EnterAssignedJob;
propertyBox.OnTextChanged = TextBoxChanged;
y = y + 30;
return editingHUD;
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.None, Job assignedJob = null)
{
List<WayPoint> wayPoints = new List<WayPoint>();
foreach (WayPoint wp in WayPointList)
{
if (spawnType != SpawnType.None && 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())];
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew)
{
List<WayPoint> unassignedWayPoints = new List<WayPoint>();
foreach (WayPoint wp in WayPointList)
{
if (wp.spawnType == SpawnType.Human) unassignedWayPoints.Add(wp);
}
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 WayPointList)
{
if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) continue;
assignedWayPoints[i] = wp;
break;
}
if (assignedWayPoints[i] != null) continue;
//everything else failed -> just give a random spawnpoint
assignedWayPoints[i] = GetRandom(SpawnType.Human);
}
return assignedWayPoints;
}
public override XElement Save(XDocument doc)
{
if (MoveWithLevel) return null;
XElement element = new XElement("WayPoint");
element.Add(new XAttribute("ID", ID),
new XAttribute("x", rect.X),
new XAttribute("y", rect.Y),
new XAttribute("spawn", spawnType));
if (idCardTags.Length > 0)
{
element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags)));
}
if (assignedJob != null)
{
element.Add(new XAttribute("job", assignedJob.Name));
}
doc.Root.Add(element);
if (linkedTo != null)
{
int i = 0;
foreach (MapEntity e in linkedTo)
{
element.Add(new XAttribute("linkedto" + i, e.ID));
i += 1;
}
}
return element;
}
public static void Load(XElement element)
{
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);
w.ID = int.Parse(element.Attribute("ID").Value);
w.spawnType = (SpawnType)Enum.Parse(typeof(SpawnType),
ToolBox.GetAttributeString(element, "spawn", "None"));
string idCardTagString = ToolBox.GetAttributeString(element, "idcardtags", "");
if (!string.IsNullOrWhiteSpace(idCardTagString))
{
w.IdCardTags = idCardTagString.Split(',');
}
string jobName = ToolBox.GetAttributeString(element, "job", "").ToLower();
if (!string.IsNullOrWhiteSpace(jobName))
{
w.assignedJob = JobPrefab.List.Find(jp => jp.Name.ToLower() == jobName);
}
w.linkedToID = new List<int>();
int i = 0;
while (element.Attribute("linkedto" + i) != null)
{
w.linkedToID.Add(int.Parse(element.Attribute("linkedto" + i).Value));
i += 1;
}
}
public override void Remove()
{
base.Remove();
WayPointList.Remove(this);
}
}
}