(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,119 @@
using Barotrauma.Lights;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
partial class Explosion
{
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull)
{
if (shockwave)
{
GameMain.ParticleManager.CreateParticle("shockwave", worldPosition,
Vector2.Zero, 0.0f, hull);
}
hull = hull ?? Hull.FindHull(worldPosition, useWorldCoordinates: true);
bool underwater = hull == null || worldPosition.Y < hull.WorldSurface;
if (underwater && underwaterBubble)
{
var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition, Vector2.Zero, 0.0f, hull);
if (underwaterExplosion != null)
{
underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 150.0f, 0.5f, 10.0f);
underwaterExplosion.StartDelay = 0.0f;
}
}
for (int i = 0; i < attack.Range * 0.1f; i++)
{
if (!underwater)
{
float particleSpeed = Rand.Range(0.0f, 1.0f);
particleSpeed = particleSpeed * particleSpeed * attack.Range;
if (flames)
{
float particleScale = MathHelper.Clamp(attack.Range * 0.0025f, 0.5f, 2.0f);
var flameParticle = GameMain.ParticleManager.CreateParticle("explosionfire",
ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull),
Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
if (flameParticle != null) flameParticle.Size *= particleScale;
}
if (smoke)
{
var smokeParticle = GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke",
ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull),
Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
}
}
else if (underwaterBubble)
{
Vector2 bubblePos = Rand.Vector(Rand.Range(0.0f, attack.Range * 0.5f));
GameMain.ParticleManager.CreateParticle("risingbubbles", worldPosition + bubblePos,
Vector2.Zero, 0.0f, hull);
if (i < attack.Range * 0.02f)
{
var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition + bubblePos,
Vector2.Zero, 0.0f, hull);
if (underwaterExplosion != null)
{
underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 300.0f, 0.5f, 2.0f) * Rand.Range(0.8f, 1.2f);
}
}
}
if (sparks)
{
GameMain.ParticleManager.CreateParticle("spark", worldPosition,
Rand.Vector(Rand.Range(500.0f, 800.0f)), 0.0f, hull);
}
}
if (hull != null && !string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
{
hull.AddDecal(decal, worldPosition, decalSize);
}
if (flash)
{
float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range;
if (displayRange < 0.1f) { return; }
var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null);
CoroutineManager.StartCoroutine(DimLight(light));
}
}
private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull)
{
if (hull == null) return particlePos;
return new Vector2(
MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right),
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
}
private IEnumerable<object> DimLight(LightSource light)
{
float currBrightness = 1.0f;
float startRange = light.Range;
while (light.Color.A > 0.0f && flashDuration > 0.0f)
{
light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, currBrightness);
currBrightness -= (1.0f / flashDuration) * CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
light.Remove();
yield return CoroutineStatus.Success;
}
}
}
@@ -0,0 +1,83 @@
using Barotrauma.Lights;
using Barotrauma.Particles;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
partial class FireSource
{
private LightSource lightSource;
partial void UpdateProjSpecific(float growModifier)
{
EmitParticles(size, WorldPosition, hull, growModifier, OnChangeHull);
lightSource.Color = new Color(1.0f, 0.45f, 0.3f) * Rand.Range(0.8f, 1.0f);
if (Math.Abs((lightSource.Range * 0.2f) - Math.Max(size.X, size.Y)) > 1.0f) lightSource.Range = Math.Max(size.X, size.Y) * 5.0f;
if (Vector2.DistanceSquared(lightSource.Position,position) > 5.0f) lightSource.Position = position + Vector2.UnitY * 30.0f;
if (size.X > 256.0f)
{
if (burnDecals.Count == 0)
{
var newDecal = hull.AddDecal("burnt", WorldPosition + size/2);
if (newDecal != null) burnDecals.Add(newDecal);
}
else if (WorldPosition.X < burnDecals[0].WorldPosition.X - 256.0f)
{
var newDecal = hull.AddDecal("burnt", WorldPosition);
if (newDecal != null) burnDecals.Insert(0, newDecal);
}
else if (WorldPosition.X + size.X > burnDecals[burnDecals.Count-1].WorldPosition.X + 256.0f)
{
var newDecal = hull.AddDecal("burnt", WorldPosition + Vector2.UnitX * size.X);
if (newDecal != null) burnDecals.Add(newDecal);
}
}
foreach (Decal d in burnDecals)
{
//prevent the decals from fading out as long as the firesource is alive
d.FadeTimer = Math.Min(d.FadeTimer, d.FadeInTime);
}
}
public static void EmitParticles(Vector2 size, Vector2 worldPosition, Hull hull, float growModifier, Particle.OnChangeHullHandler onChangeHull = null)
{
float particleCount = Rand.Range(0.0f, size.X / 50.0f);
for (int i = 0; i < particleCount; i++)
{
Vector2 particlePos = new Vector2(
worldPosition.X + Rand.Range(0.0f, size.X),
Rand.Range(worldPosition.Y - size.Y, worldPosition.Y + 20.0f));
Vector2 particleVel = new Vector2(
(particlePos.X - (worldPosition.X + size.X / 2.0f)),
(float)Math.Sqrt(size.X) * Rand.Range(0.0f, 15.0f) * growModifier);
var particle = GameMain.ParticleManager.CreateParticle("flame",
particlePos, particleVel, 0.0f, hull);
if (particle == null) continue;
//make some of the particles create another firesource when they enter another hull
if (Rand.Int(20) == 1) particle.OnChangeHull = onChangeHull;
particle.Size *= MathHelper.Clamp(size.X / 60.0f * Math.Max(hull.Oxygen / hull.Volume, 0.4f), 0.5f, 1.0f);
if (Rand.Int(5) == 1)
{
var smokeParticle = GameMain.ParticleManager.CreateParticle("smoke",
particlePos, new Vector2(particleVel.X, particleVel.Y * 0.1f), 0.0f, hull);
if (smokeParticle != null)
{
smokeParticle.Size *= MathHelper.Clamp(size.X / 100.0f * Math.Max(hull.Oxygen / hull.Volume, 0.4f), 0.5f, 1.0f);
}
}
}
}
}
}
@@ -0,0 +1,254 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
partial class Gap : MapEntity
{
private float particleTimer;
public override bool SelectableInEditor
{
get
{
return ShowGaps;
}
}
public override bool IsVisible(Rectangle worldView)
{
return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
}
public override void Draw(SpriteBatch sb, bool editing, bool back = true)
{
if (GameMain.DebugDraw)
{
Vector2 center = new Vector2(WorldRect.X + rect.Width / 2.0f, -(WorldRect.Y - rect.Height / 2.0f));
GUI.DrawLine(sb, center, center + new Vector2(flowForce.X, -flowForce.Y) / 10.0f, GUI.Style.Red);
GUI.DrawLine(sb, center + Vector2.One * 5.0f, center + new Vector2(lerpedFlowForce.X, -lerpedFlowForce.Y) / 10.0f + Vector2.One * 5.0f, GUI.Style.Orange);
if (outsideCollisionBlocker.Enabled && Submarine != null)
{
var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape;
Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + Submarine.Position;
Vector2 endPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex2)) + Submarine.Position;
startPos.Y = -startPos.Y;
endPos.Y = -endPos.Y;
GUI.DrawLine(sb, startPos, endPos, Color.Gray, 0, 5);
}
}
if (!editing || !ShowGaps) return;
Color clr = (open == 0.0f) ? GUI.Style.Red : Color.Cyan;
if (IsHighlighted) clr = Color.Gold;
float depth = (ID % 255) * 0.000001f;
GUI.DrawRectangle(
sb, new Rectangle(WorldRect.X, -WorldRect.Y, rect.Width, rect.Height),
clr * 0.2f, true, depth);
int lineWidth = 5;
if (IsHorizontal)
{
GUI.DrawLine(sb,
new Vector2(WorldRect.X, -WorldRect.Y + lineWidth / 2),
new Vector2(WorldRect.Right, -WorldRect.Y + lineWidth / 2),
clr * 0.6f, width: lineWidth);
GUI.DrawLine(sb,
new Vector2(WorldRect.X, -WorldRect.Y + rect.Height - lineWidth / 2),
new Vector2(WorldRect.Right, -WorldRect.Y + rect.Height - lineWidth / 2),
clr * 0.6f, width: lineWidth);
}
else
{
GUI.DrawLine(sb,
new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y),
new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y + rect.Height),
clr * 0.6f, width: lineWidth);
GUI.DrawLine(sb,
new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y),
new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y + rect.Height),
clr * 0.6f, width: lineWidth);
}
for (int i = 0; i < linkedTo.Count; i++)
{
Vector2 dir = IsHorizontal ?
new Vector2(Math.Sign(linkedTo[i].Rect.Center.X - rect.Center.X), 0.0f)
: new Vector2(0.0f, Math.Sign((linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f) - (rect.Y - rect.Height / 2.0f)));
Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2));
float arrowWidth = 32.0f;
float arrowSize = 15.0f;
bool invalidDir = false;
if (dir == Vector2.Zero)
{
invalidDir = true;
dir = IsHorizontal ? Vector2.UnitX : Vector2.UnitY;
}
GUI.Arrow.Draw(sb,
arrowPos, invalidDir ? Color.Red : clr * 0.8f,
GUI.Arrow.Origin, MathUtils.VectorToAngle(dir) + MathHelper.PiOver2,
IsHorizontal ?
new Vector2(Math.Min(rect.Height, arrowWidth) / GUI.Arrow.size.X, arrowSize / GUI.Arrow.size.Y) :
new Vector2(Math.Min(rect.Width, arrowWidth) / GUI.Arrow.size.X, arrowSize / GUI.Arrow.size.Y),
SpriteEffects.None, depth);
}
if (IsSelected)
{
GUI.DrawRectangle(sb,
new Vector2(WorldRect.X - 5, -WorldRect.Y - 5),
new Vector2(rect.Width + 10, rect.Height + 10),
GUI.Style.Red,
false,
depth,
(int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
}
}
partial void EmitParticles(float deltaTime)
{
if (flowTargetHull == null) return;
Vector2 pos = Position;
if (IsHorizontal)
{
pos.X += Math.Sign(flowForce.X);
pos.Y = MathHelper.Clamp((higherSurface + lowerSurface) / 2.0f, rect.Y - rect.Height, rect.Y) + 10;
}
else
{
pos.Y += Math.Sign(flowForce.Y) * rect.Height / 2.0f;
}
//light dripping
if (open < 0.2f && LerpedFlowForce.LengthSquared() > 100.0f)
{
particleTimer += deltaTime;
float particlesPerSec = open * 100.0f;
float emitInterval = 1.0f / particlesPerSec;
while (particleTimer > emitInterval)
{
Vector2 velocity = flowForce;
if (!IsHorizontal)
{
velocity.X = Rand.Range(-500.0f, 500.0f) * open;
}
else
{
velocity.X *= Rand.Range(1.0f, 3.0f);
}
if (flowTargetHull.WaterVolume < flowTargetHull.Volume)
{
GameMain.ParticleManager.CreateParticle(
Rand.Range(0.0f, open) < 0.05f ? "waterdrop" : "watersplash",
(Submarine == null ? pos : pos + Submarine.Position),
velocity, 0, flowTargetHull);
}
GameMain.ParticleManager.CreateParticle(
"bubbles",
(Submarine == null ? pos : pos + Submarine.Position),
velocity, 0, flowTargetHull);
particleTimer -= emitInterval;
}
}
//heavy flow -> strong waterfall type of particles
else if (LerpedFlowForce.LengthSquared() > 20000.0f)
{
particleTimer += deltaTime;
if (IsHorizontal)
{
float particlesPerSec = open * rect.Height * 0.1f;
float emitInterval = 1.0f / particlesPerSec;
while (particleTimer > emitInterval)
{
Vector2 velocity = new Vector2(
MathHelper.Clamp(flowForce.X, -5000.0f, 5000.0f) * Rand.Range(0.5f, 0.7f),
flowForce.Y * Rand.Range(0.5f, 0.7f));
if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f)
{
var particle = GameMain.ParticleManager.CreateParticle(
"watersplash",
(Submarine == null ? pos : pos + Submarine.Position) - Vector2.UnitY * Rand.Range(0.0f, 10.0f),
velocity, 0, flowTargetHull);
if (particle != null)
{
particle.Size = particle.Size * Math.Min(Math.Abs(flowForce.X / 1000.0f), 5.0f);
}
}
if (Math.Abs(flowForce.X) > 300.0f)
{
pos.X += Math.Sign(flowForce.X) * 10.0f;
if (rect.Height < 32)
{
pos.Y = rect.Y - rect.Height / 2;
}
else
{
float bottomY = rect.Y - rect.Height + 16;
float topY = MathHelper.Clamp(lowerSurface, bottomY, rect.Y - 16);
pos.Y = Rand.Range(bottomY, topY);
}
GameMain.ParticleManager.CreateParticle(
"bubbles",
Submarine == null ? pos : pos + Submarine.Position,
flowForce / 10.0f, 0, flowTargetHull);
}
particleTimer -= emitInterval;
}
}
else
{
if (Math.Sign(flowTargetHull.Rect.Y - rect.Y) != Math.Sign(lerpedFlowForce.Y)) return;
float particlesPerSec = open * rect.Width * 0.3f;
float emitInterval = 1.0f / particlesPerSec;
while (particleTimer > emitInterval)
{
pos.X = Rand.Range(rect.X, rect.X + rect.Width);
Vector2 velocity = new Vector2(
lerpedFlowForce.X * Rand.Range(0.5f, 0.7f),
MathHelper.Clamp(lerpedFlowForce.Y, -500.0f, 1000.0f) * Rand.Range(0.5f, 0.7f));
if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f)
{
var splash = GameMain.ParticleManager.CreateParticle(
"watersplash",
Submarine == null ? pos : pos + Submarine.Position,
velocity, 0, FlowTargetHull);
if (splash != null) splash.Size = splash.Size * MathHelper.Clamp(rect.Width / 50.0f, 0.8f, 4.0f);
}
if (Math.Abs(flowForce.Y) > 190.0f && Rand.Range(0.0f, 1.0f) < 0.3f)
{
GameMain.ParticleManager.CreateParticle(
"bubbles",
Submarine == null ? pos : pos + Submarine.Position,
flowForce / 2.0f, 0, FlowTargetHull);
}
particleTimer -= emitInterval;
}
}
}
else
{
particleTimer = 0.0f;
}
}
}
}
@@ -0,0 +1,619 @@
using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable
{
public const int MaxDecalsPerHull = 10;
private List<Decal> decals = new List<Decal>();
private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources;
private bool networkUpdatePending;
private float networkUpdateTimer;
public override bool SelectableInEditor
{
get
{
return ShowHulls;
}
}
public override bool DrawBelowWater
{
get
{
return decals.Count > 0;
}
}
public override bool DrawOverWater
{
get
{
return true;
}
}
public override bool IsVisible(Rectangle worldView)
{
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw)
{
if (decals.Count == 0) { return false; }
Rectangle worldRect = WorldRect;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
}
return true;
}
public override bool IsMouseOn(Vector2 position)
{
if (!GameMain.DebugDraw && !ShowHulls) return false;
return (Submarine.RectContains(WorldRect, position) &&
!Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -8), position));
}
public Decal AddDecal(string decalName, Vector2 worldPosition, float scale = 1.0f)
{
if (decals.Count >= MaxDecalsPerHull) return null;
var decal = GameMain.DecalManager.CreateDecal(decalName, scale, worldPosition, this);
if (decal != null)
{
decals.Add(decal);
}
return decal;
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null);
new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont);
PositionEditingHUD();
return editingHUD;
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData as Hull != this)
{
editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen);
}
if (!PlayerInput.KeyDown(Keys.Space)) return;
bool lClick = PlayerInput.PrimaryMouseButtonClicked();
bool rClick = PlayerInput.SecondaryMouseButtonClicked();
if (!lClick && !rClick) return;
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (lClick)
{
foreach (MapEntity entity in mapEntityList)
{
if (entity == this || !entity.IsHighlighted) continue;
if (!entity.IsMouseOn(position)) continue;
if (entity.Linkable && entity.linkedTo != null)
{
entity.linkedTo.Add(this);
linkedTo.Add(entity);
}
}
}
else
{
foreach (MapEntity entity in mapEntityList)
{
if (entity == this || !entity.IsHighlighted) continue;
if (!entity.IsMouseOn(position)) continue;
if (entity.linkedTo != null && entity.linkedTo.Contains(this))
{
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
}
}
}
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
serverUpdateDelay -= deltaTime;
if (serverUpdateDelay <= 0.0f)
{
ApplyRemoteState();
}
if (networkUpdatePending)
{
networkUpdateTimer += deltaTime;
if (networkUpdateTimer > 0.2f)
{
GameMain.NetworkMember?.CreateEntityEvent(this);
networkUpdatePending = false;
networkUpdateTimer = 0.0f;
}
}
if (EditWater)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
if (PlayerInput.PrimaryMouseButtonHeld())
{
WaterVolume += 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
else if (PlayerInput.SecondaryMouseButtonHeld())
{
WaterVolume -= 1500.0f;
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
}
}
else if (EditFire)
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (Submarine.RectContains(WorldRect, position))
{
if (PlayerInput.PrimaryMouseButtonClicked())
{
new FireSource(position, this, isNetworkMessage: true);
networkUpdatePending = true;
serverUpdateDelay = 0.5f;
}
}
}
foreach (Decal decal in decals)
{
decal.Update(deltaTime);
}
decals.RemoveAll(d => d.FadeTimer >= d.LifeTime);
if (waterVolume < 1.0f) return;
for (int i = 1; i < waveY.Length - 1; i++)
{
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
if (maxDelta > Rand.Range(1.0f, 10.0f))
{
var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]);
if (Submarine != null) particlePos += Submarine.Position;
GameMain.ParticleManager.CreateParticle("mist",
particlePos,
new Vector2(0.0f, -50.0f), 0.0f, this);
}
}
}
private void DrawDecals(SpriteBatch spriteBatch)
{
Rectangle hullDrawRect = rect;
if (Submarine != null) hullDrawRect.Location += Submarine.DrawPosition.ToPoint();
float depth = 1.0f;
foreach (Decal d in decals)
{
d.Draw(spriteBatch, this, depth);
depth -= 0.000001f;
}
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (back && Screen.Selected != GameMain.SubEditorScreen)
{
DrawDecals(spriteBatch);
return;
}
/*if (!Visible)
{
drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(rect.Width, rect.Height),
Color.Black, true,
0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
}*/
if (!ShowHulls && !GameMain.DebugDraw) return;
if (!editing && !GameMain.DebugDraw) return;
Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(rect.Width, rect.Height),
Color.Blue, false, (ID % 255) * 0.000001f, (int)Math.Max((1.5f / Screen.Selected.Cam.Zoom), 1.0f));
GUI.DrawRectangle(spriteBatch,
new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height),
GUI.Style.Red * ((100.0f - OxygenPercentage) / 400.0f), true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
if (GameMain.DebugDraw)
{
GUI.SmallFont.DrawString(spriteBatch, "Pressure: " + ((int)pressure - rect.Y).ToString() +
" - Oxygen: " + ((int)OxygenPercentage), new Vector2(drawRect.X + 5, -drawRect.Y + 5), Color.White);
GUI.SmallFont.DrawString(spriteBatch, waterVolume + " / " + Volume, new Vector2(drawRect.X + 5, -drawRect.Y + 20), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * Math.Min(waterVolume / Volume, 1.0f))), Color.Cyan, true);
if (WaterVolume > Volume)
{
float maxExcessWater = Volume * MaxCompress;
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * (waterVolume - Volume) / maxExcessWater)), GUI.Style.Red, true);
}
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, 100), Color.Black);
foreach (FireSource fs in FireSources)
{
Rectangle fireSourceRect = new Rectangle((int)fs.WorldPosition.X, -(int)fs.WorldPosition.Y, (int)fs.Size.X, (int)fs.Size.Y);
GUI.DrawRectangle(spriteBatch, fireSourceRect, GUI.Style.Orange, false, 0, 5);
//GUI.DrawRectangle(spriteBatch, new Rectangle((int)fs.LastExtinguishPos.X, (int)-fs.LastExtinguishPos.Y, 5,5), Color.Yellow, true);
}
/*GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
for (int i = 0; i < waveY.Length - 1; i++)
{
GUI.DrawLine(spriteBatch,
new Vector2(drawRect.X + WaveWidth * i, -WorldSurface - waveY[i] - 10),
new Vector2(drawRect.X + WaveWidth * (i + 1), -WorldSurface - waveY[i + 1] - 10), Color.Blue * 0.5f);
}*/
}
if ((IsSelected || IsHighlighted) && editing)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X + 5, -drawRect.Y + 5),
new Vector2(rect.Width - 10, rect.Height - 10),
IsHighlighted ? Color.LightBlue * 0.5f : GUI.Style.Red * 0.5f, true, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
}
foreach (MapEntity e in linkedTo)
{
if (e is Hull)
{
Hull linkedHull = (Hull)e;
Rectangle connectedHullRect = e.Submarine == null ?
linkedHull.rect :
new Rectangle(
(int)(Submarine.DrawPosition.X + linkedHull.WorldPosition.X),
(int)(Submarine.DrawPosition.Y + linkedHull.WorldPosition.Y),
linkedHull.WorldRect.Width, linkedHull.WorldRect.Height);
//center of the hull
Rectangle currentHullRect = Submarine == null ?
WorldRect :
new Rectangle(
(int)(Submarine.DrawPosition.X + WorldPosition.X),
(int)(Submarine.DrawPosition.Y + WorldPosition.Y),
WorldRect.Width, WorldRect.Height);
GUI.DrawLine(spriteBatch,
new Vector2(currentHullRect.X, -currentHullRect.Y),
new Vector2(connectedHullRect.X, -connectedHullRect.Y),
GUI.Style.Green, width: 2);
}
}
}
public static void UpdateVertices(GraphicsDevice graphicsDevice, Camera cam, WaterRenderer renderer)
{
foreach (EntityGrid entityGrid in EntityGrids)
{
if (entityGrid.WorldRect.X > cam.WorldView.Right || entityGrid.WorldRect.Right < cam.WorldView.X) continue;
if (entityGrid.WorldRect.Y - entityGrid.WorldRect.Height > cam.WorldView.Y || entityGrid.WorldRect.Y < cam.WorldView.Y - cam.WorldView.Height) continue;
var allEntities = entityGrid.GetAllEntities();
foreach (Hull hull in allEntities)
{
hull.UpdateVertices(graphicsDevice, cam, entityGrid, renderer);
}
}
}
private void UpdateVertices(GraphicsDevice graphicsDevice, Camera cam, EntityGrid entityGrid, WaterRenderer renderer)
{
Vector2 submarinePos = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
//if there's no more space in the buffer, don't render the water in the hull
//not an ideal solution, but this seems to only happen in cases where the missing
//water is not very noticeable (e.g. zoomed very far out so that multiple subs and ruins are visible)
if (renderer.PositionInBuffer > renderer.vertices.Length - 6)
{
return;
}
if (!renderer.IndoorsVertices.ContainsKey(entityGrid))
{
renderer.IndoorsVertices[entityGrid] = new VertexPositionColorTexture[WaterRenderer.DefaultIndoorsBufferSize];
renderer.PositionInIndoorsBuffer[entityGrid] = 0;
}
//calculate where the surface should be based on the water volume
float top = rect.Y + submarinePos.Y;
float bottom = top - rect.Height;
float renderSurface = drawSurface + submarinePos.Y;
if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) return;
Matrix transform = cam.Transform * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
if (!update)
{
// create the four corners of our triangle.
Vector3[] corners = new Vector3[4];
corners[0] = new Vector3(rect.X, rect.Y, 0.0f);
corners[1] = new Vector3(rect.X + rect.Width, rect.Y, 0.0f);
corners[2] = new Vector3(corners[1].X, rect.Y - rect.Height, 0.0f);
corners[3] = new Vector3(corners[0].X, corners[2].Y, 0.0f);
Vector2[] uvCoords = new Vector2[4];
for (int i = 0; i < 4; i++)
{
corners[i] += new Vector3(submarinePos, 0.0f);
uvCoords[i] = Vector2.Transform(new Vector2(corners[i].X, -corners[i].Y), transform);
}
renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(corners[0], uvCoords[0]);
renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]);
renderer.vertices[renderer.PositionInBuffer + 2] = new VertexPositionTexture(corners[2], uvCoords[2]);
renderer.vertices[renderer.PositionInBuffer + 3] = new VertexPositionTexture(corners[0], uvCoords[0]);
renderer.vertices[renderer.PositionInBuffer + 4] = new VertexPositionTexture(corners[2], uvCoords[2]);
renderer.vertices[renderer.PositionInBuffer + 5] = new VertexPositionTexture(corners[3], uvCoords[3]);
renderer.PositionInBuffer += 6;
return;
}
float x = rect.X;
if (Submarine != null) { x += Submarine.DrawPosition.X; }
int start = (int)Math.Floor((cam.WorldView.X - x) / WaveWidth);
start = Math.Max(start, 0);
int end = (waveY.Length - 1) - (int)Math.Floor(((x + rect.Width) - (cam.WorldView.Right)) / WaveWidth);
end = Math.Min(end, waveY.Length - 1);
x += start * WaveWidth;
Vector3[] prevCorners = new Vector3[2];
Vector2[] prevUVs = new Vector2[2];
int width = WaveWidth;
for (int i = start; i < end; i++)
{
Vector3[] corners = new Vector3[6];
//top left
corners[0] = new Vector3(x, top, 0.0f);
//watersurface left
corners[3] = new Vector3(corners[0].X, renderSurface + waveY[i], 0.0f);
//top right
corners[1] = new Vector3(x + width, top, 0.0f);
//watersurface right
corners[2] = new Vector3(corners[1].X, renderSurface + waveY[i + 1], 0.0f);
//bottom left
corners[4] = new Vector3(x, bottom, 0.0f);
//bottom right
corners[5] = new Vector3(x + width, bottom, 0.0f);
Vector2[] uvCoords = new Vector2[4];
for (int n = 0; n < 4; n++)
{
uvCoords[n] = Vector2.Transform(new Vector2(corners[n].X, -corners[n].Y), transform);
}
if (renderer.PositionInBuffer <= renderer.vertices.Length - 6)
{
if (i == start)
{
prevCorners[0] = corners[0];
prevCorners[1] = corners[3];
prevUVs[0] = uvCoords[0];
prevUVs[1] = uvCoords[3];
}
//we only create a new quad if this is the first or the last one, of if there's a wave large enough that we need more geometry
if (i == end - 1 || i == start || Math.Abs(prevCorners[1].Y - corners[3].Y) > 1.0f)
{
renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(prevCorners[0], prevUVs[0]);
renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]);
renderer.vertices[renderer.PositionInBuffer + 2] = new VertexPositionTexture(corners[2], uvCoords[2]);
renderer.vertices[renderer.PositionInBuffer + 3] = new VertexPositionTexture(prevCorners[0], prevUVs[0]);
renderer.vertices[renderer.PositionInBuffer + 4] = new VertexPositionTexture(corners[2], uvCoords[2]);
renderer.vertices[renderer.PositionInBuffer + 5] = new VertexPositionTexture(prevCorners[1], prevUVs[1]);
prevCorners[0] = corners[1];
prevCorners[1] = corners[2];
prevUVs[0] = uvCoords[1];
prevUVs[1] = uvCoords[2];
renderer.PositionInBuffer += 6;
}
}
if (renderer.PositionInIndoorsBuffer[entityGrid] <= renderer.IndoorsVertices[entityGrid].Length - 12 &&
cam.Zoom > 0.6f)
{
const float SurfaceSize = 10.0f;
const float SineFrequency1 = 0.01f;
const float SineFrequency2 = 0.05f;
//surface shrinks and finally disappears when the water level starts to reach the top of the hull
float surfaceScale = 1.0f - MathHelper.Clamp(corners[3].Y - (top - SurfaceSize), 0.0f, 1.0f);
Vector3 surfaceOffset = new Vector3(0.0f, -SurfaceSize, 0.0f);
surfaceOffset.Y += (float)Math.Sin((rect.X + i * WaveWidth) * SineFrequency1 + renderer.WavePos.X * 0.25f) * 2;
surfaceOffset.Y += (float)Math.Sin((rect.X + i * WaveWidth) * SineFrequency2 - renderer.WavePos.X) * 2;
surfaceOffset *= surfaceScale;
Vector3 surfaceOffset2 = new Vector3(0.0f, -SurfaceSize, 0.0f);
surfaceOffset2.Y += (float)Math.Sin((rect.X + i * WaveWidth + width) * SineFrequency1 + renderer.WavePos.X * 0.25f) * 2;
surfaceOffset2.Y += (float)Math.Sin((rect.X + i * WaveWidth + width) * SineFrequency2 - renderer.WavePos.X) * 2;
surfaceOffset2 *= surfaceScale;
int posInBuffer = renderer.PositionInIndoorsBuffer[entityGrid];
renderer.IndoorsVertices[entityGrid][posInBuffer + 0] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsWaterColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 1] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsWaterColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 2] = new VertexPositionColorTexture(corners[5], renderer.IndoorsWaterColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 3] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsWaterColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 4] = new VertexPositionColorTexture(corners[5], renderer.IndoorsWaterColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 5] = new VertexPositionColorTexture(corners[4], renderer.IndoorsWaterColor, Vector2.Zero);
posInBuffer += 6;
renderer.PositionInIndoorsBuffer[entityGrid] = posInBuffer;
if (surfaceScale > 0)
{
renderer.IndoorsVertices[entityGrid][posInBuffer + 0] = new VertexPositionColorTexture(corners[3], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 1] = new VertexPositionColorTexture(corners[2], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 2] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 3] = new VertexPositionColorTexture(corners[3], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 4] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
renderer.IndoorsVertices[entityGrid][posInBuffer + 5] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
renderer.PositionInIndoorsBuffer[entityGrid] += 6;
}
}
x += WaveWidth;
//clamp the last segment to the right edge of the hull
if (i == end - 2)
{
width -= (int)Math.Max((x + WaveWidth) - (Submarine == null ? rect.Right : (rect.Right + Submarine.DrawPosition.X)), 0);
}
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
msg.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{
msg.WriteRangedInteger(Math.Min(FireSources.Count, 16), 0, 16);
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = FireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
{
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
bool hasFireSources = message.ReadBoolean();
int fireSourceCount = 0;
remoteFireSources = new List<Vector3>();
if (hasFireSources)
{
fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
{
remoteFireSources.Add(new Vector3(
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
message.ReadRangedSingle(0.0f, 1.0f, 8)));
}
}
if (serverUpdateDelay > 0.0f) { return; }
ApplyRemoteState();
}
private void ApplyRemoteState()
{
if (remoteFireSources == null)
{
return;
}
WaterVolume = remoteWaterVolume;
OxygenPercentage = remoteOxygenPercentage;
for (int i = 0; i < remoteFireSources.Count; i++)
{
Vector2 pos = new Vector2(
rect.X + rect.Width * remoteFireSources[i].X,
rect.Y - rect.Height + (rect.Height * remoteFireSources[i].Y));
float size = remoteFireSources[i].Z * rect.Width;
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
//ignore if the fire wasn't added to this room (invalid position)?
if (!FireSources.Contains(newFire))
{
newFire.Remove();
continue;
}
}
for (int i = FireSources.Count - 1; i >= remoteFireSources.Count; i--)
{
FireSources[i].Remove();
if (i < FireSources.Count)
{
FireSources.RemoveAt(i);
}
}
remoteFireSources = null;
}
}
}
@@ -0,0 +1,101 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class ItemAssemblyPrefab
{
public void DrawIcon(SpriteBatch spriteBatch, GUICustomComponent guiComponent)
{
Rectangle drawArea = guiComponent.Rect;
float scale = Math.Min(drawArea.Width / (float)Bounds.Width, drawArea.Height / (float)Bounds.Height) * 0.9f;
foreach (Pair<MapEntityPrefab, Rectangle> entity in DisplayEntities)
{
Rectangle drawRect = entity.Second;
drawRect = new Rectangle(
(int)(drawRect.X * scale) + drawArea.Center.X, (int)((drawRect.Y) * scale) - drawArea.Center.Y,
(int)(drawRect.Width * scale), (int)(drawRect.Height * scale));
entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale * scale);
}
}
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{
base.DrawPlacing(spriteBatch, cam);
foreach (Pair<MapEntityPrefab, Rectangle> entity in DisplayEntities)
{
Rectangle drawRect = entity.Second;
drawRect.Location += Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint();
entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale);
}
}
public static XElement Save(List<MapEntity> entities, string name, string description, bool hideInMenus = false)
{
XElement element = new XElement("ItemAssembly",
new XAttribute("name", name),
new XAttribute("description", description),
new XAttribute("hideinmenus", hideInMenus));
//move the entities so that their "center of mass" is at {0,0}
var assemblyEntities = MapEntity.CopyEntities(entities);
//find wires and items that are contained inside another item
//place them at {0,0} to prevent them from messing up the origin of the prefab and to hide them in preview
List<MapEntity> disabledEntities = new List<MapEntity>();
foreach (MapEntity mapEntity in assemblyEntities)
{
if (mapEntity is Item item)
{
var wire = item.GetComponent<Wire>();
if (item.ParentInventory != null ||
(wire != null && wire.Connections.Any(c => c != null)))
{
item.SetTransform(Vector2.Zero, 0.0f);
disabledEntities.Add(mapEntity);
}
}
}
float minX = int.MaxValue, maxX = int.MinValue;
float minY = int.MaxValue, maxY = int.MinValue;
foreach (MapEntity mapEntity in assemblyEntities)
{
if (disabledEntities.Contains(mapEntity)) { continue; }
minX = Math.Min(minX, mapEntity.WorldRect.X);
maxX = Math.Max(maxX, mapEntity.WorldRect.Right);
minY = Math.Min(minY, mapEntity.WorldRect.Y - mapEntity.WorldRect.Height);
maxY = Math.Max(maxY, mapEntity.WorldRect.Y);
}
Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f);
if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; }
center.X -= center.X % Submarine.GridSize.X;
center.Y -= center.Y % Submarine.GridSize.Y;
MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities)
{
mapEntity.Move(-center);
mapEntity.Submarine = Submarine.MainSub;
var entityElement = mapEntity.Save(element);
if (disabledEntities.Contains(mapEntity))
{
entityElement.Add(new XAttribute("hideinassemblypreview", "true"));
}
}
return element;
}
}
}
@@ -0,0 +1,235 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
class BackgroundCreature : ISteerable
{
const float MaxDepth = 100.0f;
const float CheckWallsInterval = 5.0f;
public bool Enabled;
private BackgroundCreaturePrefab prefab;
private Vector2 position;
private Vector3 velocity;
private float depth;
private SteeringManager steeringManager;
private float checkWallsTimer;
private float wanderZPhase;
private Vector2 obstacleDiff;
private float obstacleDist;
public Swarm Swarm;
Vector2 drawPosition;
public Vector2 TransformedPosition
{
get { return drawPosition; }
}
public Vector2 SimPosition
{
get { return FarseerPhysics.ConvertUnits.ToSimUnits(position); }
}
public Vector2 WorldPosition
{
get { return position; }
}
public Vector2 Velocity
{
get { return new Vector2(velocity.X, velocity.Y); }
}
public Vector2 Steering
{
get;
set;
}
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
{
this.prefab = prefab;
this.position = position;
drawPosition = position;
steeringManager = new SteeringManager(this);
velocity = new Vector3(
Rand.Range(-prefab.Speed, prefab.Speed),
Rand.Range(-prefab.Speed, prefab.Speed),
Rand.Range(0.0f, prefab.WanderZAmount));
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval);
}
public void Update(float deltaTime)
{
position += new Vector2(velocity.X, velocity.Y) * deltaTime;
depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, 0.0f, MaxDepth);
checkWallsTimer -= deltaTime;
if (checkWallsTimer <= 0.0f && Level.Loaded != null)
{
checkWallsTimer = CheckWallsInterval;
obstacleDiff = Vector2.Zero;
if (position.Y > Level.Loaded.Size.Y)
{
obstacleDiff = Vector2.UnitY;
}
else if (position.Y < 0.0f)
{
obstacleDiff = -Vector2.UnitY;
}
else if (position.X < 0.0f)
{
obstacleDiff = Vector2.UnitX;
}
else if (position.X > Level.Loaded.Size.X)
{
obstacleDiff = -Vector2.UnitX;
}
else
{
var cells = Level.Loaded.GetCells(position, 1);
if (cells.Count > 0)
{
int cellCount = 0;
foreach (Voronoi2.VoronoiCell cell in cells)
{
Vector2 diff = cell.Center - position;
if (diff.LengthSquared() > 5000.0f * 5000.0f) continue;
obstacleDiff += diff;
cellCount++;
}
if (cellCount > 0)
{
obstacleDiff /= cellCount;
obstacleDist = obstacleDiff.Length();
obstacleDiff = Vector2.Normalize(obstacleDiff);
}
}
}
}
if (Swarm != null)
{
Vector2 midPoint = Swarm.MidPoint();
float midPointDist = Vector2.Distance(SimPosition, midPoint) * 100.0f;
if (midPointDist > Swarm.MaxDistance)
{
steeringManager.SteeringSeek(midPoint, ((midPointDist / Swarm.MaxDistance) - 1.0f) * prefab.Speed);
}
steeringManager.SteeringManual(deltaTime, Swarm.AvgVelocity() * Swarm.Cohesion);
}
if (prefab.WanderAmount > 0.0f)
{
steeringManager.SteeringWander(prefab.Speed);
}
if (obstacleDiff != Vector2.Zero)
{
steeringManager.SteeringManual(deltaTime, -obstacleDiff * (1.0f - obstacleDist / 5000.0f) * prefab.Speed);
}
steeringManager.Update(prefab.Speed);
if (prefab.WanderZAmount > 0.0f)
{
wanderZPhase += Rand.Range(-prefab.WanderZAmount, prefab.WanderZAmount);
velocity.Z = (float)Math.Sin(wanderZPhase) * prefab.Speed;
}
velocity = Vector3.Lerp(velocity, new Vector3(Steering.X, Steering.Y, velocity.Z), deltaTime);
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
float rotation = 0.0f;
if (!prefab.DisableRotation)
{
rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
if (velocity.X < 0.0f) rotation -= MathHelper.Pi;
}
drawPosition = position;
if (depth > 0.0f)
{
Vector2 camOffset = drawPosition - cam.WorldViewCenter;
drawPosition -= camOffset * (depth / MaxDepth) * 0.05f;
}
prefab.Sprite.Draw(spriteBatch,
new Vector2(drawPosition.X, -drawPosition.Y),
Color.Lerp(Color.White, Level.Loaded.BackgroundColor, (depth / MaxDepth) * 0.2f),
rotation, (1.0f - (depth / MaxDepth) * 0.2f) * prefab.Scale,
velocity.X > 0.0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally,
(depth / MaxDepth));
}
}
class Swarm
{
public List<BackgroundCreature> Members;
public readonly float MaxDistance;
public readonly float Cohesion;
public Vector2 MidPoint()
{
if (Members.Count == 0) return Vector2.Zero;
Vector2 midPoint = Vector2.Zero;
foreach (BackgroundCreature member in Members)
{
midPoint += member.SimPosition;
}
midPoint /= Members.Count;
return midPoint;
}
public Vector2 AvgVelocity()
{
if (Members.Count == 0) return Vector2.Zero;
Vector2 avgVel = Vector2.Zero;
foreach (BackgroundCreature member in Members)
{
avgVel += member.Velocity;
}
avgVel /= Members.Count;
return avgVel;
}
public Swarm(List<BackgroundCreature> members, float maxDistance, float cohesion)
{
Members = members;
MaxDistance = maxDistance;
Cohesion = cohesion;
foreach (BackgroundCreature bgSprite in members)
{
bgSprite.Swarm = this;
}
}
}
}
@@ -0,0 +1,152 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreatureManager
{
const int MaxSprites = 100;
const float CheckActiveInterval = 1.0f;
private float checkActiveTimer;
private List<BackgroundCreaturePrefab> prefabs = new List<BackgroundCreaturePrefab>();
private List<BackgroundCreature> activeSprites = new List<BackgroundCreature>();
public BackgroundCreatureManager(string configPath)
{
LoadConfig(new ContentFile(configPath, ContentType.BackgroundCreaturePrefabs));
}
public BackgroundCreatureManager(IEnumerable<ContentFile> files)
{
foreach(var file in files)
{
LoadConfig(file);
}
}
private void LoadConfig(ContentFile config)
{
try
{
XDocument doc = XMLExtensions.TryLoadXml(config.Path);
if (doc == null) { return; }
var mainElement = doc.Root;
if (mainElement.IsOverride())
{
mainElement = doc.Root.FirstElement();
prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{config.Path}'", Color.Yellow);
}
else if (prefabs.Any())
{
DebugConsole.NewMessage($"Loading additional background creatures from file '{config.Path}'");
}
foreach (XElement element in mainElement.Elements())
{
prefabs.Add(new BackgroundCreaturePrefab(element));
};
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", config.Path), e);
}
}
public void SpawnSprites(int count, Vector2? position = null)
{
activeSprites.Clear();
if (prefabs.Count == 0) return;
count = Math.Min(count, MaxSprites);
for (int i = 0; i < count; i++ )
{
Vector2 pos = Vector2.Zero;
if (position == null)
{
var wayPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine==null);
if (wayPoints.Any())
{
WayPoint wp = wayPoints[Rand.Int(wayPoints.Count, Rand.RandSync.ClientOnly)];
pos = new Vector2(wp.Rect.X, wp.Rect.Y);
pos += Rand.Vector(200.0f, Rand.RandSync.ClientOnly);
}
else
{
pos = Rand.Vector(2000.0f, Rand.RandSync.ClientOnly);
}
}
else
{
pos = (Vector2)position;
}
var prefab = prefabs[Rand.Int(prefabs.Count, Rand.RandSync.ClientOnly)];
int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax, Rand.RandSync.ClientOnly);
List<BackgroundCreature> swarmMembers = new List<BackgroundCreature>();
for (int n = 0; n < amount; n++)
{
var newSprite = new BackgroundCreature(prefab, pos);
activeSprites.Add(newSprite);
swarmMembers.Add(newSprite);
}
if (amount > 0)
{
new Swarm(swarmMembers, prefab.SwarmRadius, prefab.SwarmCohesion);
}
}
}
public void ClearSprites()
{
activeSprites.Clear();
}
public void Update(float deltaTime, Camera cam)
{
if (checkActiveTimer < 0.0f)
{
foreach (BackgroundCreature sprite in activeSprites)
{
sprite.Enabled = Math.Abs(sprite.TransformedPosition.X - cam.WorldViewCenter.X) < cam.WorldView.Width &&
Math.Abs(sprite.TransformedPosition.Y - cam.WorldViewCenter.Y) < cam.WorldView.Height;
}
checkActiveTimer = CheckActiveInterval;
}
else
{
checkActiveTimer -= deltaTime;
}
foreach (BackgroundCreature sprite in activeSprites)
{
if (!sprite.Enabled) continue;
sprite.Update(deltaTime);
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature sprite in activeSprites)
{
if (!sprite.Enabled) continue;
sprite.Draw(spriteBatch, cam);
}
}
}
}
@@ -0,0 +1,50 @@
using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreaturePrefab
{
public readonly Sprite Sprite;
public readonly float Speed;
public readonly float WanderAmount;
public readonly float WanderZAmount;
public readonly int SwarmMin, SwarmMax;
public readonly float SwarmRadius, SwarmCohesion;
public readonly bool DisableRotation;
public readonly float Scale;
public BackgroundCreaturePrefab(XElement element)
{
Speed = element.GetAttributeFloat("speed", 1.0f);
WanderAmount = element.GetAttributeFloat("wanderamount", 0.0f);
WanderZAmount = element.GetAttributeFloat("wanderzamount", 0.0f);
SwarmMin = element.GetAttributeInt("swarmmin", 1);
SwarmMax = element.GetAttributeInt("swarmmax", 1);
SwarmRadius = element.GetAttributeFloat("swarmradius", 200.0f);
SwarmCohesion = element.GetAttributeFloat("swarmcohesion", 0.2f);
DisableRotation = element.GetAttributeBool("disablerotation", false);
Scale = element.GetAttributeFloat("scale", 1.0f);
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("sprite", System.StringComparison.OrdinalIgnoreCase)) { continue; }
Sprite = new Sprite(subElement, lazyLoad: true);
break;
}
}
}
}
@@ -0,0 +1,173 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Voronoi2;
namespace Barotrauma
{
static partial class CaveGenerator
{
public static List<VertexPositionTexture> GenerateRenderVerticeList(List<Vector2[]> triangles)
{
var verticeList = new List<VertexPositionTexture>();
for (int i = 0; i < triangles.Count; i++)
{
foreach (Vector2 vertex in triangles[i])
{
//shift the coordinates around a bit to make the texture repetition less obvious
Vector2 uvCoords = new Vector2(
vertex.X / 2000.0f + (float)Math.Sin(vertex.X / 500.0f) * 0.15f,
vertex.Y / 2000.0f + (float)Math.Sin(vertex.Y / 700.0f) * 0.15f);
verticeList.Add(new VertexPositionTexture(new Vector3(vertex, 1.0f), uvCoords));
}
}
return verticeList;
}
public static VertexPositionTexture[] GenerateWallShapes(List<VoronoiCell> cells, Level level)
{
float outWardThickness = 30.0f;
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
foreach (VoronoiCell cell in cells)
{
CompareCCW compare = new CompareCCW(cell.Center);
foreach (GraphEdge edge in cell.Edges)
{
if (edge.Cell1 != null && edge.Cell1.Body == null && edge.Cell1.CellType != CellType.Empty) edge.Cell1 = null;
if (edge.Cell2 != null && edge.Cell2.Body == null && edge.Cell2.CellType != CellType.Empty) edge.Cell2 = null;
if (compare.Compare(edge.Point1, edge.Point2) == -1)
{
var temp = edge.Point1;
edge.Point1 = edge.Point2;
edge.Point2 = temp;
}
}
}
foreach (VoronoiCell cell in cells)
{
foreach (GraphEdge edge in cell.Edges)
{
if (!edge.IsSolid) continue;
GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1 == e.Point1 || edge.Point1 == e.Point2));
GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2 == e.Point1 || edge.Point2 == e.Point2));
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
float inwardThickness1 = 100;
float inwardThickness2 = 100;
if (leftEdge != null && !leftEdge.IsSolid)
{
leftNormal = edge.Point1 == leftEdge.Point1 ?
Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) :
Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2);
inwardThickness1 = Vector2.Distance(leftEdge.Point1, leftEdge.Point2) / 2;
}
else
{
leftNormal = Vector2.Normalize(cell.Center - edge.Point1);
inwardThickness1 = Vector2.Distance(edge.Point1, cell.Center) / 2;
}
if (!MathUtils.IsValid(leftNormal))
{
#if DEBUG
DebugConsole.ThrowError("Invalid left normal");
#endif
GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed,
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
"Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")");
if (cell.Body != null)
{
GameMain.World.Remove(cell.Body);
cell.Body = null;
}
leftNormal = Vector2.UnitX;
break;
}
if (rightEdge != null && !rightEdge.IsSolid)
{
rightNormal = edge.Point2 == rightEdge.Point1 ?
Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) :
Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2);
inwardThickness2 = Vector2.Distance(rightEdge.Point1, rightEdge.Point2) / 2;
}
else
{
rightNormal = Vector2.Normalize(cell.Center - edge.Point2);
inwardThickness2 = Vector2.Distance(edge.Point2, cell.Center) / 2;
}
if (!MathUtils.IsValid(rightNormal))
{
#if DEBUG
DebugConsole.ThrowError("Invalid right normal");
#endif
GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed,
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
"Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")");
if (cell.Body != null)
{
GameMain.World.Remove(cell.Body);
cell.Body = null;
}
rightNormal = Vector2.UnitX;
break;
}
float point1UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point1 - cell.Center));
float point2UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point2 - cell.Center));
//handle wrapping around 0/360
if (point1UV - point2UV > MathHelper.Pi)
{
point2UV += MathHelper.TwoPi;
}
//the texture wraps around the cell 4 times
//TODO: define the uv scale in level generation parameters?
point1UV = point1UV / MathHelper.TwoPi * 4;
point2UV = point2UV / MathHelper.TwoPi * 4;
for (int i = 0; i < 2; i++)
{
Vector2[] verts = new Vector2[3];
VertexPositionTexture[] vertPos = new VertexPositionTexture[3];
if (i == 0)
{
verts[0] = edge.Point1 - leftNormal * outWardThickness;
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point1 + leftNormal * inwardThickness1;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(point1UV, 0.0f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(point1UV, 0.5f));
}
else
{
verts[0] = edge.Point1 + leftNormal * inwardThickness1;
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point2 + rightNormal * inwardThickness2;
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(point1UV, 0.5f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(point2UV, 0.5f));
}
verticeList.AddRange(vertPos);
}
}
}
return verticeList.ToArray();
}
}
}
@@ -0,0 +1,117 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Dynamics;
using System.Linq;
using System.Collections.Generic;
using FarseerPhysics;
namespace Barotrauma
{
partial class Level
{
private LevelRenderer renderer;
private BackgroundCreatureManager backgroundCreatureManager;
public LevelRenderer Renderer => renderer;
public void ReloadTextures()
{
renderer.ReloadTextures();
HashSet<Texture2D> uniqueTextures = new HashSet<Texture2D>();
HashSet<Sprite> uniqueSprites = new HashSet<Sprite>();
var allLevelObjects = levelObjectManager.GetAllObjects();
foreach (var levelObj in allLevelObjects)
{
foreach (Sprite sprite in levelObj.Prefab.Sprites)
{
if (!uniqueTextures.Contains(sprite.Texture))
{
uniqueTextures.Add(sprite.Texture);
uniqueSprites.Add(sprite);
}
}
foreach (Sprite specularSprite in levelObj.Prefab.SpecularSprites)
{
if (!uniqueTextures.Contains(specularSprite.Texture))
{
uniqueTextures.Add(specularSprite.Texture);
uniqueSprites.Add(specularSprite);
}
}
}
foreach (Sprite sprite in uniqueSprites)
{
sprite.ReloadTexture();
}
}
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
if (renderer == null) return;
renderer.Draw(spriteBatch, cam);
if (GameMain.DebugDraw)
{
foreach (InterestingPosition pos in positionsOfInterest)
{
Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave)
{
color = Color.DarkOrange;
}
else if (pos.PositionType == PositionType.Ruin)
{
color = Color.LightGray;
}
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
}
foreach (RuinGeneration.Ruin ruin in ruins)
{
Rectangle ruinArea = ruin.Area;
ruinArea.Y = -ruinArea.Y - ruinArea.Height;
GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
}
}
}
public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam)
{
float brightness = MathHelper.Clamp(1.1f + (cam.Position.Y - Size.Y) / 100000.0f, 0.1f, 1.0f);
var lightColorHLS = generationParams.AmbientLightColor.RgbToHLS();
lightColorHLS.Y *= brightness;
GameMain.LightManager.AmbientLight = ToolBox.HLSToRGB(lightColorHLS);
graphics.Clear(BackgroundColor);
if (renderer == null) return;
renderer.DrawBackground(spriteBatch, cam, levelObjectManager, backgroundCreatureManager);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
foreach (LevelWall levelWall in extraWalls)
{
if (levelWall.Body.BodyType == BodyType.Static) continue;
Vector2 bodyPos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
levelWall.MoveState = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 16);
if (Vector2.DistanceSquared(bodyPos, levelWall.Body.Position) > 0.5f)
{
levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation);
}
}
}
}
}
@@ -0,0 +1,296 @@
using Barotrauma.Lights;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.SpriteDeformations;
using System.Linq;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
partial class LevelObject
{
public float SwingTimer;
public float ScaleOscillateTimer;
public float CurrentSwingAmount;
public Vector2 CurrentScaleOscillation;
public float CurrentRotation;
private List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>();
public Vector2 CurrentScale
{
get;
private set;
} = Vector2.One;
public LightSource[] LightSources
{
get;
private set;
}
public LevelTrigger[] LightSourceTriggers
{
get;
private set;
}
public ParticleEmitter[] ParticleEmitters
{
get;
private set;
}
public LevelTrigger[] ParticleEmitterTriggers
{
get;
private set;
}
public RoundSound[] Sounds
{
get;
private set;
}
public SoundChannel[] SoundChannels
{
get;
private set;
}
public LevelTrigger[] SoundTriggers
{
get;
private set;
}
public Vector2[,] CurrentSpriteDeformation
{
get;
private set;
}
partial void InitProjSpecific()
{
Sprite?.EnsureLazyLoaded();
SpecularSprite?.EnsureLazyLoaded();
Prefab.DeformableSprite?.EnsureLazyLoaded();
CurrentSwingAmount = Prefab.SwingAmountRad;
CurrentScaleOscillation = Prefab.ScaleOscillation;
SwingTimer = Rand.Range(0.0f, MathHelper.TwoPi);
ScaleOscillateTimer = Rand.Range(0.0f, MathHelper.TwoPi);
if (Prefab.ParticleEmitterPrefabs != null)
{
ParticleEmitters = new ParticleEmitter[Prefab.ParticleEmitterPrefabs.Count];
ParticleEmitterTriggers = new LevelTrigger[Prefab.ParticleEmitterPrefabs.Count];
for (int i = 0; i < Prefab.ParticleEmitterPrefabs.Count; i++)
{
ParticleEmitters[i] = new ParticleEmitter(Prefab.ParticleEmitterPrefabs[i]);
ParticleEmitterTriggers[i] = Prefab.ParticleEmitterTriggerIndex[i] > -1 ?
Triggers[Prefab.ParticleEmitterTriggerIndex[i]] : null;
}
}
if (Prefab.LightSourceParams != null)
{
LightSources = new LightSource[Prefab.LightSourceParams.Count];
LightSourceTriggers = new LevelTrigger[Prefab.LightSourceParams.Count];
for (int i = 0; i < Prefab.LightSourceParams.Count; i++)
{
LightSources[i] = new LightSource(Prefab.LightSourceParams[i])
{
Position = new Vector2(Position.X, Position.Y),
IsBackground = true
};
LightSourceTriggers[i] = Prefab.LightSourceTriggerIndex[i] > -1 ?
Triggers[Prefab.LightSourceTriggerIndex[i]] : null;
}
}
Sounds = new RoundSound[Prefab.Sounds.Count];
SoundChannels = new SoundChannel[Prefab.Sounds.Count];
SoundTriggers = new LevelTrigger[Prefab.Sounds.Count];
for (int i = 0; i < Prefab.Sounds.Count; i++)
{
Sounds[i] = Submarine.LoadRoundSound(Prefab.Sounds[i].SoundElement, false);
SoundTriggers[i] = Prefab.Sounds[i].TriggerIndex > -1 ? Triggers[Prefab.Sounds[i].TriggerIndex] : null;
}
int j = 0;
foreach (XElement subElement in Prefab.Config.Elements())
{
if (!subElement.Name.ToString().Equals("deformablesprite", StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (XElement animationElement in subElement.Elements())
{
var newDeformation = SpriteDeformation.Load(animationElement, Prefab.Name);
if (newDeformation != null)
{
newDeformation.Params = Prefab.SpriteDeformations[j].Params;
spriteDeformations.Add(newDeformation);
j++;
}
}
}
}
public void Update(float deltaTime)
{
if (ParticleEmitters != null)
{
for (int i = 0; i < ParticleEmitters.Length; i++)
{
if (ParticleEmitterTriggers[i] != null && !ParticleEmitterTriggers[i].IsTriggered) continue;
Vector2 emitterPos = LocalToWorld(Prefab.EmitterPositions[i]);
ParticleEmitters[i].Emit(deltaTime, emitterPos, hullGuess: null,
angle: ParticleEmitters[i].Prefab.CopyEntityAngle ? Rotation : 0.0f);
}
}
CurrentRotation = Rotation;
if (ActivePrefab.SwingFrequency > 0.0f)
{
SwingTimer += deltaTime * ActivePrefab.SwingFrequency;
SwingTimer = SwingTimer % MathHelper.TwoPi;
//lerp the swing amount to the correct value to prevent it from abruptly changing to a different value
//when a trigger changes the swing amoung
CurrentSwingAmount = MathHelper.Lerp(CurrentSwingAmount, ActivePrefab.SwingAmountRad, deltaTime * 10.0f);
if (ActivePrefab.SwingAmountRad > 0.0f)
{
CurrentRotation += (float)Math.Sin(SwingTimer) * CurrentSwingAmount;
}
}
CurrentScale = Vector2.One * Scale;
if (ActivePrefab.ScaleOscillationFrequency > 0.0f)
{
ScaleOscillateTimer += deltaTime * ActivePrefab.ScaleOscillationFrequency;
ScaleOscillateTimer = ScaleOscillateTimer % MathHelper.TwoPi;
CurrentScaleOscillation = Vector2.Lerp(CurrentScaleOscillation, ActivePrefab.ScaleOscillation, deltaTime * 10.0f);
float sin = (float)Math.Sin(ScaleOscillateTimer);
CurrentScale *= new Vector2(
1.0f + sin * CurrentScaleOscillation.X,
1.0f + sin * CurrentScaleOscillation.Y);
}
if (LightSources != null)
{
for (int i = 0; i < LightSources.Length; i++)
{
if (LightSourceTriggers[i] != null) LightSources[i].Enabled = LightSourceTriggers[i].IsTriggered;
LightSources[i].Rotation = -CurrentRotation;
LightSources[i].SpriteScale = CurrentScale;
}
}
if (spriteDeformations.Count > 0)
{
UpdateDeformations(deltaTime);
}
for (int i = 0; i < Sounds.Length; i++)
{
if (Sounds[i] == null) { continue; }
if (SoundTriggers[i] == null || SoundTriggers[i].IsTriggered)
{
RoundSound roundSound = Sounds[i];
Vector2 soundPos = LocalToWorld(new Vector2(Prefab.Sounds[i].Position.X, Prefab.Sounds[i].Position.Y));
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), soundPos) <
roundSound.Range * roundSound.Range)
{
if (SoundChannels[i] == null || !SoundChannels[i].IsPlaying)
{
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, soundPos);
}
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
}
}
else if (SoundChannels[i] != null && SoundChannels[i].IsPlaying)
{
SoundChannels[i].FadeOutAndDispose();
SoundChannels[i] = null;
}
}
}
private void UpdateDeformations(float deltaTime)
{
foreach (SpriteDeformation deformation in spriteDeformations)
{
if (deformation is PositionalDeformation positionalDeformation)
{
UpdatePositionalDeformation(positionalDeformation, deltaTime);
}
deformation.Update(deltaTime);
}
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size);
foreach (LightSource lightSource in LightSources)
{
if (lightSource?.DeformableLightSprite != null)
{
lightSource.DeformableLightSprite.Deform(CurrentSpriteDeformation);
}
}
}
private void UpdatePositionalDeformation(PositionalDeformation positionalDeformation, float deltaTime)
{
Matrix matrix = ActivePrefab.DeformableSprite.GetTransform(
Position,
ActivePrefab.DeformableSprite.Origin,
CurrentRotation,
Vector2.One * Scale);
Matrix rotationMatrix = Matrix.CreateRotationZ(CurrentRotation);
foreach (LevelTrigger trigger in Triggers)
{
foreach (Entity triggerer in trigger.Triggerers)
{
Vector2 moveAmount = triggerer.WorldPosition - trigger.TriggererPosition[triggerer];
moveAmount = Vector2.Transform(moveAmount, rotationMatrix);
moveAmount /= (ActivePrefab.DeformableSprite.Size * Scale);
moveAmount.Y = -moveAmount.Y;
positionalDeformation.Deform(trigger.WorldPosition, moveAmount, deltaTime, Matrix.Invert(matrix) *
Matrix.CreateScale(1.0f / ActivePrefab.DeformableSprite.Size.X, 1.0f / ActivePrefab.DeformableSprite.Size.Y, 1));
}
}
}
public void ClientRead(IReadMessage msg)
{
for (int i = 0; i < Triggers.Count; i++)
{
if (!Triggers[i].UseNetworkSyncing) continue;
Triggers[i].ClientRead(msg);
}
}
partial void RemoveProjSpecific()
{
for (int i = 0; i < Sounds.Length; i++)
{
SoundChannels[i]?.Dispose();
SoundChannels[i] = null;
}
if (LightSources != null)
{
for (int i = 0; i < LightSources.Length; i++)
{
LightSources[i].Remove();
}
LightSources = null;
}
}
}
}
@@ -0,0 +1,173 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class LevelObjectManager
{
private List<LevelObject> visibleObjectsBack = new List<LevelObject>();
private List<LevelObject> visibleObjectsFront = new List<LevelObject>();
private Rectangle currentGridIndices;
partial void UpdateProjSpecific(float deltaTime)
{
foreach (LevelObject obj in visibleObjectsBack)
{
obj.Update(deltaTime);
}
foreach (LevelObject obj in visibleObjectsFront)
{
obj.Update(deltaTime);
}
}
public IEnumerable<LevelObject> GetVisibleObjects()
{
return visibleObjectsBack.Union(visibleObjectsFront);
}
/// <summary>
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
/// </summary>
private void RefreshVisibleObjects(Rectangle currentIndices)
{
visibleObjectsBack.Clear();
visibleObjectsFront.Clear();
for (int x = currentIndices.X; x <= currentIndices.Width; x++)
{
for (int y = currentIndices.Y; y <= currentIndices.Height; y++)
{
if (objectGrid[x, y] == null) continue;
foreach (LevelObject obj in objectGrid[x, y])
{
var objectList = obj.Position.Z >= 0 ? visibleObjectsBack : visibleObjectsFront;
int drawOrderIndex = 0;
for (int i = 0; i < objectList.Count; i++)
{
if (objectList[i] == obj)
{
drawOrderIndex = -1;
break;
}
if (objectList[i].Position.Z < obj.Position.Z)
{
break;
}
else
{
drawOrderIndex = i + 1;
}
}
if (drawOrderIndex >= 0)
{
objectList.Insert(drawOrderIndex, obj);
}
}
}
}
currentGridIndices = currentIndices;
}
public void DrawObjects(SpriteBatch spriteBatch, Camera cam, bool drawFront, bool specular = false)
{
Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
if (indices.X >= objectGrid.GetLength(0)) return;
indices.Y = (int)Math.Floor((cam.WorldView.Y - cam.WorldView.Height - Level.Loaded.BottomPos) / (float)GridSize);
if (indices.Y >= objectGrid.GetLength(1)) return;
indices.Width = (int)Math.Floor(cam.WorldView.Right / (float)GridSize) + 1;
if (indices.Width < 0) return;
indices.Height = (int)Math.Floor((cam.WorldView.Y - Level.Loaded.BottomPos) / (float)GridSize) + 1;
if (indices.Height < 0) return;
indices.X = Math.Max(indices.X, 0);
indices.Y = Math.Max(indices.Y, 0);
indices.Width = Math.Min(indices.Width, objectGrid.GetLength(0) - 1);
indices.Height = Math.Min(indices.Height, objectGrid.GetLength(1) - 1);
float z = 0.0f;
if (currentGridIndices != indices)
{
RefreshVisibleObjects(indices);
}
var objectList = drawFront ? visibleObjectsFront : visibleObjectsBack;
foreach (LevelObject obj in objectList)
{
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
camDiff.Y = -camDiff.Y;
Sprite activeSprite = specular ? obj.SpecularSprite : obj.Sprite;
activeSprite?.Draw(
spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f,
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 5000.0f),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
SpriteEffects.None,
z);
if (specular) continue;
if (obj.ActivePrefab.DeformableSprite != null)
{
if (obj.CurrentSpriteDeformation != null)
{
obj.ActivePrefab.DeformableSprite.Deform(obj.CurrentSpriteDeformation);
}
else
{
obj.ActivePrefab.DeformableSprite.Reset();
}
obj.ActivePrefab.DeformableSprite?.Draw(cam,
new Vector3(new Vector2(obj.Position.X, obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f, z * 10.0f),
obj.ActivePrefab.DeformableSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 5000.0f));
}
if (GameMain.DebugDraw)
{
GUI.DrawRectangle(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(10.0f, 10.0f), GUI.Style.Red, true);
foreach (LevelTrigger trigger in obj.Triggers)
{
if (trigger.PhysicsBody == null) continue;
GUI.DrawLine(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
Vector2 flowForce = trigger.GetWaterFlowVelocity();
if (flowForce.LengthSquared() > 1)
{
flowForce.Y = -flowForce.Y;
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUI.Style.Orange, 0, 5);
}
trigger.PhysicsBody.UpdateDrawPosition();
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
}
}
z += 0.0001f;
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
int objIndex = msg.ReadRangedInteger(0, objects.Count);
objects[objIndex].ClientRead(msg);
}
}
}
@@ -0,0 +1,181 @@
using Barotrauma.Lights;
using Barotrauma.Particles;
using Barotrauma.SpriteDeformations;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObjectPrefab
{
public class SoundConfig
{
public readonly XElement SoundElement;
public readonly Vector2 Position;
public readonly int TriggerIndex;
public SoundConfig(XElement element, int triggerIndex)
{
SoundElement = element;
Position = element.GetAttributeVector2("position", Vector2.Zero);
TriggerIndex = triggerIndex;
}
}
public List<int> ParticleEmitterTriggerIndex
{
get;
private set;
}
public List<ParticleEmitterPrefab> ParticleEmitterPrefabs
{
get;
private set;
}
public List<Vector2> EmitterPositions
{
get;
private set;
}
public List<SoundConfig> Sounds
{
get;
private set;
} = new List<SoundConfig>();
public List<int> LightSourceTriggerIndex
{
get;
private set;
} = new List<int>();
public List<LightSourceParams> LightSourceParams
{
get;
private set;
} = new List<Lights.LightSourceParams>();
/// <summary>
/// Only used for editing sprite deformation parameters. The actual LevelObjects use separate SpriteDeformation instances.
/// </summary>
public List<SpriteDeformation> SpriteDeformations
{
get;
private set;
} = new List<SpriteDeformation>();
partial void InitProjSpecific(XElement element)
{
LoadElementsProjSpecific(element, -1);
}
private void LoadElementsProjSpecific(XElement element, int parentTriggerIndex)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "leveltrigger":
case "trigger":
LoadElementsProjSpecific(subElement, LevelTriggerElements.IndexOf(subElement));
break;
case "lightsource":
LightSourceTriggerIndex.Add(parentTriggerIndex);
LightSourceParams.Add(new LightSourceParams(subElement));
break;
case "particleemitter":
if (ParticleEmitterPrefabs == null)
{
ParticleEmitterPrefabs = new List<ParticleEmitterPrefab>();
EmitterPositions = new List<Vector2>();
ParticleEmitterTriggerIndex = new List<int>();
}
ParticleEmitterPrefabs.Add(new ParticleEmitterPrefab(subElement));
ParticleEmitterTriggerIndex.Add(parentTriggerIndex);
EmitterPositions.Add(subElement.GetAttributeVector2("position", Vector2.Zero));
break;
case "sound":
Sounds.Add(new SoundConfig(subElement, parentTriggerIndex));
break;
case "deformablesprite":
foreach (XElement deformElement in subElement.Elements())
{
var deformation = SpriteDeformation.Load(deformElement, Name);
if (deformation != null)
{
SpriteDeformations.Add(deformation);
}
}
break;
}
}
}
public void Save(XElement element)
{
this.Config = element;
SerializableProperty.SerializeProperties(this, element);
foreach (XElement subElement in element.Elements().ToList())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "childobject":
case "lightsource":
subElement.Remove();
break;
case "deformablesprite":
subElement.RemoveNodes();
foreach (SpriteDeformation deformation in SpriteDeformations)
{
var deformationElement = new XElement("SpriteDeformation");
deformation.Save(deformationElement);
subElement.Add(deformationElement);
}
break;
}
}
foreach (LightSourceParams lightSourceParams in LightSourceParams)
{
var lightElement = new XElement("LightSource");
SerializableProperty.SerializeProperties(lightSourceParams, lightElement);
element.Add(lightElement);
}
foreach (ChildObject childObj in ChildObjects)
{
element.Add(new XElement("ChildObject",
new XAttribute("names", string.Join(", ", childObj.AllowedNames)),
new XAttribute("mincount", childObj.MinCount),
new XAttribute("maxcount", childObj.MaxCount)));
}
foreach (KeyValuePair<string, float> overrideCommonness in OverrideCommonness)
{
bool elementFound = false;
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("overridecommonness", System.StringComparison.OrdinalIgnoreCase)
&& subElement.GetAttributeString("leveltype", "") == overrideCommonness.Key)
{
subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture);
elementFound = true;
break;
}
}
if (!elementFound)
{
element.Add(new XElement("overridecommonness",
new XAttribute("leveltype", overrideCommonness.Key),
new XAttribute("commonness", overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture))));
}
}
}
}
}
@@ -0,0 +1,19 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class LevelTrigger
{
public void ClientRead(IReadMessage msg)
{
if (ForceFluctuationStrength > 0.0f)
{
currentForceFluctuation = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
if (stayTriggeredDelay > 0.0f)
{
triggeredTimer = msg.ReadRangedSingle(0.0f, stayTriggeredDelay, 16);
}
}
}
}
@@ -0,0 +1,390 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Voronoi2;
namespace Barotrauma
{
class LevelRenderer : IDisposable
{
private static BasicEffect wallEdgeEffect, wallCenterEffect;
private Vector2 dustOffset;
private Vector2 defaultDustVelocity;
private Vector2 dustVelocity;
private RasterizerState cullNone;
private Level level;
private VertexBuffer wallVertices, bodyVertices;
public LevelRenderer(Level level)
{
defaultDustVelocity = Vector2.UnitY * 10.0f;
cullNone = new RasterizerState() { CullMode = CullMode.None };
if (wallEdgeEffect == null)
{
wallEdgeEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
DiffuseColor = new Vector3(0.8f, 0.8f, 0.8f),
VertexColorEnabled = true,
TextureEnabled = true,
Texture = level.GenerationParams.WallEdgeSprite.Texture
};
wallEdgeEffect.CurrentTechnique = wallEdgeEffect.Techniques["BasicEffect_Texture"];
}
if (wallCenterEffect == null)
{
wallCenterEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true,
TextureEnabled = true,
Texture = level.GenerationParams.WallSprite.Texture
};
wallCenterEffect.CurrentTechnique = wallCenterEffect.Techniques["BasicEffect_Texture"];
}
this.level = level;
}
public void ReloadTextures()
{
level.GenerationParams.WallEdgeSprite.ReloadTexture();
wallEdgeEffect.Texture = level.GenerationParams.WallEdgeSprite.Texture;
level.GenerationParams.WallSprite.ReloadTexture();
wallCenterEffect.Texture = level.GenerationParams.WallSprite.Texture;
}
public void Update(float deltaTime, Camera cam)
{
//calculate the sum of the forces of nearby level triggers
//and use it to move the dust texture and water distortion effect
Vector2 currentDustVel = defaultDustVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
{
//use the largest water flow velocity of all the triggers
Vector2 objectMaxFlow = Vector2.Zero;
foreach (LevelTrigger trigger in levelObject.Triggers)
{
Vector2 vel = trigger.GetWaterFlowVelocity(cam.WorldViewCenter);
if (vel.LengthSquared() > objectMaxFlow.LengthSquared())
{
objectMaxFlow = vel;
}
}
currentDustVel += objectMaxFlow;
}
dustVelocity = Vector2.Lerp(dustVelocity, currentDustVel, deltaTime);
WaterRenderer.Instance?.ScrollWater(dustVelocity, deltaTime);
if (level.GenerationParams.WaterParticles != null)
{
Vector2 waterTextureSize = level.GenerationParams.WaterParticles.size * level.GenerationParams.WaterParticleScale;
dustOffset += new Vector2(dustVelocity.X, -dustVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime;
while (dustOffset.X <= -waterTextureSize.X) dustOffset.X += waterTextureSize.X;
while (dustOffset.X >= waterTextureSize.X) dustOffset.X -= waterTextureSize.X;
while (dustOffset.Y <= -waterTextureSize.Y) dustOffset.Y += waterTextureSize.Y;
while (dustOffset.Y >= waterTextureSize.Y) dustOffset.Y -= waterTextureSize.Y;
}
}
public static VertexPositionColorTexture[] GetColoredVertices(VertexPositionTexture[] vertices, Color color)
{
VertexPositionColorTexture[] verts = new VertexPositionColorTexture[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
verts[i] = new VertexPositionColorTexture(vertices[i].Position, color, vertices[i].TextureCoordinate);
}
return verts;
}
public void SetWallVertices(VertexPositionTexture[] vertices, Color color)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(GetColoredVertices(vertices, color));
}
public void SetBodyVertices(VertexPositionTexture[] vertices, Color color)
{
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(GetColoredVertices(vertices, color));
}
public void SetWallVertices(VertexPositionColorTexture[] vertices)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length,BufferUsage.WriteOnly);
wallVertices.SetData(vertices);
}
public void SetBodyVertices(VertexPositionColorTexture[] vertices)
{
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(vertices);
}
public void DrawBackground(SpriteBatch spriteBatch, Camera cam,
LevelObjectManager backgroundSpriteManager = null,
BackgroundCreatureManager backgroundCreatureManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap);
Vector2 backgroundPos = cam.WorldViewCenter;
backgroundPos.Y = -backgroundPos.Y;
backgroundPos *= 0.05f;
if (level.GenerationParams.BackgroundTopSprite != null)
{
int backgroundSize = (int)level.GenerationParams.BackgroundTopSprite.size.Y;
if (backgroundPos.Y < backgroundSize)
{
if (backgroundPos.Y < 0)
{
var backgroundTop = level.GenerationParams.BackgroundTopSprite;
backgroundTop.SourceRect = new Rectangle((int)backgroundPos.X, (int)backgroundPos.Y, backgroundSize, (int)Math.Min(-backgroundPos.Y, backgroundSize));
backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, Math.Min(-backgroundPos.Y, GameMain.GraphicsHeight)),
color: level.BackgroundTextureColor);
}
if (-backgroundPos.Y < GameMain.GraphicsHeight && level.GenerationParams.BackgroundSprite != null)
{
var background = level.GenerationParams.BackgroundSprite;
background.SourceRect = new Rectangle((int)backgroundPos.X, (int)Math.Max(backgroundPos.Y, 0), backgroundSize, backgroundSize);
background.DrawTiled(spriteBatch,
(backgroundPos.Y < 0) ? new Vector2(0.0f, (int)-backgroundPos.Y) : Vector2.Zero,
new Vector2(GameMain.GraphicsWidth, (int)Math.Min(Math.Ceiling(backgroundSize - backgroundPos.Y), backgroundSize)),
color: level.BackgroundTextureColor);
}
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
cam.Transform);
if (backgroundSpriteManager != null) backgroundSpriteManager.DrawObjects(spriteBatch, cam, drawFront: false);
if (backgroundCreatureManager != null) backgroundCreatureManager.Draw(spriteBatch, cam);
if (level.GenerationParams.WaterParticles != null)
{
float textureScale = level.GenerationParams.WaterParticleScale;
Rectangle srcRect = new Rectangle(0, 0, 2048, 2048);
Vector2 origin = new Vector2(cam.WorldView.X, -cam.WorldView.Y);
Vector2 offset = -origin + dustOffset;
while (offset.X <= -srcRect.Width * textureScale) offset.X += srcRect.Width * textureScale;
while (offset.X > 0.0f) offset.X -= srcRect.Width * textureScale;
while (offset.Y <= -srcRect.Height * textureScale) offset.Y += srcRect.Height * textureScale;
while (offset.Y > 0.0f) offset.Y -= srcRect.Height * textureScale;
for (int i = 0; i < 4; i++)
{
float scale = (1.0f - i * 0.2f);
//alpha goes from 1.0 to 0.0 when scale is in the range of 0.1 - 0.05
float alpha = (cam.Zoom * scale) < 0.1f ? (cam.Zoom * scale - 0.05f) * 20.0f : 1.0f;
if (alpha <= 0.0f) continue;
Vector2 offsetS = offset * scale
+ new Vector2(cam.WorldView.Width, cam.WorldView.Height) * (1.0f - scale) * 0.5f
- new Vector2(256.0f * i);
float texScale = scale * textureScale;
while (offsetS.X <= -srcRect.Width * texScale) offsetS.X += srcRect.Width * texScale;
while (offsetS.X > 0.0f) offsetS.X -= srcRect.Width * texScale;
while (offsetS.Y <= -srcRect.Height * texScale) offsetS.Y += srcRect.Height * texScale;
while (offsetS.Y > 0.0f) offsetS.Y -= srcRect.Height * texScale;
level.GenerationParams.WaterParticles.DrawTiled(
spriteBatch, origin + offsetS,
new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y),
rect: srcRect, color: Color.White * alpha, textureScale: new Vector2(texScale));
}
}
spriteBatch.End();
RenderWalls(GameMain.Instance.GraphicsDevice, cam, specular: false);
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
cam.Transform);
if (backgroundSpriteManager != null) backgroundSpriteManager.DrawObjects(spriteBatch, cam, drawFront: true);
spriteBatch.End();
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
if (GameMain.DebugDraw && cam.Zoom > 0.05f)
{
var cells = level.GetCells(cam.WorldViewCenter, 2);
foreach (VoronoiCell cell in cells)
{
GUI.DrawRectangle(spriteBatch, new Vector2(cell.Center.X - 10.0f, -cell.Center.Y - 10.0f), new Vector2(20.0f, 20.0f), Color.Cyan, true);
GUI.DrawLine(spriteBatch,
new Vector2(cell.Edges[0].Point1.X + cell.Translation.X, -(cell.Edges[0].Point1.Y + cell.Translation.Y)),
new Vector2(cell.Center.X, -(cell.Center.Y)),
Color.Blue * 0.5f);
foreach (GraphEdge edge in cell.Edges)
{
GUI.DrawLine(spriteBatch, new Vector2(edge.Point1.X + cell.Translation.X, -(edge.Point1.Y + cell.Translation.Y)),
new Vector2(edge.Point2.X + cell.Translation.X, -(edge.Point2.Y + cell.Translation.Y)), cell.Body == null ? Color.Cyan * 0.5f : Color.White);
}
foreach (Vector2 point in cell.BodyVertices)
{
GUI.DrawRectangle(spriteBatch, new Vector2(point.X + cell.Translation.X, -(point.Y + cell.Translation.Y)), new Vector2(10.0f, 10.0f), Color.White, true);
}
}
foreach (List<Point> nodeList in level.SmallTunnels)
{
for (int i = 1; i < nodeList.Count; i++)
{
GUI.DrawLine(spriteBatch,
new Vector2(nodeList[i - 1].X, -nodeList[i - 1].Y),
new Vector2(nodeList[i].X, -nodeList[i].Y),
Color.Lerp(Color.Yellow, GUI.Style.Red, i / (float)nodeList.Count), 0, 10);
}
}
foreach (var ruin in level.Ruins)
{
ruin.DebugDraw(spriteBatch);
}
}
Vector2 pos = new Vector2(0.0f, -level.Size.Y);
if (cam.WorldView.Y >= -pos.Y - 1024)
{
pos.X = cam.WorldView.X -1024;
int width = (int)(Math.Ceiling(cam.WorldView.Width / 1024 + 4.0f) * 1024);
GUI.DrawRectangle(spriteBatch,new Rectangle(
(int)(MathUtils.Round(pos.X, 1024)),
-cam.WorldView.Y,
width,
(int)(cam.WorldView.Y + pos.Y) - 30),
Color.Black, true);
spriteBatch.Draw(level.GenerationParams.WallEdgeSprite.Texture,
new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)pos.Y-1000, width, 1024),
new Rectangle(0, 0, width, -1024),
level.BackgroundTextureColor, 0.0f,
Vector2.Zero,
SpriteEffects.None, 0.0f);
}
if (cam.WorldView.Y - cam.WorldView.Height < level.SeaFloorTopPos + 1024)
{
pos = new Vector2(cam.WorldView.X - 1024, -level.BottomPos);
int width = (int)(Math.Ceiling(cam.WorldView.Width / 1024 + 4.0f) * 1024);
GUI.DrawRectangle(spriteBatch, new Rectangle(
(int)(MathUtils.Round(pos.X, 1024)),
(int)-(level.BottomPos - 30),
width,
(int)(level.BottomPos - (cam.WorldView.Y - cam.WorldView.Height))),
Color.Black, true);
spriteBatch.Draw(level.GenerationParams.WallEdgeSprite.Texture,
new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)-level.BottomPos, width, 1024),
new Rectangle(0, 0, width, -1024),
level.BackgroundTextureColor, 0.0f,
Vector2.Zero,
SpriteEffects.FlipVertically, 0.0f);
}
}
public void RenderWalls(GraphicsDevice graphicsDevice, Camera cam, bool specular)
{
if (wallVertices == null) return;
bool renderLevel = cam.WorldView.Y >= 0.0f;
bool renderSeaFloor = cam.WorldView.Y - cam.WorldView.Height < level.SeaFloorTopPos + 1024;
if (!renderLevel && !renderSeaFloor) return;
Matrix transformMatrix = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 100) * 0.5f;
wallEdgeEffect.Texture = specular && level.GenerationParams.WallEdgeSpriteSpecular != null ?
level.GenerationParams.WallEdgeSpriteSpecular.Texture :
level.GenerationParams.WallEdgeSprite.Texture;
wallEdgeEffect.World = transformMatrix;
wallCenterEffect.Texture = specular && level.GenerationParams.WallSpriteSpecular != null ?
level.GenerationParams.WallSpriteSpecular.Texture :
level.GenerationParams.WallSprite.Texture;
wallCenterEffect.World = transformMatrix;
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
if (renderLevel)
{
graphicsDevice.SetVertexBuffer(bodyVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(bodyVertices.VertexCount / 3.0f));
}
foreach (LevelWall wall in level.ExtraWalls)
{
if (!renderSeaFloor && wall == level.SeaFloor) continue;
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.BodyVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.BodyVertices.VertexCount / 3.0f));
}
var defaultRasterizerState = graphicsDevice.RasterizerState;
graphicsDevice.RasterizerState = cullNone;
wallEdgeEffect.World = transformMatrix;
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
if (renderLevel)
{
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wallVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wallVertices.VertexCount / 3.0f));
}
foreach (LevelWall wall in level.ExtraWalls)
{
if (!renderSeaFloor && wall == level.SeaFloor) continue;
wallEdgeEffect.World = wall.GetTransform() * transformMatrix;
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallVertices.VertexCount / 3.0f));
}
graphicsDevice.RasterizerState = defaultRasterizerState;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (wallVertices != null) wallVertices.Dispose();
if (bodyVertices != null) bodyVertices.Dispose();
}
}
}
@@ -0,0 +1,56 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
partial class LevelWall : IDisposable
{
private VertexBuffer wallVertices, bodyVertices;
public VertexBuffer WallVertices
{
get { return wallVertices; }
}
public VertexBuffer BodyVertices
{
get { return bodyVertices; }
}
public Matrix GetTransform()
{
return body.BodyType == BodyType.Static ?
Matrix.Identity :
Matrix.CreateRotationZ(body.Rotation) *
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(body.Position), 0.0f));
}
public void SetWallVertices(VertexPositionTexture[] vertices, Color color)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(LevelRenderer.GetColoredVertices(vertices, color));
}
public void SetBodyVertices(VertexPositionTexture[] vertices, Color color)
{
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(LevelRenderer.GetColoredVertices(vertices, color));
}
public void SetWallVertices(VertexPositionColorTexture[] vertices)
{
if (wallVertices != null && !wallVertices.IsDisposed) wallVertices.Dispose();
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(vertices);
}
public void SetBodyVertices(VertexPositionColorTexture[] vertices)
{
if (bodyVertices != null && !bodyVertices.IsDisposed) bodyVertices.Dispose();
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
bodyVertices.SetData(vertices);
}
}
}
@@ -0,0 +1,20 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.RuinGeneration
{
partial class Ruin
{
public void DebugDraw(SpriteBatch spriteBatch)
{
foreach (RuinShape shape in allShapes)
{
GUI.DrawString(spriteBatch, new Vector2(shape.Center.X, -shape.Center.Y - 50), shape.DistanceFromEntrance.ToString(), Color.White, Color.Black * 0.5f, font: GUI.LargeFont);
}
foreach (Line line in walls)
{
GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), GUI.Style.Red, 0.0f, 10);
}
}
}
}
@@ -0,0 +1,232 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
struct WaterVertexData
{
float DistortStrengthX;
float DistortStrengthY;
float WaterColorStrength;
float WaterAlpha;
public WaterVertexData(float distortStrengthX, float distortStrengthY, float waterColorStrength, float waterAlpha)
{
DistortStrengthX = distortStrengthX;
DistortStrengthY = distortStrengthY;
WaterColorStrength = waterColorStrength;
WaterAlpha = waterAlpha;
}
public static implicit operator Color(WaterVertexData wd)
{
return new Color(wd.DistortStrengthX, wd.DistortStrengthY, wd.WaterColorStrength, wd.WaterAlpha);
}
}
class WaterRenderer : IDisposable
{
public static WaterRenderer Instance;
public const int DefaultBufferSize = 2000;
public const int DefaultIndoorsBufferSize = 3000;
public static Vector2 DistortionScale = new Vector2(2f, 2f);
public static Vector2 DistortionStrength = new Vector2(0.25f, 0.25f);
public static float BlurAmount = 0.0f;
public Vector2 WavePos
{
get;
private set;
}
public readonly Color waterColor = new Color(0.75f * 0.5f, 0.8f * 0.5f, 0.9f * 0.5f, 1.0f);
public readonly WaterVertexData IndoorsWaterColor = new WaterVertexData(0.1f, 0.1f, 0.5f, 1.0f);
public readonly WaterVertexData IndoorsSurfaceTopColor = new WaterVertexData(0.5f, 0.5f, 0.0f, 1.0f);
public readonly WaterVertexData IndoorsSurfaceBottomColor = new WaterVertexData(0.2f, 0.1f, 0.9f, 1.0f);
public VertexPositionTexture[] vertices = new VertexPositionTexture[DefaultBufferSize];
public Dictionary<EntityGrid, VertexPositionColorTexture[]> IndoorsVertices = new Dictionary<EntityGrid, VertexPositionColorTexture[]>();// VertexPositionColorTexture[DefaultBufferSize * 2];
public Effect WaterEffect
{
get;
private set;
}
private BasicEffect basicEffect;
public int PositionInBuffer = 0;
public Dictionary<EntityGrid, int> PositionInIndoorsBuffer = new Dictionary<EntityGrid, int>();
public Texture2D WaterTexture { get; }
public WaterRenderer(GraphicsDevice graphicsDevice, ContentManager content)
{
#if WINDOWS
WaterEffect = content.Load<Effect>("Effects/watershader");
#endif
#if LINUX || OSX
WaterEffect = content.Load<Effect>("Effects/watershader_opengl");
#endif
WaterTexture = TextureLoader.FromFile("Content/Effects/waterbump.png");
WaterEffect.Parameters["xWaterBumpMap"].SetValue(WaterTexture);
WaterEffect.Parameters["waterColor"].SetValue(waterColor.ToVector4());
if (basicEffect == null)
{
basicEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
basicEffect.VertexColorEnabled = false;
basicEffect.TextureEnabled = true;
}
}
public void RenderWater(SpriteBatch spriteBatch, RenderTarget2D texture, Camera cam)
{
spriteBatch.GraphicsDevice.BlendState = BlendState.NonPremultiplied;
WaterEffect.Parameters["xTexture"].SetValue(texture);
Vector2 distortionStrength = cam == null ? DistortionStrength : DistortionStrength * cam.Zoom;
WaterEffect.Parameters["xWaveWidth"].SetValue(DistortionStrength.X);
WaterEffect.Parameters["xWaveHeight"].SetValue(DistortionStrength.Y);
if (BlurAmount > 0.0f)
{
WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShaderBlurred"];
WaterEffect.Parameters["xBlurDistance"].SetValue(BlurAmount / 100.0f);
}
else
{ WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShader"];
}
Vector2 offset = WavePos;
if (cam != null)
{
offset += (cam.Position - new Vector2(cam.WorldView.Width / 2.0f, -cam.WorldView.Height / 2.0f));
offset.Y += cam.WorldView.Height;
offset.X += cam.WorldView.Width;
offset *= DistortionScale;
}
offset.Y = -offset.Y;
WaterEffect.Parameters["xUvOffset"].SetValue(new Vector2((offset.X / GameMain.GraphicsWidth) % 1.0f, (offset.Y / GameMain.GraphicsHeight) % 1.0f));
WaterEffect.Parameters["xBumpPos"].SetValue(Vector2.Zero);
if (cam != null)
{
WaterEffect.Parameters["xBumpScale"].SetValue(new Vector2(
(float)cam.WorldView.Width / GameMain.GraphicsWidth * DistortionScale.X,
(float)cam.WorldView.Height / GameMain.GraphicsHeight * DistortionScale.Y));
WaterEffect.Parameters["xTransform"].SetValue(cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f);
WaterEffect.Parameters["xUvTransform"].SetValue(cam.ShaderTransform
* Matrix.CreateOrthographicOffCenter(0, spriteBatch.GraphicsDevice.Viewport.Width * 2, spriteBatch.GraphicsDevice.Viewport.Height * 2, 0, 0, 1) * Matrix.CreateTranslation(0.5f, 0.5f, 0.0f));
}
else
{
WaterEffect.Parameters["xBumpScale"].SetValue(new Vector2(1.0f, 1.0f));
WaterEffect.Parameters["xTransform"].SetValue(Matrix.Identity * Matrix.CreateTranslation(-1.0f, 1.0f, 0.0f));
WaterEffect.Parameters["xUvTransform"].SetValue(Matrix.CreateScale(0.5f, -0.5f, 0.0f));
}
WaterEffect.CurrentTechnique.Passes[0].Apply();
VertexPositionColorTexture[] verts = new VertexPositionColorTexture[6];
Rectangle view = cam != null ? cam.WorldView : spriteBatch.GraphicsDevice.Viewport.Bounds;
var corners = new Vector3[4];
corners[0] = new Vector3(view.X, view.Y, 0.1f);
corners[1] = new Vector3(view.Right, view.Y, 0.1f);
corners[2] = new Vector3(view.Right, view.Y - view.Height, 0.1f);
corners[3] = new Vector3(view.X, view.Y - view.Height, 0.1f);
WaterVertexData backGroundColor = new WaterVertexData(0.1f, 0.1f, 0.5f, 1.0f);
verts[0] = new VertexPositionColorTexture(corners[0], backGroundColor, Vector2.Zero);
verts[1] = new VertexPositionColorTexture(corners[1], backGroundColor, Vector2.Zero);
verts[2] = new VertexPositionColorTexture(corners[2], backGroundColor, Vector2.Zero);
verts[3] = new VertexPositionColorTexture(corners[0], backGroundColor, Vector2.Zero);
verts[4] = new VertexPositionColorTexture(corners[2], backGroundColor, Vector2.Zero);
verts[5] = new VertexPositionColorTexture(corners[3], backGroundColor, Vector2.Zero);
spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, verts, 0, 2);
foreach (KeyValuePair<EntityGrid, VertexPositionColorTexture[]> subVerts in IndoorsVertices)
{
if (!PositionInIndoorsBuffer.ContainsKey(subVerts.Key) || PositionInIndoorsBuffer[subVerts.Key] == 0) continue;
offset = WavePos;
if (subVerts.Key.Submarine != null) { offset -= subVerts.Key.Submarine.WorldPosition; }
if (cam != null)
{
offset += cam.Position - new Vector2(cam.WorldView.Width / 2.0f, -cam.WorldView.Height / 2.0f);
offset.Y += cam.WorldView.Height;
offset.X += cam.WorldView.Width;
offset *= DistortionScale;
}
offset.Y = -offset.Y;
WaterEffect.Parameters["xUvOffset"].SetValue(new Vector2((offset.X / GameMain.GraphicsWidth) % 1.0f, (offset.Y / GameMain.GraphicsHeight) % 1.0f));
WaterEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, subVerts.Value, 0, PositionInIndoorsBuffer[subVerts.Key] / 3);
}
}
public void ScrollWater(Vector2 vel, float deltaTime)
{
WavePos = WavePos - vel * deltaTime;
}
public void RenderAir(GraphicsDevice graphicsDevice, Camera cam, RenderTarget2D texture, Matrix transform)
{
if (vertices == null || vertices.Length < 0 || PositionInBuffer <= 0) return;
basicEffect.Texture = texture;
basicEffect.View = Matrix.Identity;
basicEffect.World = transform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f * Matrix.CreateTranslation(0.0f,0.0f,0f);
basicEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SamplerStates[0] = SamplerState.PointWrap;
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, PositionInBuffer / 3);
}
public void ResetBuffers()
{
PositionInBuffer = 0;
PositionInIndoorsBuffer.Clear();
IndoorsVertices.Clear();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
if (WaterEffect != null)
{
WaterEffect.Dispose();
WaterEffect = null;
}
if (basicEffect != null)
{
basicEffect.Dispose();
basicEffect = null;
}
}
}
}
@@ -0,0 +1,679 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Barotrauma.Lights
{
class ConvexHullList
{
private List<ConvexHull> list;
public HashSet<ConvexHull> IsHidden;
public readonly Submarine Submarine;
public List<ConvexHull> List
{
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
IsHidden.RemoveWhere(ch => !list.Contains(ch));
}
}
public ConvexHullList(Submarine submarine)
{
Submarine = submarine;
list = new List<ConvexHull>();
IsHidden = new HashSet<ConvexHull>();
}
}
class Segment
{
public SegmentPoint Start;
public SegmentPoint End;
public ConvexHull ConvexHull;
public bool IsHorizontal;
public bool IsAxisAligned;
public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
{
if (start.Pos.Y > end.Pos.Y)
{
var temp = start;
start = end;
end = temp;
}
Start = start;
End = end;
ConvexHull = convexHull;
start.ConvexHull = convexHull;
end.ConvexHull = convexHull;
IsHorizontal = Math.Abs(start.Pos.X - end.Pos.X) > Math.Abs(start.Pos.Y - end.Pos.Y);
IsAxisAligned = Math.Abs(start.Pos.X - end.Pos.X) < 0.1f || Math.Abs(start.Pos.Y - end.Pos.Y) < 0.001f;
}
}
struct SegmentPoint
{
public Vector2 Pos;
public Vector2 WorldPos;
public ConvexHull ConvexHull;
public SegmentPoint(Vector2 pos, ConvexHull convexHull)
{
Pos = pos;
WorldPos = pos;
ConvexHull = convexHull;
}
public override string ToString()
{
return Pos.ToString();
}
}
class ConvexHull
{
public static List<ConvexHullList> HullLists = new List<ConvexHullList>();
public static BasicEffect shadowEffect;
public static BasicEffect penumbraEffect;
private Segment[] segments = new Segment[4];
private SegmentPoint[] vertices = new SegmentPoint[4];
private SegmentPoint[] losVertices = new SegmentPoint[4];
private readonly bool[] backFacing;
private readonly bool[] ignoreEdge;
private readonly bool isHorizontal;
public VertexPositionColor[] ShadowVertices { get; private set; }
public VertexPositionTexture[] PenumbraVertices { get; private set; }
public int ShadowVertexCount { get; private set; }
public MapEntity ParentEntity { get; private set; }
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled == value) return;
enabled = value;
LastVertexChangeTime = (float)Timing.TotalTime;
}
}
/// <summary>
/// The elapsed gametime when the vertices of this hull last changed
/// </summary>
public float LastVertexChangeTime
{
get;
private set;
}
public Rectangle BoundingBox { get; private set; }
public ConvexHull(Vector2[] points, Color color, MapEntity parent)
{
if (shadowEffect == null)
{
shadowEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true
};
}
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
TextureEnabled = true,
LightingEnabled = false,
Texture = TextureLoader.FromFile("Content/Lights/penumbra.png")
};
}
ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 2];
PenumbraVertices = new VertexPositionTexture[6];
backFacing = new bool[4];
ignoreEdge = new bool[4];
SetVertices(points);
Enabled = true;
isHorizontal = BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure)
{
isHorizontal = structure.IsHorizontal;
}
else if (ParentEntity is Item item)
{
var door = item.GetComponent<Door>();
if (door != null) { isHorizontal = door.IsHorizontal; }
}
var chList = HullLists.Find(x => x.Submarine == parent.Submarine);
if (chList == null)
{
chList = new ConvexHullList(parent.Submarine);
HullLists.Add(chList);
}
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
}
chList.List.Add(this);
}
private void MergeOverlappingSegments(ConvexHull ch)
{
if (ch == this) return;
if (isHorizontal == ch.isHorizontal)
{
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
float mergeDist = 32;
float mergeDistSqr = mergeDist * mergeDist;
for (int i = 0; i < segments.Length; i++)
{
for (int j = 0; j < ch.segments.Length; j++)
{
if (segments[i].IsHorizontal != ch.segments[j].IsHorizontal) { continue; }
//the segments must be at different sides of the convex hulls to be merged
//(e.g. the right edge of a wall piece and the left edge of another one)
var segment1Center = (segments[i].Start.Pos + segments[i].End.Pos) / 2.0f;
var segment2Center = (ch.segments[j].Start.Pos + ch.segments[j].End.Pos) / 2.0f;
if (Vector2.Dot(segment1Center - BoundingBox.Center.ToVector2(), segment2Center - ch.BoundingBox.Center.ToVector2()) > 0) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], true);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], false);
}
}
}
}
else
{
//TODO: do something to corner areas where a vertical wall meets a horizontal one
}
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.X >= ch.BoundingBox.X && vertices[i].Pos.X <= ch.BoundingBox.Right &&
vertices[i].Pos.Y >= ch.BoundingBox.Y && vertices[i].Pos.Y <= ch.BoundingBox.Bottom)
{
Vector2 p = vertices[(i + 1) % vertices.Length].Pos;
if (p.X >= ch.BoundingBox.X && p.X <= ch.BoundingBox.Right &&
p.Y >= ch.BoundingBox.Y && p.Y <= ch.BoundingBox.Bottom)
{
ignoreEdge[i] = true;
}
}
}
}
private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch)
{
int startPointIndex = -1, endPointIndex = -1;
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.NearlyEquals(segment1.Start.Pos))
startPointIndex = i;
else if (vertices[i].Pos.NearlyEquals(segment1.End.Pos))
endPointIndex = i;
}
if (startPointIndex == -1 || endPointIndex == -1) { return; }
int startPoint2Index = -1, endPoint2Index = -1;
for (int i = 0; i < segment2.ConvexHull.vertices.Length; i++)
{
if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.Start.Pos))
startPoint2Index = i;
else if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.End.Pos))
endPoint2Index = i;
}
if (startPoint2Index == -1 || endPoint2Index == -1) { return; }
if (startPointsMatch)
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.End.Pos) / 2.0f;
}
else
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
}
}
public void Rotate(Vector2 origin, float amount)
{
Matrix rotationMatrix =
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix);
}
private void CalculateDimensions()
{
float minX = vertices[0].Pos.X, minY = vertices[0].Pos.Y, maxX = vertices[0].Pos.X, maxY = vertices[0].Pos.Y;
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X;
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y;
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X;
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
public void Move(Vector2 amount)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos += amount;
losVertices[i].Pos += amount;
segments[i].Start.Pos += amount;
segments[i].End.Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime;
CalculateDimensions();
}
public void SetVertices(Vector2[] points, Matrix? rotationMatrix = null)
{
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
LastVertexChangeTime = (float)Timing.TotalTime;
for (int i = 0; i < 4; i++)
{
vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this);
}
for (int i = 0; i < 4; i++)
{
ignoreEdge[i] = false;
}
int margin = 0;
if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[2].Y))
{
losVertices[0].Pos = new Vector2(points[0].X + margin, points[0].Y);
losVertices[1].Pos = new Vector2(points[1].X + margin, points[1].Y);
losVertices[2].Pos = new Vector2(points[2].X - margin, points[2].Y);
losVertices[3].Pos = new Vector2(points[3].X - margin, points[3].Y);
}
else
{
losVertices[0].Pos = new Vector2(points[0].X, points[0].Y + margin);
losVertices[1].Pos = new Vector2(points[1].X, points[1].Y - margin);
losVertices[2].Pos = new Vector2(points[2].X, points[2].Y - margin);
losVertices[3].Pos = new Vector2(points[3].X, points[3].Y + margin);
}
if (rotationMatrix.HasValue)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value);
losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value);
}
}
for (int i = 0; i < 4; i++)
{
segments[i] = new Segment(vertices[i], vertices[(i + 1) % 4], this);
}
CalculateDimensions();
if (ParentEntity == null) return;
var chList = HullLists.Find(x => x.Submarine == ParentEntity.Submarine);
if (chList != null)
{
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
}
}
}
public bool Intersects(Rectangle rect)
{
if (!Enabled) return false;
Rectangle transformedBounds = BoundingBox;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
transformedBounds.X += (int)ParentEntity.Submarine.Position.X;
transformedBounds.Y += (int)ParentEntity.Submarine.Position.Y;
}
return transformedBounds.Intersects(rect);
}
/// <summary>
/// Returns the segments that are facing towards viewPosition
/// </summary>
public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments, bool ignoreEdges)
{
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i] && ignoreEdges) continue;
Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
Vector2 middle = (pos1 + pos2) / 2;
Vector2 L = viewPosition - middle;
Vector2 N = new Vector2(
-(pos2.Y - pos1.Y),
pos2.X - pos1.X);
if (Vector2.Dot(N, L) > 0)
{
visibleSegments.Add(segments[i]);
}
}
}
public void RefreshWorldPositions()
{
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos = vertices[i].Pos;
segments[i].Start.WorldPos = segments[i].Start.Pos;
segments[i].End.WorldPos = segments[i].End.Pos;
}
if (ParentEntity == null || ParentEntity.Submarine == null) { return; }
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition;
segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition;
segments[i].End.WorldPos += ParentEntity.Submarine.DrawPosition;
}
}
public void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true)
{
Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f);
}
ShadowVertexCount = 0;
var vertices = los ? losVertices : this.vertices;
//compute facing of each edge, using N*L
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i])
{
backFacing[i] = false;
continue;
}
Vector2 firstVertex = vertices[i].Pos;
Vector2 secondVertex = vertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
Vector2 N = new Vector2(
-(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0) == los;
}
//find beginning and ending vertices which
//belong to the shadow
int startingIndex = 0;
int endingIndex = 0;
for (int i = 0; i < 4; i++)
{
int currentEdge = i;
int nextEdge = (i + 1) % 4;
if (backFacing[currentEdge] && !backFacing[nextEdge])
endingIndex = nextEdge;
if (!backFacing[currentEdge] && backFacing[nextEdge])
startingIndex = nextEdge;
}
//nr of vertices that are in the shadow
if (endingIndex > startingIndex)
ShadowVertexCount = endingIndex - startingIndex + 1;
else
ShadowVertexCount = 4 + 1 - startingIndex + endingIndex;
//shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != ShadowVertexCount * 2)
{
Vector3 vertexPos = new Vector3(vertices[currentIndex].Pos, 0.0f);
int i = los ? svCount : svCount + 1;
int j = los ? svCount + 1 : svCount;
//one vertex on the hull
ShadowVertices[i] = new VertexPositionColor
{
Color = los ? Color.Black : Color.Transparent,
Position = vertexPos + offset
};
//one extruded by the light direction
ShadowVertices[j] = new VertexPositionColor
{
Color = ShadowVertices[i].Color
};
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
L2P.Normalize();
ShadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000 + offset;
svCount += 2;
currentIndex = (currentIndex + 1) % 4;
}
if (los)
{
CalculatePenumbraVertices(startingIndex, endingIndex, lightSourcePos, los);
}
}
private void CalculatePenumbraVertices(int startingIndex, int endingIndex, Vector2 lightSourcePos, bool los)
{
var vertices = los ? losVertices : this.vertices;
Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f);
}
for (int n = 0; n < 4; n += 3)
{
Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f);
PenumbraVertices[n] = new VertexPositionTexture
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
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 + offset;
if (los)
{
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
else
{
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero;
}
}
if (n > 0)
{
var temp = PenumbraVertices[4];
PenumbraVertices[4] = PenumbraVertices[5];
PenumbraVertices[5] = temp;
}
}
}
public static List<ConvexHull> GetHullsInRange(Vector2 position, float range, Submarine ParentSub)
{
List<ConvexHull> list = new List<ConvexHull>();
foreach (ConvexHullList chList in HullLists)
{
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhull are both outside
if (chList.Submarine == null)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light is outside, convexhull inside a sub
else
{
Rectangle subBorders = chList.Submarine.Borders;
subBorders.Y -= chList.Submarine.Borders.Height;
if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) continue;
lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition);
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
else
{
//light is inside, convexhull outside
if (chList.Submarine == null)
{
lightPos += (ParentSub.WorldPosition - ParentSub.HiddenSubPosition);
HashSet<RuinGeneration.Ruin> visibleRuins = new HashSet<RuinGeneration.Ruin>();
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, ruin.Area)) { continue; }
visibleRuins.Add(ruin);
}
list.AddRange(chList.List.FindAll(ch => ch.ParentEntity?.ParentRuin != null && visibleRuins.Contains(ch.ParentEntity.ParentRuin)));
continue;
}
//light and convexhull are both inside the same sub
if (chList.Submarine == ParentSub)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light and convexhull are inside different subs
else
{
lightPos -= (chList.Submarine.Position - ParentSub.Position);
Rectangle subBorders = chList.Submarine.Borders;
subBorders.Location += chList.Submarine.HiddenSubPosition.ToPoint() - new Point(0, chList.Submarine.Borders.Height);
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) continue;
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
}
return list;
}
public void Remove()
{
var chList = HullLists.Find(x => x.Submarine == ParentEntity.Submarine);
if (chList != null)
{
chList.List.Remove(this);
if (chList.List.Count == 0)
{
HullLists.Remove(chList);
}
}
}
}
}
@@ -0,0 +1,717 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Collections.Generic;
using System.Linq;
using System;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
namespace Barotrauma.Lights
{
class LightManager
{
private const float AmbientLightUpdateInterval = 0.2f;
private const float AmbientLightFalloff = 0.8f;
/// <summary>
/// Enables a feature that makes lights inside the hull increase the brightness of the entire hull
/// and adjacent ones to some extent, if there are gaps for the lights to pass through.
/// Prevents unnaturally dark looking shadows in otherwise well-lit submarines, but disabled at least for
/// the time being because it makes the lighting behave unpredictably and may cause rooms to appear
/// excessively bright if different lighting conditions aren't tested and accounted for.
/// </summary>
private static readonly bool UseHullSpecificAmbientLight = false;
public static Entity ViewTarget { get; set; }
private float currLightMapScale;
public Color AmbientLight;
public RenderTarget2D LightMap
{
get;
private set;
}
public RenderTarget2D LimbLightMap
{
get;
private set;
}
public RenderTarget2D LosTexture
{
get;
private set;
}
public RenderTarget2D HighlightMap
{
get;
private set;
}
private readonly Texture2D highlightRaster;
private BasicEffect lightEffect;
public Effect LosEffect { get; private set; }
public Effect SolidColorEffect { get; private set; }
private List<LightSource> lights;
public bool LosEnabled = true;
public LosMode LosMode = LosMode.Transparent;
public bool LightingEnabled = true;
public bool ObstructVision;
private Texture2D visionCircle;
private Dictionary<Hull, Color> hullAmbientLights;
private Dictionary<Hull, Color> smoothedHullAmbientLights;
private float ambientLightUpdateTimer;
public IEnumerable<LightSource> Lights
{
get { return lights; }
}
public LightManager(GraphicsDevice graphics, ContentManager content)
{
lights = new List<LightSource>();
AmbientLight = new Color(20, 20, 20, 255);
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
GameMain.Instance.OnResolutionChanged += () =>
{
CreateRenderTargets(graphics);
};
CrossThread.RequestExecutionOnMainThread(() =>
{
CreateRenderTargets(graphics);
#if WINDOWS
LosEffect = content.Load<Effect>("Effects/losshader");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor");
#else
LosEffect = content.Load<Effect>("Effects/losshader_opengl");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor_opengl");
#endif
if (lightEffect == null)
{
lightEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true,
TextureEnabled = true,
Texture = LightSource.LightTexture
};
}
});
hullAmbientLights = new Dictionary<Hull, Color>();
smoothedHullAmbientLights = new Dictionary<Hull, Color>();
}
private void CreateRenderTargets(GraphicsDevice graphics)
{
var pp = graphics.PresentationParameters;
currLightMapScale = GameMain.Config.LightMapScale;
LightMap?.Dispose();
LightMap = CreateRenderTarget();
LimbLightMap?.Dispose();
LimbLightMap = CreateRenderTarget();
HighlightMap?.Dispose();
HighlightMap = CreateRenderTarget();
RenderTarget2D CreateRenderTarget()
{
return new RenderTarget2D(graphics,
(int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), (int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false,
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
RenderTargetUsage.DiscardContents);
}
LosTexture?.Dispose();
LosTexture = new RenderTarget2D(graphics,
(int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale),
(int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false, SurfaceFormat.Color, DepthFormat.None);
}
public void AddLight(LightSource light)
{
if (!lights.Contains(light)) lights.Add(light);
}
public void RemoveLight(LightSource light)
{
lights.Remove(light);
}
public void OnMapLoaded()
{
foreach (LightSource light in lights)
{
light.NeedsHullCheck = true;
light.NeedsRecalculation = true;
}
}
public void Update(float deltaTime)
{
if (UseHullSpecificAmbientLight)
{
if (ambientLightUpdateTimer > 0.0f)
{
ambientLightUpdateTimer -= deltaTime;
}
else
{
CalculateAmbientLights();
ambientLightUpdateTimer = AmbientLightUpdateInterval;
}
foreach (Hull hull in hullAmbientLights.Keys)
{
if (!smoothedHullAmbientLights.ContainsKey(hull))
{
smoothedHullAmbientLights.Add(hull, Color.TransparentBlack);
}
}
foreach (Hull hull in smoothedHullAmbientLights.Keys.ToList())
{
Color targetColor = Color.TransparentBlack;
hullAmbientLights.TryGetValue(hull, out targetColor);
smoothedHullAmbientLights[hull] = Color.Lerp(smoothedHullAmbientLights[hull], targetColor, deltaTime);
}
}
}
private List<LightSource> activeLights = new List<LightSource>(capacity: 100);
public void UpdateLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) return;
if (Math.Abs(currLightMapScale - GameMain.Config.LightMapScale) > 0.01f)
{
//lightmap scale has changed -> recreate render targets
CreateRenderTargets(graphics);
}
Matrix spriteBatchTransform = cam.Transform * Matrix.CreateScale(new Vector3(GameMain.Config.LightMapScale, GameMain.Config.LightMapScale, 1.0f));
Matrix transform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
bool highlightsVisible = UpdateHighlights(graphics, spriteBatch, spriteBatchTransform, cam);
Rectangle viewRect = cam.WorldView;
viewRect.Y -= cam.WorldView.Height;
//check which lights need to be drawn
activeLights.Clear();
foreach (LightSource light in lights)
{
if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
if (light.ParentBody != null)
{
light.Position = light.ParentBody.DrawPosition;
if (light.ParentSub != null) { light.Position -= light.ParentSub.DrawPosition; }
}
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.LightSourceParams.TextureRange, viewRect)) { continue; }
activeLights.Add(light);
}
//draw light sprites attached to characters
//render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending)
//to prevent the lights from showing through other characters or other light sprites attached to the same character
//---------------------------------------------------------------------------------------------------
graphics.SetRenderTarget(LimbLightMap);
graphics.Clear(Color.Black);
graphics.BlendState = BlendState.NonPremultiplied;
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
if (light.IsBackground) { continue; }
//draw limb lights at this point, because they were skipped over previously to prevent them from being obstructed
if (light.ParentBody?.UserData is Limb) { light.DrawSprite(spriteBatch, cam); }
}
spriteBatch.End();
//draw background lights
//---------------------------------------------------------------------------------------------------
graphics.SetRenderTarget(LightMap);
graphics.Clear(Color.Black);
graphics.BlendState = BlendState.Additive;
bool backgroundSpritesDrawn = false;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
if (!light.IsBackground) { continue; }
light.DrawSprite(spriteBatch, cam);
if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); }
backgroundSpritesDrawn = true;
}
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
spriteBatch.End();
//draw a black rectangle on hulls to hide background lights behind subs
//---------------------------------------------------------------------------------------------------
Dictionary<Hull, Rectangle> visibleHulls = null;
if (backgroundSpritesDrawn)
{
if (backgroundObstructor != null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black);
spriteBatch.End();
}
visibleHulls = GetVisibleHulls(cam);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform);
foreach (Rectangle drawRect in visibleHulls.Values)
{
//TODO: draw some sort of smoothed rectangle
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(drawRect.Width, drawRect.Height),
Color.Black, true);
}
spriteBatch.End();
graphics.BlendState = BlendState.Additive;
}
//draw the focused item and character to highlight them,
//and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
//---------------------------------------------------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
//don't draw limb lights at this point, they need to be drawn after lights have been obstructed by characters
if (light.IsBackground || light.ParentBody?.UserData is Limb) { continue; }
light.DrawSprite(spriteBatch, cam);
}
spriteBatch.End();
if (highlightsVisible)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
spriteBatch.Draw(HighlightMap, Vector2.Zero, Color.White);
spriteBatch.End();
}
//draw characters to obstruct the highlighted items/characters and light sprites
//---------------------------------------------------------------------------------------------------
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList)
{
if (character.CurrentHull == null || !character.Enabled) continue;
if (Character.Controlled?.FocusedCharacter == character) continue;
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.DeformSprite != null) continue;
limb.Draw(spriteBatch, cam, Color.Black);
}
}
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList)
{
if (character.CurrentHull == null || !character.Enabled) continue;
if (Character.Controlled?.FocusedCharacter == character) continue;
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.DeformSprite == null) continue;
limb.Draw(spriteBatch, cam, Color.Black);
}
}
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
graphics.BlendState = BlendState.Additive;
//draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
//---------------------------------------------------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
GUI.DrawRectangle(spriteBatch, new Rectangle(cam.WorldView.X, -cam.WorldView.Y, cam.WorldView.Width, cam.WorldView.Height), AmbientLight, isFilled: true);
spriteBatch.Draw(LimbLightMap, new Rectangle(cam.WorldView.X, -cam.WorldView.Y, cam.WorldView.Width, cam.WorldView.Height), Color.White);
foreach (ElectricalDischarger discharger in ElectricalDischarger.List)
{
discharger.DrawElectricity(spriteBatch);
}
foreach (LightSource light in activeLights)
{
if (light.IsBackground) { continue; }
if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); }
}
lightEffect.World = transform;
GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive);
if (UseHullSpecificAmbientLight)
{
if (visibleHulls == null)
{
visibleHulls = GetVisibleHulls(cam);
}
foreach (Hull hull in smoothedHullAmbientLights.Keys)
{
if (smoothedHullAmbientLights[hull].A < 0.01f) continue;
if (!visibleHulls.TryGetValue(hull, out Rectangle drawRect)) continue;
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(hull.Rect.Width, hull.Rect.Height),
smoothedHullAmbientLights[hull], true);
}
}
if (Character.Controlled != null)
{
Vector2 haloDrawPos = Character.Controlled.DrawPosition;
haloDrawPos.Y = -haloDrawPos.Y;
//ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough)
float ambientBrightness = (AmbientLight.R + AmbientLight.B + AmbientLight.G) / 255.0f / 3.0f;
Color haloColor = Color.White.Multiply(0.3f - ambientBrightness);
if (haloColor.A > 0)
{
float scale = 512.0f / LightSource.LightTexture.Width;
spriteBatch.Draw(
LightSource.LightTexture, haloDrawPos, null, haloColor, 0.0f,
new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f);
}
}
spriteBatch.End();
//draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
//---------------------------------------------------------------------------------------------------
graphics.SetRenderTarget(null);
graphics.BlendState = BlendState.NonPremultiplied;
}
private readonly List<Entity> highlightedEntities = new List<Entity>();
private bool UpdateHighlights(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam)
{
if (GUI.DisableItemHighlights) { return false; }
highlightedEntities.Clear();
if (Character.Controlled != null)
{
if (Character.Controlled.FocusedItem != null)
{
highlightedEntities.Add(Character.Controlled.FocusedItem);
}
if (Character.Controlled.FocusedCharacter != null)
{
highlightedEntities.Add(Character.Controlled.FocusedCharacter);
}
foreach (Item item in Item.ItemList)
{
if (item.IsHighlighted && !highlightedEntities.Contains(item))
{
highlightedEntities.Add(item);
}
}
}
if (highlightedEntities.Count == 0) { return false; }
//draw characters in light blue first
graphics.SetRenderTarget(HighlightMap);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4());
SolidColorEffect.CurrentTechnique.Passes[0].Apply();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.LightBlue.ToVector4());
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Entity highlighted in highlightedEntities)
{
if (highlighted is Item item)
{
item.Draw(spriteBatch, false, true);
}
else if (highlighted is Character character)
{
character.Draw(spriteBatch, cam);
}
}
spriteBatch.End();
//draw characters in black with a bit of blur, leaving the white edges visible
float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1
Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase);
SolidColorEffect.Parameters["color"].SetValue(overlayColor);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"];
SolidColorEffect.CurrentTechnique.Passes[0].Apply();
DeformableSprite.Effect.Parameters["solidColor"].SetValue(overlayColor);
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Entity highlighted in highlightedEntities)
{
if (highlighted is Item item)
{
SolidColorEffect.Parameters["blurDistance"].SetValue(0.02f);
item.Draw(spriteBatch, false, true);
}
else if (highlighted is Character character)
{
SolidColorEffect.Parameters["blurDistance"].SetValue(0.05f);
character.Draw(spriteBatch, cam);
}
}
spriteBatch.End();
//raster pattern on top of everything
spriteBatch.Begin(blendState: BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap);
spriteBatch.Draw(highlightRaster,
new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height),
new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)),
Color.White * 0.5f);
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
return true;
}
private Dictionary<Hull, Rectangle> GetVisibleHulls(Camera cam)
{
Dictionary<Hull, Rectangle> visibleHulls = new Dictionary<Hull, Rectangle>();
foreach (Hull hull in Hull.hullList)
{
var drawRect =
hull.Submarine == null ?
hull.Rect :
new Rectangle((int)(hull.Submarine.DrawPosition.X + hull.Rect.X), (int)(hull.Submarine.DrawPosition.Y + hull.Rect.Y), hull.Rect.Width, hull.Rect.Height);
if (drawRect.Right < cam.WorldView.X || drawRect.X > cam.WorldView.Right ||
drawRect.Y - drawRect.Height > cam.WorldView.Y || drawRect.Y < cam.WorldView.Y - cam.WorldView.Height)
{
continue;
}
visibleHulls.Add(hull, drawRect);
}
return visibleHulls;
}
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return;
if (ViewTarget == null) return;
graphics.SetRenderTarget(LosTexture);
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameMain.Config.LightMapScale, GameMain.Config.LightMapScale, 1.0f)));
if (ObstructVision)
{
graphics.Clear(Color.Black);
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
diff.Y = -diff.Y;
float rotation = MathUtils.VectorToAngle(diff);
Vector2 scale = new Vector2(
MathHelper.Clamp(diff.Length() / 256.0f, 2.0f, 5.0f), 2.0f);
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
}
else
{
graphics.Clear(Color.White);
}
spriteBatch.End();
//--------------------------------------
if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
{
Vector2 pos = ViewTarget.DrawPosition;
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
Matrix shadowTransform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width*0.75f, ViewTarget.Submarine);
if (convexHulls != null)
{
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Enabled || !convexHull.Intersects(camView)) continue;
Vector2 relativeLightPos = pos;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position;
convexHull.CalculateShadowVertices(relativeLightPos, true);
//convert triangle strips to a triangle list
for (int i = 0; i < convexHull.ShadowVertexCount * 2 - 2; i++)
{
if (i % 2 == 0)
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
}
else
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
}
}
penumbraVerts.AddRange(convexHull.PenumbraVertices);
}
if (shadowVerts.Count > 0)
{
ConvexHull.shadowEffect.World = shadowTransform;
ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
if (penumbraVerts.Count > 0)
{
ConvexHull.penumbraEffect.World = shadowTransform;
ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
}
}
}
}
graphics.SetRenderTarget(null);
}
private void CalculateAmbientLights()
{
hullAmbientLights.Clear();
foreach (LightSource light in lights)
{
if (light.Color.A < 1f || light.Range < 1.0f || light.IsBackground) continue;
var newAmbientLights = AmbientLightHulls(light);
foreach (Hull hull in newAmbientLights.Keys)
{
if (hullAmbientLights.ContainsKey(hull))
{
//hull already lit by some other light source -> add the ambient lights up
hullAmbientLights[hull] = new Color(
hullAmbientLights[hull].R + newAmbientLights[hull].R,
hullAmbientLights[hull].G + newAmbientLights[hull].G,
hullAmbientLights[hull].B + newAmbientLights[hull].B,
hullAmbientLights[hull].A + newAmbientLights[hull].A);
}
else
{
hullAmbientLights.Add(hull, newAmbientLights[hull]);
}
}
}
}
/// <summary>
/// Add ambient light to the hull the lightsource is inside + all adjacent hulls connected by a gap
/// </summary>
private Dictionary<Hull, Color> AmbientLightHulls(LightSource light)
{
Dictionary<Hull, Color> hullAmbientLight = new Dictionary<Hull, Color>();
var hull = Hull.FindHull(light.WorldPosition);
if (hull == null) return hullAmbientLight;
return AmbientLightHulls(hull, hullAmbientLight, light.Color * Math.Min(light.Range / 1000.0f, 1.0f));
}
/// <summary>
/// A flood fill algorithm that adds ambient light to all hulls the starting hull is connected to
/// </summary>
private Dictionary<Hull, Color> AmbientLightHulls(Hull hull, Dictionary<Hull, Color> hullAmbientLight, Color currColor)
{
if (hullAmbientLight.ContainsKey(hull))
{
if (hullAmbientLight[hull].A > currColor.A)
return hullAmbientLight;
else
hullAmbientLight[hull] = currColor;
}
else
{
hullAmbientLight.Add(hull, currColor);
}
Color nextHullLight = currColor * AmbientLightFalloff;
//light getting too dark to notice -> no need to spread further
if (nextHullLight.A < 20) return hullAmbientLight;
//use hashset to make sure that each hull is only included once
HashSet<Hull> hulls = new HashSet<Hull>();
foreach (Gap g in hull.ConnectedGaps)
{
if (!g.IsRoomToRoom || !g.PassAmbientLight || g.Open < 0.5f) continue;
hulls.Add((g.linkedTo[0] == hull ? g.linkedTo[1] : g.linkedTo[0]) as Hull);
}
foreach (Hull h in hulls)
{
hullAmbientLight = AmbientLightHulls(h, hullAmbientLight, nextHullLight);
}
return hullAmbientLight;
}
public void ClearLights()
{
lights.Clear();
}
}
class CustomBlendStates
{
static CustomBlendStates()
{
Multiplicative = new BlendState
{
ColorSourceBlend = Blend.DestinationColor,
ColorDestinationBlend = Blend.SourceColor,
ColorBlendFunction = BlendFunction.Add
};
}
public static BlendState Multiplicative { get; private set; }
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,152 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class LinkedSubmarine : MapEntity
{
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (!editing || wallVertices == null) return;
Color color = (IsHighlighted) ? GUI.Style.Orange : GUI.Style.Green;
if (IsSelected) color = GUI.Style.Red;
Vector2 pos = Position;
for (int i = 0; i < wallVertices.Count; i++)
{
Vector2 startPos = wallVertices[i] + pos;
startPos.Y = -startPos.Y;
Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos;
endPos.Y = -endPos.Y;
GUI.DrawLine(spriteBatch,
startPos,
endPos,
color, 0.0f, 5);
}
pos.Y = -pos.Y;
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
Rectangle drawRect = rect;
drawRect.Y = -rect.Y;
GUI.DrawRectangle(spriteBatch, drawRect, GUI.Style.Red, true);
if (!Item.ShowLinks) return;
foreach (MapEntity e in linkedTo)
{
bool isLinkAllowed = e is Item item && item.HasTag("dock");
GUI.DrawLine(spriteBatch,
new Vector2(WorldPosition.X, -WorldPosition.Y),
new Vector2(e.WorldPosition.X, -e.WorldPosition.Y),
isLinkAllowed ? GUI.Style.Green * 0.5f : GUI.Style.Red * 0.5f, width: 3);
}
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this)
{
editingHUD = CreateEditingHUD();
}
editingHUD.UpdateManually((float)Timing.Step);
if (!PlayerInput.PrimaryMouseButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) return;
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList)
{
if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) continue;
if (((Item)entity).GetComponent<DockingPort>() == null) continue;
if (linkedTo.Contains(entity))
{
linkedTo.Remove(entity);
}
else
{
linkedTo.Add(entity);
}
}
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) })
{
UserData = this
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), editingHUD.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = (int)(GUI.Scale * 5)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("LinkedSub"), font: GUI.LargeFont);
if (!inGame)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("LinkLinkedSub"), textColor: GUI.Style.Orange, font: GUI.SmallFont);
}
var pathContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true);
var pathBox = new GUITextBox(new RectTransform(new Vector2(0.75f, 1.0f), pathContainer.RectTransform), filePath, font: GUI.SmallFont);
var reloadButton = new GUIButton(new RectTransform(new Vector2(0.25f / pathBox.RectTransform.RelativeSize.X, 1.0f), pathBox.RectTransform, Anchor.CenterRight, Pivot.CenterLeft),
TextManager.Get("ReloadLinkedSub"), style: "GUIButtonSmall")
{
OnClicked = Reload,
UserData = pathBox,
ToolTip = TextManager.Get("ReloadLinkedSubTooltip")
};
editingHUD.RectTransform.Resize(new Point(
editingHUD.Rect.Width,
(int)(paddedFrame.Children.Sum(c => c.Rect.Height + paddedFrame.AbsoluteSpacing) / paddedFrame.RectTransform.RelativeSize.Y)));
PositionEditingHUD();
return editingHUD;
}
private bool Reload(GUIButton button, object obj)
{
var pathBox = obj as GUITextBox;
if (!File.Exists(pathBox.Text))
{
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariable("ReloadLinkedSubError", "[file]", pathBox.Text));
pathBox.Flash(GUI.Style.Red);
pathBox.Text = filePath;
return false;
}
XDocument doc = Submarine.OpenFile(pathBox.Text);
if (doc == null || doc.Root == null) return false;
pathBox.Flash(GUI.Style.Green);
GenerateWallVertices(doc.Root);
saveElement = doc.Root;
saveElement.Name = "LinkedSubmarine";
filePath = pathBox.Text;
return true;
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Location
{
private HireManager hireManager;
public void RemoveHireableCharacter(CharacterInfo character)
{
if (!Type.HasHireableCharacters)
{
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace);
return;
}
if (hireManager == null)
{
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace);
return;
}
hireManager.RemoveCharacter(character);
}
public IEnumerable<CharacterInfo> GetHireableCharacters()
{
if (!Type.HasHireableCharacters)
{
return Enumerable.Empty<CharacterInfo>();
}
if (hireManager == null)
{
hireManager = new HireManager();
}
if (!hireManager.AvailableCharacters.Any())
{
hireManager.GenerateCharacters(location: this, amount: HireManager.MaxAvailableCharacters);
}
return hireManager.AvailableCharacters;
}
partial void RemoveProjSpecific()
{
hireManager?.Remove();
}
}
}
@@ -0,0 +1,832 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Map
{
//how much larger the ice background is compared to the size of the map
private const float BackgroundScale = 1.5f;
class MapAnim
{
public Location StartLocation;
public Location EndLocation;
public string StartMessage;
public string EndMessage;
public float? StartZoom;
public float? EndZoom;
private float startDelay;
public float StartDelay
{
get { return startDelay; }
set
{
startDelay = value;
Timer = -startDelay;
}
}
public Vector2? StartPos;
public float Duration;
public float Timer;
public bool Finished;
}
private Queue<MapAnim> mapAnimQueue = new Queue<MapAnim>();
private Location highlightedLocation;
public Location HighlightedLocation => highlightedLocation;
private Vector2 drawOffset;
private Vector2 drawOffsetNoise;
private float subReticleAnimState;
private float targetReticleAnimState;
private Vector2 subReticlePosition;
private float zoom = 3.0f;
private Rectangle borders;
private MapTile[,] mapTiles;
private bool messageBoxOpen;
public Vector2 CenterOffset;
#if DEBUG
private GUIComponent editor;
private void CreateEditor()
{
editor = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), GUI.Canvas, Anchor.TopRight, minSize: new Point(400, 0)));
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), editor.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f,
CanBeFocused = false
};
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), paddedFrame.RectTransform, Anchor.Center));
new SerializableEntityEditor(listBox.Content.RectTransform, generationParams, false, true);
new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), "Generate")
{
OnClicked =(btn, userData) =>
{
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
Generate();
return true;
}
};
}
#endif
struct MapTile
{
public readonly Sprite Sprite;
public SpriteEffects SpriteEffect;
public Vector2 Offset;
public MapTile(Sprite sprite, SpriteEffects spriteEffect)
{
Sprite = sprite;
SpriteEffect = spriteEffect;
Offset = Rand.Vector(Rand.Range(0.0f, 1.0f));
}
}
partial void InitProjectSpecific()
{
OnLocationChanged += LocationChanged;
borders = new Rectangle(
(int)Locations.Min(l => l.MapPosition.X),
(int)Locations.Min(l => l.MapPosition.Y),
(int)Locations.Max(l => l.MapPosition.X),
(int)Locations.Max(l => l.MapPosition.Y));
borders.Width = borders.Width - borders.X;
borders.Height = borders.Height - borders.Y;
mapTiles = new MapTile[
(int)Math.Ceiling(size * BackgroundScale / generationParams.TileSpriteSpacing.X),
(int)Math.Ceiling(size * BackgroundScale / generationParams.TileSpriteSpacing.Y)];
for (int x = 0; x < mapTiles.GetLength(0); x++)
{
for (int y = 0; y < mapTiles.GetLength(1); y++)
{
mapTiles[x, y] = new MapTile(
generationParams.BackgroundTileSprites[Rand.Int(generationParams.BackgroundTileSprites.Count)], Rand.Range(0.0f, 1.0f) < 0.5f ?
SpriteEffects.FlipHorizontally : SpriteEffects.None);
}
}
drawOffset = -CurrentLocation.MapPosition;
}
private static Texture2D rawNoiseTexture;
private static Sprite rawNoiseSprite;
private static Texture2D noiseTexture;
partial void GenerateNoiseMapProjSpecific()
{
if (noiseTexture == null)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
noiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution);
rawNoiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution);
});
rawNoiseSprite = new Sprite(rawNoiseTexture, null, null);
}
Color[] crackTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution];
Color[] noiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution];
Color[] rawNoiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution];
for (int x = 0; x < generationParams.NoiseResolution; x++)
{
for (int y = 0; y < generationParams.NoiseResolution; y++)
{
noiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.Transparent, Noise[x, y]);
rawNoiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.White, Rand.Range(0.0f,1.0f));
}
}
float mapRadius = size / 2;
Vector2 mapCenter = Vector2.One * mapRadius;
foreach (LocationConnection connection in connections)
{
float centerDist = Vector2.Distance(connection.CenterPos, mapCenter);
Vector2 connectionStart = connection.Locations[0].MapPosition;
Vector2 connectionEnd = connection.Locations[1].MapPosition;
float connectionLength = Vector2.Distance(connectionStart, connectionEnd);
int iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier));
connection.CrackSegments = MathUtils.GenerateJaggedLine(
connectionStart, connectionEnd,
iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier);
iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIterationMultiplier));
var visualCrackSegments = MathUtils.GenerateJaggedLine(
connectionStart, connectionEnd,
iterations, connectionLength * generationParams.ConnectionDisplacementMultiplier);
float totalLength = Vector2.Distance(visualCrackSegments[0][0], visualCrackSegments.Last()[1]);
for (int i = 0; i < visualCrackSegments.Count; i++)
{
Vector2 start = visualCrackSegments[i][0] * (generationParams.NoiseResolution / (float)size);
Vector2 end = visualCrackSegments[i][1] * (generationParams.NoiseResolution / (float)size);
float length = Vector2.Distance(start, end);
for (float x = 0; x < 1; x += 1.0f / length)
{
Vector2 pos = Vector2.Lerp(start, end, x);
SetNoiseColorOnArea(pos, MathHelper.Clamp((int)(totalLength / 30), 2, 5) + Rand.Range(-1,1), Color.Transparent);
}
}
}
void SetNoiseColorOnArea(Vector2 pos, int dist, Color color)
{
for (int x = -dist; x < dist; x++)
{
for (int y = -dist; y < dist; y++)
{
float d = 1.0f - new Vector2(x, y).Length() / dist;
if (d <= 0) continue;
int xIndex = (int)pos.X + x;
if (xIndex < 0 || xIndex >= generationParams.NoiseResolution) continue;
int yIndex = (int)pos.Y + y;
if (yIndex < 0 || yIndex >= generationParams.NoiseResolution) continue;
float perlin = (float)PerlinNoise.CalculatePerlin(
xIndex / (float)generationParams.NoiseResolution * 100.0f,
yIndex / (float)generationParams.NoiseResolution * 100.0f, 0);
byte a = Math.Max(crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A, (byte)((d * perlin) * 255));
crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A = a;
}
}
}
for (int i = 0; i < noiseTextureData.Length; i++)
{
float darken = noiseTextureData[i].A / 255.0f;
Color pathColor = Color.Lerp(Color.White, Color.Transparent, noiseTextureData[i].A / 255.0f);
noiseTextureData[i] =
Color.Lerp(noiseTextureData[i], pathColor, crackTextureData[i].A / 255.0f * 0.5f);
}
CrossThread.RequestExecutionOnMainThread(() =>
{
noiseTexture.SetData(noiseTextureData);
rawNoiseTexture.SetData(rawNoiseTextureData);
});
}
private void LocationChanged(Location prevLocation, Location newLocation)
{
if (prevLocation == newLocation) return;
//focus on starting location
mapAnimQueue.Enqueue(new MapAnim()
{
EndZoom = 2.0f,
EndLocation = prevLocation,
Duration = MathHelper.Clamp(Vector2.Distance(-drawOffset, prevLocation.MapPosition) / 1000.0f, 0.1f, 0.5f),
});
mapAnimQueue.Enqueue(new MapAnim()
{
EndZoom = 3.0f,
StartLocation = prevLocation,
EndLocation = newLocation,
Duration = 2.0f,
StartDelay = 0.5f
});
}
partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change)
{
//focus on the location
var mapAnim = new MapAnim()
{
EndZoom = zoom * 1.5f,
EndLocation = location,
Duration = CurrentLocation == location ? 1.0f : 2.0f,
StartDelay = 1.0f
};
if (change.Messages != null && change.Messages.Count > 0)
{
mapAnim.EndMessage = change.Messages[Rand.Range(0, change.Messages.Count)]
.Replace("[previousname]", prevName)
.Replace("[name]", location.Name);
}
mapAnimQueue.Enqueue(mapAnim);
mapAnimQueue.Enqueue(new MapAnim()
{
EndZoom = zoom,
StartLocation = location,
EndLocation = CurrentLocation,
Duration = 1.0f,
StartDelay = 0.5f
});
}
partial void ClearAnimQueue()
{
mapAnimQueue.Clear();
}
public void Update(float deltaTime, GUICustomComponent mapContainer)
{
Rectangle rect = mapContainer.Rect;
subReticlePosition = Vector2.Lerp(subReticlePosition, CurrentLocation.MapPosition, deltaTime);
subReticleAnimState = 0.8f - Vector2.Distance(subReticlePosition, CurrentLocation.MapPosition) / 50.0f;
subReticleAnimState = MathHelper.Clamp(subReticleAnimState + (float)Math.Sin(Timing.TotalTime * 3.5f) * 0.2f, 0.0f, 1.0f);
targetReticleAnimState = SelectedLocation == null ?
Math.Max(targetReticleAnimState - deltaTime, 0.0f) :
Math.Min(targetReticleAnimState + deltaTime, 0.6f + (float)Math.Sin(Timing.TotalTime * 2.5f) * 0.4f);
#if DEBUG
if (GameMain.DebugDraw)
{
if (editor == null) CreateEditor();
editor.AddToGUIUpdateList(order: 1);
}
#endif
if (mapAnimQueue.Count > 0)
{
hudOpenState = Math.Max(hudOpenState - deltaTime, 0.0f);
UpdateMapAnim(mapAnimQueue.Peek(), deltaTime);
if (mapAnimQueue.Peek().Finished)
{
mapAnimQueue.Dequeue();
}
return;
}
hudOpenState = Math.Min(hudOpenState + deltaTime, 0.75f + (float)Math.Sin(Timing.TotalTime * 3.0f) * 0.25f);
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y) + CenterOffset;
float closestDist = 0.0f;
highlightedLocation = null;
if (GUI.MouseOn == null || GUI.MouseOn == mapContainer)
{
for (int i = 0; i < Locations.Count; i++)
{
Location location = Locations[i];
Vector2 pos = rectCenter + (location.MapPosition + drawOffset) * zoom;
if (!rect.Contains(pos)) { continue; }
float iconScale = MapGenerationParams.Instance.LocationIconSize / location.Type.Sprite.size.X;
Rectangle drawRect = location.Type.Sprite.SourceRect;
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (!drawRect.Contains(PlayerInput.MousePosition)) continue;
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
if (highlightedLocation == null || dist < closestDist)
{
closestDist = dist;
highlightedLocation = location;
}
}
}
if (GUI.KeyboardDispatcher.Subscriber == null)
{
float moveSpeed = 1000.0f;
Vector2 moveAmount = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { moveAmount += Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { moveAmount -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Up)) { moveAmount += Vector2.UnitY; }
if (PlayerInput.KeyDown(InputType.Down)) { moveAmount -= Vector2.UnitY; }
drawOffset += moveAmount * moveSpeed / zoom * deltaTime;
}
if (GUI.MouseOn == mapContainer)
{
foreach (LocationConnection connection in connections)
{
if (highlightedLocation != CurrentLocation &&
connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(CurrentLocation))
{
if (PlayerInput.PrimaryMouseButtonClicked() &&
SelectedLocation != highlightedLocation && highlightedLocation != null)
{
//clients aren't allowed to select the location without a permission
if (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign))
{
SelectedConnection = connection;
SelectedLocation = highlightedLocation;
targetReticleAnimState = 0.0f;
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
GameMain.Client?.SendCampaignState();
}
}
}
}
zoom += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoom = MathHelper.Clamp(zoom, 1.0f, 4.0f);
if (PlayerInput.MidButtonHeld() || (highlightedLocation == null && PlayerInput.PrimaryMouseButtonHeld()))
{
drawOffset += PlayerInput.MouseSpeed / zoom;
}
#if DEBUG
if (PlayerInput.DoubleClicked() && highlightedLocation != null)
{
var passedConnection = CurrentLocation.Connections.Find(c => c.OtherLocation(CurrentLocation) == highlightedLocation);
if (passedConnection != null)
{
passedConnection.Passed = true;
}
Location prevLocation = CurrentLocation;
CurrentLocation = highlightedLocation;
CurrentLocation.Discovered = true;
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
SelectLocation(-1);
ProgressWorld();
}
#endif
}
}
public void Draw(SpriteBatch spriteBatch, GUICustomComponent mapContainer)
{
Rectangle rect = mapContainer.Rect;
Vector2 viewSize = new Vector2(rect.Width / zoom, rect.Height / zoom);
float edgeBuffer = size * (BackgroundScale - 1.0f) / 2;
drawOffset.X = MathHelper.Clamp(drawOffset.X, -size - edgeBuffer + viewSize.X / 2.0f, edgeBuffer -viewSize.X / 2.0f);
drawOffset.Y = MathHelper.Clamp(drawOffset.Y, -size - edgeBuffer + viewSize.Y / 2.0f, edgeBuffer -viewSize.Y / 2.0f);
drawOffsetNoise = new Vector2(
(float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.1f % 255, Timing.TotalTime * 0.1f % 255, 0) - 0.5f,
(float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.2f % 255, Timing.TotalTime * 0.2f % 255, 0.5f) - 0.5f) * 10.0f;
Vector2 viewOffset = drawOffset + drawOffsetNoise;
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y) + CenterOffset;
Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
for (int x = 0; x < mapTiles.GetLength(0); x++)
{
for (int y = 0; y < mapTiles.GetLength(1); y++)
{
Vector2 mapPos = new Vector2(
x * generationParams.TileSpriteSpacing.X + ((y % 2 == 0) ? 0.0f : generationParams.TileSpriteSpacing.X * 0.5f),
y * generationParams.TileSpriteSpacing.Y);
mapPos.X -= size / 2 * (BackgroundScale - 1.0f);
mapPos.Y -= size / 2 * (BackgroundScale - 1.0f);
Vector2 scale = new Vector2(
generationParams.TileSpriteSize.X / mapTiles[x, y].Sprite.size.X,
generationParams.TileSpriteSize.Y / mapTiles[x, y].Sprite.size.Y);
mapTiles[x, y].Sprite.Draw(spriteBatch, rectCenter + (mapPos + viewOffset) * zoom, Color.White,
origin: new Vector2(256.0f, 256.0f), rotate: 0, scale: scale * zoom, spriteEffect: mapTiles[x, y].SpriteEffect);
}
}
#if DEBUG
if (generationParams.ShowNoiseMap)
{
GUI.DrawRectangle(spriteBatch, rectCenter + (borders.Location.ToVector2() + viewOffset) * zoom, borders.Size.ToVector2() * zoom, Color.White, true);
}
#endif
Vector2 topLeft = rectCenter + viewOffset * zoom;
topLeft.X = (int)topLeft.X;
topLeft.Y = (int)topLeft.Y;
Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(size,size)) * zoom;
bottomRight.X = (int)bottomRight.X;
bottomRight.Y = (int)bottomRight.Y;
spriteBatch.Draw(noiseTexture,
destinationRectangle: new Rectangle((int)topLeft.X, (int)topLeft.Y, (int)(bottomRight.X- topLeft.X), (int)(bottomRight.Y - topLeft.Y)),
sourceRectangle: null,
color: Color.White);
if (topLeft.X > rect.X)
GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X, rect.Y, (int)(topLeft.X- rect.X), rect.Height), Color.Black* 0.8f, true);
if (topLeft.Y > rect.Y)
GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, rect.Y, (int)(bottomRight.X - topLeft.X), (int)(topLeft.Y - rect.Y)), Color.Black * 0.8f, true);
if (bottomRight.X < rect.Right)
GUI.DrawRectangle(spriteBatch, new Rectangle((int)bottomRight.X, rect.Y, (int)(rect.Right - bottomRight.X), rect.Height), Color.Black * 0.8f, true);
if (bottomRight.Y < rect.Bottom)
GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, (int)bottomRight.Y, (int)(bottomRight.X - topLeft.X), (int)(rect.Bottom - bottomRight.Y)), Color.Black * 0.8f, true);
var sourceRect = rect;
float rawNoiseScale = 1.0f + Noise[(int)(Timing.TotalTime * 100 % Noise.GetLength(0) - 1), (int)(Timing.TotalTime * 100 % Noise.GetLength(1) - 1)];
cameraNoiseStrength = Noise[(int)(Timing.TotalTime * 10 % Noise.GetLength(0) - 1), (int)(Timing.TotalTime * 10 % Noise.GetLength(1) - 1)];
rawNoiseSprite.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Point(Rand.Range(0,rawNoiseSprite.SourceRect.Width), Rand.Range(0, rawNoiseSprite.SourceRect.Height)),
color : Color.White * cameraNoiseStrength * 0.5f,
textureScale: Vector2.One * rawNoiseScale);
rawNoiseSprite.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Point(Rand.Range(0, rawNoiseSprite.SourceRect.Width), Rand.Range(0, rawNoiseSprite.SourceRect.Height)),
color: new Color(20,20,20,100),
textureScale: Vector2.One * rawNoiseScale * 2);
if (generationParams.ShowLocations)
{
foreach (LocationConnection connection in connections)
{
Color connectionColor;
if (GameMain.DebugDraw)
{
float sizeFactor = MathUtils.InverseLerp(
MapGenerationParams.Instance.SmallLevelConnectionLength,
MapGenerationParams.Instance.LargeLevelConnectionLength,
connection.Length);
connectionColor = ToolBox.GradientLerp(sizeFactor, Color.LightGreen, GUI.Style.Orange, GUI.Style.Red);
}
else
{
connectionColor = ToolBox.GradientLerp(connection.Difficulty / 100.0f,
MapGenerationParams.Instance.LowDifficultyColor,
MapGenerationParams.Instance.MediumDifficultyColor,
MapGenerationParams.Instance.HighDifficultyColor);
}
int width = (int)(3 * zoom);
if (SelectedLocation != CurrentLocation &&
(connection.Locations.Contains(SelectedLocation) && connection.Locations.Contains(CurrentLocation)))
{
connectionColor = Color.Gold;
width *= 2;
}
else if (highlightedLocation != CurrentLocation &&
(connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(CurrentLocation)))
{
connectionColor = Color.Lerp(connectionColor, Color.White, 0.5f);
width *= 2;
}
else if (!connection.Passed)
{
//crackColor *= 0.5f;
}
for (int i = 0; i < connection.CrackSegments.Count; i++)
{
var segment = connection.CrackSegments[i];
Vector2 start = rectCenter + (segment[0] + viewOffset) * zoom;
Vector2 end = rectCenter + (segment[1] + viewOffset) * zoom;
if (!rect.Contains(start) && !rect.Contains(end))
{
continue;
}
else
{
if (MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(rect.X, rect.Y + rect.Height, rect.Width, rect.Height), out Vector2 intersection))
{
if (!rect.Contains(start))
{
start = intersection;
}
else
{
end = intersection;
}
}
}
float distFromPlayer = Vector2.Distance(CurrentLocation.MapPosition, (segment[0] + segment[1]) / 2.0f);
float dist = Vector2.Distance(start, end);
float a = GameMain.DebugDraw ? 1.0f : (200.0f - distFromPlayer) / 200.0f;
spriteBatch.Draw(generationParams.ConnectionSprite.Texture,
new Rectangle((int)start.X, (int)start.Y, (int)(dist - 1 * zoom), width),
null, connectionColor * MathHelper.Clamp(a, 0.1f, 0.5f), MathUtils.VectorToAngle(end - start),
new Vector2(0, 16), SpriteEffects.None, 0.01f);
}
if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames)
{
Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom;
if (rect.Contains(center))
{
GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White);
}
}
}
rect.Inflate(8, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.LightGray);
for (int i = 0; i < Locations.Count; i++)
{
Location location = Locations[i];
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
Rectangle drawRect = location.Type.Sprite.SourceRect;
drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (!rect.Intersects(drawRect)) { continue; }
Color color = location.Type.SpriteColor;
if (location.Connections.Find(c => c.Locations.Contains(CurrentLocation)) == null)
{
color *= 0.5f;
}
float iconScale = location == CurrentLocation ? 1.2f : 1.0f;
if (location == highlightedLocation)
{
iconScale *= 1.1f;
color = Color.Lerp(color, Color.White, 0.5f);
}
float distFromPlayer = Vector2.Distance(CurrentLocation.MapPosition, location.MapPosition);
color *= MathHelper.Clamp((1000.0f - distFromPlayer) / 500.0f, 0.1f, 1.0f);
location.Type.Sprite.Draw(spriteBatch, pos, color,
scale: MapGenerationParams.Instance.LocationIconSize / location.Type.Sprite.size.X * iconScale * zoom);
MapGenerationParams.Instance.LocationIndicator.Draw(spriteBatch, pos, color,
scale: MapGenerationParams.Instance.LocationIconSize / MapGenerationParams.Instance.LocationIndicator.size.X * iconScale * zoom * 1.4f);
}
//PLACEHOLDER until the stuff at the center of the map is implemented
float centerIconSize = 50.0f;
Vector2 centerPos = rectCenter + (new Vector2(size / 2) + viewOffset) * zoom;
bool mouseOn = Vector2.Distance(PlayerInput.MousePosition, centerPos) < centerIconSize * zoom;
var centerLocationType = LocationType.List.Last();
Color centerColor = centerLocationType.SpriteColor * (mouseOn ? 1.0f : 0.6f);
centerLocationType.Sprite.Draw(spriteBatch, centerPos, centerColor,
scale: centerIconSize / centerLocationType.Sprite.size.X * zoom);
MapGenerationParams.Instance.LocationIndicator.Draw(spriteBatch, centerPos, centerColor,
scale: centerIconSize / MapGenerationParams.Instance.LocationIndicator.size.X * zoom * 1.2f);
if (mouseOn && PlayerInput.PrimaryMouseButtonClicked() && !messageBoxOpen)
{
if (TextManager.ContainsTag("centerarealockedheader") && TextManager.ContainsTag("centerarealockedtext") )
{
var messageBox = new GUIMessageBox(
TextManager.Get("centerarealockedheader"),
TextManager.Get("centerarealockedtext"));
messageBoxOpen = true;
CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox));
}
else
{
//if the message cannot be shown in the selected language,
//show the campaign roadmap (which mentions the center location not being reachable)
var messageBox = new GUIMessageBox(TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("CampaignRoadMapText"));
messageBoxOpen = true;
CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox));
}
}
}
DrawDecorativeHUD(spriteBatch, rect);
for (int i = 0; i < 2; i++)
{
Location location = (i == 0) ? highlightedLocation : CurrentLocation;
if (location == null) continue;
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
pos.X += 25 * zoom;
pos.Y -= 5 * zoom;
Vector2 size = GUI.LargeFont.MeasureString(location.Name);
GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch, new Rectangle((int)pos.X - 30, (int)(pos.Y - 10), (int)size.X + 60, (int)(size.Y + 50 * GUI.Scale)), Color.Black * hudOpenState * 0.7f);
GUI.DrawString(spriteBatch, pos,
location.Name, Color.White * hudOpenState * 1.5f, font: GUI.LargeFont);
GUI.DrawString(spriteBatch, pos + Vector2.UnitY * size.Y * 0.8f,
location.Type.Name, Color.White * hudOpenState * 1.5f);
}
spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
private IEnumerable<object> WaitForMessageBoxClosed(GUIMessageBox box)
{
messageBoxOpen = true;
while (GUIMessageBox.MessageBoxes.Contains(box)) yield return null;
yield return new WaitForSeconds(.1f);
messageBoxOpen = false;
}
private float hudOpenState;
private float cameraNoiseStrength;
private void DrawDecorativeHUD(SpriteBatch spriteBatch, Rectangle rect)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, blendState: BlendState.Additive, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
Vector2 rectCenter = rect.Center.ToVector2() + CenterOffset;
if (generationParams.ShowOverlay)
{
Vector2 mapCenter = rectCenter + (new Vector2(size, size) / 2 + drawOffset + drawOffsetNoise) * zoom;
Vector2 centerDiff = CurrentLocation.MapPosition - new Vector2(size) / 2;
int currentZone = (int)Math.Floor((centerDiff.Length() / (size * 0.5f) * generationParams.DifficultyZones));
for (int i = 0; i < generationParams.DifficultyZones; i++)
{
float radius = size / 2 * ((i + 1.0f) / generationParams.DifficultyZones);
float textureSize = (radius / (generationParams.MapCircle.size.X / 2) * zoom);
generationParams.MapCircle.Draw(spriteBatch,
mapCenter,
i == currentZone || i == currentZone - 1 ? Color.White * 0.5f : Color.White * 0.2f,
i * 0.4f + (float)Timing.TotalTime * 0.01f, textureSize);
}
}
float animPulsate = (float)Math.Sin(Timing.TotalTime * 2.0f) * 0.1f;
Vector2 frameSize = generationParams.DecorativeGraphSprite.FrameSize.ToVector2();
generationParams.DecorativeGraphSprite.Draw(spriteBatch, (int)((cameraNoiseStrength + animPulsate) * hudOpenState * generationParams.DecorativeGraphSprite.FrameCount),
new Vector2(rect.Right, rect.Bottom), Color.White, frameSize, 0,
Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 10), frameSize));
/*frameSize = generationParams.DecorativeMapSprite.FrameSize.ToVector2();
generationParams.DecorativeMapSprite.Draw(spriteBatch, (int)((cameraNoiseStrength + animPulsate) * hudOpenState * generationParams.DecorativeMapSprite.FrameCount),
new Vector2(rect.X, rect.Y + rect.Height * 0.17f), Color.White, new Vector2(0, frameSize.Y * 0.2f), 0,
Vector2.Divide(new Vector2(rect.Width / 3, rect.Height / 5), frameSize), spriteEffect: SpriteEffects.FlipVertically);
GUI.DrawString(spriteBatch,
new Vector2(rect.X + rect.Width / 15, rect.Y + rect.Height / 11),
"JOVIAN FLUX " + ((cameraNoiseStrength + Rand.Range(-0.02f, 0.02f)) * 500), Color.White * hudOpenState, font: GUI.SmallFont);*/
GUI.DrawString(spriteBatch,
new Vector2(rect.X + rect.Width * 0.27f, rect.Y + rect.Height * 0.93f),
"LAT " + (-drawOffset.Y / 100.0f) + " LON " + (-drawOffset.X / 100.0f), Color.White * hudOpenState, font: GUI.SmallFont);
System.Text.StringBuilder sb = new System.Text.StringBuilder("GEST F ");
for (int i = 0; i < 20; i++)
{
sb.Append(Rand.Range(0.0f, 1.0f) < cameraNoiseStrength ? ToolBox.RandomSeed(1) : "0");
}
GUI.DrawString(spriteBatch,
new Vector2(rect.X + rect.Width * 0.8f, rect.Y + rect.Height * 0.96f),
sb.ToString(), Color.White * hudOpenState, font: GUI.SmallFont);
frameSize = generationParams.DecorativeLineTop.FrameSize.ToVector2();
generationParams.DecorativeLineTop.Draw(spriteBatch, (int)(hudOpenState * generationParams.DecorativeLineTop.FrameCount),
new Vector2(rect.Right, rect.Y), Color.White, new Vector2(frameSize.X, frameSize.Y * 0.2f), 0,
Vector2.Divide(new Vector2(rect.Width * 0.72f, rect.Height / 9), frameSize));
frameSize = generationParams.DecorativeLineBottom.FrameSize.ToVector2();
generationParams.DecorativeLineBottom.Draw(spriteBatch, (int)(hudOpenState * generationParams.DecorativeLineBottom.FrameCount),
new Vector2(rect.X, rect.Bottom), Color.White, new Vector2(0, frameSize.Y * 0.6f), 0,
Vector2.Divide(new Vector2(rect.Width * 0.72f, rect.Height / 9), frameSize));
frameSize = generationParams.DecorativeLineCorner.FrameSize.ToVector2();
generationParams.DecorativeLineCorner.Draw(spriteBatch, (int)((hudOpenState + animPulsate) * generationParams.DecorativeLineCorner.FrameCount),
new Vector2(rect.Right - rect.Width / 8, rect.Bottom), Color.White, frameSize * 0.8f, 0,
Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 4), frameSize), spriteEffect: SpriteEffects.FlipVertically);
generationParams.DecorativeLineCorner.Draw(spriteBatch, (int)((hudOpenState + animPulsate) * generationParams.DecorativeLineCorner.FrameCount),
new Vector2(rect.X + rect.Width / 8, rect.Y), Color.White, frameSize * 0.1f, 0,
Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 4), frameSize), spriteEffect: SpriteEffects.FlipHorizontally);
//reticles
generationParams.ReticleLarge.Draw(spriteBatch, (int)(subReticleAnimState * generationParams.ReticleLarge.FrameCount),
rectCenter + (subReticlePosition + drawOffset - drawOffsetNoise * 2) * zoom, Color.White,
generationParams.ReticleLarge.Origin, 0, Vector2.One * (float)Math.Sqrt(zoom) * 0.4f);
generationParams.ReticleMedium.Draw(spriteBatch, (int)(subReticleAnimState * generationParams.ReticleMedium.FrameCount),
rectCenter + (subReticlePosition + drawOffset - drawOffsetNoise) * zoom, Color.White,
generationParams.ReticleMedium.Origin, 0, new Vector2(1.0f, 0.7f) * (float)Math.Sqrt(zoom) * 0.4f);
if (SelectedLocation != null)
{
generationParams.ReticleSmall.Draw(spriteBatch, (int)(targetReticleAnimState * generationParams.ReticleSmall.FrameCount),
rectCenter + (SelectedLocation.MapPosition + drawOffset + drawOffsetNoise * 2) * zoom, Color.White,
generationParams.ReticleSmall.Origin, 0, new Vector2(1.0f, 0.7f) * (float)Math.Sqrt(zoom) * 0.4f);
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
private void UpdateMapAnim(MapAnim anim, float deltaTime)
{
//pause animation while there are messageboxes on screen
if (GUIMessageBox.MessageBoxes.Count > 0) return;
if (!string.IsNullOrEmpty(anim.StartMessage))
{
new GUIMessageBox("", anim.StartMessage);
anim.StartMessage = null;
return;
}
if (anim.StartZoom == null) anim.StartZoom = zoom;
if (anim.EndZoom == null) anim.EndZoom = zoom;
anim.StartPos = (anim.StartLocation == null) ? -drawOffset : anim.StartLocation.MapPosition;
anim.Timer = Math.Min(anim.Timer + deltaTime, anim.Duration);
float t = anim.Duration <= 0.0f ? 1.0f : Math.Max(anim.Timer / anim.Duration, 0.0f);
drawOffset = -Vector2.SmoothStep(anim.StartPos.Value, anim.EndLocation.MapPosition, t);
drawOffset += new Vector2(
(float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.3f % 255, Timing.TotalTime * 0.4f % 255, 0) - 0.5f,
(float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.4f % 255, Timing.TotalTime * 0.3f % 255, 0.5f) - 0.5f) * 50.0f * (float)Math.Sin(t * MathHelper.Pi);
zoom = MathHelper.SmoothStep(anim.StartZoom.Value, anim.EndZoom.Value, t);
if (anim.Timer >= anim.Duration)
{
if (!string.IsNullOrEmpty(anim.EndMessage))
{
new GUIMessageBox("", anim.EndMessage);
anim.EndMessage = null;
return;
}
anim.Finished = true;
}
}
partial void RemoveProjSpecific()
{
rawNoiseSprite?.Remove();
rawNoiseSprite = null;
rawNoiseTexture?.Dispose();
rawNoiseTexture = null;
noiseTexture?.Dispose();
noiseTexture = null;
}
}
}
@@ -0,0 +1,945 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
abstract partial class MapEntity : Entity
{
protected static Vector2 selectionPos = Vector2.Zero;
protected static Vector2 selectionSize = Vector2.Zero;
private static Vector2 startMovingPos = Vector2.Zero;
public static Vector2 StartMovingPos => startMovingPos;
private static bool resizing;
private int resizeDirX, resizeDirY;
//which entities have been selected for editing
private static List<MapEntity> selectedList = new List<MapEntity>();
public static List<MapEntity> SelectedList
{
get
{
return selectedList;
}
}
private static List<MapEntity> copiedList = new List<MapEntity>();
private static List<MapEntity> highlightedList = new List<MapEntity>();
// Test feature. Not yet saved.
public static Dictionary<MapEntity, List<MapEntity>> SelectionGroups { get; private set; } = new Dictionary<MapEntity, List<MapEntity>>();
private static float highlightTimer;
private static GUIListBox highlightedListBox;
public static GUIListBox HighlightedListBox
{
get { return highlightedListBox; }
}
protected static GUIComponent editingHUD;
public static GUIComponent EditingHUD
{
get
{
return editingHUD;
}
}
//protected bool isSelected;
private static bool disableSelect;
public static bool DisableSelect
{
get { return disableSelect; }
set
{
disableSelect = value;
if (disableSelect)
{
startMovingPos = Vector2.Zero;
selectionSize = Vector2.Zero;
selectionPos = Vector2.Zero;
}
}
}
public virtual bool SelectableInEditor
{
get { return true; }
}
public static bool SelectedAny
{
get { return selectedList.Count > 0; }
}
public static IEnumerable<MapEntity> CopiedList
{
get { return copiedList; }
}
public bool IsSelected
{
get { return selectedList.Contains(this); }
}
public virtual bool IsVisible(Rectangle worldView)
{
return true;
}
public virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { }
/// <summary>
/// Update the selection logic in submarine editor
/// </summary>
public static void UpdateSelecting(Camera cam)
{
if (resizing)
{
if (selectedList.Count == 0) resizing = false;
return;
}
foreach (MapEntity e in mapEntityList)
{
e.isHighlighted = false;
}
if (DisableSelect)
{
DisableSelect = false;
return;
}
if (GUI.MouseOn != null || !PlayerInput.MouseInsideWindow)
{
if (highlightedListBox == null ||
(GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn)))
{
UpdateHighlightedListBox(null);
return;
}
}
if (MapEntityPrefab.Selected != null)
{
selectionPos = Vector2.Zero;
selectedList.Clear();
return;
}
if (GUI.KeyboardDispatcher.Subscriber == null)
{
if (PlayerInput.KeyDown(Keys.Delete))
{
selectedList.ForEach(e => e.Remove());
selectedList.Clear();
}
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
{
if (PlayerInput.KeyHit(Keys.C))
{
Copy(selectedList);
}
else if (PlayerInput.KeyHit(Keys.X))
{
Cut(selectedList);
}
else if (PlayerInput.KeyHit(Keys.V))
{
Paste(cam.WorldViewCenter);
}
else if (PlayerInput.KeyHit(Keys.G))
{
if (selectedList.Any())
{
if (SelectionGroups.ContainsKey(selectedList.Last()))
{
// Ungroup all selected
selectedList.ForEach(e => SelectionGroups.Remove(e));
}
else
{
foreach (var entity in selectedList)
{
// Remove the old group, if any
SelectionGroups.Remove(entity);
// Create a group that can be accessed with any member
SelectionGroups.Add(entity, selectedList);
}
}
}
}
else if (PlayerInput.KeyHit(Keys.Z))
{
SetPreviousRects(e => e.rectMemento.Undo());
}
else if (PlayerInput.KeyHit(Keys.R))
{
SetPreviousRects(e => e.rectMemento.Redo());
}
void SetPreviousRects(Func<MapEntity, Rectangle> memoryMethod)
{
foreach (var e in SelectedList)
{
if (e.rectMemento != null)
{
Point diff = memoryMethod(e).Location - e.Rect.Location;
// We have to call the move method, because there's a lot more than just storing the rect (in some cases)
// We also have to reassign the rect, because the move method does not set the width and height. They might have changed too.
// The Rect property is virtual and it's overridden for structs. Setting the rect via the property should automatically recreate the sections for resizable structures.
e.Move(diff.ToVector2());
e.Rect = e.rectMemento.Current;
}
}
}
}
}
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
MapEntity highLightedEntity = null;
if (startMovingPos == Vector2.Zero)
{
List<MapEntity> highlightedEntities = new List<MapEntity>();
if (highlightedListBox != null && highlightedListBox.IsParentOf(GUI.MouseOn))
{
highLightedEntity = GUI.MouseOn.UserData as MapEntity;
}
else
{
foreach (MapEntity e in mapEntityList)
{
if (!e.SelectableInEditor) continue;
if (e.IsMouseOn(position))
{
int i = 0;
while (i < highlightedEntities.Count &&
e.Sprite != null &&
(highlightedEntities[i].Sprite == null || highlightedEntities[i].SpriteDepth < e.SpriteDepth))
{
i++;
}
highlightedEntities.Insert(i, e);
if (i == 0) highLightedEntity = e;
}
}
if (PlayerInput.MouseSpeed.LengthSquared() > 10)
{
highlightTimer = 0.0f;
}
else
{
bool mouseNearHighlightBox = false;
if (highlightedListBox != null)
{
Rectangle expandedRect = highlightedListBox.Rect;
expandedRect.Inflate(20, 20);
mouseNearHighlightBox = expandedRect.Contains(PlayerInput.MousePosition);
if (!mouseNearHighlightBox) highlightedListBox = null;
}
highlightTimer += (float)Timing.Step;
if (highlightTimer > 1.0f)
{
if (!mouseNearHighlightBox)
{
UpdateHighlightedListBox(highlightedEntities);
highlightTimer = 0.0f;
}
}
}
}
if (highLightedEntity != null) highLightedEntity.isHighlighted = true;
}
if (GUI.KeyboardDispatcher.Subscriber == null)
{
Vector2 nudgeAmount = Vector2.Zero;
if (PlayerInput.KeyHit(Keys.Up)) nudgeAmount.Y = 1f;
if (PlayerInput.KeyHit(Keys.Down)) nudgeAmount.Y = -1f;
if (PlayerInput.KeyHit(Keys.Left)) nudgeAmount.X = -1f;
if (PlayerInput.KeyHit(Keys.Right)) nudgeAmount.X = 1f;
if (nudgeAmount != Vector2.Zero)
{
foreach (MapEntity entityToNudge in selectedList)
{
entityToNudge.Move(nudgeAmount);
}
}
}
//started moving selected entities
if (startMovingPos != Vector2.Zero)
{
if (PlayerInput.PrimaryMouseButtonReleased())
{
//mouse released -> move the entities to the new position of the mouse
Vector2 moveAmount = position - startMovingPos;
moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X;
moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y;
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y)
{
moveAmount = Submarine.VectorToWorldGrid(moveAmount);
//clone
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
{
var clones = Clone(selectedList);
selectedList = clones;
selectedList.ForEach(c => c.Move(moveAmount));
}
else // move
{
foreach (MapEntity e in selectedList)
{
if (e.rectMemento == null)
{
e.rectMemento = new Memento<Rectangle>();
e.rectMemento.Store(e.Rect);
}
e.Move(moveAmount);
e.rectMemento.Store(e.Rect);
}
}
}
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)
{
if (SelectionGroups.TryGetValue(highLightedEntity, out List<MapEntity> group))
{
newSelection.AddRange(group);
}
else
{
newSelection.Add(highLightedEntity);
}
}
}
if (PlayerInput.PrimaryMouseButtonReleased())
{
if (PlayerInput.KeyDown(Keys.LeftControl) ||
PlayerInput.KeyDown(Keys.RightControl))
{
foreach (MapEntity e in newSelection)
{
if (selectedList.Contains(e))
{
RemoveSelection(e);
}
else
{
AddSelection(e);
}
}
}
else
{
selectedList = new List<MapEntity>(newSelection);
//selectedList.Clear();
//newSelection.ForEach(e => AddSelection(e));
foreach (var entity in newSelection)
{
HandleDoorGapLinks(entity,
onGapFound: (door, gap) =>
{
door.RefreshLinkedGap();
if (!selectedList.Contains(gap))
{
selectedList.Add(gap);
}
},
onDoorFound: (door, gap) =>
{
if (!selectedList.Contains(door.Item))
{
selectedList.Add(door.Item);
}
});
}
}
//select wire if both items it's connected to are selected
var selectedItems = selectedList.Where(e => e is Item).Cast<Item>().ToList();
foreach (Item item in selectedItems)
{
if (item.Connections == null) continue;
foreach (Connection c in item.Connections)
{
foreach (Wire w in c.Wires)
{
if (w == null || selectedList.Contains(w.Item)) continue;
if (w.OtherConnection(c) != null && selectedList.Contains(w.OtherConnection(c).Item))
{
selectedList.Add(w.Item);
}
}
}
}
selectionPos = Vector2.Zero;
selectionSize = Vector2.Zero;
}
}
//default, not doing anything specific yet
else
{
if (PlayerInput.PrimaryMouseButtonHeld() &&
PlayerInput.KeyUp(Keys.Space) &&
(highlightedListBox == null || (GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn))))
{
//if clicking a selected entity, start moving it
foreach (MapEntity e in selectedList)
{
if (e.IsMouseOn(position)) startMovingPos = position;
}
selectionPos = position;
//stop camera movement to prevent accidental dragging or rect selection
Screen.Selected.Cam.StopMovement();
}
}
}
private static void UpdateHighlightedListBox(List<MapEntity> highlightedEntities)
{
if (highlightedEntities == null || highlightedEntities.Count < 2)
{
highlightedListBox = null;
return;
}
if (highlightedListBox != null)
{
if (GUI.MouseOn == highlightedListBox || highlightedListBox.IsParentOf(GUI.MouseOn)) return;
if (highlightedEntities.SequenceEqual(highlightedList)) return;
}
highlightedList = highlightedEntities;
highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas)
{
ScreenSpaceOffset = PlayerInput.MousePosition.ToPoint() + new Point(15)
}, style: "GUIToolTip");
foreach (MapEntity entity in highlightedEntities)
{
var textBlock = new GUITextBlock(new RectTransform(new Point(highlightedListBox.Content.Rect.Width, 15), highlightedListBox.Content.RectTransform),
ToolBox.LimitString(entity.Name, GUI.SmallFont, 140), font: GUI.SmallFont)
{
UserData = entity
};
}
highlightedListBox.OnSelected = (GUIComponent component, object obj) =>
{
MapEntity entity = obj as MapEntity;
if (PlayerInput.KeyDown(Keys.LeftControl) ||
PlayerInput.KeyDown(Keys.RightControl))
{
if (selectedList.Contains(entity))
{
RemoveSelection(entity);
}
else
{
AddSelection(entity);
}
}
else
{
SelectEntity(entity);
}
return true;
};
}
public static void AddSelection(MapEntity entity)
{
if (selectedList.Contains(entity)) { return; }
selectedList.Add(entity);
HandleDoorGapLinks(entity,
onGapFound: (door, gap) =>
{
door.RefreshLinkedGap();
if (!selectedList.Contains(gap))
{
selectedList.Add(gap);
}
},
onDoorFound: (door, gap) =>
{
if (!selectedList.Contains(door.Item))
{
selectedList.Add(door.Item);
}
});
}
private static void HandleDoorGapLinks(MapEntity entity, Action<Door, Gap> onGapFound, Action<Door, Gap> onDoorFound)
{
if (entity is Item i)
{
var door = i.GetComponent<Door>();
if (door != null)
{
var gap = door.LinkedGap;
if (gap != null)
{
onGapFound(door, gap);
}
}
}
else if (entity is Gap gap)
{
var door = gap.ConnectedDoor;
if (door != null)
{
onDoorFound(door, gap);
}
}
}
public static void RemoveSelection(MapEntity entity)
{
selectedList.Remove(entity);
HandleDoorGapLinks(entity,
onGapFound: (door, gap) => selectedList.Remove(gap),
onDoorFound: (door, gap) => selectedList.Remove(door.Item));
}
static partial void UpdateAllProjSpecific(float deltaTime)
{
var entitiesToRender = Submarine.VisibleEntities ?? mapEntityList;
foreach (MapEntity me in entitiesToRender)
{
if (me is Item item)
{
item.UpdateSpriteStates(deltaTime);
}
}
}
/// <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 (GUI.MouseOn != null) return;
Vector2 position = PlayerInput.MousePosition;
position = cam.ScreenToWorld(position);
if (startMovingPos != Vector2.Zero)
{
Vector2 moveAmount = position - startMovingPos;
moveAmount.Y = -moveAmount.Y;
moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X;
moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y;
//started moving the selected entities
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y)
{
foreach (MapEntity e in selectedList)
{
SpriteEffects spriteEffects = SpriteEffects.None;
if (e is Item item)
{
if (item.FlippedX && item.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally;
if (item.flippedY && item.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically;
}
else if (e is Structure structure)
{
if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally;
if (structure.flippedY && structure.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically;
}
else if (e is WayPoint wayPoint)
{
Vector2 drawPos = e.WorldPosition;
drawPos.Y = -drawPos.Y;
drawPos += moveAmount;
wayPoint.Draw(spriteBatch, drawPos);
continue;
}
e.prefab?.DrawPlacing(spriteBatch,
new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects);
GUI.DrawRectangle(spriteBatch,
new Vector2(e.WorldRect.X, -e.WorldRect.Y) + moveAmount,
new Vector2(e.rect.Width, e.rect.Height),
Color.White, false, 0, (int)Math.Max(3.0f / GameScreen.Selected.Cam.Zoom, 2.0f));
}
//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, false, 0, (int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f));
}
}
public static List<MapEntity> FilteredSelectedList { get; private set; } = new List<MapEntity>();
public static void UpdateEditor(Camera cam)
{
if (highlightedListBox != null) highlightedListBox.UpdateManually((float)Timing.Step);
if (editingHUD != null)
{
if (FilteredSelectedList.Count == 0 || editingHUD.UserData != FilteredSelectedList[0])
{
foreach (GUIComponent component in editingHUD.Children)
{
var textBox = component as GUITextBox;
if (textBox == null) continue;
textBox.Deselect();
}
editingHUD = null;
}
}
FilteredSelectedList.Clear();
if (selectedList.Count == 0) return;
foreach (var e in selectedList)
{
if (e is Gap gap && gap.ConnectedDoor != null) { continue; }
FilteredSelectedList.Add(e);
}
var first = FilteredSelectedList.FirstOrDefault();
if (first != null)
{
first.UpdateEditing(cam);
if (first.ResizeHorizontal || first.ResizeVertical)
{
first.UpdateResizing(cam);
}
}
if ((PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)))
{
if (PlayerInput.KeyHit(Keys.N))
{
float minX = selectedList[0].WorldRect.X, maxX = selectedList[0].WorldRect.Right;
for (int i = 0; i < selectedList.Count; i++)
{
minX = Math.Min(minX, selectedList[i].WorldRect.X);
maxX = Math.Max(maxX, selectedList[i].WorldRect.Right);
}
float centerX = (minX + maxX) / 2.0f;
foreach (MapEntity me in selectedList)
{
me.FlipX(false);
me.Move(new Vector2((centerX - me.WorldPosition.X) * 2.0f, 0.0f));
}
}
else if (PlayerInput.KeyHit(Keys.M))
{
float minY = selectedList[0].WorldRect.Y - selectedList[0].WorldRect.Height, maxY = selectedList[0].WorldRect.Y;
for (int i = 0; i < selectedList.Count; i++)
{
minY = Math.Min(minY, selectedList[i].WorldRect.Y - selectedList[i].WorldRect.Height);
maxY = Math.Max(maxY, selectedList[i].WorldRect.Y);
}
float centerY = (minY + maxY) / 2.0f;
foreach (MapEntity me in selectedList)
{
me.FlipY(false);
me.Move(new Vector2(0.0f, (centerY - me.WorldPosition.Y) * 2.0f));
}
}
}
}
public static void DrawEditor(SpriteBatch spriteBatch, Camera cam)
{
if (selectedList.Count == 1)
{
selectedList[0].DrawEditing(spriteBatch, cam);
if (selectedList[0].ResizeHorizontal || selectedList[0].ResizeVertical)
{
selectedList[0].DrawResizing(spriteBatch, cam);
}
}
}
public static void DeselectAll()
{
selectedList.Clear();
}
public static void SelectEntity(MapEntity entity)
{
DeselectAll();
AddSelection(entity);
}
/// <summary>
/// Copy the selected entities to the "clipboard" (copiedList)
/// </summary>
public static void Copy(List<MapEntity> entities)
{
if (entities.Count == 0) { return; }
CopyEntities(entities);
}
/// <summary>
/// Copy the entities to the "clipboard" (copiedList) and delete them
/// </summary>
public static void Cut(List<MapEntity> entities)
{
if (entities.Count == 0) { return; }
CopyEntities(entities);
entities.ForEach(e => e.Remove());
entities.Clear();
}
public static void Paste(Vector2 position)
{
if (copiedList.Count == 0) { return; }
List<MapEntity> prevEntities = new List<MapEntity>(mapEntityList);
Clone(copiedList);
var clones = mapEntityList.Except(prevEntities).ToList();
var nonWireClones = clones.Where(c => !(c is Item item) || item.GetComponent<Wire>() == null);
Vector2 center = Vector2.Zero;
nonWireClones.ForEach(c => center += c.WorldPosition);
center = Submarine.VectorToWorldGrid(center / nonWireClones.Count());
Vector2 moveAmount = Submarine.VectorToWorldGrid(position - center);
selectedList = new List<MapEntity>(clones);
foreach (MapEntity clone in selectedList)
{
clone.Move(moveAmount);
clone.Submarine = Submarine.MainSub;
}
}
/// <summary>
/// copies a list of entities to the "clipboard" (copiedList)
/// </summary>
public static List<MapEntity> CopyEntities(List<MapEntity> entities)
{
List<MapEntity> prevEntities = new List<MapEntity>(mapEntityList);
copiedList = Clone(entities);
//find all new entities created during cloning
var newEntities = mapEntityList.Except(prevEntities).ToList();
//do a "shallow remove" (removes the entities from the game without removing links between them)
// -> items will stay in their containers
newEntities.ForEach(e => e.ShallowRemove());
return newEntities;
}
public virtual void AddToGUIUpdateList()
{
if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList();
}
public virtual void UpdateEditing(Camera cam) { }
protected static void PositionEditingHUD()
{
int maxHeight = 100;
if (Screen.Selected == GameMain.SubEditorScreen)
{
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
editingHUD.RectTransform.AbsoluteOffset = new Point(0, GameMain.SubEditorScreen.TopPanel.Rect.Bottom);
maxHeight = (GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height) - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20;
}
else
{
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
editingHUD.RectTransform.RelativeOffset = new Vector2(0.0f, (HUDLayoutSettings.CrewArea.Bottom + 10.0f) / (editingHUD.RectTransform.Parent ?? GUI.Canvas).Rect.Height);
maxHeight = HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
}
var listBox = editingHUD.GetChild<GUIListBox>();
if (listBox != null)
{
int padding = 20;
int contentHeight = 0;
foreach (GUIComponent child in listBox.Content.Children)
{
contentHeight += child.Rect.Height + listBox.Spacing;
child.RectTransform.MaxSize = new Point(int.MaxValue, child.Rect.Height);
child.RectTransform.MinSize = new Point(0, child.Rect.Height);
}
editingHUD.RectTransform.Resize(
new Point(
editingHUD.RectTransform.NonScaledSize.X,
MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false);
listBox.RectTransform.Resize(new Point(listBox.RectTransform.NonScaledSize.X, editingHUD.RectTransform.NonScaledSize.Y - padding * 2), resizeChildren: false);
}
}
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
private void UpdateResizing(Camera cam)
{
isHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0;
for (int x = startX; x < 2; x += 2)
{
for (int y = StartY; y < 2; y += 2)
{
Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f + 5), y * (rect.Height * 0.5f + 5)));
bool highlighted = Vector2.Distance(PlayerInput.MousePosition, handlePos) < 5.0f;
if (highlighted && PlayerInput.PrimaryMouseButtonDown())
{
selectionPos = Vector2.Zero;
resizeDirX = x;
resizeDirY = y;
resizing = true;
}
}
}
if (resizing)
{
if (rectMemento == null)
{
rectMemento = new Memento<Rectangle>();
rectMemento.Store(Rect);
}
Vector2 placePosition = new Vector2(rect.X, rect.Y);
Vector2 placeSize = new Vector2(rect.Width, rect.Height);
Vector2 mousePos = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (resizeDirX > 0)
{
mousePos.X = Math.Max(mousePos.X, rect.X + Submarine.GridSize.X);
placeSize.X = mousePos.X - placePosition.X;
}
else if (resizeDirX < 0)
{
mousePos.X = Math.Min(mousePos.X, rect.Right - Submarine.GridSize.X);
placeSize.X = (placePosition.X + placeSize.X) - mousePos.X;
placePosition.X = mousePos.X;
}
if (resizeDirY < 0)
{
mousePos.Y = Math.Min(mousePos.Y, rect.Y - Submarine.GridSize.Y);
placeSize.Y = placePosition.Y - mousePos.Y;
}
else if (resizeDirY > 0)
{
mousePos.Y = Math.Max(mousePos.Y, rect.Y - rect.Height + Submarine.GridSize.X);
placeSize.Y = mousePos.Y - (rect.Y - rect.Height);
placePosition.Y = mousePos.Y;
}
if ((int)placePosition.X != rect.X || (int)placePosition.Y != rect.Y || (int)placeSize.X != rect.Width || (int)placeSize.Y != rect.Height)
{
Rect = new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y);
}
if (!PlayerInput.PrimaryMouseButtonHeld())
{
rectMemento.Store(Rect);
resizing = false;
Resized?.Invoke(rect);
}
}
}
private void DrawResizing(SpriteBatch spriteBatch, Camera cam)
{
isHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0;
for (int x = startX; x < 2; x += 2)
{
for (int y = StartY; y < 2; y += 2)
{
Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f + 5), y * (rect.Height * 0.5f + 5)));
bool highlighted = Vector2.Distance(PlayerInput.MousePosition, handlePos) < 5.0f;
GUI.DrawRectangle(spriteBatch,
handlePos - new Vector2(3.0f, 3.0f),
new Vector2(6.0f, 6.0f),
Color.White * (highlighted ? 1.0f : 0.6f),
true, 0,
(int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f));
}
}
}
/// <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 (!e.SelectableInEditor) continue;
if (Submarine.RectsOverlap(selectionRect, e.rect)) foundEntities.Add(e);
}
return foundEntities;
}
}
}
@@ -0,0 +1,97 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
abstract partial class MapEntityPrefab : IPrefab, IDisposable
{
public virtual void UpdatePlacing(Camera cam)
{
Vector2 placeSize = Submarine.GridSize;
if (placePosition == Vector2.Zero)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (PlayerInput.PrimaryMouseButtonHeld()) placePosition = position;
}
else
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (ResizeHorizontal) placeSize.X = position.X - placePosition.X;
if (ResizeVertical) placeSize.Y = placePosition.Y - position.Y;
Rectangle newRect = Submarine.AbsRect(placePosition, placeSize);
newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X);
newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y);
if (Submarine.MainSub != null)
{
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
}
if (PlayerInput.PrimaryMouseButtonReleased())
{
CreateInstance(newRect);
placePosition = Vector2.Zero;
selected = null;
}
newRect.Y = -newRect.Y;
}
if (PlayerInput.SecondaryMouseButtonHeld())
{
placePosition = Vector2.Zero;
selected = null;
}
}
public virtual void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{
if (placePosition == Vector2.Zero)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
GUI.DrawLine(spriteBatch, new Vector2(position.X - GameMain.GraphicsWidth, -position.Y), new Vector2(position.X + GameMain.GraphicsWidth, -position.Y), Color.White, 0, (int)(2.0f / cam.Zoom));
GUI.DrawLine(spriteBatch, new Vector2(position.X, -(position.Y - GameMain.GraphicsHeight)), new Vector2(position.X, -(position.Y + GameMain.GraphicsHeight)), Color.White, 0, (int)(2.0f / cam.Zoom));
}
else
{
Vector2 placeSize = Submarine.GridSize;
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (ResizeHorizontal) placeSize.X = position.X - placePosition.X;
if (ResizeVertical) placeSize.Y = placePosition.Y - position.Y;
Rectangle newRect = Submarine.AbsRect(placePosition, placeSize);
newRect.Width = (int)Math.Max(newRect.Width, Submarine.GridSize.X);
newRect.Height = (int)Math.Max(newRect.Height, Submarine.GridSize.Y);
if (Submarine.MainSub != null)
{
newRect.Location -= Submarine.MainSub.Position.ToPoint();
}
newRect.Y = -newRect.Y;
GUI.DrawRectangle(spriteBatch, newRect, Color.DarkBlue);
}
}
public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{
if (Submarine.MainSub != null)
{
drawRect.Location -= Submarine.MainSub.Position.ToPoint();
}
drawRect.Y = -drawRect.Y;
GUI.DrawRectangle(spriteBatch, drawRect, Color.White);
}
public void DrawListLine(SpriteBatch spriteBatch, Vector2 pos, Color color)
{
GUI.Font.DrawString(spriteBatch, originalName, pos, color);
}
}
}
@@ -0,0 +1,376 @@
using Barotrauma.Extensions;
using Barotrauma.Lights;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Structure : MapEntity, IDamageable, IServerSerializable
{
public static bool ShowWalls = true, ShowStructures = true;
private List<ConvexHull> convexHulls;
public override bool SelectableInEditor
{
get
{
return HasBody ? ShowWalls : ShowStructures;;
}
}
private string specialTag;
[Editable, Serialize("", true)]
public string SpecialTag
{
get { return specialTag; }
set { specialTag = value; }
}
partial void InitProjSpecific()
{
Prefab.sprite?.EnsureLazyLoaded();
Prefab.BackgroundSprite?.EnsureLazyLoaded();
}
partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation)
{
if (!CastShadow) { return; }
if (convexHulls == null)
{
convexHulls = new List<ConvexHull>();
}
Vector2 halfSize = size / 2;
Vector2[] verts = new Vector2[]
{
position + new Vector2(-halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, -halfSize.Y),
position + new Vector2(-halfSize.X, -halfSize.Y),
};
var h = new ConvexHull(verts, Color.Black, this);
if (Math.Abs(rotation) > 0.001f)
{
h.Rotate(position, rotation);
}
convexHulls.Add(h);
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData as Structure != this)
{
editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen);
}
}
public GUIComponent CreateEditingHUD(bool inGame = false)
{
int heightScaled = (int)(20 * GUI.Scale);
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null);
var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont);
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.01f
};
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"))
{
OnClicked = (button, data) =>
{
Sprite.ReloadXML();
Sprite.ReloadTexture();
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ResetToPrefab"))
{
OnClicked = (button, data) =>
{
Reset();
CreateEditingHUD();
return true;
}
};
buttonContainer.RectTransform.Resize(new Point(buttonContainer.Rect.Width, buttonContainer.RectTransform.Children.Max(c => c.MinSize.Y)));
buttonContainer.RectTransform.IsFixedSize = true;
GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(b => ((GUIButton)b).TextBlock));
editor.AddCustomContent(buttonContainer, editor.ContentCount);
PositionEditingHUD();
return editingHUD;
}
partial void OnImpactProjSpecific(Fixture f1, Fixture f2, Contact contact)
{
if (!Prefab.Platform && Prefab.StairDirection == Direction.None)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
int section = FindSectionIndex(pos);
if (section > -1)
{
Vector2 normal = contact.Manifold.LocalNormal;
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal) * f2.Body.Mass * 0.1f;
if (impact > 10.0f)
{
SoundPlayer.PlayDamageSound("StructureBlunt", impact, SectionPosition(section, true), tags: Tags);
}
}
}
}
public override bool IsVisible(Rectangle worldView)
{
Rectangle worldRect = WorldRect;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) return false;
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) return false;
return true;
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (prefab.sprite == null) return;
if (editing)
{
if (!HasBody && !ShowStructures) return;
if (HasBody && !ShowWalls) return;
}
Draw(spriteBatch, editing, back, null);
}
public void DrawDamage(SpriteBatch spriteBatch, Effect damageEffect, bool editing)
{
Draw(spriteBatch, editing, false, damageEffect);
}
public float GetDrawDepth()
{
float depth = SpriteDepthOverrideIsSet ? SpriteOverrideDepth : prefab.sprite.Depth;
depth -= (ID % 255) * 0.000001f;
return depth;
}
private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null)
{
if (prefab.sprite == null) return;
if (editing)
{
if (!HasBody && !ShowStructures) return;
if (HasBody && !ShowWalls) return;
}
Color color = IsHighlighted ? GUI.Style.Orange : spriteColor;
if (IsSelected && editing)
{
//color = Color.Lerp(color, Color.Gold, 0.5f);
color = spriteColor;
Vector2 rectSize = rect.Size.ToVector2();
if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
Vector2 bodyPos = WorldPosition + BodyOffset;
GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White,
thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
}
Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
float depth = GetDrawDepth();
Vector2 textureOffset = this.textureOffset;
if (FlippedX) textureOffset.X = -textureOffset.X;
if (FlippedY) textureOffset.Y = -textureOffset.Y;
if (back && damageEffect == null)
{
if (Prefab.BackgroundSprite != null)
{
Vector2 dropShadowOffset = Vector2.Zero;
if (UseDropShadow)
{
dropShadowOffset = DropShadowOffset;
if (dropShadowOffset == Vector2.Zero)
{
if (Submarine == null)
{
dropShadowOffset = Vector2.UnitY * 10.0f;
}
else
{
dropShadowOffset = IsHorizontal ?
new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) :
new Vector2(Math.Sign(Submarine.HiddenSubPosition.X - Position.X) * 10.0f, 0.0f);
}
}
dropShadowOffset.Y = -dropShadowOffset.Y;
}
SpriteEffects oldEffects = Prefab.BackgroundSprite.effects;
Prefab.BackgroundSprite.effects ^= SpriteEffects;
Point backGroundOffset = new Point(
MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width),
MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height));
Prefab.BackgroundSprite.DrawTiled(
spriteBatch,
new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)),
new Vector2(rect.Width, rect.Height),
color: Prefab.BackgroundSpriteColor,
textureScale: TextureScale * Scale,
startOffset: backGroundOffset,
depth: Math.Max(Prefab.BackgroundSprite.Depth + (ID % 255) * 0.000001f, depth + 0.000001f));
if (UseDropShadow)
{
Prefab.BackgroundSprite.DrawTiled(
spriteBatch,
new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset,
new Vector2(rect.Width, rect.Height),
color: Color.Black * 0.5f,
textureScale: TextureScale * Scale,
startOffset: backGroundOffset,
depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f);
}
Prefab.BackgroundSprite.effects = oldEffects;
}
}
if (back == depth > 0.5f)
{
SpriteEffects oldEffects = prefab.sprite.effects;
prefab.sprite.effects ^= SpriteEffects;
for (int i = 0; i < Sections.Length; i++)
{
if (damageEffect != null)
{
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health);
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
{
damageEffect.Parameters["aCutoff"].SetValue(newCutoff);
damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f);
damageEffect.Parameters["inColor"].SetValue(color.ToVector4());
damageEffect.CurrentTechnique.Passes[0].Apply();
Submarine.DamageEffectCutoff = newCutoff;
Submarine.DamageEffectColor = color;
}
}
Point sectionOffset = new Point(
Math.Abs(rect.Location.X - Sections[i].rect.Location.X),
Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y));
if (FlippedX && IsHorizontal) sectionOffset.X = Sections[i].rect.Right - rect.Right;
if (FlippedY && !IsHorizontal) sectionOffset.Y = (rect.Y - rect.Height) - (Sections[i].rect.Y - Sections[i].rect.Height);
sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width);
sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height);
prefab.sprite.DrawTiled(
spriteBatch,
new Vector2(Sections[i].rect.X + drawOffset.X, -(Sections[i].rect.Y + drawOffset.Y)),
new Vector2(Sections[i].rect.Width, Sections[i].rect.Height),
color: color,
startOffset: sectionOffset,
depth: depth,
textureScale: TextureScale * Scale);
}
prefab.sprite.effects = oldEffects;
}
if (GameMain.DebugDraw)
{
if (Bodies != null)
{
for (int i = 0; i < Bodies.Count; i++)
{
Vector2 pos = FarseerPhysics.ConvertUnits.ToDisplayUnits(Bodies[i].Position);
if (Submarine != null) pos += Submarine.Position;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch,
pos,
FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].X),
FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].Y),
-Bodies[i].Rotation, Color.White);
}
}
if (SectionCount > 0 && HasBody)
{
for (int i = 0; i < SectionCount; i++)
{
if (GetSection(i).damage > 0)
{
var textPos = SectionPosition(i, true);
textPos.Y = -textPos.Y;
GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / Health) * 100f) + "%", Color.Yellow);
}
}
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
byte sectionCount = msg.ReadByte();
if (sectionCount != Sections.Length)
{
string errorMsg = $"Error while reading a network event for the structure \"{Name}\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
DebugConsole.NewMessage(errorMsg, Color.Red);
GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
for (int i = 0; i < sectionCount; i++)
{
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health;
if (i < Sections.Length)
{
SetDamage(i, damage);
}
}
}
}
}
@@ -0,0 +1,104 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
partial class StructurePrefab : MapEntityPrefab
{
public Color BackgroundSpriteColor
{
get;
private set;
}
public override void UpdatePlacing(Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
Vector2 size = ScaledSize;
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
if (placePosition == Vector2.Zero)
{
if (PlayerInput.PrimaryMouseButtonHeld())
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
newRect.X = (int)position.X;
newRect.Y = (int)position.Y;
}
else
{
Vector2 placeSize = size;
if (ResizeHorizontal) placeSize.X = position.X - placePosition.X;
if (ResizeVertical) placeSize.Y = placePosition.Y - position.Y;
//don't allow resizing width/height to less than the grid size
if (ResizeHorizontal && Math.Abs(placeSize.X) < Submarine.GridSize.X)
{
placeSize.X = Submarine.GridSize.X;
}
if (ResizeVertical && Math.Abs(placeSize.Y) < Submarine.GridSize.Y)
{
placeSize.Y = Submarine.GridSize.Y;
}
newRect = Submarine.AbsRect(placePosition, placeSize);
if (PlayerInput.PrimaryMouseButtonReleased())
{
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
var structure = new Structure(newRect, this, Submarine.MainSub)
{
Submarine = Submarine.MainSub
};
selected = null;
return;
}
}
if (PlayerInput.SecondaryMouseButtonHeld()) selected = null;
}
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)ScaledSize.X, (int)ScaledSize.Y);
if (placePosition == Vector2.Zero)
{
if (PlayerInput.PrimaryMouseButtonHeld())
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
newRect.X = (int)position.X;
newRect.Y = (int)position.Y;
}
else
{
Vector2 placeSize = ScaledSize;
if (ResizeHorizontal) placeSize.X = position.X - placePosition.X;
if (ResizeVertical) placeSize.Y = placePosition.Y - position.Y;
newRect = Submarine.AbsRect(placePosition, placeSize);
}
sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), textureScale: TextureScale * Scale);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - GameMain.GraphicsWidth, -newRect.Y, newRect.Width + GameMain.GraphicsWidth * 2, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White);
}
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{
SpriteEffects oldEffects = sprite.effects;
sprite.effects ^= spriteEffects;
sprite.DrawTiled(
spriteBatch,
new Vector2(placeRect.X, -placeRect.Y),
new Vector2(placeRect.Width, placeRect.Height),
color: Color.White * 0.8f,
textureScale: TextureScale * scale);
sprite.effects = oldEffects;
}
}
}
@@ -0,0 +1,625 @@
using Barotrauma.Networking;
using Barotrauma.RuinGeneration;
using Barotrauma.Sounds;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class RoundSound
{
public Sound Sound;
public readonly float Volume;
public readonly float Range;
public readonly bool Stream;
public string Filename
{
get { return Sound?.Filename; }
}
public RoundSound(XElement element, Sound sound)
{
Sound = sound;
Stream = sound.Stream;
Range = element.GetAttributeFloat("range", 1000.0f);
Volume = element.GetAttributeFloat("volume", 1.0f);
}
}
partial class Submarine : Entity, IServerSerializable
{
public Sprite PreviewImage;
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{
Vector2 position = PlayerInput.MousePosition;
position = cam.ScreenToWorld(position);
Vector2 worldGridPos = VectorToWorldGrid(position);
if (sub != null)
{
worldGridPos.X += sub.Position.X % GridSize.X;
worldGridPos.Y += sub.Position.Y % GridSize.Y;
}
return worldGridPos;
}
private static List<RoundSound> roundSounds = null;
public static RoundSound LoadRoundSound(XElement element, bool stream = false)
{
if (GameMain.SoundManager?.Disabled ?? true) { return null; }
string filename = element.GetAttributeString("file", "");
if (string.IsNullOrEmpty(filename)) filename = element.GetAttributeString("sound", "");
if (string.IsNullOrEmpty(filename))
{
string errorMsg = "Error when loading round sound (" + element + ") - file path not set";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace);
return null;
}
filename = Path.GetFullPath(filename.CleanUpPath()).CleanUpPath();
Sound existingSound = null;
if (roundSounds == null)
{
roundSounds = new List<RoundSound>();
}
else
{
existingSound = roundSounds.Find(s => s.Filename == filename && s.Stream == stream)?.Sound;
}
if (existingSound == null)
{
try
{
existingSound = GameMain.SoundManager.LoadSound(filename, stream);
if (existingSound == null) { return null; }
}
catch (FileNotFoundException e)
{
string errorMsg = "Failed to load sound file \"" + filename + "\".";
DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + filename, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace);
return null;
}
}
RoundSound newSound = new RoundSound(element, existingSound);
roundSounds.Add(newSound);
return newSound;
}
private static void RemoveRoundSound(RoundSound roundSound)
{
roundSound.Sound?.Dispose();
if (roundSounds == null) return;
if (roundSounds.Contains(roundSound)) roundSounds.Remove(roundSound);
foreach (RoundSound otherSound in roundSounds)
{
if (otherSound.Sound == roundSound.Sound) otherSound.Sound = null;
}
}
public static void RemoveAllRoundSounds()
{
if (roundSounds == null) return;
for (int i = roundSounds.Count - 1; i >= 0; i--)
{
RemoveRoundSound(roundSounds[i]);
}
}
//drawing ----------------------------------------------------
private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>();
private static readonly HashSet<Ruin> visibleRuins = new HashSet<Ruin>();
public static void CullEntities(Camera cam)
{
visibleSubs.Clear();
foreach (Submarine sub in Loaded)
{
if (sub.WorldPosition.Y < Level.MaxEntityDepth) continue;
Rectangle worldBorders = new Rectangle(
sub.Borders.X + (int)sub.WorldPosition.X - 500,
sub.Borders.Y + (int)sub.WorldPosition.Y + 500,
sub.Borders.Width + 1000,
sub.Borders.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleSubs.Add(sub);
}
}
visibleRuins.Clear();
if (Level.Loaded != null)
{
foreach (Ruin ruin in Level.Loaded.Ruins)
{
Rectangle worldBorders = new Rectangle(
ruin.Area.X - 500,
ruin.Area.Y + ruin.Area.Height + 500,
ruin.Area.Width + 1000,
ruin.Area.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleRuins.Add(ruin);
}
}
}
if (visibleEntities == null)
{
visibleEntities = new List<MapEntity>(MapEntity.mapEntityList.Count);
}
else
{
visibleEntities.Clear();
}
Rectangle worldView = cam.WorldView;
foreach (MapEntity entity in MapEntity.mapEntityList)
{
if (entity.Submarine != null)
{
if (!visibleSubs.Contains(entity.Submarine)) { continue; }
}
else if (entity.ParentRuin != null)
{
if (!visibleRuins.Contains(entity.ParentRuin)) { continue; }
}
if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); }
}
}
public static void Draw(SpriteBatch spriteBatch, bool editing = false)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
foreach (MapEntity e in entitiesToRender)
{
e.Draw(spriteBatch, editing);
}
}
public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
foreach (MapEntity e in entitiesToRender)
{
if (!e.DrawOverWater) continue;
if (predicate != null)
{
if (!predicate(e)) continue;
}
e.Draw(spriteBatch, editing, false);
}
if (GameMain.DebugDraw)
{
foreach (Submarine sub in Loaded)
{
Rectangle worldBorders = sub.Borders;
worldBorders.Location += sub.WorldPosition.ToPoint();
worldBorders.Y = -worldBorders.Y;
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
if (sub.subBody.PositionBuffer.Count < 2) continue;
Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
prevPos.Y = -prevPos.Y;
for (int i = 1; i < sub.subBody.PositionBuffer.Count; i++)
{
Vector2 currPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[i].Position);
currPos.Y = -currPos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 10, (int)currPos.Y - 10, 20, 20), Color.Blue * 0.6f, true, 0.01f);
GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.5f, 0, 5);
prevPos = currPos;
}
}
}
}
public static float DamageEffectCutoff;
public static Color DamageEffectColor;
private static readonly List<Structure> depthSortedDamageable = new List<Structure>();
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
depthSortedDamageable.Clear();
//insertion sort according to draw depth
foreach (MapEntity e in entitiesToRender)
{
if (e is Structure structure && structure.DrawDamageEffect)
{
float drawDepth = structure.GetDrawDepth();
int i = 0;
while (i < depthSortedDamageable.Count)
{
float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
if (otherDrawDepth < drawDepth) { break; }
i++;
}
depthSortedDamageable.Insert(i, structure);
}
}
foreach (Structure s in depthSortedDamageable)
{
s.DrawDamage(spriteBatch, damageEffect, editing);
}
if (damageEffect != null)
{
damageEffect.Parameters["aCutoff"].SetValue(0.0f);
damageEffect.Parameters["cCutoff"].SetValue(0.0f);
DamageEffectCutoff = 0.0f;
}
}
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
foreach (MapEntity e in entitiesToRender)
{
if (!e.DrawBelowWater) continue;
if (predicate != null)
{
if (!predicate(e)) continue;
}
e.Draw(spriteBatch, editing, true);
}
}
public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f)
{
var horizontalLine = GUI.Style.GetComponentStyle("HorizontalLine").Sprites[GUIComponent.ComponentState.None].First();
var verticalLine = GUI.Style.GetComponentStyle("VerticalLine").Sprites[GUIComponent.ComponentState.None].First();
Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2;
Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2;
for (int i = 0; i < gridCells; i++)
{
float distFromGridX = (MathUtils.RoundTowardsClosest(gridCenter.X, GridSize.X) - gridCenter.X) / GridSize.X;
float distFromGridY = (MathUtils.RoundTowardsClosest(gridCenter.X, GridSize.Y) - gridCenter.X) / GridSize.Y;
float normalizedDistX = Math.Abs(i + distFromGridX - gridCells / 2) / (gridCells / 2);
float normalizedDistY = Math.Abs(i - distFromGridY - gridCells / 2) / (gridCells / 2);
float expandX = MathHelper.Lerp(30.0f, 0.0f, normalizedDistX);
float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
GUI.DrawLine(spriteBatch,
horizontalLine.Sprite,
new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y),
new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y),
Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3);
GUI.DrawLine(spriteBatch,
verticalLine.Sprite,
new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY),
new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY),
Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3);
}
}
public static bool SaveCurrent(string filePath, MemoryStream previewImage = null)
{
if (MainSub == null)
{
MainSub = new Submarine(filePath);
}
MainSub.filePath = filePath;
return MainSub.SaveAs(filePath, previewImage);
}
public void CreatePreviewWindow(GUIComponent parent)
{
var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null);
if (PreviewImage == null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded"));
}
else
{
var submarinePreviewBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), style: null) { Color = Color.Black };
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), PreviewImage, scaleToFit: true);
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black);
}
var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter))
{
UserData = "descriptionbox",
ScrollBarVisible = true,
Spacing = 5
};
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), descriptionBox.Content.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("submarine.name." + Name, true) ?? Name, font: GUI.LargeFont, wrap: true) { ForceUpperCase = true, CanBeFocused = false };
float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth;
ScalableFont font = descriptionBox.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
}
if (!string.IsNullOrEmpty(RecommendedCrewExperience))
{
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
}
if (RequiredContentPackages.Any())
{
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
}
// show what game version the submarine was created on
if (!IsVanillaSubmarine() && GameVersion != null)
{
var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
}
GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>());
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
if (!string.IsNullOrEmpty(Description))
{
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true };
}
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: font, wrap: true)
{
CanBeFocused = false
};
}
public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null)
{
Rectangle worldBorders = GetDockedBorders();
worldBorders.Location += WorldPosition.ToPoint();
//create a container that has the same "aspect ratio" as the sub
float aspectRatio = worldBorders.Width / (float)worldBorders.Height;
float parentAspectRatio = parent.Rect.Width / (float)parent.Rect.Height;
float scale = 0.9f;
GUIFrame hullContainer = new GUIFrame(new RectTransform(
(parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
parent.RectTransform, Anchor.Center),
style: null);
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != this && !DockedTo.Contains(hull.Submarine)) continue;
Vector2 relativeHullPos = new Vector2(
(hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width,
(worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height);
Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height);
var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: Color.DarkCyan * 0.8f)
{
UserData = hull
};
new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: Color.DarkCyan * 0.8f);
}
if (pointsOfInterest != null)
{
foreach (Entity entity in pointsOfInterest)
{
Vector2 relativePos = new Vector2(
(entity.WorldPosition.X - worldBorders.X) / worldBorders.Width,
(worldBorders.Y - entity.WorldPosition.Y) / worldBorders.Height);
new GUIFrame(new RectTransform(new Point(1, 1), hullContainer.RectTransform) { RelativeOffset = relativePos }, style: null)
{
CanBeFocused = false,
UserData = entity
};
}
}
}
public bool IsVanillaSubmarine()
{
var vanilla = GameMain.VanillaContent;
if (vanilla != null)
{
var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine);
string pathToCompare = filePath.Replace(@"\", @"/").ToLowerInvariant();
if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare))
{
return true;
}
}
return false;
}
public void CheckForErrors()
{
List<string> errorMsgs = new List<string>();
if (!Hull.hullList.Any())
{
errorMsgs.Add(TextManager.Get("NoHullsWarning"));
}
foreach (Item item in Item.ItemList)
{
if (item.GetComponent<Items.Components.Vent>() == null) continue;
if (!item.linkedTo.Any())
{
errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning"));
break;
}
}
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
{
errorMsgs.Add(TextManager.Get("NoWaypointsWarning"));
}
if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null)
{
errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning"));
}
if (!Item.ItemList.Any(it => it.GetComponent<Items.Components.Pump>() != null && it.HasTag("ballast")))
{
errorMsgs.Add(TextManager.Get("NoBallastTagsWarning"));
}
if (Gap.GapList.Any(g => g.linkedTo.Count == 0))
{
errorMsgs.Add(TextManager.Get("NonLinkedGapsWarning"));
}
int disabledItemLightCount = 0;
foreach (Item item in Item.ItemList)
{
if (item.ParentInventory == null) { continue; }
disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
}
int count = GameMain.LightManager.Lights.Count(l => l.CastShadows) - disabledItemLightCount;
if (count > 45)
{
errorMsgs.Add(TextManager.Get("subeditor.shadowcastinglightswarning"));
}
if (errorMsgs.Any())
{
new GUIMessageBox(TextManager.Get("Warning"), string.Join("\n\n", errorMsgs), new Vector2(0.25f, 0.0f), new Point(400, 200));
}
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
{
//move disabled items (wires, items inside containers) inside the sub
if (e is Item item && item.body != null && !item.body.Enabled)
{
item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f);
}
}
}
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
{
var msgBox = new GUIMessageBox(
TextManager.Get("Warning"),
TextManager.Get("FarAwayEntitiesWarning"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.SubEditorScreen.Cam.Position = e.WorldPosition;
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
break;
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Name);
msg.ReadPadBits();
if (posInfo != null)
{
int index = 0;
while (index < subBody.PositionBuffer.Count && sendingTime > subBody.PositionBuffer[index].Timestamp)
{
index++;
}
subBody.PositionBuffer.Insert(index, posInfo);
}
}
}
}
@@ -0,0 +1,67 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
partial class SubmarineBody
{
partial void ClientUpdatePosition(float deltaTime)
{
if (GameMain.Client == null) { return; }
Vector2 newVelocity = Body.LinearVelocity;
Vector2 newPosition = Body.SimPosition;
Body.CorrectPosition(positionBuffer, out newPosition, out newVelocity, out _, out _);
Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition);
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity) || moveAmount.LengthSquared() < 0.0001f)
{
return;
}
List<Submarine> subsToMove = submarine.GetConnectedSubs();
foreach (Submarine dockedSub in subsToMove)
{
if (dockedSub == submarine) continue;
//clear the position buffer of the docked subs to prevent unnecessary position corrections
dockedSub.SubBody.positionBuffer.Clear();
}
Submarine closestSub = null;
if (Character.Controlled == null)
{
closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.Position);
}
else
{
closestSub = Character.Controlled.Submarine;
}
bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f;
foreach (Submarine sub in subsToMove)
{
sub.PhysicsBody.LinearVelocity = newVelocity;
if (displace)
{
sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
sub.SubBody.DisplaceCharacters(moveAmount);
}
else
{
sub.PhysicsBody.SetTransformIgnoreContacts(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
}
}
if (closestSub != null && subsToMove.Contains(closestSub))
{
GameMain.GameScreen.Cam.Position += moveAmount;
if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount;
if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount;
}
}
}
}
@@ -0,0 +1,321 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Barotrauma.Items.Components;
using System.Linq;
namespace Barotrauma
{
partial class WayPoint : MapEntity
{
private static Dictionary<SpawnType, Sprite> iconSprites;
private const int WaypointSize = 12, SpawnPointSize = 32;
public override bool IsVisible(Rectangle worldView)
{
return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
}
public override bool SelectableInEditor
{
get { return !IsHidden(); }
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (!editing && !GameMain.DebugDraw) { return; }
if (IsHidden()) { return; }
Vector2 drawPos = Position;
if (Submarine != null) { drawPos += Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
Draw(spriteBatch, drawPos);
}
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos)
{
Color clr = currentHull == null ? Color.CadetBlue : GUI.Style.Green;
if (spawnType != SpawnType.Path) { clr = Color.Gray; }
if (isObstructed)
{
clr = Color.Black;
}
if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
if (ConnectedGap != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); }
if (IsSelected || IsHighlighted)
{
int glowSize = (int)(iconSize * 1.5f);
GUI.Style.UIGlowCircular.Draw(spriteBatch,
new Rectangle((int)(drawPos.X - glowSize / 2), (int)(drawPos.Y - glowSize / 2), glowSize, glowSize),
Color.White);
}
Sprite sprite = iconSprites[SpawnType];
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
{
sprite = iconSprites[SpawnType.Path];
}
sprite.Draw(spriteBatch, drawPos, clr, scale: iconSize / (float)sprite.SourceRect.Width, depth: 0.001f);
sprite.RelativeOrigin = Vector2.One * 0.5f;
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
{
AssignedJob.Icon.Draw(spriteBatch, drawPos, AssignedJob.UIColor, scale: iconSize / (float)AssignedJob.Icon.SourceRect.Width * 0.8f, depth: 0.0f);
}
foreach (MapEntity e in linkedTo)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
(isObstructed ? Color.Gray : GUI.Style.Green) * 0.7f, width: 5, depth: 0.002f);
}
GUI.SmallFont.DrawString(spriteBatch,
ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
Color.WhiteSmoke);
}
public override bool IsMouseOn(Vector2 position)
{
if (IsHidden()) { return false; }
float dist = Vector2.DistanceSquared(position, WorldPosition);
float radius = (SpawnType == SpawnType.Path ? WaypointSize : SpawnPointSize) * 0.6f;
return dist < radius * radius;
}
private bool IsHidden()
{
if (spawnType == SpawnType.Path)
{
return (!GameMain.DebugDraw && !ShowWayPoints);
}
else
{
return (!GameMain.DebugDraw && !ShowSpawnPoints);
}
}
public override void UpdateEditing(Camera cam)
{
if (editingHUD == null || editingHUD.UserData != this)
{
editingHUD = CreateEditingHUD();
}
if (IsSelected && PlayerInput.PrimaryMouseButtonClicked())
{
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
// Update gaps, ladders, and stairs
UpdateLinkedEntity(position, Gap.GapList, gap => ConnectedGap = gap, gap =>
{
if (ConnectedGap == gap)
{
ConnectedGap = null;
}
});
UpdateLinkedEntity(position, Item.ItemList, i =>
{
var ladder = i?.GetComponent<Ladder>();
if (ladder != null)
{
Ladders = ladder;
}
}, i =>
{
var ladder = i?.GetComponent<Ladder>();
if (ladder != null)
{
if (Ladders == ladder)
{
Ladders = null;
}
}
}, inflate: 5);
// TODO: Cannot check the rectangle, since the rectangle is not rotated -> Need to use the collider.
//var stairList = mapEntityList.Where(me => me is Structure s && s.StairDirection != Direction.None).Select(me => me as Structure);
//UpdateLinkedEntity(position, stairList, s =>
//{
// Stairs = s;
//}, s =>
//{
// if (Stairs == s)
// {
// Stairs = null;
// }
//});
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 void UpdateLinkedEntity<T>(Vector2 worldPos, IEnumerable<T> list, Action<T> match, Action<T> noMatch, int inflate = 0) where T : MapEntity
{
foreach (var entity in list)
{
var rect = entity.WorldRect;
rect.Inflate(inflate, inflate);
if (Submarine.RectContains(rect, worldPos))
{
match(entity);
}
else
{
noMatch(entity);
}
}
}
private bool ChangeSpawnType(GUIButton button, object obj)
{
GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock;
spawnType += (int)button.UserData;
if (spawnType > SpawnType.Cargo) spawnType = SpawnType.Human;
if (spawnType < SpawnType.Human) spawnType = SpawnType.Cargo;
spawnTypeText.Text = spawnType.ToString();
return true;
}
private bool EnterIDCardDesc(GUITextBox textBox, string text)
{
IdCardDesc = text;
textBox.Text = text;
textBox.Color = GUI.Style.Green;
textBox.Deselect();
return true;
}
private bool EnterIDCardTags(GUITextBox textBox, string text)
{
IdCardTags = text.Split(',');
textBox.Text = string.Join(",", IdCardTags);
textBox.Flash(GUI.Style.Green);
textBox.Deselect();
return true;
}
private bool TextBoxChanged(GUITextBox textBox, string text)
{
textBox.Color = GUI.Style.Red;
return true;
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
int width = 500;
int height = spawnType == SpawnType.Path ? 80 : 200;
int x = GameMain.GraphicsWidth / 2 - width / 2, y = 30;
editingHUD = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas) { ScreenSpaceOffset = new Point(x, y) })
{
UserData = this
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), editingHUD.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f
};
if (spawnType == SpawnType.Path)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Waypoint"), font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("LinkWaypoint"));
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Spawnpoint"), font: GUI.LargeFont);
var spawnTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), spawnTypeContainer.RectTransform), TextManager.Get("SpawnType"));
var button = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), spawnTypeContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton")
{
UserData = -1,
OnClicked = ChangeSpawnType
};
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), spawnTypeContainer.RectTransform), spawnType.ToString(), textAlignment: Alignment.Center)
{
UserData = "spawntypetext"
};
button = new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), spawnTypeContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton")
{
UserData = 1,
OnClicked = ChangeSpawnType
};
var descText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("IDCardDescription"), font: GUI.SmallFont);
GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), idCardDesc)
{
MaxTextLength = 150,
OnEnterPressed = EnterIDCardDesc,
ToolTip = TextManager.Get("IDCardDescriptionTooltip")
};
propertyBox.OnTextChanged += TextBoxChanged;
var tagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("IDCardTags"), font: GUI.SmallFont);
propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), tagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags))
{
MaxTextLength = 60,
OnEnterPressed = EnterIDCardTags,
ToolTip = TextManager.Get("IDCardTagsTooltip")
};
propertyBox.OnTextChanged += TextBoxChanged;
var jobsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("SpawnpointJobs"), font: GUI.SmallFont)
{
ToolTip = TextManager.Get("SpawnpointJobsTooltip")
};
var jobDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), jobsText.RectTransform, Anchor.CenterRight))
{
ToolTip = TextManager.Get("SpawnpointJobsTooltip"),
OnSelected = (selected, userdata) =>
{
assignedJob = userdata as JobPrefab;
return true;
}
};
jobDropDown.AddItem(TextManager.Get("Any"), null);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
jobDropDown.AddItem(jobPrefab.Name, jobPrefab);
}
jobDropDown.SelectItem(assignedJob);
}
PositionEditingHUD();
return editingHUD;
}
}
}