Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Map/Gap.cs
1 using FarseerPhysics;
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using System;
5 using System.Linq;
6 
7 namespace Barotrauma
8 {
9  partial class Gap : MapEntity
10  {
11  private float particleTimer;
12 
13  public override bool SelectableInEditor
14  {
15  get
16  {
17  return ShowGaps && SubEditorScreen.IsLayerVisible(this);
18  }
19  }
20 
21  public override bool IsVisible(Rectangle worldView)
22  {
24  }
25 
26  public override void Draw(SpriteBatch sb, bool editing, bool back = true)
27  {
28  float depth = (ID % 255) * 0.000001f;
29 
30  if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
31  {
32  if (FlowTargetHull != null)
33  {
34  DrawArrow(FlowTargetHull, IsHorizontal ? rect.Height: rect.Width, Math.Abs(lerpedFlowForce.Length()), Color.Red * 0.3f);
35  }
36 
37  if (Submarine != null && outsideCollisionBlocker != null && outsideCollisionBlocker.Enabled)
38  {
39  var edgeShape = outsideCollisionBlocker.FixtureList[0].Shape as FarseerPhysics.Collision.Shapes.EdgeShape;
40  Vector2 startPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex1)) + Submarine.Position;
41  Vector2 endPos = ConvertUnits.ToDisplayUnits(outsideCollisionBlocker.GetWorldPoint(edgeShape.Vertex2)) + Submarine.Position;
42  startPos.Y = -startPos.Y;
43  endPos.Y = -endPos.Y;
44  GUI.DrawLine(sb, startPos, endPos, Color.Gray, 0, 5);
45  }
46  }
47 
48  if (!editing || !ShowGaps || !SubEditorScreen.IsLayerVisible(this)) { return; }
49 
50  Color clr = (open == 0.0f) ? GUIStyle.Red : Color.Cyan;
51  if (IsHighlighted) { clr = Color.Gold; }
52 
53  GUI.DrawRectangle(
54  sb, new Rectangle(WorldRect.X, -WorldRect.Y, rect.Width, rect.Height),
55  clr * 0.2f, true, depth);
56 
57  int lineWidth = 5;
58  if (IsHorizontal)
59  {
60  GUI.DrawLine(sb,
61  new Vector2(WorldRect.X, -WorldRect.Y + lineWidth / 2),
62  new Vector2(WorldRect.Right, -WorldRect.Y + lineWidth / 2),
63  clr * 0.6f, width: lineWidth);
64  GUI.DrawLine(sb,
65  new Vector2(WorldRect.X, -WorldRect.Y + rect.Height - lineWidth / 2),
66  new Vector2(WorldRect.Right, -WorldRect.Y + rect.Height - lineWidth / 2),
67  clr * 0.6f, width: lineWidth);
68  }
69  else
70  {
71  GUI.DrawLine(sb,
72  new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y),
73  new Vector2(WorldRect.X + lineWidth / 2, -WorldRect.Y + rect.Height),
74  clr * 0.6f, width: lineWidth);
75  GUI.DrawLine(sb,
76  new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y),
77  new Vector2(WorldRect.Right - lineWidth / 2, -WorldRect.Y + rect.Height),
78  clr * 0.6f, width: lineWidth);
79  }
80 
81  if (linkedTo.Count != 2 || linkedTo[0] != linkedTo[1])
82  {
83  for (int i = 0; i < linkedTo.Count; i++)
84  {
85  if (linkedTo[i] is Hull hull)
86  {
87  DrawArrow(hull, 32.0f, 15f, clr);
88  }
89  }
90  }
91 
92  void DrawArrow(Hull targetHull, float arrowWidth, float arrowLength, Color clr)
93  {
94  Vector2 dir = IsHorizontal ?
95  new Vector2(Math.Sign(targetHull.Rect.Center.X - rect.Center.X), 0.0f)
96  : new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (targetHull.Rect.Y - targetHull.Rect.Height / 2.0f)));
97 
98  Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
99  arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2));
100 
101  bool invalidDir = false;
102  if (dir == Vector2.Zero)
103  {
104  invalidDir = true;
105  dir = IsHorizontal ? Vector2.UnitX : Vector2.UnitY;
106  }
107 
108  GUI.Arrow.Draw(sb,
109  arrowPos, invalidDir ? Color.Red : clr * 0.8f,
110  GUI.Arrow.Origin, MathUtils.VectorToAngle(dir) + MathHelper.PiOver2,
111  IsHorizontal ?
112  new Vector2(Math.Min(rect.Height, arrowWidth) / GUI.Arrow.size.X, arrowLength / GUI.Arrow.size.Y) :
113  new Vector2(Math.Min(rect.Width, arrowWidth) / GUI.Arrow.size.X, arrowLength / GUI.Arrow.size.Y),
114  SpriteEffects.None, depth);
115  }
116 
117  if (IsSelected)
118  {
119  GUI.DrawRectangle(sb,
120  new Vector2(WorldRect.X - 5, -WorldRect.Y - 5),
121  new Vector2(rect.Width + 10, rect.Height + 10),
122  GUIStyle.Red,
123  false,
124  depth,
125  (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f));
126  }
127  }
128 
129  partial void EmitParticles(float deltaTime)
130  {
131  if (flowTargetHull == null) { return; }
132 
133  if (linkedTo.Count == 2 && linkedTo[0] is Hull hull1 && linkedTo[1] is Hull hull2)
134  {
135  //no flow particles between linked hulls (= rooms consisting of multiple hulls)
136  if (hull1.linkedTo.Contains(hull2)) { return; }
137  foreach (var linkedEntity in hull1.linkedTo)
138  {
139  if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
140  }
141  foreach (var linkedEntity in hull2.linkedTo)
142  {
143  if (linkedEntity is Hull h && h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
144  }
145  }
146 
147  Vector2 pos = Position;
148  if (IsHorizontal)
149  {
150  pos.X += Math.Sign(flowForce.X);
151  pos.Y = MathHelper.Clamp(Rand.Range(higherSurface, lowerSurface), rect.Y - rect.Height, rect.Y);
152  }
153  if (flowTargetHull != null)
154  {
155  pos.X = MathHelper.Clamp(pos.X, flowTargetHull.Rect.X + 1, flowTargetHull.Rect.Right - 1);
156  pos.Y = MathHelper.Clamp(pos.Y, flowTargetHull.Rect.Y - flowTargetHull.Rect.Height + 1, flowTargetHull.Rect.Y - 1);
157  }
158 
159  //spawn less particles when there's already a large number of them
160  float particleAmountMultiplier = 1.0f - GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles;
161  particleAmountMultiplier *= particleAmountMultiplier;
162 
163  //heavy flow -> strong waterfall type of particles
164  if (LerpedFlowForce.LengthSquared() > 20000.0f)
165  {
166  particleTimer += deltaTime;
167  if (IsHorizontal)
168  {
169  float particlesPerSec = open * rect.Height * 0.1f * particleAmountMultiplier;
170  if (openedTimer > 0.0f) { particlesPerSec *= 1.0f + openedTimer * 10.0f; }
171  float emitInterval = 1.0f / particlesPerSec;
172  while (particleTimer > emitInterval)
173  {
174  Vector2 velocity = new Vector2(
175  MathHelper.Clamp(flowForce.X, -5000.0f, 5000.0f) * Rand.Range(0.5f, 0.7f),
176  flowForce.Y * Rand.Range(0.5f, 0.7f));
177 
178  if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f)
179  {
180  var particle = GameMain.ParticleManager.CreateParticle(
181  "watersplash",
182  (Submarine == null ? pos : pos + Submarine.Position) - Vector2.UnitY * Rand.Range(0.0f, 10.0f),
183  velocity, 0, flowTargetHull);
184  if (particle != null)
185  {
186  if (particle.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(particle); }
187  particle.Size *= Math.Min(Math.Abs(flowForce.X / 500.0f), 5.0f);
188  }
189  if (GapSize() <= Structure.WallSectionSize || !IsRoomToRoom)
190  {
191  CreateWaterSpatter();
192  }
193  }
194 
195  if (Math.Abs(flowForce.X) > 300.0f && flowTargetHull.WaterVolume > flowTargetHull.Volume * 0.1f)
196  {
197  pos.X += Math.Sign(flowForce.X) * 10.0f;
198  if (rect.Height < 32)
199  {
200  pos.Y = rect.Y - rect.Height / 2;
201  }
202  else
203  {
204  float bottomY = rect.Y - rect.Height + 16;
205  float topY = MathHelper.Clamp(lowerSurface, bottomY, rect.Y - 16);
206  pos.Y = Rand.Range(bottomY, topY);
207  }
208  GameMain.ParticleManager.CreateParticle(
209  "bubbles",
210  Submarine == null ? pos : pos + Submarine.Position,
211  velocity, 0, flowTargetHull);
212  }
213  particleTimer -= emitInterval;
214  }
215  }
216  else
217  {
218  //do not emit particles unless water is flowing towards the target hull
219  //(using lerpedFlowForce smooths out "flickers" when the direction of flow is rapidly changing)
220  if (Math.Sign(flowTargetHull.WorldPosition.Y - WorldPosition.Y) != Math.Sign(lerpedFlowForce.Y)) { return; }
221 
222  float particlesPerSec = Math.Max(open * rect.Width * particleAmountMultiplier, 10.0f);
223  float emitInterval = 1.0f / particlesPerSec;
224  while (particleTimer > emitInterval)
225  {
226  pos.X = Rand.Range(rect.X, rect.X + rect.Width + 1);
227  Vector2 velocity = new Vector2(
228  lerpedFlowForce.X * Rand.Range(0.5f, 0.7f),
229  MathHelper.Clamp(lerpedFlowForce.Y, -500.0f, 1000.0f) * Rand.Range(0.5f, 0.7f));
230 
231  if (flowTargetHull.WaterVolume < flowTargetHull.Volume * 0.95f)
232  {
233  var splash = GameMain.ParticleManager.CreateParticle(
234  "watersplash",
235  Submarine == null ? pos : pos + Submarine.Position,
236  velocity, 0, FlowTargetHull);
237  if (splash != null)
238  {
239  if (splash.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(splash); }
240  splash.Size *= MathHelper.Clamp(rect.Width / 50.0f, 1.5f, 4.0f);
241  }
242  if (GapSize() <= Structure.WallSectionSize || !IsRoomToRoom)
243  {
244  CreateWaterSpatter();
245  }
246  }
247  if (Math.Abs(flowForce.Y) > 190.0f && Rand.Range(0.0f, 1.0f) < 0.3f && flowTargetHull.WaterVolume > flowTargetHull.Volume * 0.1f)
248  {
249  GameMain.ParticleManager.CreateParticle(
250  "bubbles",
251  Submarine == null ? pos : pos + Submarine.Position,
252  flowForce / 2.0f, 0, FlowTargetHull);
253  }
254  particleTimer -= emitInterval;
255  }
256  }
257  }
258  //light dripping
259  else if (LerpedFlowForce.LengthSquared() > 100.0f &&
260  /*no dripping from large gaps between rooms (looks bad)*/
261  ((GapSize() <= Structure.WallSectionSize) || !IsRoomToRoom))
262  {
263  particleTimer += deltaTime;
264  float particlesPerSec = open * 10.0f * particleAmountMultiplier;
265  float emitInterval = 1.0f / particlesPerSec;
266  while (particleTimer > emitInterval)
267  {
268  Vector2 velocity = flowForce;
269  if (!IsHorizontal)
270  {
271  velocity.X *= Rand.Range(1.0f, 3.0f);
272  }
273 
274  if (flowTargetHull.WaterVolume < flowTargetHull.Volume)
275  {
276  GameMain.ParticleManager.CreateParticle(
277  Rand.Range(0.0f, open) < 0.05f ? "waterdrop" : "watersplash",
278  Submarine == null ? pos : pos + Submarine.Position,
279  velocity, 0, flowTargetHull);
280  CreateWaterSpatter();
281  }
282 
283  GameMain.ParticleManager.CreateParticle(
284  "bubbles",
285  (Submarine == null ? pos : pos + Submarine.Position),
286  velocity, 0, flowTargetHull);
287 
288  particleTimer -= emitInterval;
289  }
290  }
291  else
292  {
293  particleTimer = 0.0f;
294  }
295 
296  void CreateWaterSpatter()
297  {
298  Vector2 spatterPos = pos;
299  float rotation;
300  if (IsHorizontal)
301  {
302  rotation = LerpedFlowForce.X > 0 ? 0 : MathHelper.Pi;
303  spatterPos.Y = rect.Y - rect.Height / 2;
304  }
305  else
306  {
307  rotation = LerpedFlowForce.Y > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
308  spatterPos.X = rect.Center.X;
309  }
310  var spatter = GameMain.ParticleManager.CreateParticle(
311  "waterspatter",
312  Submarine == null ? spatterPos : spatterPos + Submarine.Position,
313  Vector2.Zero, rotation, flowTargetHull);
314  if (spatter != null)
315  {
316  if (spatter.CurrentHull == null) { GameMain.ParticleManager.RemoveParticle(spatter); }
317  spatter.Size *= MathHelper.Clamp(LerpedFlowForce.Length() / 200.0f, 0.5f, 1.0f);
318  }
319  }
320 
321  float GapSize()
322  {
323  return IsHorizontal ? rect.Height : rect.Width;
324  }
325  }
326 
327  public override void UpdateEditing(Camera cam, float deltaTime)
328  {
329  if (editingHUD == null || editingHUD.UserData != this)
330  {
331  editingHUD = CreateEditingHUD();
332  }
333  }
334  private GUIComponent CreateEditingHUD(bool inGame = false)
335  {
336  editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) })
337  {
338  UserData = this
339  };
340 
341  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), editingHUD.RectTransform, Anchor.Center))
342  {
343  Stretch = true,
344  AbsoluteSpacing = (int)(GUI.Scale * 5)
345  };
346 
347  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("entityname.gap"), font: GUIStyle.LargeFont);
348  var hiddenInGameTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), TextManager.Get("sp.hiddeningame.name"))
349  {
351  };
352  hiddenInGameTickBox.OnSelected += (GUITickBox tickbox) =>
353  {
354  HiddenInGame = tickbox.Selected;
355  return true;
356  };
357  editingHUD.RectTransform.Resize(new Point(
358  editingHUD.Rect.Width,
359  (int)(paddedFrame.Children.Sum(c => c.Rect.Height + paddedFrame.AbsoluteSpacing) / paddedFrame.RectTransform.RelativeSize.Y * 1.25f)));
360 
362 
363  return editingHUD;
364  }
365  }
366 }
float? Zoom
Definition: Camera.cs:78
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
virtual Rectangle Rect
RectTransform RectTransform
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static bool DebugDraw
Definition: GameMain.cs:29
override void Draw(SpriteBatch sb, bool editing, bool back=true)
override bool IsVisible(Rectangle worldView)
override void UpdateEditing(Camera cam, float deltaTime)
void Resize(Point absoluteSize, bool resizeChildren=true)
static bool IsLayerVisible(MapEntity entity)