diff --git a/Barotrauma/BarotraumaClient/BarotraumaClient.csproj b/Barotrauma/BarotraumaClient/BarotraumaClient.csproj index f845f84d6..5fe307587 100644 --- a/Barotrauma/BarotraumaClient/BarotraumaClient.csproj +++ b/Barotrauma/BarotraumaClient/BarotraumaClient.csproj @@ -184,6 +184,9 @@ + + + diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index b39f450a8..a22e302fa 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -42,6 +42,7 @@ namespace Barotrauma public static NetworkMember NetworkMember; public static ParticleManager ParticleManager; + public static DecalManager DecalManager; public static World World; @@ -274,6 +275,7 @@ namespace Barotrauma yield return CoroutineStatus.Running; ParticleManager = new ParticleManager("Content/Particles/ParticlePrefabs.xml", GameScreen.Cam); + DecalManager = new DecalManager("Content/Particles/DecalPrefabs.xml"); yield return CoroutineStatus.Running; LocationType.Init(); diff --git a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs index e5255d49b..ee0f53fed 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Hull.cs @@ -1,19 +1,41 @@ using Barotrauma.Networking; +using Barotrauma.Particles; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma { partial class Hull : MapEntity, IPropertyObject, IServerSerializable { + public const int MaxDecalsPerHull = 10; + public static WaterRenderer renderer; + private List decals = new List(); + private Sound currentFlowSound; private int soundIndex; private float soundVolume; + public override bool DrawBelowWater + { + get + { + return decals.Count > 0; + } + } + + public override bool DrawOverWater + { + get + { + return true; + } + } + public override bool IsMouseOn(Vector2 position) { if (!GameMain.DebugDraw && !ShowHulls) return false; @@ -22,9 +44,169 @@ namespace Barotrauma !Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -8), position)); } + public void AddDecal(string decalName, Vector2 position, float scale) + { + if (decals.Count >= MaxDecalsPerHull) return; + + var decal = GameMain.DecalManager.CreateDecal(decalName, scale, position, this); + + if (decal != null) + { + decals.Add(decal); + } + } + + partial void UpdateProjSpecific(float deltaTime, Camera cam) + { + if (EditWater) + { + Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); + if (Submarine.RectContains(WorldRect, position)) + { + if (PlayerInput.LeftButtonHeld()) + { + //waveY[GetWaveIndex(position.X - rect.X - Submarine.Position.X) / WaveWidth] = 100.0f; + Volume = Volume + 1500.0f; + } + else if (PlayerInput.RightButtonHeld()) + { + Volume = Volume - 1500.0f; + } + } + } + else if (EditFire) + { + Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); + if (Submarine.RectContains(WorldRect, position)) + { + if (PlayerInput.LeftButtonClicked()) + { + new FireSource(position, this); + } + } + } + + foreach (Decal decal in decals) + { + decal.Update(deltaTime); + } + + decals.RemoveAll(d => d.FadeTimer >= d.LifeTime); + + float strongestFlow = 0.0f; + foreach (Gap gap in ConnectedGaps) + { + if (gap.IsRoomToRoom) + { + //only the first linked hull plays the flow sound + if (gap.linkedTo[1] == this) continue; + } + + float gapFlow = gap.LerpedFlowForce.Length(); + + if (gapFlow > strongestFlow) + { + strongestFlow = gapFlow; + } + } + + if (strongestFlow > 1.0f) + { + soundVolume = soundVolume + ((strongestFlow < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f); + soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f); + + int index = (int)Math.Floor(strongestFlow / 100.0f); + index = Math.Min(index, 2); + + var flowSound = SoundPlayer.flowSounds[index]; + if (flowSound != currentFlowSound && soundIndex > -1) + { + Sounds.SoundManager.Stop(soundIndex); + currentFlowSound = null; + soundIndex = -1; + } + + currentFlowSound = flowSound; + soundIndex = currentFlowSound.Loop(soundIndex, soundVolume, WorldPosition, Math.Min(strongestFlow * 5.0f, 2000.0f)); + } + else + { + if (soundIndex > -1) + { + Sounds.SoundManager.Stop(soundIndex); + currentFlowSound = null; + soundIndex = -1; + } + } + + for (int i = 0; i < waveY.Length; i++) + { + float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i])); + if (maxDelta > Rand.Range(1.0f, 10.0f)) + { + var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]); + if (Submarine != null) particlePos += Submarine.Position; + + GameMain.ParticleManager.CreateParticle("mist", + particlePos, + new Vector2(0.0f, -50.0f), 0.0f, this); + } + } + } + + private void DrawDecals(SpriteBatch spriteBatch) + { + Rectangle hullDrawRect = rect; + if (Submarine != null) hullDrawRect.Location += Submarine.DrawPosition.ToPoint(); + + + + foreach (Decal d in decals) + { + d.Draw(spriteBatch, this); + continue; + + Vector2 drawPos = d.Position + rect.Location.ToVector2(); + + Rectangle drawRect = new Rectangle( + (int)(drawPos.X - d.Sprite.size.X / 2), + (int)(drawPos.Y + d.Sprite.size.Y / 2), + (int)d.Sprite.size.X, + (int)d.Sprite.size.Y); + + Rectangle overFlowAmount = new Rectangle( + (int)Math.Max(hullDrawRect.X - drawRect.X, 0.0f), + (int)Math.Max(drawRect.Y - hullDrawRect.Y, 0.0f), + (int)Math.Max(drawRect.Right - hullDrawRect.Right, 0.0f), + (int)Math.Max((hullDrawRect.Y - hullDrawRect.Height) - (drawRect.Y - drawRect.Height), 0.0f)); + + var clippedSourceRect = new Rectangle(d.Sprite.SourceRect.X + overFlowAmount.X, + d.Sprite.SourceRect.Y + overFlowAmount.Y, + d.Sprite.SourceRect.Width - overFlowAmount.X - overFlowAmount.Width, + d.Sprite.SourceRect.Height - overFlowAmount.Y - overFlowAmount.Height); + + drawRect = new Rectangle( + drawRect.X + overFlowAmount.X, + drawRect.Y - overFlowAmount.Y, + drawRect.Width - overFlowAmount.X - overFlowAmount.Width, + drawRect.Height - overFlowAmount.Y - overFlowAmount.Height); + + drawPos.Y = -drawPos.Y; + drawRect.Y = -drawRect.Y; + GUI.DrawRectangle(spriteBatch, drawRect, Color.Red); + GUI.DrawRectangle(spriteBatch, drawPos, Vector2.One * 3, Color.Red); + spriteBatch.Draw(d.Sprite.Texture, drawRect, clippedSourceRect, d.Color, 0, Vector2.Zero, SpriteEffects.None, 1.0f); + } + } + public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { - //if (back) return; + if (back) + { + DrawDecals(spriteBatch); + return; + } + Rectangle drawRect; if (!Visible) { diff --git a/Barotrauma/BarotraumaClient/Source/Particles/Decal.cs b/Barotrauma/BarotraumaClient/Source/Particles/Decal.cs new file mode 100644 index 000000000..ea0907b45 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/Particles/Decal.cs @@ -0,0 +1,89 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Barotrauma.Particles +{ + class Decal + { + public readonly DecalPrefab Prefab; + public Vector2 Position; + + public readonly Sprite Sprite; + + private float fadeTimer; + + public Color Color + { + get { return Prefab.Color; } + } + + public float FadeTimer + { + get { return fadeTimer; } + } + + public float LifeTime + { + get { return Prefab.LifeTime; } + } + + private float scale; + + private Rectangle clippedSourceRect; + + public Decal(DecalPrefab prefab, float scale, Vector2 worldPosition, Hull hull) + { + Prefab = prefab; + + //transform to hull-relative coordinates so we don't have to worry about the hull moving + Position = worldPosition - hull.WorldRect.Location.ToVector2(); + + Vector2 drawPos = Position + hull.Rect.Location.ToVector2(); + + Sprite = prefab.Sprites[Rand.Range(0, prefab.Sprites.Count, Rand.RandSync.Unsynced)]; + + Rectangle drawRect = new Rectangle( + (int)(drawPos.X - Sprite.size.X / 2 * scale), + (int)(drawPos.Y + Sprite.size.Y / 2 * scale), + (int)(Sprite.size.X * scale), + (int)(Sprite.size.Y * scale)); + + Rectangle overFlowAmount = new Rectangle( + (int)Math.Max(hull.Rect.X - drawRect.X, 0.0f), + (int)Math.Max(drawRect.Y - hull.Rect.Y, 0.0f), + (int)Math.Max(drawRect.Right - hull.Rect.Right, 0.0f), + (int)Math.Max((hull.Rect.Y - hull.Rect.Height) - (drawRect.Y - drawRect.Height), 0.0f)); + + clippedSourceRect = new Rectangle( + Sprite.SourceRect.X + (int)(overFlowAmount.X / scale), + Sprite.SourceRect.Y + (int)(overFlowAmount.Y / scale), + Sprite.SourceRect.Width - (int)((overFlowAmount.X + overFlowAmount.Width) / scale), + Sprite.SourceRect.Height - (int)((overFlowAmount.Y + overFlowAmount.Height) / scale)); + + Position -= new Vector2(Sprite.size.X / 2 * scale - overFlowAmount.X, -Sprite.size.Y / 2 * scale + overFlowAmount.Y); + + this.scale = scale; + } + + public void Update(float deltaTime) + { + fadeTimer += deltaTime; + } + + public void Draw(SpriteBatch spriteBatch, Hull hull) + { + Vector2 drawPos = Position + hull.Rect.Location.ToVector2(); + drawPos += hull.Submarine.DrawPosition; + drawPos.Y = -drawPos.Y; + + float a = 1.0f; + if (fadeTimer > Prefab.LifeTime - Prefab.FadeTime) + { + a = (Prefab.LifeTime - fadeTimer) / Prefab.FadeTime; + } + + spriteBatch.Draw(Sprite.Texture, drawPos, clippedSourceRect, Color * a, 0, Vector2.Zero , scale, SpriteEffects.None, 1); + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/Particles/DecalManager.cs b/Barotrauma/BarotraumaClient/Source/Particles/DecalManager.cs new file mode 100644 index 000000000..61c2b2e14 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/Particles/DecalManager.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Particles +{ + class DecalManager + { + private Dictionary prefabs; + + public DecalManager(string configFile) + { + XDocument doc = ToolBox.TryLoadXml(configFile); + if (doc == null || doc.Root == null) return; + + prefabs = new Dictionary(); + + foreach (XElement element in doc.Root.Elements()) + { + if (prefabs.ContainsKey(element.Name.ToString())) + { + DebugConsole.ThrowError("Error in " + configFile + "! Each decal prefab must have a unique name."); + continue; + } + prefabs.Add(element.Name.ToString(), new DecalPrefab(element)); + } + } + + public Decal CreateDecal(string decalName, float scale, Vector2 worldPosition, Hull hull) + { + DecalPrefab prefab; + prefabs.TryGetValue(decalName, out prefab); + + if (prefab == null) + { + DebugConsole.ThrowError("Decal prefab " + decalName + " not found!"); + return null; + } + + return new Decal(prefab, scale, worldPosition, hull); + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/Particles/DecalPrefab.cs b/Barotrauma/BarotraumaClient/Source/Particles/DecalPrefab.cs new file mode 100644 index 000000000..9ee8d0e08 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/Particles/DecalPrefab.cs @@ -0,0 +1,39 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Particles +{ + class DecalPrefab + { + public readonly string Name; + + public readonly List Sprites; + + public readonly Color Color; + + public readonly float LifeTime; + public readonly float FadeTime; + + public DecalPrefab(XElement element) + { + Name = element.Name.ToString(); + + Sprites = new List(); + + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() == "sprite") + { + Sprites.Add(new Sprite(subElement)); + } + } + + Color = new Color(ToolBox.GetAttributeVector4(element, "color", Vector4.One)); + + LifeTime = ToolBox.GetAttributeFloat(element, "lifetime", 10.0f); + FadeTime = Math.Min(LifeTime, ToolBox.GetAttributeFloat(element, "fadetime", 1.0f)); + } + } +} diff --git a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems index f4ba0d930..9c6b52de7 100644 --- a/Barotrauma/BarotraumaShared/BarotraumaShared.projitems +++ b/Barotrauma/BarotraumaShared/BarotraumaShared.projitems @@ -616,6 +616,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Content/Particles/DecalPrefabs.xml b/Barotrauma/BarotraumaShared/Content/Particles/DecalPrefabs.xml new file mode 100644 index 000000000..57459dc8a --- /dev/null +++ b/Barotrauma/BarotraumaShared/Content/Particles/DecalPrefabs.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 1c5da477e..045877ef9 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1511,6 +1511,13 @@ namespace Barotrauma { if (joint.CanBeSevered && (joint.LimbA == limbHit || joint.LimbB == limbHit)) { +#if CLIENT + if (CurrentHull != null) + { + CurrentHull.AddDecal("blood", WorldPosition, Rand.Range(0.5f, 1.5f)); + } +#endif + AnimController.SeverLimbJoint(joint); if (joint.LimbA == limbHit) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs index cec973422..ea63b8483 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs @@ -394,6 +394,11 @@ namespace Barotrauma } } + if (bloodParticleAmount > 0 && character.CurrentHull != null) + { + character.CurrentHull.AddDecal("blood", WorldPosition, MathHelper.Clamp(bloodParticleSize, 0.5f, 1.0f)); + } + #endif damage += Math.Max(amount,bleedingAmount) / character.MaxHealth * 100.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 23f4fed3a..0e9e56fd7 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -27,7 +27,7 @@ namespace Barotrauma public static bool EditWater, EditFire; private List fireSources; - + public const float OxygenDistributionSpeed = 500.0f; public const float OxygenDetoriationSpeed = 0.3f; public const float OxygenConsumptionSpeed = 1000.0f; @@ -392,89 +392,15 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam) { - Oxygen -= OxygenDetoriationSpeed * deltaTime; + UpdateProjSpecific(deltaTime, cam); - if (EditWater) - { - Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); - if (Submarine.RectContains(WorldRect, position)) - { - if (PlayerInput.LeftButtonHeld()) - { - //waveY[GetWaveIndex(position.X - rect.X - Submarine.Position.X) / WaveWidth] = 100.0f; - Volume = Volume + 1500.0f; - } - else if (PlayerInput.RightButtonHeld()) - { - Volume = Volume - 1500.0f; - } - } - } - else if (EditFire) - { - Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); - if (Submarine.RectContains(WorldRect, position)) - { - if (PlayerInput.LeftButtonClicked()) - { - new FireSource(position, this); - } - } - } + Oxygen -= OxygenDetoriationSpeed * deltaTime; FireSource.UpdateAll(fireSources, deltaTime); aiTarget.SightRange = Submarine == null ? 0.0f : Math.Max(Submarine.Velocity.Length() * 500.0f, 500.0f); aiTarget.SoundRange -= deltaTime * 1000.0f; - - float strongestFlow = 0.0f; - foreach (Gap gap in ConnectedGaps) - { - if (gap.IsRoomToRoom) - { - //only the first linked hull plays the flow sound - if (gap.linkedTo[1] == this) continue; - } - - float gapFlow = gap.LerpedFlowForce.Length(); - - if (gapFlow > strongestFlow) - { - strongestFlow = gapFlow; - } - } - -#if CLIENT - if (strongestFlow > 1.0f) - { - soundVolume = soundVolume + ((strongestFlow < 100.0f) ? -deltaTime * 0.5f : deltaTime * 0.5f); - soundVolume = MathHelper.Clamp(soundVolume, 0.0f, 1.0f); - - int index = (int)Math.Floor(strongestFlow / 100.0f); - index = Math.Min(index, 2); - - var flowSound = SoundPlayer.flowSounds[index]; - if (flowSound != currentFlowSound && soundIndex > -1) - { - Sounds.SoundManager.Stop(soundIndex); - currentFlowSound = null; - soundIndex = -1; - } - - currentFlowSound = flowSound; - soundIndex = currentFlowSound.Loop(soundIndex, soundVolume, WorldPosition, Math.Min(strongestFlow*5.0f, 2000.0f)); - } - else - { - if (soundIndex > -1) - { - Sounds.SoundManager.Stop(soundIndex); - currentFlowSound = null; - soundIndex = -1; - } - } -#endif - + //update client hulls if the amount of water has changed by >10% //or if oxygen percentage has changed by 5% if (Math.Abs(lastSentVolume - volume) > FullVolume * 0.1f || @@ -499,23 +425,9 @@ namespace Barotrauma return; } - float surfaceY = rect.Y - rect.Height + Volume / rect.Width; for (int i = 0; i < waveY.Length; i++) { - float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i])); -#if CLIENT - if (maxDelta > Rand.Range(1.0f,10.0f)) - { - var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]); - if (Submarine != null) particlePos += Submarine.Position; - - GameMain.ParticleManager.CreateParticle("mist", - particlePos, - new Vector2(0.0f, -50.0f), 0.0f, this); - } -#endif - waveY[i] = waveY[i] + waveVel[i]; if (surfaceY + waveY[i] > rect.Y) @@ -572,6 +484,8 @@ namespace Barotrauma } } + partial void UpdateProjSpecific(float deltaTime, Camera cam); + public void ApplyFlowForces(float deltaTime, Item item) { foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))