v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -0,0 +1,395 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.MapCreatures.Behavior
{
partial class BallastFloraBehavior
{
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global, UnusedAutoPropertyAccessor.Global, MemberCanBePrivate.Global
internal class DamageParticle
{
[Serialize(defaultValue: "", isSaveable: false)]
public string Identifier { get; set; } = "";
[Serialize(defaultValue: 0f, isSaveable: false)]
public float MinRotation { get; set; }
[Serialize(defaultValue: 0f, isSaveable: false)]
public float MaxRotation { get; set; }
[Serialize(defaultValue: 0f, isSaveable: false)]
public float MinVelocity { get; set; }
[Serialize(defaultValue: 0f, isSaveable: false)]
public float MaxVelocity { get; set; }
[Serialize(defaultValue: "255,255,255,255", isSaveable: false)]
public Color ColorMultiplier { get; set; }
private float RandRotation() => Rand.Range(MinRotation, MaxRotation);
private float RandVelocity() => Rand.Range(MinVelocity, MaxVelocity);
public void Emit(Vector2 pos)
{
Particle particle = GameMain.ParticleManager.CreateParticle(Identifier, pos, RandRotation(), RandVelocity());
if (particle != null)
{
particle.ColorMultiplier = ColorMultiplier.ToVector4();
}
}
public DamageParticle(XElement element)
{
SerializableProperty.DeserializeProperties(this, element);
}
}
public Sprite? branchAtlas, decayAtlas;
public readonly Dictionary<VineTileType, VineSprite> BranchSprites = new Dictionary<VineTileType, VineSprite>();
public readonly List<Sprite> FlowerSprites = new List<Sprite>(), DamagedFlowerSprites = new List<Sprite>();
public readonly List<Sprite> HiddenFlowerSprites = new List<Sprite>();
public readonly List<Sprite> LeafSprites = new List<Sprite>(), DamagedLeafSprites = new List<Sprite>();
public readonly List<DamageParticle> DamageParticles = new List<DamageParticle>();
public readonly List<DamageParticle> DeathParticles = new List<DamageParticle>();
partial void LoadPrefab(XElement element)
{
string? branchAtlasPath = element.GetAttributeString("branchatlas", null);
string? decayAtlasPath = element.GetAttributeString("decayatlas", null);
if (branchAtlasPath != null)
{
branchAtlas = new Sprite(branchAtlasPath, Rectangle.Empty);
}
if (decayAtlasPath != null)
{
decayAtlas = new Sprite(decayAtlasPath, Rectangle.Empty);
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "branchsprite":
var tileType = subElement.GetAttributeString("type", null);
VineTileType type = Enum.Parse<VineTileType>(tileType);
BranchSprites.Add(type, new VineSprite(subElement));
break;
case "flowersprite":
FlowerSprites.Add(new Sprite(subElement));
break;
case "damagedflowersprite":
DamagedFlowerSprites.Add(new Sprite(subElement));
break;
case "hiddenflowersprite":
HiddenFlowerSprites.Add(new Sprite(subElement));
break;
case "leafsprite":
LeafSprites.Add(new Sprite(subElement));
break;
case "damagedleafsprite":
DamagedLeafSprites.Add(new Sprite(subElement));
break;
case "damageparticle":
DamageParticles.Add(new DamageParticle(subElement));
break;
case "deathparticle":
DeathParticles.Add(new DamageParticle(subElement));
break;
case "targets":
LoadTargets(subElement);
break;
}
flowerVariants = FlowerSprites.Count;
leafVariants = LeafSprites.Count;
}
}
private void CreateShapnel(Vector2 pos)
{
float particleAmount = Rand.Range(16, 32);
for (int i = 0; i < particleAmount; i++)
{
GameMain.ParticleManager.CreateParticle("shrapnel", pos, Rand.Vector(Rand.Range(-50f, 50.0f)));
}
}
private void CreateDamageParticle(BallastFloraBranch branch, float damage)
{
Vector2 pos = GetWorldPosition() + branch.Position;
int amount = (int)Math.Clamp(damage / 10f, 1, 10);
for (int i = 0; i < amount; i++)
{
foreach (DamageParticle particle in DamageParticles)
{
particle.Emit(pos);
}
}
}
private void CreateDeathParticle(BallastFloraBranch branch)
{
Vector2 pos = GetWorldPosition() + branch.Position;
int amount = (int)Math.Clamp(branch.MaxHealth / 10f, 1, 10);
for (int i = 0; i < amount; i++)
{
foreach (DamageParticle particle in DeathParticles)
{
particle.Emit(pos);
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
const float zStep = 0.000001f;
float leafDepth = zStep;
float flowerDepth = zStep;
if (GameMain.DebugDraw)
{
foreach (Body body in bodies)
{
Vector2 pos = Parent.Submarine.DrawPosition + ConvertUnits.ToDisplayUnits(body.Position);
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, pos, 32f, 32f, 0f, Color.Cyan, 0.1f, thickness: 1);
}
foreach (var (key, steps) in IgnoredTargets)
{
string label = $"Ignored \"{key.Name}\" for {steps} steps";
var (sizeX, sizeY) = GUI.SubHeadingFont.MeasureString(label);
Vector2 targetPos = key.WorldPosition;
targetPos.Y = -targetPos.Y;
GUI.DrawString(spriteBatch, targetPos - new Vector2(sizeX / 2f, sizeY), label, GUI.Style.Red, font: GUI.SubHeadingFont);
}
}
foreach (BallastFloraBranch branch in Branches)
{
Vector2 pos = Parent.DrawPosition + Offset + branch.Position;
pos.Y = -pos.Y;
float depth = BranchDepth;
float layer1 = depth + 0.01f,
layer2 = depth + 0.02f,
layer3 = depth + 0.03f;
VineSprite branchSprite = BranchSprites[branch.Type];
Color branchColor = Color.White;
if (GameMain.DebugDraw)
{
#if DEBUG
Vector2 basePos = Parent.WorldPosition;
foreach (var (from, to) in debugSearchLines)
{
Vector2 pos1 = basePos - from;
pos1.Y = -pos1.Y;
Vector2 pos2 = basePos - to;
pos2.Y = -pos2.Y;
GUI.DrawLine(spriteBatch, pos1, pos2, GUI.Style.Yellow * 0.8f, width: 4);
}
#endif
string label = "";
if (branch == Branches[^1])
{
label += $"Current State: {StateMachine.State?.GetType().Name ?? "null!"}\n";
}
if (StateMachine.State is GrowToTargetState targetState)
{
if (targetState.TargetBranches.Contains(branch))
{
GUI.DrawRectangle(spriteBatch, pos, branch.Rect.Width, branch.Rect.Height, 0f, Color.Red, thickness: 4);
}
if (targetState.TargetBranches[^1] == branch)
{
label += $"Target: {targetState.Target.Name}\n";
Vector2 targetPos = targetState.Target.WorldPosition;
targetPos.Y = -targetPos.Y;
GUI.DrawLine(spriteBatch, pos, targetPos, Color.Red, width: 4);
}
}
var (sizeX, sizeY) = GUI.SubHeadingFont.MeasureString(label);
GUI.DrawString(spriteBatch, pos - new Vector2(sizeX / 2f, branch.Rect.Height + sizeY), label, Color.White, font: GUI.SubHeadingFont);
}
bool isDamaged = branch.Health < branch.MaxHealth;
if (HasBrokenThrough)
{
if (branchAtlas != null && branchAtlas.Loaded)
{
spriteBatch.Draw(branchAtlas.Texture, pos + branch.offset, branchSprite.SourceRect, branchColor, 0f, branchSprite.AbsoluteOrigin, BaseBranchScale * branch.VineStep, SpriteEffects.None, layer2);
}
if (decayAtlas != null && isDamaged && decayAtlas.Loaded)
{
spriteBatch.Draw(decayAtlas.Texture, pos + branch.offset, branchSprite.SourceRect, branch.HealthColor, 0f, branchSprite.AbsoluteOrigin, BaseBranchScale * branch.VineStep, SpriteEffects.None, layer2 - zStep);
}
}
if (branch.FlowerConfig.Variant >= 0)
{
int variant = branch.FlowerConfig.Variant;
Sprite flowerSprite = HasBrokenThrough ? FlowerSprites[variant] : HiddenFlowerSprites[variant];
float flowerScale = BaseFlowerScale * branch.FlowerConfig.Scale * branch.FlowerStep;
if (HasBrokenThrough) { flowerScale *= branch.Pulse; }
flowerSprite.Draw(spriteBatch, pos, branchColor, flowerSprite.Origin, scale: flowerScale, rotate: branch.FlowerConfig.Rotation, depth: layer1 - flowerDepth);
if (isDamaged && HasBrokenThrough && DamagedFlowerSprites.Count > variant)
{
DamagedFlowerSprites[variant].Draw(spriteBatch, pos, branch.HealthColor, flowerSprite.Origin, scale: flowerScale, rotate: branch.FlowerConfig.Rotation, depth: layer1 - flowerDepth - zStep);
}
flowerDepth -= zStep;
if (flowerDepth > 0.01f)
{
flowerDepth = zStep;
}
}
if (branch.LeafConfig.Variant >= 0 && HasBrokenThrough)
{
int variant = branch.LeafConfig.Variant;
Sprite leafSprite = LeafSprites[variant];
leafSprite.Draw(spriteBatch, pos, branchColor, leafSprite.Origin, scale: BaseLeafScale * branch.LeafConfig.Scale * branch.FlowerStep, rotate: branch.LeafConfig.Rotation, depth: layer3 + leafDepth);
if (isDamaged && DamagedLeafSprites.Count > variant)
{
DamagedLeafSprites[variant].Draw(spriteBatch, pos, branch.HealthColor, leafSprite.Origin, scale: BaseLeafScale * branch.LeafConfig.Scale * branch.FlowerStep, rotate: branch.LeafConfig.Rotation, depth: layer3 + leafDepth - zStep);
}
leafDepth += zStep;
if (leafDepth > 0.01f)
{
flowerDepth = zStep;
}
}
}
}
public void ClientRead(IReadMessage msg, NetworkHeader header)
{
switch (header)
{
case NetworkHeader.Infect:
int infectBranch = -1;
ushort itemId = msg.ReadUInt16();
bool infect = msg.ReadBoolean();
if (infect)
{
infectBranch = msg.ReadInt32();
}
Entity? entity = Entity.FindEntityByID(itemId);
if (entity is Item item)
{
if (infect)
{
ClaimTarget(item, Branches.FirstOrDefault(b => b.ID == infectBranch));
}
else
{
RemoveClaim(itemId);
}
}
else
{
DebugConsole.AddWarning($"Received Infect.{infect} Network Header with invalid item ID: {itemId}, which belongs to {entity?.ToString() ?? "null!"}");
}
break;
case NetworkHeader.BranchCreate:
int parentId = msg.ReadInt32();
BallastFloraBranch branch = ReadBranch(msg);
BallastFloraBranch? parent = Branches.FirstOrDefault(b => b.ID == parentId);
if (parent == null)
{
DebugConsole.AddWarning($"Received BranchCreate with an invalid parent ID: {parentId}, Maximum ID is {Branches.Max(b => b.ID)}");
}
UpdateConnections(branch, parent);
Branches.Add(branch);
OnBranchGrowthSuccess(branch);
break;
case NetworkHeader.BranchRemove:
int removedBranchId = msg.ReadInt32();
BallastFloraBranch removedBranch = Branches.FirstOrDefault(b => b.ID == removedBranchId);
if (removedBranch != null)
{
RemoveBranch(removedBranch);
}
else
{
DebugConsole.AddWarning($"Received BranchRemove for a branch that doesn't exist. ID: {removedBranchId}, Maximum ID is {Branches.Max(b => b.ID)}");
}
break;
case NetworkHeader.BranchDamage:
int damageBranchId = msg.ReadInt32();
float damage = msg.ReadSingle();
float health = msg.ReadSingle();
BallastFloraBranch damagedBranch = Branches.FirstOrDefault(b => b.ID == damageBranchId);
if (damagedBranch != null)
{
CreateDamageParticle(damagedBranch, damage);
damagedBranch.Health = health;
}
else
{
DebugConsole.AddWarning($"Received BranchDamage for a branch that doesn't exist. ID: {damageBranchId}, Maximum ID is {Branches.Max(b => b.ID)}");
}
break;
case NetworkHeader.Kill:
Kill();
break;
}
PowerConsumptionTimer = msg.ReadSingle();
}
private BallastFloraBranch ReadBranch(IReadMessage msg)
{
int id = msg.ReadInt32();
byte type = (byte) msg.ReadRangedInteger(0b0000, 0b1111);
byte sides = (byte) msg.ReadRangedInteger(0b0000, 0b1111);
int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
int maxHealth = msg.ReadUInt16();
int posX = msg.ReadInt32(), posY = msg.ReadInt32();
Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
return new BallastFloraBranch(this, pos, (VineTileType)type, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))
{
ID = id,
MaxHealth = maxHealth,
Sides = (TileSide) sides
};
}
}
}
@@ -36,8 +36,8 @@ namespace Barotrauma
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);
particlePos.X - (worldPosition.X + size.X / 2.0f),
Math.Max((float)Math.Sqrt(size.X) * Rand.Range(0.0f, 15.0f) * growModifier, 0.0f));
particleVel.X = MathHelper.Clamp(particleVel.X, -200.0f, 200.0f);
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.MapCreatures.Behavior;
namespace Barotrauma
{
@@ -48,7 +49,7 @@ namespace Barotrauma
{
get
{
return decals.Count > 0;
return decals.Count > 0 || BallastFlora != null;
}
}
@@ -65,6 +66,8 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
if (BallastFlora != null) { return true; }
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw)
{
if (decals.Count == 0 && paintAmount < minimumPaintAmountToDraw) { return false; }
@@ -229,6 +232,7 @@ namespace Barotrauma
{
if (back && Screen.Selected != GameMain.SubEditorScreen)
{
BallastFlora?.Draw(spriteBatch);
DrawDecals(spriteBatch);
return;
}
@@ -244,7 +248,7 @@ namespace Barotrauma
alpha = Math.Min((float)(Timing.TotalTime - lastAmbientLightEditTime) / hideTimeAfterEdit - 1.0f, 1.0f);
}
Rectangle drawRect =
Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
if ((IsSelected || IsHighlighted) && editing)
@@ -332,6 +336,7 @@ namespace Barotrauma
public void DrawSectionColors(SpriteBatch spriteBatch)
{
if (BackgroundSections == null || BackgroundSections.Count == 0) { return; }
Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
Point sectionSize = BackgroundSections[0].Rect.Size;
Vector2 drawPos = drawOffset + new Vector2(rect.Location.X + sectionSize.X / 2, rect.Location.Y - sectionSize.Y / 2);
@@ -607,6 +612,26 @@ namespace Barotrauma
public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
{
bool isBallastFloraUpdate = message.ReadBoolean();
if (isBallastFloraUpdate)
{
BallastFloraBehavior.NetworkHeader header = (BallastFloraBehavior.NetworkHeader) message.ReadByte();
if (header == BallastFloraBehavior.NetworkHeader.Spawn)
{
string identifier = message.ReadString();
float x = message.ReadSingle();
float y = message.ReadSingle();
BallastFlora = new BallastFloraBehavior(this, BallastFloraPrefab.Find(identifier), new Vector2(x, y), firstGrowth: true)
{
PowerConsumptionTimer = message.ReadSingle()
};
}
else if (BallastFlora != null)
{
BallastFlora.ClientRead(message, header);
}
return;
}
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
@@ -1,30 +1,38 @@
using Microsoft.Xna.Framework;
using Barotrauma.SpriteDeformations;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreature : ISteerable
{
const float MaxDepth = 100.0f;
const float MaxDepth = 10000.0f;
const float CheckWallsInterval = 5.0f;
public bool Enabled;
public bool Visible;
private BackgroundCreaturePrefab prefab;
public readonly BackgroundCreaturePrefab Prefab;
private readonly List<SpriteDeformation> uniqueSpriteDeformations = new List<SpriteDeformation>();
private readonly List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>();
private readonly List<SpriteDeformation> lightSpriteDeformations = new List<SpriteDeformation>();
private Vector2 position;
private Vector3 velocity;
private float depth;
private SteeringManager steeringManager;
private float checkWallsTimer;
private float alpha = 1.0f;
private readonly SteeringManager steeringManager;
private float checkWallsTimer, flashTimer;
private float wanderZPhase;
private Vector2 obstacleDiff;
@@ -33,9 +41,16 @@ namespace Barotrauma
public Swarm Swarm;
Vector2 drawPosition;
public Vector2 TransformedPosition
public Vector2[,] CurrentSpriteDeformation
{
get { return drawPosition; }
get;
private set;
}
public Vector2[,] CurrentLightSpriteDeformation
{
get;
private set;
}
public Vector2 SimPosition
@@ -58,11 +73,10 @@ namespace Barotrauma
get;
set;
}
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
{
this.prefab = prefab;
this.Prefab = prefab;
this.position = position;
drawPosition = position;
@@ -70,18 +84,73 @@ namespace Barotrauma
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));
Rand.Range(-prefab.Speed, prefab.Speed, Rand.RandSync.ClientOnly),
Rand.Range(-prefab.Speed, prefab.Speed, Rand.RandSync.ClientOnly),
Rand.Range(0.0f, prefab.WanderZAmount, Rand.RandSync.ClientOnly));
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval);
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval, Rand.RandSync.ClientOnly);
foreach (XElement subElement in prefab.Config.Elements())
{
List<SpriteDeformation> deformationList = null;
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "deformablesprite":
deformationList = spriteDeformations;
break;
case "deformablelightsprite":
deformationList = lightSpriteDeformations;
break;
default:
continue;
}
foreach (XElement animationElement in subElement.Elements())
{
SpriteDeformation deformation = null;
int sync = animationElement.GetAttributeInt("sync", -1);
if (sync > -1)
{
string typeName = animationElement.GetAttributeString("type", "").ToLowerInvariant();
deformation = uniqueSpriteDeformations.Find(d => d.TypeName == typeName && d.Sync == sync);
}
if (deformation == null)
{
deformation = SpriteDeformation.Load(animationElement, prefab.Name);
if (deformation != null)
{
uniqueSpriteDeformations.Add(deformation);
}
}
if (deformation != null)
{
deformationList.Add(deformation);
}
}
}
}
public void Update(float deltaTime)
{
position += new Vector2(velocity.X, velocity.Y) * deltaTime;
depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, 0.0f, MaxDepth);
depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, Prefab.MinDepth, Prefab.MaxDepth * 10);
if (Prefab.FlashInterval > 0.0f)
{
flashTimer -= deltaTime;
if (flashTimer > 0.0f)
{
alpha = 0.0f;
}
else
{
//value goes from 0 to 1 and back to 0 during the flash
alpha = (float)Math.Sin(-flashTimer / Prefab.FlashDuration * MathHelper.Pi) * PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.1f, (float)Timing.TotalTime * 0.2f);
if (flashTimer < -Prefab.FlashDuration)
{
flashTimer = Prefab.FlashInterval;
}
}
}
checkWallsTimer -= deltaTime;
if (checkWallsTimer <= 0.0f && Level.Loaded != null)
@@ -134,54 +203,145 @@ namespace Barotrauma
float midPointDist = Vector2.Distance(SimPosition, midPoint) * 100.0f;
if (midPointDist > Swarm.MaxDistance)
{
steeringManager.SteeringSeek(midPoint, ((midPointDist / Swarm.MaxDistance) - 1.0f) * prefab.Speed);
steeringManager.SteeringSeek(midPoint, ((midPointDist / Swarm.MaxDistance) - 1.0f) * Prefab.Speed);
}
steeringManager.SteeringManual(deltaTime, Swarm.AvgVelocity() * Swarm.Cohesion);
}
if (prefab.WanderAmount > 0.0f)
if (Prefab.WanderAmount > 0.0f)
{
steeringManager.SteeringWander(prefab.Speed);
steeringManager.SteeringWander(Prefab.Speed);
}
if (obstacleDiff != Vector2.Zero)
{
steeringManager.SteeringManual(deltaTime, -obstacleDiff * (1.0f - obstacleDist / 5000.0f) * prefab.Speed);
steeringManager.SteeringManual(deltaTime, -obstacleDiff * (1.0f - obstacleDist / 5000.0f) * Prefab.Speed);
}
steeringManager.Update(prefab.Speed);
steeringManager.Update(Prefab.Speed);
if (prefab.WanderZAmount > 0.0f)
if (Prefab.WanderZAmount > 0.0f)
{
wanderZPhase += Rand.Range(-prefab.WanderZAmount, prefab.WanderZAmount);
velocity.Z = (float)Math.Sin(wanderZPhase) * prefab.Speed;
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);
UpdateDeformations(deltaTime);
}
public void DrawLightSprite(SpriteBatch spriteBatch, Camera cam)
{
Draw(spriteBatch, cam, Prefab.LightSprite, Prefab.DeformableLightSprite, CurrentLightSpriteDeformation, Color.White * alpha);
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
Draw(spriteBatch,
cam,
Prefab.Sprite,
Prefab.DeformableSprite,
CurrentSpriteDeformation,
Color.Lerp(Color.White, Level.Loaded.BackgroundColor, depth / Math.Max(MaxDepth, Prefab.MaxDepth)) * alpha);
}
private void Draw(SpriteBatch spriteBatch, Camera cam, Sprite sprite, DeformableSprite deformableSprite, Vector2[,] currentSpriteDeformation, Color color)
{
if (sprite == null && deformableSprite == null) { return; }
if (color.A == 0) { return; }
float rotation = 0.0f;
if (!prefab.DisableRotation)
if (!Prefab.DisableRotation)
{
rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
if (velocity.X < 0.0f) rotation -= MathHelper.Pi;
if (velocity.X < 0.0f) { rotation -= MathHelper.Pi; }
}
drawPosition = position;
if (depth > 0.0f)
drawPosition = GetDrawPosition(cam);
float scale = GetScale();
sprite?.Draw(spriteBatch,
new Vector2(drawPosition.X, -drawPosition.Y),
color,
rotation,
scale,
Prefab.DisableFlipping || velocity.X > 0.0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally,
Math.Min(depth / MaxDepth, 1.0f));
if (deformableSprite != null)
{
if (currentSpriteDeformation != null)
{
deformableSprite.Deform(currentSpriteDeformation);
}
else
{
deformableSprite.Reset();
}
deformableSprite?.Draw(cam,
new Vector3(drawPosition.X, drawPosition.Y, Math.Min(depth / 10000.0f, 1.0f)),
deformableSprite.Origin,
rotation,
Vector2.One * scale,
color,
mirror: Prefab.DisableFlipping || velocity.X <= 0.0f);
}
}
public Vector2 GetDrawPosition(Camera cam)
{
Vector2 drawPosition = WorldPosition;
if (depth >= 0)
{
Vector2 camOffset = drawPosition - cam.WorldViewCenter;
drawPosition -= camOffset * (depth / MaxDepth) * 0.05f;
drawPosition -= camOffset * depth / MaxDepth;
}
return drawPosition;
}
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));
public float GetScale()
{
return Math.Max(1.0f - depth / MaxDepth, 0.05f) * Prefab.Scale;
}
public Rectangle GetExtents(Camera cam)
{
Vector2 min = GetDrawPosition(cam);
Vector2 max = min;
float scale = GetScale();
GetSpriteExtents(Prefab.Sprite, ref min, ref max);
GetSpriteExtents(Prefab.LightSprite, ref min, ref max);
GetSpriteExtents(Prefab.DeformableSprite?.Sprite, ref min, ref max);
GetSpriteExtents(Prefab.DeformableLightSprite?.Sprite, ref min, ref max);
return new Rectangle(min.ToPoint(), (max - min).ToPoint());
void GetSpriteExtents(Sprite sprite, ref Vector2 min, ref Vector2 max)
{
if (sprite == null) { return; }
min.X = Math.Min(min.X, min.X - sprite.size.X * sprite.RelativeOrigin.X * scale);
min.Y = Math.Min(min.Y, min.Y - sprite.size.Y * sprite.RelativeOrigin.Y * scale);
max.X = Math.Max(max.X, max.X + sprite.size.X * (1.0f - sprite.RelativeOrigin.X) * scale);
max.Y = Math.Max(max.Y, max.Y + sprite.size.Y * (1.0f - sprite.RelativeOrigin.Y) * scale);
}
}
private void UpdateDeformations(float deltaTime)
{
foreach (SpriteDeformation deformation in uniqueSpriteDeformations)
{
deformation.Update(deltaTime);
}
if (spriteDeformations.Count > 0)
{
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, Prefab.DeformableSprite.Size);
}
if (lightSpriteDeformations.Count > 0)
{
CurrentLightSpriteDeformation = SpriteDeformation.GetDeformation(lightSpriteDeformations, Prefab.DeformableLightSprite.Size);
}
}
}
@@ -9,14 +9,14 @@ namespace Barotrauma
{
class BackgroundCreatureManager
{
const int MaxSprites = 100;
const int MaxCreatures = 100;
const float CheckActiveInterval = 1.0f;
const float VisibilityCheckInterval = 1.0f;
private float checkActiveTimer;
private float checkVisibleTimer;
private List<BackgroundCreaturePrefab> prefabs = new List<BackgroundCreaturePrefab>();
private List<BackgroundCreature> activeSprites = new List<BackgroundCreature>();
private readonly List<BackgroundCreaturePrefab> prefabs = new List<BackgroundCreaturePrefab>();
private readonly List<BackgroundCreature> creatures = new List<BackgroundCreature>();
public BackgroundCreatureManager(string configPath)
{
@@ -60,92 +60,111 @@ namespace Barotrauma
}
}
public void SpawnSprites(int count, Vector2? position = null)
public void SpawnCreatures(Level level, int count, Vector2? position = null)
{
activeSprites.Clear();
creatures.Clear();
if (prefabs.Count == 0) return;
if (prefabs.Count == 0) { return; }
count = Math.Min(count, MaxSprites);
count = Math.Min(count, MaxCreatures);
for (int i = 0; i < count; i++ )
List<BackgroundCreaturePrefab> availablePrefabs = new List<BackgroundCreaturePrefab>(prefabs);
for (int i = 0; i < count; i++)
{
Vector2 pos = Vector2.Zero;
if (position == null)
{
var wayPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine==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)];
var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level.GenerationParams)).ToList(), Rand.RandSync.ClientOnly);
if (prefab == null) { break; }
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);
var creature = new BackgroundCreature(prefab, pos + Rand.Vector(Rand.Range(0.0f, prefab.SwarmRadius, Rand.RandSync.ClientOnly), Rand.RandSync.ClientOnly));
creatures.Add(creature);
swarmMembers.Add(creature);
}
if (amount > 0)
if (amount > 1)
{
new Swarm(swarmMembers, prefab.SwarmRadius, prefab.SwarmCohesion);
}
if (creatures.Count(c => c.Prefab == prefab) > prefab.MaxCount)
{
availablePrefabs.Remove(prefab);
if (availablePrefabs.Count <= 0) { break; }
}
}
}
public void ClearSprites()
public void Clear()
{
activeSprites.Clear();
creatures.Clear();
}
public void Update(float deltaTime, Camera cam)
{
if (checkActiveTimer < 0.0f)
if (checkVisibleTimer < 0.0f)
{
foreach (BackgroundCreature sprite in activeSprites)
int margin = 500;
foreach (BackgroundCreature creature in creatures)
{
sprite.Enabled = Math.Abs(sprite.TransformedPosition.X - cam.WorldViewCenter.X) < cam.WorldView.Width &&
Math.Abs(sprite.TransformedPosition.Y - cam.WorldViewCenter.Y) < cam.WorldView.Height;
Rectangle extents = creature.GetExtents(cam);
bool wasVisible = creature.Visible;
creature.Visible =
extents.Right >= cam.WorldView.X - margin &&
extents.X <= cam.WorldView.Right + margin &&
extents.Bottom >= cam.WorldView.Y - cam.WorldView.Height - margin &&
extents.Y <= cam.WorldView.Y + margin;
}
checkActiveTimer = CheckActiveInterval;
checkVisibleTimer = VisibilityCheckInterval;
}
else
{
checkActiveTimer -= deltaTime;
checkVisibleTimer -= deltaTime;
}
foreach (BackgroundCreature sprite in activeSprites)
foreach (BackgroundCreature creature in creatures)
{
if (!sprite.Enabled) continue;
sprite.Update(deltaTime);
if (!creature.Visible) { continue; }
creature.Update(deltaTime);
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature sprite in activeSprites)
foreach (BackgroundCreature creature in creatures)
{
if (!sprite.Enabled) continue;
sprite.Draw(spriteBatch, cam);
if (!creature.Visible) { continue; }
creature.Draw(spriteBatch, cam);
}
}
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in creatures)
{
if (!creature.Visible) { continue; }
creature.DrawLightSprite(spriteBatch, cam);
}
}
}
@@ -1,50 +1,117 @@
using System.Xml.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreaturePrefab
{
public readonly Sprite Sprite;
public readonly Sprite Sprite, LightSprite;
public readonly DeformableSprite DeformableSprite, DeformableLightSprite;
public readonly float Speed;
public readonly string Name;
public readonly float WanderAmount;
public readonly XElement Config;
public readonly float WanderZAmount;
[Serialize(1.0f, true)]
public float Speed { get; private set; }
public readonly int SwarmMin, SwarmMax;
public readonly float SwarmRadius, SwarmCohesion;
[Serialize(0.0f, true)]
public float WanderAmount { get; private set; }
public readonly bool DisableRotation;
[Serialize(0.0f, true)]
public float WanderZAmount { get; private set; }
[Serialize(1, true)]
public int SwarmMin { get; private set; }
[Serialize(1, true)]
public int SwarmMax { get; private set; }
[Serialize(200.0f, true)]
public float SwarmRadius { get; private set; }
[Serialize(0.2f, true)]
public float SwarmCohesion { get; private set; }
[Serialize(10.0f, true)]
public float MinDepth { get; private set; }
[Serialize(1000.0f, true)]
public float MaxDepth { get; private set; }
[Serialize(false, true)]
public bool DisableRotation { get; private set; }
[Serialize(false, true)]
public bool DisableFlipping { get; private set; }
[Serialize(1.0f, true)]
public float Scale { get; private set; }
[Serialize(1.0f, true)]
public float Commonness { get; private set; }
[Serialize(1000, true)]
public int MaxCount { get; private set; }
[Serialize(0.0f, true)]
public float FlashInterval { get; private set; }
[Serialize(0.0f, true)]
public float FlashDuration { get; private set; }
/// <summary>
/// Overrides the commonness of the object in a specific level type.
/// Key = name of the level type, value = commonness in that level type.
/// </summary>
public Dictionary<string, float> OverrideCommonness = new Dictionary<string, float>();
public readonly float Scale;
public BackgroundCreaturePrefab(XElement element)
{
Speed = element.GetAttributeFloat("speed", 1.0f);
Name = element.Name.ToString();
WanderAmount = element.GetAttributeFloat("wanderamount", 0.0f);
Config = element;
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);
SerializableProperty.DeserializeProperties(this, element);
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("sprite", System.StringComparison.OrdinalIgnoreCase)) { continue; }
Sprite = new Sprite(subElement, lazyLoad: true);
break;
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
Sprite = new Sprite(subElement, lazyLoad: true);
break;
case "deformablesprite":
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
break;
case "lightsprite":
LightSprite = new Sprite(subElement, lazyLoad: true);
break;
case "deformablelightsprite":
DeformableLightSprite = new DeformableSprite(subElement, lazyLoad: true);
break;
case "overridecommonness":
string levelType = subElement.GetAttributeString("leveltype", "").ToLowerInvariant();
if (!OverrideCommonness.ContainsKey(levelType))
{
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
}
break;
}
}
}
public float GetCommonness(LevelGenerationParams generationParams)
{
if (generationParams?.Identifier != null &&
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
(generationParams.OldIdentifier != null && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
{
return commonness;
}
return Commonness;
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -8,72 +9,82 @@ namespace Barotrauma
{
static partial class CaveGenerator
{
public static List<VertexPositionTexture> GenerateRenderVerticeList(List<Vector2[]> triangles)
public static List<VertexPositionTexture> GenerateWallVertices(List<Vector2[]> triangles, LevelGenerationParams generationParams, float zCoord)
{
var verticeList = new List<VertexPositionTexture>();
var vertices = 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));
Vector2 uvCoords = vertex / generationParams.WallTextureSize;
vertices.Add(new VertexPositionTexture(new Vector3(vertex, zCoord), uvCoords));
}
}
return verticeList;
return vertices;
}
public static VertexPositionTexture[] GenerateWallShapes(List<VoronoiCell> cells, Level level)
public static List<VertexPositionTexture> GenerateWallEdgeVertices(List<VoronoiCell> cells, Level level, float zCoord)
{
float outWardThickness = 30.0f;
float outWardThickness = level.GenerationParams.WallEdgeExpandOutwardsAmount;
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
List<VertexPositionTexture> vertices = new List<VertexPositionTexture>();
foreach (VoronoiCell cell in cells)
{
CompareCCW compare = new CompareCCW(cell.Center);
Vector2 minVert = cell.Edges[0].Point1;
Vector2 maxVert = cell.Edges[0].Point1;
float circumference = 0.0f;
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;
}
circumference += Vector2.Distance(edge.Point1, edge.Point2);
minVert = new Vector2(
Math.Min(minVert.X, edge.Point1.X),
Math.Min(minVert.Y, edge.Point1.Y));
maxVert = new Vector2(
Math.Max(maxVert.X, edge.Point1.X),
Math.Max(maxVert.Y, edge.Point1.Y));
}
}
foreach (VoronoiCell cell in cells)
{
Vector2 center = (minVert + maxVert) / 2;
foreach (GraphEdge edge in cell.Edges)
{
if (!edge.IsSolid) continue;
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));
GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
var leftAdjacentCell = leftEdge?.AdjacentCell(cell);
if (leftAdjacentCell != null)
{
var adjEdge = leftAdjacentCell.Edges.Find(e => e != leftEdge && e.IsSolid && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
if (adjEdge != null) { leftEdge = adjEdge; }
}
GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
var rightAdjacentCell = rightEdge?.AdjacentCell(cell);
if (rightAdjacentCell != null)
{
var adjEdge = rightAdjacentCell.Edges.Find(e => e != rightEdge && e.IsSolid && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
if (adjEdge != null) { rightEdge = adjEdge; }
}
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
float inwardThickness1 = 100;
float inwardThickness2 = 100;
float inwardThickness1 = level.GenerationParams.WallEdgeExpandInwardsAmount;
float inwardThickness2 = level.GenerationParams.WallEdgeExpandInwardsAmount;
if (leftEdge != null && !leftEdge.IsSolid)
{
leftNormal = edge.Point1 == leftEdge.Point1 ?
leftNormal = edge.Point1.NearlyEquals(leftEdge.Point1) ?
Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) :
Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2);
inwardThickness1 = Vector2.Distance(leftEdge.Point1, leftEdge.Point2) / 2;
}
else if (leftEdge != null)
{
leftNormal = -Vector2.Normalize(edge.GetNormal(cell) + leftEdge.GetNormal(leftAdjacentCell ?? cell));
if (!MathUtils.IsValid(leftNormal)) { leftNormal = -edge.GetNormal(cell); }
}
else
{
leftNormal = Vector2.Normalize(cell.Center - edge.Point1);
inwardThickness1 = Vector2.Distance(edge.Point1, cell.Center) / 2;
}
inwardThickness1 = Math.Min(Vector2.Distance(edge.Point1, cell.Center), inwardThickness1);
if (!MathUtils.IsValid(leftNormal))
{
@@ -86,7 +97,7 @@ namespace Barotrauma
if (cell.Body != null)
{
GameMain.World.Remove(cell.Body);
if (GameMain.World.BodyList.Contains(cell.Body)) { GameMain.World.Remove(cell.Body); }
cell.Body = null;
}
leftNormal = Vector2.UnitX;
@@ -95,16 +106,20 @@ namespace Barotrauma
if (rightEdge != null && !rightEdge.IsSolid)
{
rightNormal = edge.Point2 == rightEdge.Point1 ?
rightNormal = edge.Point2.NearlyEquals(rightEdge.Point1) ?
Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) :
Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2);
inwardThickness2 = Vector2.Distance(rightEdge.Point1, rightEdge.Point2) / 2;
}
else if (rightEdge != null)
{
rightNormal = -Vector2.Normalize(edge.GetNormal(cell) + rightEdge.GetNormal(rightAdjacentCell ?? cell));
if (!MathUtils.IsValid(rightNormal)) { rightNormal = -edge.GetNormal(cell); }
}
else
{
rightNormal = Vector2.Normalize(cell.Center - edge.Point2);
inwardThickness2 = Vector2.Distance(edge.Point2, cell.Center) / 2;
}
inwardThickness2 = Math.Min(Vector2.Distance(edge.Point2, cell.Center), inwardThickness2);
if (!MathUtils.IsValid(rightNormal))
{
@@ -117,24 +132,23 @@ namespace Barotrauma
if (cell.Body != null)
{
GameMain.World.Remove(cell.Body);
if (GameMain.World.BodyList.Contains(cell.Body)) { 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));
float point1UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point1 - center));
float point2UV = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(edge.Point2 - center));
//handle wrapping around 0/360
if (point1UV - point2UV > MathHelper.Pi)
{
point2UV += MathHelper.TwoPi;
if (point1UV - point2UV > MathHelper.Pi)
{
point1UV -= 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;
int textureRepeatCount = (int)Math.Max(circumference / 2 / level.GenerationParams.WallEdgeTextureWidth, 1);
point1UV = point1UV / MathHelper.TwoPi * textureRepeatCount;
point2UV = point2UV / MathHelper.TwoPi * textureRepeatCount;
for (int i = 0; i < 2; i++)
{
@@ -147,9 +161,9 @@ namespace Barotrauma
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));
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 0.0f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point1UV, 1.0f));
}
else
{
@@ -158,16 +172,16 @@ namespace Barotrauma
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));
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 1.0f));
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point2UV, 1.0f));
}
verticeList.AddRange(vertPos);
vertices.AddRange(vertPos);
}
}
}
return verticeList.ToArray();
return vertices;
}
}
}
@@ -0,0 +1,62 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
partial class DestructibleLevelWall : LevelWall, IDamageable
{
public override float Alpha
{
get
{
if (FadeOutDuration <= 0.0f || FadeOutTimer < FadeOutDuration - 1.0f) { return 1.0f; }
return MathHelper.Clamp(FadeOutDuration - FadeOutTimer, 0.0f, 1.0f);
}
}
partial void AddDamageProjSpecific(float damage, Vector2 worldPosition)
{
if (damage <= 0.0f) { return; }
Vector2 particlePos = worldPosition;
if (!Cells.Any(c => c.IsPointInside(particlePos)))
{
bool intersectionFound = false;
foreach (var cell in Cells)
{
foreach (var edge in cell.Edges)
{
if (MathUtils.GetLineIntersection(worldPosition, cell.Center, edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, out Vector2 intersection))
{
intersectionFound = true;
particlePos = intersection;
break;
}
}
if (intersectionFound) { break; }
}
}
Vector2 particleDir = particlePos - WorldPosition;
if (particleDir.LengthSquared() > 0.0001f) { particleDir = Vector2.Normalize(particleDir); }
int particleAmount = MathHelper.Clamp((int)damage, 1, 10);
for (int i = 0; i < particleAmount; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("iceshards",
particlePos + Rand.Vector(5.0f),
particleDir * Rand.Range(200.0f, 500.0f) + Rand.Vector(100.0f));
}
}
public void SetDamage(float damage)
{
Damage = damage;
if (Damage >= MaxHealth && !Destroyed)
{
CreateFragments();
Destroy();
}
}
}
}
@@ -14,6 +14,8 @@ namespace Barotrauma
private BackgroundCreatureManager backgroundCreatureManager;
public BackgroundCreatureManager BackgroundCreatureManager => backgroundCreatureManager;
public LevelRenderer Renderer => renderer;
public void ReloadTextures()
@@ -33,14 +35,6 @@ namespace Barotrauma
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)
@@ -51,7 +45,7 @@ namespace Barotrauma
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
if (renderer == null) return;
if (renderer == null) { return; }
renderer.Draw(spriteBatch, cam);
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
@@ -123,22 +117,34 @@ namespace Barotrauma
if (renderer == null) return;
renderer.DrawBackground(spriteBatch, cam, LevelObjectManager, backgroundCreatureManager);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
foreach (LevelWall levelWall in ExtraWalls)
bool isGlobalUpdate = msg.ReadBoolean();
if (isGlobalUpdate)
{
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)
foreach (LevelWall levelWall in ExtraWalls)
{
levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation);
if (levelWall.Body.BodyType == BodyType.Static) { continue; }
Vector2 bodyPos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
levelWall.MoveState = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 16);
DestructibleLevelWall destructibleWall = levelWall as DestructibleLevelWall;
if (Vector2.DistanceSquared(bodyPos, levelWall.Body.Position) > 0.5f && (destructibleWall == null || !destructibleWall.Destroyed))
{
levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation);
}
}
}
else
{
int index = msg.ReadUInt16();
byte damageByte = msg.ReadByte();
if (index < ExtraWalls.Count && ExtraWalls[index] is DestructibleLevelWall destructibleWall)
{
destructibleWall.SetDamage(destructibleWall.MaxHealth * damageByte / 255.0f);
}
}
}
@@ -1,14 +1,14 @@
using Barotrauma.Lights;
using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Barotrauma.Networking;
using Barotrauma.SpriteDeformations;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.SpriteDeformations;
using System.Linq;
using FarseerPhysics.Dynamics;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -74,10 +74,21 @@ namespace Barotrauma
private set;
}
public bool VisibleOnSonar
{
get;
private set;
}
public float SonarRadius
{
get;
private set;
}
partial void InitProjSpecific()
{
Sprite?.EnsureLazyLoaded();
SpecularSprite?.EnsureLazyLoaded();
Prefab.DeformableSprite?.EnsureLazyLoaded();
CurrentSwingAmount = Prefab.SwingAmountRad;
@@ -98,7 +109,7 @@ namespace Barotrauma
}
}
if (Prefab.LightSourceParams != null)
if (Prefab.LightSourceParams != null && Prefab.LightSourceParams.Count > 0)
{
LightSources = new LightSource[Prefab.LightSourceParams.Count];
LightSourceTriggers = new LevelTrigger[Prefab.LightSourceParams.Count];
@@ -138,6 +149,13 @@ namespace Barotrauma
}
}
}
VisibleOnSonar = Prefab.SonarDisruption > 0.0f || Prefab.OverrideProperties.Any(p => p != null && p.SonarDisruption > 0.0f) ||
(Triggers != null && Triggers.Any(t => !MathUtils.NearlyEqual(t.Force, Vector2.Zero) && t.ForceMode != LevelTrigger.TriggerForceMode.LimitVelocity || !string.IsNullOrWhiteSpace(t.InfectIdentifier)));
if (VisibleOnSonar && Triggers.Any())
{
SonarRadius = Triggers.Select(t => t.ColliderRadius * 1.5f).Max();
}
}
public void Update(float deltaTime)
@@ -232,17 +250,21 @@ namespace Barotrauma
deformation.Update(deltaTime);
}
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size);
foreach (LightSource lightSource in LightSources)
if (LightSources != null)
{
if (lightSource?.DeformableLightSprite != null)
foreach (LightSource lightSource in LightSources)
{
lightSource.DeformableLightSprite.Deform(CurrentSpriteDeformation);
if (lightSource?.DeformableLightSprite != null)
{
lightSource.DeformableLightSprite.Deform(CurrentSpriteDeformation);
}
}
}
}
private void UpdatePositionalDeformation(PositionalDeformation positionalDeformation, float deltaTime)
{
if (Triggers == null) { return; }
Matrix matrix = ActivePrefab.DeformableSprite.GetTransform(
Position,
ActivePrefab.DeformableSprite.Origin,
@@ -258,7 +280,7 @@ namespace Barotrauma
Vector2 moveAmount = triggerer.WorldPosition - trigger.TriggererPosition[triggerer];
moveAmount = Vector2.Transform(moveAmount, rotationMatrix);
moveAmount /= (ActivePrefab.DeformableSprite.Size * Scale);
moveAmount /= ActivePrefab.DeformableSprite.Size * Scale;
moveAmount.Y = -moveAmount.Y;
positionalDeformation.Deform(trigger.WorldPosition, moveAmount, deltaTime, Matrix.Invert(matrix) *
@@ -269,9 +291,10 @@ namespace Barotrauma
public void ClientRead(IReadMessage msg)
{
if (Triggers == null) { return; }
for (int i = 0; i < Triggers.Count; i++)
{
if (!Triggers[i].UseNetworkSyncing) continue;
if (!Triggers[i].UseNetworkSyncing) { continue; }
Triggers[i].ClientRead(msg);
}
}
@@ -12,6 +12,8 @@ namespace Barotrauma
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>();
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>();
private double NextRefreshTime;
//Maximum number of visible objects drawn at once. Should be large enough to not have an effect during normal gameplay,
//but small enough to prevent wrecking performance when zooming out very far
const int MaxVisibleObjects = 500;
@@ -38,11 +40,13 @@ namespace Barotrauma
/// <summary>
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
/// </summary>
private void RefreshVisibleObjects(Rectangle currentIndices)
private void RefreshVisibleObjects(Rectangle currentIndices, float zoom)
{
visibleObjectsBack.Clear();
visibleObjectsFront.Clear();
float minSizeToDraw = MathHelper.Lerp(10.0f, 5.0f, Math.Min(zoom * 20.0f, 1.0f));
for (int x = currentIndices.X; x <= currentIndices.Width; x++)
{
for (int y = currentIndices.Y; y <= currentIndices.Height; y++)
@@ -50,6 +54,22 @@ namespace Barotrauma
if (objectGrid[x, y] == null) { continue; }
foreach (LevelObject obj in objectGrid[x, y])
{
if (zoom < 0.05f)
{
//hide if the sprite is very small when zoomed this far out
if ((obj.Sprite != null && Math.Min(obj.Sprite.size.X * zoom, obj.Sprite.size.Y * zoom) < 5.0f) ||
(obj.ActivePrefab?.DeformableSprite != null && Math.Min(obj.ActivePrefab.DeformableSprite.Sprite.size.X * zoom, obj.ActivePrefab.DeformableSprite.Sprite.size.Y * zoom) < minSizeToDraw))
{
continue;
}
float zCutoff = MathHelper.Lerp(5000.0f, 500.0f, (0.05f - zoom) * 20.0f);
if (obj.Position.Z > zCutoff)
{
continue;
}
}
var objectList = obj.Position.Z >= 0 ? visibleObjectsBack : visibleObjectsFront;
int drawOrderIndex = 0;
for (int i = 0; i < objectList.Count; i++)
@@ -83,7 +103,7 @@ namespace Barotrauma
}
public void DrawObjects(SpriteBatch spriteBatch, Camera cam, bool drawFront, bool specular = false)
public void DrawObjects(SpriteBatch spriteBatch, Camera cam, bool drawFront)
{
Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
@@ -102,9 +122,14 @@ namespace Barotrauma
indices.Height = Math.Min(indices.Height, objectGrid.GetLength(1) - 1);
float z = 0.0f;
if (currentGridIndices != indices)
if (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime)
{
RefreshVisibleObjects(indices);
RefreshVisibleObjects(indices, cam.Zoom);
if (cam.Zoom < 0.1f)
{
//when zoomed very far out, refresh a little less often
NextRefreshTime = Timing.TotalTime + MathHelper.Lerp(1.0f, 0.0f, cam.Zoom * 10.0f);
}
}
var objectList = drawFront ? visibleObjectsFront : visibleObjectsBack;
@@ -113,19 +138,17 @@ namespace Barotrauma
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
camDiff.Y = -camDiff.Y;
Sprite activeSprite = specular ? obj.SpecularSprite : obj.Sprite;
Sprite activeSprite = 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),
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 3000.0f),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
SpriteEffects.None,
z);
if (specular) continue;
if (obj.ActivePrefab.DeformableSprite != null)
{
if (obj.CurrentSpriteDeformation != null)
@@ -149,6 +172,7 @@ namespace Barotrauma
{
GUI.DrawRectangle(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(10.0f, 10.0f), GUI.Style.Red, true);
if (obj.Triggers == null) { continue; }
foreach (LevelTrigger trigger in obj.Triggers)
{
if (trigger.PhysicsBody == null) continue;
@@ -126,7 +126,6 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "childobject":
case "lightsource":
subElement.Remove();
break;
case "deformablesprite":
@@ -141,11 +140,31 @@ namespace Barotrauma
}
}
foreach (LightSourceParams lightSourceParams in LightSourceParams)
for (int i = 0; i < LightSourceParams.Count; i++)
{
var lightElement = new XElement("LightSource");
SerializableProperty.SerializeProperties(lightSourceParams, lightElement);
element.Add(lightElement);
int elementIndex = 0;
bool wasSaved = false;
foreach (XElement subElement in element.Elements().ToList())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "lightsource":
if (elementIndex == i)
{
SerializableProperty.SerializeProperties(LightSourceParams[i], subElement);
wasSaved = true;
break;
}
elementIndex++;
break;
}
}
if (!wasSaved)
{
var lightElement = new XElement("LightSource");
SerializableProperty.SerializeProperties(LightSourceParams[i], lightElement);
element.Add(lightElement);
}
}
foreach (ChildObject childObj in ChildObjects)
@@ -162,7 +181,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("overridecommonness", System.StringComparison.OrdinalIgnoreCase)
&& subElement.GetAttributeString("leveltype", "") == overrideCommonness.Key)
&& subElement.GetAttributeString("leveltype", "").Equals(overrideCommonness.Key, System.StringComparison.OrdinalIgnoreCase))
{
subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture);
elementFound = true;
@@ -3,10 +3,68 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Voronoi2;
namespace Barotrauma
{
class LevelWallVertexBuffer : IDisposable
{
public VertexBuffer WallEdgeBuffer, WallBuffer;
public readonly Texture2D WallTexture, EdgeTexture;
private VertexPositionColorTexture[] wallVertices;
private VertexPositionColorTexture[] wallEdgeVertices;
public bool IsDisposed
{
get;
private set;
}
public LevelWallVertexBuffer(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
{
this.wallVertices = LevelRenderer.GetColoredVertices(wallVertices, color);
WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallVertices.Length, BufferUsage.WriteOnly);
WallBuffer.SetData(this.wallVertices);
WallTexture = wallTexture;
this.wallEdgeVertices = LevelRenderer.GetColoredVertices(wallEdgeVertices, color);
WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallEdgeVertices.Length, BufferUsage.WriteOnly);
WallEdgeBuffer.SetData(this.wallEdgeVertices);
EdgeTexture = edgeTexture;
}
public void Append(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Color color)
{
WallBuffer.Dispose();
WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallVertices.Length + wallVertices.Length, BufferUsage.WriteOnly);
int originalWallVertexCount = this.wallVertices.Length;
Array.Resize(ref this.wallVertices, originalWallVertexCount + wallVertices.Length);
Array.Copy(LevelRenderer.GetColoredVertices(wallVertices, color), 0, this.wallVertices, originalWallVertexCount, wallVertices.Length);
WallBuffer.SetData(this.wallVertices);
WallEdgeBuffer.Dispose();
WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallEdgeVertices.Length + wallEdgeVertices.Length, BufferUsage.WriteOnly);
int originalWallEdgeVertexCount = this.wallEdgeVertices.Length;
Array.Resize(ref this.wallEdgeVertices, originalWallEdgeVertexCount + wallEdgeVertices.Length);
Array.Copy(LevelRenderer.GetColoredVertices(wallEdgeVertices, color), 0, this.wallEdgeVertices, originalWallEdgeVertexCount, wallEdgeVertices.Length);
WallEdgeBuffer.SetData(this.wallEdgeVertices);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
IsDisposed = true;
WallEdgeBuffer?.Dispose();
WallBuffer?.Dispose();
}
}
class LevelRenderer : IDisposable
{
private static BasicEffect wallEdgeEffect, wallCenterEffect;
@@ -15,11 +73,11 @@ namespace Barotrauma
private Vector2 defaultDustVelocity;
private Vector2 dustVelocity;
private RasterizerState cullNone;
private readonly RasterizerState cullNone;
private Level level;
private readonly Level level;
private VertexBuffer wallVertices, bodyVertices;
private readonly List<LevelWallVertexBuffer> vertexBuffers = new List<LevelWallVertexBuffer>();
public LevelRenderer(Level level)
{
@@ -68,6 +126,7 @@ namespace Barotrauma
Vector2 currentDustVel = defaultDustVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
{
if (levelObject.Triggers == null) { continue; }
//use the largest water flow velocity of all the triggers
Vector2 objectMaxFlow = Vector2.Zero;
foreach (LevelTrigger trigger in levelObject.Triggers)
@@ -94,7 +153,6 @@ namespace Barotrauma
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)
@@ -107,38 +165,27 @@ namespace Barotrauma
return verts;
}
public void SetWallVertices(VertexPositionTexture[] vertices, Color color)
public void SetVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(GetColoredVertices(vertices, color));
var existingBuffer = vertexBuffers.Find(vb => vb.WallTexture == wallTexture && vb.EdgeTexture == edgeTexture);
if (existingBuffer != null)
{
existingBuffer.Append(wallVertices, wallEdgeVertices,color);
}
else
{
vertexBuffers.Add(new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, 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,
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;
@@ -173,10 +220,13 @@ namespace Barotrauma
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
cam.Transform);
if (backgroundSpriteManager != null) backgroundSpriteManager.DrawObjects(spriteBatch, cam, drawFront: false);
if (backgroundCreatureManager != null) backgroundCreatureManager.Draw(spriteBatch, cam);
backgroundSpriteManager?.DrawObjects(spriteBatch, cam, drawFront: false);
if (cam.Zoom > 0.05f)
{
backgroundCreatureManager?.Draw(spriteBatch, cam);
}
if (level.GenerationParams.WaterParticles != null)
if (level.GenerationParams.WaterParticles != null && cam.Zoom > 0.05f)
{
float textureScale = level.GenerationParams.WaterParticleScale;
@@ -216,7 +266,7 @@ namespace Barotrauma
spriteBatch.End();
RenderWalls(GameMain.Instance.GraphicsDevice, cam, specular: false);
RenderWalls(GameMain.Instance.GraphicsDevice, cam);
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
@@ -243,7 +293,8 @@ namespace Barotrauma
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);
new Vector2(edge.Point2.X + cell.Translation.X, -(edge.Point2.Y + cell.Translation.Y)), edge.NextToCave ? Color.Red : (cell.Body == null ? Color.Cyan * 0.5f : (edge.IsSolid ? Color.White : Color.Gray)),
width: edge.NextToCave ? 8 :1);
}
foreach (Vector2 point in cell.BodyVertices)
@@ -252,7 +303,7 @@ namespace Barotrauma
}
}
foreach (List<Point> nodeList in level.SmallTunnels)
/*foreach (List<Point> nodeList in level.SmallTunnels)
{
for (int i = 1; i < nodeList.Count; i++)
{
@@ -261,7 +312,7 @@ namespace Barotrauma
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)
{
@@ -270,108 +321,142 @@ namespace Barotrauma
}
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);
int topBarrierWidth = level.GenerationParams.WallEdgeSprite.Texture.Width;
int topBarrierHeight = level.GenerationParams.WallEdgeSprite.Texture.Height;
GUI.DrawRectangle(spriteBatch,new Rectangle(
(int)(MathUtils.Round(pos.X, 1024)),
-cam.WorldView.Y,
width,
(int)(cam.WorldView.Y + pos.Y) - 30),
pos.X = cam.WorldView.X - topBarrierWidth;
int width = (int)(Math.Ceiling(cam.WorldView.Width / 1024 + 4.0f) * topBarrierWidth);
GUI.DrawRectangle(spriteBatch, new Rectangle(
(int)MathUtils.Round(pos.X, topBarrierWidth),
-cam.WorldView.Y,
width,
(int)(cam.WorldView.Y + pos.Y) - 60),
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,
new Rectangle((int)MathUtils.Round(pos.X, topBarrierWidth), (int)(pos.Y - topBarrierHeight + level.GenerationParams.WallEdgeExpandOutwardsAmount), width, topBarrierHeight),
new Rectangle(0, 0, width, -topBarrierHeight),
GameMain.LightManager?.LightingEnabled ?? false ? GameMain.LightManager.AmbientLight : level.WallColor, 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);
int bottomBarrierWidth = level.GenerationParams.WallEdgeSprite.Texture.Width;
int bottomBarrierHeight = level.GenerationParams.WallEdgeSprite.Texture.Height;
pos = new Vector2(cam.WorldView.X - bottomBarrierWidth, -level.BottomPos);
int width = (int)(Math.Ceiling(cam.WorldView.Width / bottomBarrierWidth + 4.0f) * bottomBarrierWidth);
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))),
(int)(MathUtils.Round(pos.X, bottomBarrierWidth)),
-(level.BottomPos - 60),
width,
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,
new Rectangle((int)MathUtils.Round(pos.X, bottomBarrierWidth), -level.BottomPos - (int)level.GenerationParams.WallEdgeExpandOutwardsAmount, width, bottomBarrierHeight),
new Rectangle(0, 0, width, -bottomBarrierHeight),
GameMain.LightManager?.LightingEnabled ?? false ? GameMain.LightManager.AmbientLight : level.WallColor, 0.0f,
Vector2.Zero,
SpriteEffects.FlipVertically, 0.0f);
}
}
public void RenderWalls(GraphicsDevice graphicsDevice, Camera cam, bool specular)
public void RenderWalls(GraphicsDevice graphicsDevice, Camera cam)
{
if (wallVertices == null) return;
if (!vertexBuffers.Any()) { return; }
bool renderLevel = cam.WorldView.Y >= 0.0f;
bool renderSeaFloor = cam.WorldView.Y - cam.WorldView.Height < level.SeaFloorTopPos + 1024;
if (!renderLevel && !renderSeaFloor) return;
var defaultRasterizerState = graphicsDevice.RasterizerState;
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)
//render destructible walls
for (int i = 0; i < 2; i++)
{
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wallVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wallVertices.VertexCount / 3.0f));
var wallList = i == 0 ? level.ExtraWalls : level.UnsyncedExtraWalls;
foreach (LevelWall wall in wallList)
{
if (!(wall is DestructibleLevelWall destructibleWall) || destructibleWall.Destroyed) { continue; }
wallCenterEffect.Texture = level.GenerationParams.DestructibleWallSprite?.Texture ?? level.GenerationParams.WallSprite.Texture;
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.Alpha = wall.Alpha;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallBuffer.VertexCount / 3.0f));
if (destructibleWall.Damage > 0.0f)
{
wallCenterEffect.Texture = level.GenerationParams.WallSpriteDestroyed.Texture;
wallCenterEffect.Alpha = MathHelper.Lerp(0.2f, 1.0f, destructibleWall.Damage / destructibleWall.MaxHealth) * wall.Alpha;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallEdgeBuffer.VertexCount / 3.0f));
}
wallEdgeEffect.Texture = level.GenerationParams.DestructibleWallEdgeSprite?.Texture ?? level.GenerationParams.WallEdgeSprite.Texture;
wallEdgeEffect.World = wall.GetTransform() * transformMatrix;
wallEdgeEffect.Alpha = wall.Alpha;
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallEdgeBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallEdgeBuffer.VertexCount / 3.0f));
}
}
foreach (LevelWall wall in level.ExtraWalls)
wallEdgeEffect.Alpha = 1.0f;
wallCenterEffect.Alpha = 1.0f;
wallCenterEffect.World = transformMatrix;
wallEdgeEffect.World = transformMatrix;
//render static walls
foreach (var vertexBuffer in vertexBuffers)
{
if (!renderSeaFloor && wall == level.SeaFloor) continue;
wallEdgeEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.Texture = vertexBuffer.WallTexture;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(vertexBuffer.WallBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(vertexBuffer.WallBuffer.VertexCount / 3.0f));
wallEdgeEffect.Texture = vertexBuffer.EdgeTexture;
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallVertices);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallVertices.VertexCount / 3.0f));
graphicsDevice.SetVertexBuffer(vertexBuffer.WallEdgeBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(vertexBuffer.WallEdgeBuffer.VertexCount / 3.0f));
}
wallCenterEffect.Texture = level.GenerationParams.WallSprite.Texture;
wallEdgeEffect.Texture = level.GenerationParams.WallEdgeSprite.Texture;
//render non-destructible extra walls
for (int i = 0; i < 2; i++)
{
var wallList = i == 0 ? level.ExtraWalls : level.UnsyncedExtraWalls;
foreach (LevelWall wall in wallList)
{
if (wall is DestructibleLevelWall) { continue; }
//TODO: use LevelWallVertexBuffers for extra walls as well
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.Alpha = wall.Alpha;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallBuffer.VertexCount / 3.0f));
wallEdgeEffect.World = wall.GetTransform() * transformMatrix;
wallEdgeEffect.Alpha = wall.Alpha;
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(wall.WallEdgeBuffer);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wall.WallEdgeBuffer.VertexCount / 3.0f));
}
}
graphicsDevice.RasterizerState = defaultRasterizerState;
}
@@ -383,8 +468,11 @@ namespace Barotrauma
protected virtual void Dispose(bool disposing)
{
if (wallVertices != null) wallVertices.Dispose();
if (bodyVertices != null) bodyVertices.Dispose();
foreach (var vertexBuffer in vertexBuffers)
{
vertexBuffer.Dispose();
}
vertexBuffers.Clear();
}
}
}
@@ -2,55 +2,44 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
partial class LevelWall : IDisposable
{
private VertexBuffer wallVertices, bodyVertices;
public LevelWallVertexBuffer VertexBuffer { get; private set; }
public VertexBuffer WallVertices
{
get { return wallVertices; }
}
public VertexBuffer WallBuffer { get { return VertexBuffer.WallBuffer; } }
public VertexBuffer BodyVertices
{
get { return bodyVertices; }
}
public VertexBuffer WallEdgeBuffer { get { return VertexBuffer.WallEdgeBuffer; } }
public virtual float Alpha => 1.0f;
public Matrix GetTransform()
{
return body.BodyType == BodyType.Static ?
Matrix.Identity :
Matrix.CreateRotationZ(body.Rotation) *
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(body.Position), 0.0f));
return Body.FixedRotation ?
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(Body.Position), 0.0f)) :
Matrix.CreateRotationZ(Body.Rotation) *
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(Body.Position), 0.0f));
}
public void SetWallVertices(VertexPositionTexture[] vertices, Color color)
public void SetWallVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
{
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
wallVertices.SetData(LevelRenderer.GetColoredVertices(vertices, color));
if (VertexBuffer != null && !VertexBuffer.IsDisposed) { VertexBuffer.Dispose(); }
VertexBuffer = new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, color);
}
public void SetBodyVertices(VertexPositionTexture[] vertices, Color color)
public void GenerateVertices()
{
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);
float zCoord = this is DestructibleLevelWall ? Rand.Range(0.9f, 1.0f) : 0.9f;
List<VertexPositionTexture> wallVertices = CaveGenerator.GenerateWallVertices(triangles, level.GenerationParams, zCoord);
SetWallVertices(
wallVertices.ToArray(),
CaveGenerator.GenerateWallEdgeVertices(Cells, level, zCoord).ToArray(),
level.GenerationParams.WallSprite.Texture,
level.GenerationParams.WallEdgeSprite.Texture,
color);
}
}
}
@@ -132,7 +132,7 @@ namespace Barotrauma.Lights
public void AddLight(LightSource light)
{
if (!lights.Contains(light)) lights.Add(light);
if (!lights.Contains(light)) { lights.Add(light); }
}
public void RemoveLight(LightSource light)
@@ -151,7 +151,16 @@ namespace Barotrauma.Lights
private readonly List<LightSource> activeLights = new List<LightSource>(capacity: 100);
public void UpdateLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
public void Update(float deltaTime)
{
foreach (LightSource light in activeLights)
{
if (!light.Enabled) { continue; }
light.Update(deltaTime);
}
}
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) { return; }
@@ -203,9 +212,9 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
if (light.IsBackground) { continue; }
if (light.IsBackground || light.CurrentBrightness <= 0.0f) { 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); }
if (light.ParentBody?.UserData is Limb limb && !limb.Hide) { light.DrawSprite(spriteBatch, cam); }
}
spriteBatch.End();
@@ -215,9 +224,10 @@ namespace Barotrauma.Lights
graphics.Clear(AmbientLight);
graphics.BlendState = BlendState.Additive;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
Level.Loaded?.BackgroundCreatureManager?.DrawLights(spriteBatch, cam);
foreach (LightSource light in activeLights)
{
if (!light.IsBackground) { continue; }
if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform);
}
@@ -262,7 +272,7 @@ namespace Barotrauma.Lights
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; }
if (light.IsBackground || light.ParentBody?.UserData is Limb || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam);
}
spriteBatch.End();
@@ -327,7 +337,7 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights)
{
if (light.IsBackground) { continue; }
if (light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawLightVolume(spriteBatch, lightEffect, transform);
}
@@ -7,7 +7,6 @@ using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Lights
{
class LightSourceParams : ISerializableEntity
@@ -33,7 +32,7 @@ namespace Barotrauma.Lights
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
TextureRange = range;
if (OverrideLightTexture != null)
{
@@ -49,7 +48,63 @@ namespace Barotrauma.Lights
[Serialize("0, 0", true), Editable(ValueStep = 1, DecimalCount = 1, MinValueFloat = -1000f, MaxValueFloat = 1000f)]
public Vector2 Offset { get; set; }
[Serialize(0f, true), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)]
public float Rotation { get; set; }
public Vector2 GetOffset() => Vector2.Transform(Offset, Matrix.CreateRotationZ(Rotation));
private float flicker;
[Editable, Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
public float Flicker
{
get { return flicker; }
set
{
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
public float FlickerSpeed
{
get;
set;
}
private float pulseFrequency;
[Editable, Serialize(0.0f, true, description: "How rapidly the light pulsates (in Hz). 0 = no blinking.")]
public float PulseFrequency
{
get { return pulseFrequency; }
set
{
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
}
}
private float pulseAmount;
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, true, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
public float PulseAmount
{
get { return pulseAmount; }
set
{
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
private float blinkFrequency;
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
public float BlinkFrequency
{
get { return blinkFrequency; }
set
{
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
}
}
public float TextureRange
{
get;
@@ -88,6 +143,7 @@ namespace Barotrauma.Lights
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
case "lightsprite":
{
LightSprite = new Sprite(subElement);
float spriteAlpha = subElement.GetAttributeFloat("alpha", -1.0f);
@@ -144,6 +200,8 @@ namespace Barotrauma.Lights
private static Texture2D lightTexture;
private float blinkTimer, flickerState, pulseState;
private VertexPositionColorTexture[] vertices;
private short[] indices;
@@ -296,6 +354,12 @@ namespace Barotrauma.Lights
get { return lightSourceParams.Color; }
set { lightSourceParams.Color = value; }
}
public float CurrentBrightness
{
get;
private set;
}
public float Range
{
@@ -402,7 +466,33 @@ namespace Barotrauma.Lights
texture = LightTexture;
diffToSub = new Dictionary<Submarine, Vector2>();
if (addLight) { GameMain.LightManager.AddLight(this); }
}
public void Update(float deltaTime)
{
float brightness = 1.0f;
if (lightSourceParams.BlinkFrequency > 0.0f)
{
blinkTimer = (blinkTimer + deltaTime * lightSourceParams.BlinkFrequency) % 1.0f;
if (blinkTimer > 0.5f)
{
CurrentBrightness = 0.0f;
return;
}
}
if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f)
{
pulseState = (pulseState + deltaTime * lightSourceParams.PulseFrequency) % 1.0f;
//oscillate between 0-1
brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount;
}
if (lightSourceParams.Flicker > 0.0f)
{
flickerState += deltaTime * lightSourceParams.FlickerSpeed;
flickerState %= 255;
brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker;
}
CurrentBrightness = brightness;
}
/// <summary>
@@ -508,13 +598,12 @@ namespace Barotrauma.Lights
//recalculate vertices if the subs have moved > 5 px relative to each other
Vector2 diff = ParentSub.WorldPosition - sub.WorldPosition;
Vector2 prevDiff;
if (!diffToSub.TryGetValue(sub, out prevDiff))
if (!diffToSub.TryGetValue(sub, out Vector2 prevDiff))
{
diffToSub.Add(sub, diff);
NeedsRecalculation = true;
}
else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f*5.0f)
else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f * 5.0f)
{
diffToSub[sub] = diff;
NeedsRecalculation = true;
@@ -884,7 +973,12 @@ namespace Barotrauma.Lights
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTextureOrigin;
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; }
if (LightSpriteEffect == SpriteEffects.FlipHorizontally)
{
origin.X = OverrideLightTexture.SourceRect.Width - origin.X;
cosAngle = -cosAngle;
sinAngle = -sinAngle;
}
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
}
@@ -1021,7 +1115,7 @@ namespace Barotrauma.Lights
lightVolumeBuffer.SetData<VertexPositionColorTexture>(vertices, 0, vertexCount);
lightVolumeIndexBuffer.SetData<short>(indices, 0, indexCount);
Vector2 GetUV(Vector2 vert, SpriteEffects effects)
static Vector2 GetUV(Vector2 vert, SpriteEffects effects)
{
if (effects == SpriteEffects.FlipHorizontally)
{
@@ -1098,7 +1192,7 @@ namespace Barotrauma.Lights
if (DeformableLightSprite != null)
{
Vector2 origin = DeformableLightSprite.Origin + LightSourceParams.Offset;
Vector2 origin = DeformableLightSprite.Origin + LightSourceParams.GetOffset();
Vector2 drawPos = position;
if (ParentSub != null)
{
@@ -1115,14 +1209,14 @@ namespace Barotrauma.Lights
DeformableLightSprite.Draw(
cam, new Vector3(drawPos, 0.0f),
origin, -Rotation, SpriteScale,
new Color(Color, lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f),
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale,
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness),
LightSpriteEffect == SpriteEffects.FlipVertically);
}
if (LightSprite != null)
{
Vector2 origin = LightSprite.Origin + LightSourceParams.Offset;
Vector2 origin = LightSprite.Origin + LightSourceParams.GetOffset();
if ((LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{
origin.X = LightSprite.SourceRect.Width - origin.X;
@@ -1140,9 +1234,9 @@ namespace Barotrauma.Lights
drawPos.Y = -drawPos.Y;
LightSprite.Draw(
spriteBatch, drawPos,
new Color(Color, lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f),
origin, -Rotation, SpriteScale, LightSpriteEffect);
spriteBatch, drawPos,
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness),
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect);
}
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
@@ -1185,7 +1279,7 @@ namespace Barotrauma.Lights
public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform)
{
if (Range < 1.0f || Color.A < 1) { return; }
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
if (CastShadows)
{
@@ -1207,7 +1301,7 @@ namespace Barotrauma.Lights
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
drawPos.Y = -drawPos.Y;
spriteBatch.Draw(currentTexture, drawPos, null, Color, -rotation, center, scale, SpriteEffects.None, 1);
spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation, center, scale, SpriteEffects.None, 1);
return;
}
@@ -1229,7 +1323,7 @@ namespace Barotrauma.Lights
if (vertexCount == 0) { return; }
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f)) / 255.0f;
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f * CurrentBrightness)) / 255.0f;
if (OverrideLightTexture != null)
{
lightEffect.Texture = OverrideLightTexture.Texture;
@@ -63,6 +63,8 @@ namespace Barotrauma
private Sprite[,] mapTiles;
private bool[,] tileDiscovered;
private Pair<Rectangle, string> connectionTooltip;
#if DEBUG
private GUIComponent editor;
@@ -410,6 +412,8 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, GUICustomComponent mapContainer)
{
connectionTooltip = null;
Rectangle rect = mapContainer.Rect;
Vector2 viewSize = new Vector2(rect.Width / zoom, rect.Height / zoom);
@@ -639,6 +643,10 @@ namespace Barotrauma
{
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First);
}
if (connectionTooltip != null)
{
GUIComponent.DrawToolTip(spriteBatch, connectionTooltip.Second, connectionTooltip.First);
}
spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
@@ -689,12 +697,16 @@ namespace Barotrauma
int startIndex = connection.CrackSegments.Count > 2 ? 1 : 0;
int endIndex = connection.CrackSegments.Count > 2 ? connection.CrackSegments.Count - 1 : connection.CrackSegments.Count;
Vector2? connectionStart = null;
Vector2? connectionEnd = null;
for (int i = startIndex; i < endIndex; i++)
{
var segment = connection.CrackSegments[i];
Vector2 start = rectCenter + (segment[0] + viewOffset) * zoom;
Vector2 end = rectCenter + (segment[1] + viewOffset) * zoom;
if (!connectionStart.HasValue) { connectionStart = start; }
Vector2 end = rectCenter + (segment[1] + viewOffset) * zoom;
connectionEnd = end;
if (!viewArea.Contains(start) && !viewArea.Contains(end))
{
@@ -734,6 +746,59 @@ namespace Barotrauma
connectionSprite.SourceRect, connectionColor * a, MathUtils.VectorToAngle(end - start),
new Vector2(0, connectionSprite.size.Y / 2), SpriteEffects.None, 0.01f);
}
if (connectionStart.HasValue && connectionEnd.HasValue)
{
GUIComponentStyle crushDepthWarningIconStyle = null;
string tooltip = null;
var subCrushDepth = Submarine.MainSub?.RealWorldCrushDepth ?? Level.DefaultRealWorldCrushDepth;
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
{
var hullUpgradePrefab = UpgradePrefab.Find("increasewallhealth");
if (hullUpgradePrefab != null)
{
int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
int currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
if (pendingLevel > currentLevel)
{
string updateValueStr = hullUpgradePrefab.SourceElement?.Element("Structure")?.GetAttributeString("crushdepth", null);
if (!string.IsNullOrEmpty(updateValueStr))
{
subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel - currentLevel, updateValueStr);
}
}
}
}
if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
{
crushDepthWarningIconStyle = GUI.Style.GetComponentStyle("CrushDepthWarningHighIcon");
tooltip = "crushdepthwarninghigh";
}
else if ((connection.LevelData.InitialDepth + connection.LevelData.Size.Y) * Physics.DisplayToRealWorldRatio > subCrushDepth)
{
crushDepthWarningIconStyle = GUI.Style.GetComponentStyle("CrushDepthWarningLowIcon");
tooltip = "crushdepthwarninglow";
}
if (crushDepthWarningIconStyle != null)
{
Vector2 iconPos = (connectionStart.Value + connectionEnd.Value) / 2;
float iconSize = 32.0f * GUI.Scale;
bool mouseOn = HighlightedLocation == null && Vector2.DistanceSquared(iconPos, PlayerInput.MousePosition) < iconSize * iconSize;
Sprite crushDepthWarningIcon = crushDepthWarningIconStyle.GetDefaultSprite();
crushDepthWarningIcon.Draw(spriteBatch, iconPos,
mouseOn ? crushDepthWarningIconStyle.HoverColor : crushDepthWarningIconStyle.Color,
scale: iconSize / crushDepthWarningIcon.size.X);
if (mouseOn)
{
connectionTooltip = new Pair<Rectangle, string>(
new Rectangle(iconPos.ToPoint(), new Point((int)iconSize)),
TextManager.Get(tooltip)
.Replace("[initialdepth]", ((int)(connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio)).ToString())
.Replace("[submarinecrushdepth]", ((int)subCrushDepth).ToString()));
}
}
}
if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames)
{
@@ -114,6 +114,18 @@ namespace Barotrauma
public MapEntity ReplacedBy;
public virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { }
/// <summary>
/// A method that modifies the draw depth to prevent z-fighting between entities with the same sprite depth
/// </summary>
public float GetDrawDepth(float baseDepth, Sprite sprite)
{
float depth = baseDepth
//take texture into account to get entities with (roughly) the same base depth and texture to render consecutively to minimize texture swaps
+ (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.00001f
+ ID % 100 * 0.000001f;
return Math.Min(depth, 1.0f);
}
/// <summary>
/// Update the selection logic in submarine editor
@@ -329,8 +341,8 @@ namespace Barotrauma
{
var clones = Clone(selectedList).Where(c => c != null).ToList();
selectedList = clones;
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false));
selectedList.ForEach(c => c.Move(moveAmount));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false));
}
else // move
{
@@ -897,10 +909,10 @@ namespace Barotrauma
{
if (entities.Count == 0) { return; }
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(entities), true));
CopyEntities(entities);
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(entities), true));
entities.ForEach(e => { if (!e.Removed) { e.Remove(); } });
entities.Clear();
}
@@ -913,7 +925,6 @@ namespace Barotrauma
Clone(copiedList);
var clones = mapEntityList.Except(prevEntities).ToList();
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false));
var nonWireClones = clones.Where(c => !(c is Item item) || item.GetComponent<Wire>() == null);
if (!nonWireClones.Any()) { nonWireClones = clones; }
@@ -929,6 +940,8 @@ namespace Barotrauma
clone.Move(moveAmount);
clone.Submarine = Submarine.MainSub;
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false));
}
/// <summary>
@@ -222,9 +222,7 @@ namespace Barotrauma
public float GetDrawDepth()
{
float depth = SpriteDepthOverrideIsSet ? SpriteOverrideDepth : prefab.sprite.Depth;
depth -= (ID % 255) * 0.000001f;
return depth;
return GetDrawDepth(SpriteDepthOverrideIsSet ? SpriteOverrideDepth : prefab.sprite.Depth, prefab.sprite);
}
private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null)
@@ -238,6 +236,7 @@ namespace Barotrauma
else if (HiddenInGame) { return; }
Color color = IsHighlighted ? GUI.Style.Orange : spriteColor;
if (IsSelected && editing)
{
//color = Color.Lerp(color, Color.Gold, 0.5f);
@@ -253,15 +252,19 @@ namespace Barotrauma
thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
}
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
if (isWiringMode) { color *= 0.15f; }
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 (FlippedX) { textureOffset.X = -textureOffset.X; }
if (FlippedY) { textureOffset.Y = -textureOffset.Y; }
if (back && damageEffect == null)
if (back && damageEffect == null && !isWiringMode)
{
if (Prefab.BackgroundSprite != null)
{
@@ -299,7 +302,7 @@ namespace Barotrauma
color: Prefab.BackgroundSpriteColor,
textureScale: TextureScale * Scale,
startOffset: backGroundOffset,
depth: Math.Max(Prefab.BackgroundSprite.Depth + (ID % 255) * 0.000001f, depth + 0.000001f));
depth: Math.Max(GetDrawDepth(Prefab.BackgroundSprite.Depth, Prefab.BackgroundSprite), depth + 0.000001f));
if (UseDropShadow)
{
@@ -324,6 +327,7 @@ namespace Barotrauma
for (int i = 0; i < Sections.Length; i++)
{
Rectangle drawSection = Sections[i].rect;
if (damageEffect != null)
{
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
@@ -340,21 +344,30 @@ namespace Barotrauma
Submarine.DamageEffectColor = color;
}
}
if (!HasDamage && i == 0)
{
drawSection = new Rectangle(
drawSection.X,
drawSection.Y,
Sections[Sections.Length -1 ].rect.Right - drawSection.X,
drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height));
i = Sections.Length;
}
Vector2 sectionOffset = new Vector2(
Math.Abs(rect.Location.X - Sections[i].rect.Location.X),
Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y));
Math.Abs(rect.Location.X - drawSection.Location.X),
Math.Abs(rect.Location.Y - drawSection.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);
if (FlippedX && IsHorizontal) { sectionOffset.X = drawSection.Right - rect.Right; }
if (FlippedY && !IsHorizontal) { sectionOffset.Y = (rect.Y - rect.Height) - (drawSection.Y - drawSection.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),
new Vector2(drawSection.X + drawOffset.X, -(drawSection.Y + drawOffset.Y)),
new Vector2(drawSection.Width, drawSection.Height),
color: color,
startOffset: sectionOffset,
depth: depth,
@@ -394,12 +394,10 @@ namespace Barotrauma
float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
GUI.DrawLine(spriteBatch,
horizontalLine,
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,
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);
@@ -10,10 +10,7 @@ namespace Barotrauma
{
if (GameMain.Client == null) { return; }
Vector2 newVelocity = Body.LinearVelocity;
Vector2 newPosition = Body.SimPosition;
Body.CorrectPosition(positionBuffer, out newPosition, out newVelocity, out _, out _);
Body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out _, out _);
Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition);
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity) || moveAmount.LengthSquared() < 0.0001f)
@@ -24,12 +21,12 @@ namespace Barotrauma
List<Submarine> subsToMove = submarine.GetConnectedSubs();
foreach (Submarine dockedSub in subsToMove)
{
if (dockedSub == submarine) continue;
if (dockedSub == submarine) { continue; }
//clear the position buffer of the docked subs to prevent unnecessary position corrections
dockedSub.SubBody.positionBuffer.Clear();
}
Submarine closestSub = null;
Submarine closestSub;
if (Character.Controlled == null)
{
closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.Position);
@@ -63,5 +60,33 @@ namespace Barotrauma
if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount;
}
}
private void PlayDamageSounds(Dictionary<Structure, float> damagedStructures, Vector2 impactSimPos, float impact, string soundTag)
{
if (impact < MinCollisionImpact) { return; }
//play a damage sound for the structure that took the most damage
float maxDamage = 0.0f;
Structure maxDamageStructure = null;
foreach (KeyValuePair<Structure, float> structureDamage in damagedStructures)
{
if (maxDamageStructure == null || structureDamage.Value > maxDamage)
{
maxDamage = structureDamage.Value;
maxDamageStructure = structureDamage.Key;
}
}
if (maxDamageStructure != null)
{
SoundPlayer.PlayDamageSound(
soundTag,
impact * 10.0f,
ConvertUnits.ToDisplayUnits(impactSimPos),
MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f),
maxDamageStructure.Tags);
}
}
}
}