using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Linq; namespace Barotrauma { partial class Gap : MapEntity { private float particleTimer; public override bool SelectableInEditor { get { return ShowGaps && SubEditorScreen.IsLayerVisible(this); } } public override bool IsVisible(Rectangle worldView) { if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) { return false; } return base.IsVisible(worldView); } public override void Draw(SpriteBatch sb, bool editing, bool back = true) { float depth = (ID % 255) * 0.000001f; if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) { if (FlowTargetHull != null) { DrawArrow(FlowTargetHull, IsHorizontal ? rect.Height: rect.Width, Math.Abs(lerpedFlowForce.Length()), Color.Red * 0.3f); } if (Submarine != null && outsideCollisionBlocker != null && outsideCollisionBlocker.Enabled) { var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape; Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + Submarine.Position; Vector2 endPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex2)) + Submarine.Position; startPos.Y = -startPos.Y; endPos.Y = -endPos.Y; GUI.DrawLine(sb, startPos, endPos, Color.Gray, 0, 5); } } if (!editing || !ShowGaps || !SubEditorScreen.IsLayerVisible(this)) { return; } Color clr = (open == 0.0f) ? GUIStyle.Red : Color.Cyan; if (IsHighlighted) { clr = Color.Gold; } GUI.DrawRectangle( sb, new Rectangle(WorldRect.X, -WorldRect.Y, rect.Width, rect.Height), clr * 0.2f, true, depth); int lineWidth = 5; if (IsHorizontal) { GUI.DrawLine(sb, new Vector2(WorldRect.X, -WorldRect.Y + lineWidth / 2), new Vector2(WorldRect.Right, -WorldRect.Y + lineWidth / 2), clr * 0.6f, width: lineWidth); GUI.DrawLine(sb, new Vector2(WorldRect.X, -WorldRect.Y + rect.Height - lineWidth / 2), new Vector2(WorldRect.Right, -WorldRect.Y + rect.Height - lineWidth / 2), clr * 0.6f, width: lineWidth); } else { GUI.DrawLine(sb, new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y), new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y + rect.Height), clr * 0.6f, width: lineWidth); GUI.DrawLine(sb, new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y), new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y + rect.Height), clr * 0.6f, width: lineWidth); } if (linkedTo.Count != 2 || linkedTo[0] != linkedTo[1]) { for (int i = 0; i < linkedTo.Count; i++) { if (linkedTo[i] is Hull hull) { DrawArrow(hull, 32.0f, 15f, clr); } } } void DrawArrow(Hull targetHull, float arrowWidth, float arrowLength, Color clr) { Vector2 dir = IsHorizontal ? new Vector2(Math.Sign(targetHull.Rect.Center.X - rect.Center.X), 0.0f) : new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (targetHull.Rect.Y - targetHull.Rect.Height / 2.0f))); Vector2 arrowPos = new Vector2(WorldRect.Center.X, WorldRect.Y - WorldRect.Height / 2); if (Submarine != null) { arrowPos += (Submarine.DrawPosition - Submarine.Position); } arrowPos.Y = -arrowPos.Y; arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2)); bool invalidDir = false; if (dir == Vector2.Zero) { invalidDir = true; dir = IsHorizontal ? Vector2.UnitX : Vector2.UnitY; } GUI.Arrow.Draw(sb, arrowPos, invalidDir ? Color.Red : clr * 0.8f, GUI.Arrow.Origin, MathUtils.VectorToAngle(dir) + MathHelper.PiOver2, IsHorizontal ? new Vector2(Math.Min(rect.Height, arrowWidth) / GUI.Arrow.size.X, arrowLength / GUI.Arrow.size.Y) : new Vector2(Math.Min(rect.Width, arrowWidth) / GUI.Arrow.size.X, arrowLength / GUI.Arrow.size.Y), SpriteEffects.None, depth); } if (IsSelected) { GUI.DrawRectangle(sb, new Vector2(WorldRect.X - 5, -WorldRect.Y - 5), new Vector2(rect.Width + 10, rect.Height + 10), GUIStyle.Red, false, depth, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); } } partial void EmitParticles(float deltaTime) { if (flowTargetHull == null) { return; } if (linkedTo.Count == 2 && linkedTo[0] is Hull hull1 && linkedTo[1] is Hull hull2) { //no flow particles between linked hulls (= rooms consisting of multiple hulls) if (hull1.linkedTo.Contains(hull2)) { return; } foreach (var linkedEntity in hull1.linkedTo) { if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; } } foreach (var linkedEntity in hull2.linkedTo) { if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; } } } Vector2 pos = Position; if (IsHorizontal) { pos.X += Math.Sign(flowForce.X); pos.Y = MathHelper.Clamp(Rand.Range(higherSurface, lowerSurface), rect.Y - rect.Height, rect.Y); } if (flowTargetHull != null) { pos.X = MathHelper.Clamp(pos.X, flowTargetHull.Rect.X + 1, flowTargetHull.Rect.Right - 1); pos.Y = MathHelper.Clamp(pos.Y, flowTargetHull.Rect.Y - flowTargetHull.Rect.Height + 1, flowTargetHull.Rect.Y - 1); } //spawn less particles when there's already a large number of them float particleAmountMultiplier = 1.0f - GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles; particleAmountMultiplier *= particleAmountMultiplier; //heavy flow -> strong waterfall type of particles if (LerpedFlowForce.LengthSquared() > 20000.0f) { particleTimer += deltaTime; if (IsHorizontal) { float particlesPerSec = open * rect.Height * 0.1f * particleAmountMultiplier; if (openedTimer > 0.0f) { particlesPerSec *= 1.0f + openedTimer * 10.0f; } float emitInterval = 1.0f / particlesPerSec; while (particleTimer > emitInterval) { Vector2 velocity = new Vector2( MathHelper.Clamp(flowForce.X, -5000.0f, 5000.0f) * Rand.Range(0.5f, 0.7f), flowForce.Y * Rand.Range(0.5f, 0.7f)); if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f) { var particle = GameMain.ParticleManager.CreateParticle( "watersplash", (Submarine == null ? pos : pos + Submarine.Position) - Vector2.UnitY * Rand.Range(0.0f, 10.0f), velocity, 0, flowTargetHull); if (particle != null) { if (particle.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(particle); } particle.Size *= Math.Min(Math.Abs(flowForce.X / 500.0f), 5.0f); } if (GapSize() <= Structure.WallSectionSize || !IsRoomToRoom) { CreateWaterSpatter(); } } if (Math.Abs(flowForce.X) > 300.0f && flowTargetHull.WaterVolume > flowTargetHull.Volume * 0.1f) { pos.X += Math.Sign(flowForce.X) * 10.0f; if (rect.Height < 32) { pos.Y = rect.Y - rect.Height / 2; } else { float bottomY = rect.Y - rect.Height + 16; float topY = MathHelper.Clamp(lowerSurface, bottomY, rect.Y - 16); pos.Y = Rand.Range(bottomY, topY); } GameMain.ParticleManager.CreateParticle( "bubbles", Submarine == null ? pos : pos + Submarine.Position, velocity, 0, flowTargetHull); } particleTimer -= emitInterval; } } else { //do not emit particles unless water is flowing towards the target hull //(using lerpedFlowForce smooths out "flickers" when the direction of flow is rapidly changing) if (Math.Sign(flowTargetHull.WorldPosition.Y - WorldPosition.Y) != Math.Sign(lerpedFlowForce.Y)) { return; } float particlesPerSec = Math.Max(open * rect.Width * particleAmountMultiplier, 10.0f); float emitInterval = 1.0f / particlesPerSec; while (particleTimer > emitInterval) { pos.X = Rand.Range(rect.X, rect.X + rect.Width + 1); Vector2 velocity = new Vector2( lerpedFlowForce.X * Rand.Range(0.5f, 0.7f), MathHelper.Clamp(lerpedFlowForce.Y, -500.0f, 1000.0f) * Rand.Range(0.5f, 0.7f)); if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f) { var splash = GameMain.ParticleManager.CreateParticle( "watersplash", Submarine == null ? pos : pos + Submarine.Position, velocity, 0, FlowTargetHull); if (splash != null) { if (splash.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(splash); } splash.Size *= MathHelper.Clamp(rect.Width / 50.0f, 1.5f, 4.0f); } if (GapSize() <= Structure.WallSectionSize || !IsRoomToRoom) { CreateWaterSpatter(); } } if (Math.Abs(flowForce.Y) > 190.0f && Rand.Range(0.0f, 1.0f) < 0.3f && flowTargetHull.WaterVolume > flowTargetHull.Volume * 0.1f) { GameMain.ParticleManager.CreateParticle( "bubbles", Submarine == null ? pos : pos + Submarine.Position, flowForce / 2.0f, 0, FlowTargetHull); } particleTimer -= emitInterval; } } } //light dripping else if (LerpedFlowForce.LengthSquared() > 100.0f && /*no dripping from large gaps between rooms (looks bad)*/ ((GapSize() <= Structure.WallSectionSize) || !IsRoomToRoom)) { particleTimer += deltaTime; float particlesPerSec = open * 10.0f * particleAmountMultiplier; float emitInterval = 1.0f / particlesPerSec; while (particleTimer > emitInterval) { Vector2 velocity = flowForce; if (!IsHorizontal) { velocity.X *= Rand.Range(1.0f, 3.0f); } if (flowTargetHull.WaterVolume < flowTargetHull.Volume) { GameMain.ParticleManager.CreateParticle( Rand.Range(0.0f, open) < 0.05f ? "waterdrop" : "watersplash", Submarine == null ? pos : pos + Submarine.Position, velocity, 0, flowTargetHull); CreateWaterSpatter(); } GameMain.ParticleManager.CreateParticle( "bubbles", (Submarine == null ? pos : pos + Submarine.Position), velocity, 0, flowTargetHull); particleTimer -= emitInterval; } } else { particleTimer = 0.0f; } void CreateWaterSpatter() { Vector2 spatterPos = pos; float rotation; if (IsHorizontal) { rotation = LerpedFlowForce.X > 0 ? 0 : MathHelper.Pi; spatterPos.Y = rect.Y - rect.Height / 2; } else { rotation = LerpedFlowForce.Y > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; spatterPos.X = rect.Center.X; } var spatter = GameMain.ParticleManager.CreateParticle( "waterspatter", Submarine == null ? spatterPos : spatterPos + Submarine.Position, Vector2.Zero, rotation, flowTargetHull); if (spatter != null) { if (spatter.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(spatter); } spatter.Size *= MathHelper.Clamp(LerpedFlowForce.Length() / 200.0f, 0.5f, 1.0f); } } float GapSize() { return IsHorizontal ? rect.Height : rect.Width; } } public override void UpdateEditing(Camera cam, float deltaTime) { if (editingHUD == null || editingHUD.UserData != this) { editingHUD = CreateEditingHUD(); } } private GUIComponent CreateEditingHUD(bool inGame = false) { editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), editingHUD.RectTransform, Anchor.Center)) { Stretch = true, AbsoluteSpacing = (int)(GUI.Scale * 5) }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("entityname.gap"), font: GUIStyle.LargeFont); var hiddenInGameTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), TextManager.Get("sp.hiddeningame.name")) { Selected = HiddenInGame }; hiddenInGameTickBox.OnSelected += (GUITickBox tickbox) => { HiddenInGame = tickbox.Selected; return true; }; editingHUD.RectTransform.Resize(new Point( editingHUD.Rect.Width, (int)(paddedFrame.Children.Sum(c => c.Rect.Height + paddedFrame.AbsoluteSpacing) / paddedFrame.RectTransform.RelativeSize.Y * 1.25f))); PositionEditingHUD(); return editingHUD; } } }