Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Map/Hull.cs
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using Microsoft.Xna.Framework.Input;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
10 
11 namespace Barotrauma
12 {
13  partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable
14  {
15  private class RemoteDecal
16  {
17  public readonly UInt32 DecalId;
18  public readonly int SpriteIndex;
19  public Vector2 NormalizedPos;
20  public readonly float Scale;
21 
22  public RemoteDecal(UInt32 decalId, int spriteIndex, Vector2 normalizedPos, float scale)
23  {
24  DecalId = decalId;
25  SpriteIndex = spriteIndex;
26  NormalizedPos = normalizedPos;
27  Scale = scale;
28  }
29  }
30 
31  private float serverUpdateDelay;
32  private float remoteWaterVolume, remoteOxygenPercentage;
33  private NetworkFireSource[] remoteFireSources = null;
34  private readonly List<BackgroundSection> remoteBackgroundSections = new List<BackgroundSection>();
35  private readonly List<RemoteDecal> remoteDecals = new List<RemoteDecal>();
36 
37  private readonly HashSet<Decal> pendingDecalUpdates = new HashSet<Decal>();
38 
39  private double lastAmbientLightEditTime;
40 
41  private float drawSurface;
42 
43  public float DrawSurface
44  {
45  get { return drawSurface; }
46  set
47  {
48  if (Math.Abs(drawSurface - value) < 0.00001f) { return; }
49  drawSurface = MathHelper.Clamp(value, rect.Y - rect.Height, rect.Y);
50  update = true;
51  }
52  }
53 
54  public override bool SelectableInEditor
55  {
56  get
57  {
59  }
60  }
61 
62  public override bool DrawBelowWater
63  {
64  get
65  {
66  return decals.Count > 0 || BallastFlora != null;
67  }
68  }
69 
70  public override bool DrawOverWater
71  {
72  get
73  {
74  return true;
75  }
76  }
77 
78  private float paintAmount = 0.0f;
79  private float minimumPaintAmountToDraw;
80 
81  public override bool IsVisible(Rectangle worldView)
82  {
83  if (BallastFlora != null) { return true; }
84 
86  {
87  if (decals.Count == 0 && paintAmount < minimumPaintAmountToDraw) { return false; }
88 
89  Rectangle worldRect = WorldRect;
90  if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
91  if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
92  }
93  return true;
94  }
95 
96  public override bool IsMouseOn(Vector2 position)
97  {
98  if (!GameMain.DebugDraw && !ShowHulls) return false;
99 
100  return (Submarine.RectContains(WorldRect, position) &&
101  !Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -8), position));
102  }
103 
104  private GUIComponent CreateEditingHUD(bool inGame = false)
105  {
106  editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
107  GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
108  {
109  CanTakeKeyBoardFocus = false
110  };
111  new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont);
112 
114 
115  return editingHUD;
116  }
117 
118  public override void UpdateEditing(Camera cam, float deltaTime)
119  {
120  if (editingHUD == null || editingHUD.UserData as Hull != this)
121  {
122  editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen);
123  }
124 
125  if (!PlayerInput.KeyDown(Keys.Space)) { return; }
126  bool lClick = PlayerInput.PrimaryMouseButtonClicked();
128  if (!lClick && !rClick) { return; }
129 
130  Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
131 
132  foreach (MapEntity entity in HighlightedEntities)
133  {
134  if (entity == this) { continue; }
135  if (!entity.IsMouseOn(position)) { continue; }
136  if (entity.linkedTo == null || !entity.Linkable) { continue; }
137  if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick)
138  {
139  entity.linkedTo.Remove(this);
140  linkedTo.Remove(entity);
141  }
142  else
143  {
144  if (!entity.linkedTo.Contains(this)) { entity.linkedTo.Add(this); }
145  if (!linkedTo.Contains(this)) { linkedTo.Add(entity); }
146  }
147  }
148  }
149 
150  partial void UpdateProjSpecific(float deltaTime, Camera _)
151  {
152  float waterDepth = WaterVolume / rect.Width;
153  //interpolate the position of the rendered surface towards the "target surface"
154  drawSurface = Math.Max(MathHelper.Lerp(
155  drawSurface,
156  rect.Y - rect.Height + waterDepth,
157  deltaTime * 10.0f), rect.Y - rect.Height);
158 
159  if (GameMain.Client != null)
160  {
161  serverUpdateDelay -= deltaTime;
162  if (serverUpdateDelay <= 0.0f)
163  {
164  ApplyRemoteState();
165  }
166  if (networkUpdatePending)
167  {
168  networkUpdateTimer += deltaTime;
169  if (networkUpdateTimer > 0.2f)
170  {
171  if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any())
172  {
173  //these are used to modify the amount water/fire in the hull with console commands
174  //they should be usable even when not controlling a character
175  GameMain.Client?.CreateEntityEvent(this, new StatusEventData(), requireControlledCharacter: false);
176  }
177  foreach (Decal decal in pendingDecalUpdates)
178  {
179  GameMain.Client?.CreateEntityEvent(this, new DecalEventData(decal));
180  }
181  pendingDecalUpdates.Clear();
182  foreach (int pendingSectionUpdate in pendingSectionUpdates)
183  {
184  GameMain.Client?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate));
185  }
186  pendingSectionUpdates.Clear();
187  networkUpdatePending = false;
188  networkUpdateTimer = 0.0f;
189  }
190  }
191  }
192 
193  /*if (waterVolume < 1.0f) { return; }
194  for (int i = 1; i < waveY.Length - 1; i++)
195  {
196  float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
197  if (maxDelta > 0.1f && maxDelta > Rand.Range(0.1f, 10.0f))
198  {
199  var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]);
200  if (Submarine != null) { particlePos += Submarine.Position; }
201 
202  GameMain.ParticleManager.CreateParticle("mist",
203  particlePos,
204  new Vector2(0.0f, -50.0f), 0.0f, this);
205  }
206  }*/
207  }
208 
209  public static void UpdateCheats(float deltaTime, Camera cam)
210  {
211  bool primaryMouseButtonHeld = PlayerInput.PrimaryMouseButtonHeld();
212  bool secondaryMouseButtonHeld = PlayerInput.SecondaryMouseButtonHeld();
213  bool doubleClicked = PlayerInput.DoubleClicked();
214  bool secondaryDoubleClicked = PlayerInput.SecondaryDoubleClicked();
215  if (!primaryMouseButtonHeld && !secondaryMouseButtonHeld && !doubleClicked && !secondaryDoubleClicked) { return; }
216 
217  Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
218  Hull hull = FindHull(position);
219 
220  if (hull == null || hull.IdFreed) { return; }
221  if (EditWater)
222  {
223  const float waterIncrement = 100000.0f;
224  if (primaryMouseButtonHeld)
225  {
226  SetWaterVolume(hull.WaterVolume + waterIncrement * deltaTime);
227  }
228  else if (secondaryMouseButtonHeld)
229  {
230  SetWaterVolume(hull.WaterVolume - waterIncrement * deltaTime);
231  }
232 
233  if (doubleClicked)
234  {
235  SetWaterVolume(hull.Volume * MaxCompress);
236  }
237  else if (secondaryDoubleClicked)
238  {
239  SetWaterVolume(0f);
240  }
241 
242  void SetWaterVolume(float newVolume)
243  {
244  ShowHulls = true;
245  hull.WaterVolume = newVolume;
246  hull.networkUpdatePending = true;
247  hull.serverUpdateDelay = 0.5f;
248  }
249  }
250  else if (EditFire)
251  {
252  bool networkUpdate = false;
253 
254  if (primaryMouseButtonHeld)
255  {
256  new FireSource(position, hull, isNetworkMessage: true);
257  networkUpdate = true;
258  }
259  else if (secondaryMouseButtonHeld || secondaryDoubleClicked)
260  {
261  for (int index = hull.FireSources.Count - 1; index >= 0; index--)
262  {
263  var currentFireSource = hull.FireSources[index];
264 
265  if (secondaryMouseButtonHeld)
266  {
267  const float extinguishAmount = 120f;
268  currentFireSource.Extinguish(deltaTime, extinguishAmount);
269  networkUpdate = true;
270  }
271  else
272  {
273  currentFireSource.Remove();
274  networkUpdate = true;
275  }
276  }
277  }
278 
279  if (networkUpdate)
280  {
281  hull.networkUpdatePending = true;
282  hull.serverUpdateDelay = 0.5f;
283  }
284  }
285  }
286 
287  private void DrawDecals(SpriteBatch spriteBatch)
288  {
289  Rectangle hullDrawRect = rect;
290  if (Submarine != null) hullDrawRect.Location += Submarine.DrawPosition.ToPoint();
291 
292  float depth = 1.0f;
293  foreach (Decal d in decals)
294  {
295  d.Draw(spriteBatch, this, depth);
296  depth -= 0.000001f;
297  }
298  }
299 
300  public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
301  {
302  if (back && Screen.Selected != GameMain.SubEditorScreen)
303  {
304  BallastFlora?.Draw(spriteBatch);
305  DrawDecals(spriteBatch);
306  return;
307  }
308 
309  if ((!ShowHulls || !SubEditorScreen.IsLayerVisible(this)) && !GameMain.DebugDraw) { return; }
310 
311  if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; }
312 
313  float alpha = 1.0f;
314  float hideTimeAfterEdit = 3.0f;
315  if (lastAmbientLightEditTime > Timing.TotalTime - hideTimeAfterEdit * 2.0f)
316  {
317  alpha = Math.Min((float)(Timing.TotalTime - lastAmbientLightEditTime) / hideTimeAfterEdit - 1.0f, 1.0f);
318  }
319 
320  Rectangle drawRect =
321  Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
322 
323  if (editing)
324  {
325  if (IsSelected || IsHighlighted)
326  {
327  GUI.DrawRectangle(spriteBatch,
328  new Vector2(drawRect.X, -drawRect.Y),
329  new Vector2(rect.Width, rect.Height),
330  (IsHighlighted ? Color.LightBlue * 0.8f : GUIStyle.Red * 0.5f) * alpha, false, 0, (int)Math.Max(5.0f / Screen.Selected.Cam.Zoom, 1.0f));
331  }
332 
333  float waterHeight = WaterVolume / rect.Width;
334  GUI.DrawRectangle(spriteBatch,
335  new Vector2(drawRect.X, -drawRect.Y + drawRect.Height - waterHeight),
336  new Vector2(drawRect.Width, waterHeight),
337  Color.Blue * 0.25f, isFilled: true);
338  }
339 
340  GUI.DrawRectangle(spriteBatch,
341  new Vector2(drawRect.X, -drawRect.Y),
342  new Vector2(rect.Width, rect.Height),
343  Color.Blue * alpha, false, (ID % 255) * 0.000001f, (int)Math.Max(MathF.Ceiling(1.5f / Screen.Selected.Cam.Zoom), 1.0f));
344 
345  GUI.DrawRectangle(spriteBatch,
346  new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height),
347  GUIStyle.Red * ((100.0f - OxygenPercentage) / 400.0f) * alpha, true, 0, (int)Math.Max(MathF.Ceiling(1.5f / Screen.Selected.Cam.Zoom), 1.0f));
348 
349  if (GameMain.DebugDraw)
350  {
351  GUIStyle.SmallFont.DrawString(spriteBatch, "Pressure: " + ((int)pressure - rect.Y).ToString() +
352  " - Oxygen: " + ((int)OxygenPercentage), new Vector2(drawRect.X + 5, -drawRect.Y + 5), Color.White);
353  GUIStyle.SmallFont.DrawString(spriteBatch, waterVolume + " / " + Volume, new Vector2(drawRect.X + 5, -drawRect.Y + 20), Color.White);
354 
355  if (WaterVolume > 0)
356  {
357  drawProgressBar(50, new Point(0, 0), Math.Min(waterVolume / Volume, 1.0f), Color.Cyan);
358  if (WaterVolume > Volume)
359  {
360  float maxExcessWater = Volume * MaxCompress;
361  drawProgressBar(50, new Point(0, 0), (waterVolume - Volume) / maxExcessWater, GUIStyle.Red);
362  }
363  }
364  if (lethalPressure > 0)
365  {
366  drawProgressBar(50, new Point(20, 0), lethalPressure / 100.0f, Color.Red);
367  }
368 
369  void drawProgressBar(int height, Point offset, float fillAmount, Color color)
370  {
371  GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X - 2 + offset.X, -drawRect.Y - 2 + drawRect.Height / 2 + offset.Y, 14, height+4), Color.Black * 0.8f, depth: 0.01f, isFilled: true);
372 
373  int barHeight = (int)(fillAmount * height);
374  GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X + offset.X, -drawRect.Y + drawRect.Height / 2 + height - barHeight + offset.Y, 10, barHeight), color, isFilled: true);
375  }
376 
377  foreach (FireSource fs in FireSources)
378  {
379  Rectangle fireSourceRect = new Rectangle((int)fs.WorldPosition.X, -(int)fs.WorldPosition.Y, (int)fs.Size.X, (int)fs.Size.Y);
380  GUI.DrawRectangle(spriteBatch, fireSourceRect, GUIStyle.Red, false, 0, 5);
381  GUI.DrawRectangle(spriteBatch, new Rectangle(fireSourceRect.X - (int)fs.DamageRange, fireSourceRect.Y, fireSourceRect.Width + (int)fs.DamageRange * 2, fireSourceRect.Height), GUIStyle.Orange, false, 0, 5);
382  //GUI.DrawRectangle(spriteBatch, new Rectangle((int)fs.LastExtinguishPos.X, (int)-fs.LastExtinguishPos.Y, 5,5), Color.Yellow, true);
383  }
384  foreach (FireSource fs in FakeFireSources)
385  {
386  Rectangle fireSourceRect = new Rectangle((int)fs.WorldPosition.X, -(int)fs.WorldPosition.Y, (int)fs.Size.X, (int)fs.Size.Y);
387  GUI.DrawRectangle(spriteBatch, fireSourceRect, GUIStyle.Red, false, 0, 5);
388  GUI.DrawRectangle(spriteBatch, new Rectangle(fireSourceRect.X - (int)fs.DamageRange, fireSourceRect.Y, fireSourceRect.Width + (int)fs.DamageRange * 2, fireSourceRect.Height), GUIStyle.Orange, false, 0, 5);
389  //GUI.DrawRectangle(spriteBatch, new Rectangle((int)fs.LastExtinguishPos.X, (int)-fs.LastExtinguishPos.Y, 5,5), Color.Yellow, true);
390  }
391 
392 
393  GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
394  for (int i = 0; i < waveY.Length - 1; i++)
395  {
396  GUI.DrawLine(spriteBatch,
397  new Vector2(drawRect.X + WaveWidth * i, -WorldSurface - waveY[i] - 10),
398  new Vector2(drawRect.X + WaveWidth * (i + 1), -WorldSurface - waveY[i + 1] - 10), Color.Blue * 0.5f);
399  }
400  }
401 
402  foreach (MapEntity e in linkedTo)
403  {
404  if (e is Hull linkedHull)
405  {
406  Rectangle connectedHullRect = e.Submarine == null ?
407  linkedHull.rect :
408  new Rectangle(
409  (int)(Submarine.DrawPosition.X + linkedHull.WorldPosition.X),
410  (int)(Submarine.DrawPosition.Y + linkedHull.WorldPosition.Y),
411  linkedHull.WorldRect.Width, linkedHull.WorldRect.Height);
412 
413  //center of the hull
414  Rectangle currentHullRect = Submarine == null ?
415  WorldRect :
416  new Rectangle(
417  (int)(Submarine.DrawPosition.X + WorldPosition.X),
418  (int)(Submarine.DrawPosition.Y + WorldPosition.Y),
419  WorldRect.Width, WorldRect.Height);
420 
421  GUI.DrawLine(spriteBatch,
422  new Vector2(currentHullRect.X, -currentHullRect.Y),
423  new Vector2(connectedHullRect.X, -connectedHullRect.Y),
424  GUIStyle.Green, width: 2);
425  }
426  }
427  }
428 
429  public void DrawSectionColors(SpriteBatch spriteBatch)
430  {
431  if (BackgroundSections == null || BackgroundSections.Count == 0) { return; }
432  Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
433  Point sectionSize = BackgroundSections[0].Rect.Size;
434  Vector2 drawPos = drawOffset + new Vector2(rect.Location.X + sectionSize.X / 2, rect.Location.Y - sectionSize.Y / 2);
435 
436  for (int i = 0; i < BackgroundSections.Count; i++)
437  {
439 
440  if (section.ColorStrength < 0.01f || section.Color.A < 1) { continue; }
441 
442  if (section.GrimeSprite == null)
443  {
444  GUI.DrawRectangle(spriteBatch,
445  new Vector2(drawOffset.X + rect.X + section.Rect.X, -(drawOffset.Y + rect.Y + section.Rect.Y)),
446  new Vector2(sectionSize.X, sectionSize.Y),
447  section.GetStrengthAdjustedColor(), true, 0.0f, (int)Math.Max(1.5f / Screen.Selected.Cam.Zoom, 1.0f));
448  }
449  else
450  {
451  Vector2 sectionPos = new Vector2(drawPos.X + section.Rect.Location.X, -(drawPos.Y + section.Rect.Location.Y));
452  Vector2 randomOffset = new Vector2(section.Noise.X - 0.5f, section.Noise.Y - 0.5f) * 15.0f;
453  section.GrimeSprite.Draw(spriteBatch, sectionPos + randomOffset, section.GetStrengthAdjustedColor(), scale: 1.25f);
454  }
455  }
456  }
457 
458  public static void UpdateVertices(Camera cam, WaterRenderer renderer)
459  {
460  foreach (EntityGrid entityGrid in EntityGrids)
461  {
462  if (entityGrid.WorldRect.X > cam.WorldView.Right || entityGrid.WorldRect.Right < cam.WorldView.X) { continue; }
463  if (entityGrid.WorldRect.Y - entityGrid.WorldRect.Height > cam.WorldView.Y || entityGrid.WorldRect.Y < cam.WorldView.Y - cam.WorldView.Height) { continue; }
464 
465  var allEntities = entityGrid.GetAllEntities();
466  foreach (Hull hull in allEntities)
467  {
468  hull.UpdateVertices(cam, entityGrid, renderer);
469  }
470  }
471  }
472 
473  private static readonly Vector3[] corners = new Vector3[6];
474  private static readonly Vector2[] uvCoords = new Vector2[4];
475  private static readonly Vector3[] prevCorners = new Vector3[2];
476  private static readonly Vector2[] prevUVs = new Vector2[2];
477 
478  private void UpdateVertices(Camera cam, EntityGrid entityGrid, WaterRenderer renderer)
479  {
480  Vector2 submarinePos = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
481 
482  //if there's no more space in the buffer, don't render the water in the hull
483  //not an ideal solution, but this seems to only happen in cases where the missing
484  //water is not very noticeable (e.g. zoomed very far out so that multiple subs and ruins are visible)
485  if (renderer.PositionInBuffer > renderer.vertices.Length - 6)
486  {
487  return;
488  }
489 
490  //calculate where the surface should be based on the water volume
491  float top = rect.Y + submarinePos.Y;
492  float bottom = top - rect.Height;
493  float renderSurface = drawSurface + submarinePos.Y;
494 
495  if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) { return; }
496  if (rect.X + submarinePos.X > cam.WorldView.Right || rect.Right + submarinePos.X < cam.WorldView.X) { return; }
497 
498  Matrix transform = cam.Transform * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
499  if (!update)
500  {
501  // create the four corners of our triangle.
502 
503  corners[0] = new Vector3(rect.X, rect.Y, 0.0f);
504  corners[1] = new Vector3(rect.X + rect.Width, rect.Y, 0.0f);
505 
506  corners[2] = new Vector3(corners[1].X, rect.Y - rect.Height, 0.0f);
507  corners[3] = new Vector3(corners[0].X, corners[2].Y, 0.0f);
508 
509  for (int i = 0; i < 4; i++)
510  {
511  corners[i] += new Vector3(submarinePos, 0.0f);
512  uvCoords[i] = Vector2.Transform(new Vector2(corners[i].X, -corners[i].Y), transform);
513  }
514 
515  renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(corners[0], uvCoords[0]);
516  renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]);
517  renderer.vertices[renderer.PositionInBuffer + 2] = new VertexPositionTexture(corners[2], uvCoords[2]);
518 
519  renderer.vertices[renderer.PositionInBuffer + 3] = new VertexPositionTexture(corners[0], uvCoords[0]);
520  renderer.vertices[renderer.PositionInBuffer + 4] = new VertexPositionTexture(corners[2], uvCoords[2]);
521  renderer.vertices[renderer.PositionInBuffer + 5] = new VertexPositionTexture(corners[3], uvCoords[3]);
522 
523  renderer.PositionInBuffer += 6;
524 
525  return;
526  }
527 
528  if (!renderer.IndoorsVertices.ContainsKey(entityGrid))
529  {
530  renderer.IndoorsVertices[entityGrid] = new VertexPositionColorTexture[WaterRenderer.DefaultIndoorsBufferSize];
531  }
532  if (!renderer.PositionInIndoorsBuffer.ContainsKey(entityGrid))
533  {
534  renderer.PositionInIndoorsBuffer[entityGrid] = 0;
535  }
536 
537  float x = rect.X;
538  if (Submarine != null) { x += Submarine.DrawPosition.X; }
539 
540  int start = (int)Math.Floor((cam.WorldView.X - x) / WaveWidth);
541  start = Math.Max(start, 0);
542 
543  int end = (waveY.Length - 1) - (int)Math.Floor(((x + rect.Width) - (cam.WorldView.Right)) / WaveWidth);
544  end = Math.Min(end, waveY.Length - 1);
545 
546  x += start * WaveWidth;
547 
548  int width = WaveWidth;
549 
550  for (int i = start; i < end; i++)
551  {
552  //top left
553  corners[0] = new Vector3(x, top, 0.0f);
554  //watersurface left
555  corners[3] = new Vector3(corners[0].X, renderSurface + waveY[i], 0.0f);
556 
557  //top right
558  corners[1] = new Vector3(x + width, top, 0.0f);
559  //watersurface right
560  corners[2] = new Vector3(corners[1].X, renderSurface + waveY[i + 1], 0.0f);
561 
562  //bottom left
563  corners[4] = new Vector3(x, bottom, 0.0f);
564  //bottom right
565  corners[5] = new Vector3(x + width, bottom, 0.0f);
566 
567  Vector2[] uvCoords = new Vector2[4];
568  for (int n = 0; n < 4; n++)
569  {
570  uvCoords[n] = Vector2.Transform(new Vector2(corners[n].X, -corners[n].Y), transform);
571  }
572 
573  if (renderer.PositionInBuffer <= renderer.vertices.Length - 6)
574  {
575  if (i == start)
576  {
577  prevCorners[0] = corners[0];
578  prevCorners[1] = corners[3];
579  prevUVs[0] = uvCoords[0];
580  prevUVs[1] = uvCoords[3];
581  }
582 
583  //we only create a new quad if this is the first or the last one, of if there's a wave large enough that we need more geometry
584  if (i == end - 1 || i == start || Math.Abs(prevCorners[1].Y - corners[2].Y) > 0.01f)
585  {
586  renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(prevCorners[0], prevUVs[0]);
587  renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]);
588  renderer.vertices[renderer.PositionInBuffer + 2] = new VertexPositionTexture(corners[2], uvCoords[2]);
589 
590  renderer.vertices[renderer.PositionInBuffer + 3] = new VertexPositionTexture(prevCorners[0], prevUVs[0]);
591  renderer.vertices[renderer.PositionInBuffer + 4] = new VertexPositionTexture(corners[2], uvCoords[2]);
592  renderer.vertices[renderer.PositionInBuffer + 5] = new VertexPositionTexture(prevCorners[1], prevUVs[1]);
593 
594  prevCorners[0] = corners[1];
595  prevCorners[1] = corners[2];
596  prevUVs[0] = uvCoords[1];
597  prevUVs[1] = uvCoords[2];
598 
599  renderer.PositionInBuffer += 6;
600  }
601  }
602 
603  if (renderer.PositionInIndoorsBuffer[entityGrid] <= renderer.IndoorsVertices[entityGrid].Length - 12 &&
604  cam.Zoom > 0.6f)
605  {
606  const float SurfaceSize = 10.0f;
607  const float SineFrequency1 = 0.01f;
608  const float SineFrequency2 = 0.05f;
609 
610  //surface shrinks and finally disappears when the water level starts to reach the top of the hull
611  float surfaceScale = 1.0f - MathHelper.Clamp(corners[3].Y - (top - SurfaceSize), 0.0f, 1.0f);
612 
613  Vector3 surfaceOffset = new Vector3(0.0f, -SurfaceSize, 0.0f);
614  surfaceOffset.Y += (float)Math.Sin((rect.X + i * WaveWidth) * SineFrequency1 + renderer.WavePos.X * 0.25f) * 2;
615  surfaceOffset.Y += (float)Math.Sin((rect.X + i * WaveWidth) * SineFrequency2 - renderer.WavePos.X) * 2;
616  surfaceOffset *= surfaceScale;
617 
618  Vector3 surfaceOffset2 = new Vector3(0.0f, -SurfaceSize, 0.0f);
619  surfaceOffset2.Y += (float)Math.Sin((rect.X + i * WaveWidth + width) * SineFrequency1 + renderer.WavePos.X * 0.25f) * 2;
620  surfaceOffset2.Y += (float)Math.Sin((rect.X + i * WaveWidth + width) * SineFrequency2 - renderer.WavePos.X) * 2;
621  surfaceOffset2 *= surfaceScale;
622 
623  int posInBuffer = renderer.PositionInIndoorsBuffer[entityGrid];
624 
625  renderer.IndoorsVertices[entityGrid][posInBuffer + 0] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsWaterColor, Vector2.Zero);
626  renderer.IndoorsVertices[entityGrid][posInBuffer + 1] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsWaterColor, Vector2.Zero);
627  renderer.IndoorsVertices[entityGrid][posInBuffer + 2] = new VertexPositionColorTexture(corners[5], renderer.IndoorsWaterColor, Vector2.Zero);
628 
629  renderer.IndoorsVertices[entityGrid][posInBuffer + 3] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsWaterColor, Vector2.Zero);
630  renderer.IndoorsVertices[entityGrid][posInBuffer + 4] = new VertexPositionColorTexture(corners[5], renderer.IndoorsWaterColor, Vector2.Zero);
631  renderer.IndoorsVertices[entityGrid][posInBuffer + 5] = new VertexPositionColorTexture(corners[4], renderer.IndoorsWaterColor, Vector2.Zero);
632 
633  posInBuffer += 6;
634  renderer.PositionInIndoorsBuffer[entityGrid] = posInBuffer;
635 
636  if (surfaceScale > 0)
637  {
638  renderer.IndoorsVertices[entityGrid][posInBuffer + 0] = new VertexPositionColorTexture(corners[3], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
639  renderer.IndoorsVertices[entityGrid][posInBuffer + 1] = new VertexPositionColorTexture(corners[2], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
640  renderer.IndoorsVertices[entityGrid][posInBuffer + 2] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
641 
642  renderer.IndoorsVertices[entityGrid][posInBuffer + 3] = new VertexPositionColorTexture(corners[3], renderer.IndoorsSurfaceTopColor, Vector2.Zero);
643  renderer.IndoorsVertices[entityGrid][posInBuffer + 4] = new VertexPositionColorTexture(corners[2] + surfaceOffset2, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
644  renderer.IndoorsVertices[entityGrid][posInBuffer + 5] = new VertexPositionColorTexture(corners[3] + surfaceOffset, renderer.IndoorsSurfaceBottomColor, Vector2.Zero);
645 
646  renderer.PositionInIndoorsBuffer[entityGrid] += 6;
647  }
648  }
649 
650  x += WaveWidth;
651  //clamp the last segment to the right edge of the hull
652  if (i == end - 2)
653  {
654  width -= (int)Math.Max((x + WaveWidth) - (Submarine == null ? rect.Right : (rect.Right + Submarine.DrawPosition.X)), 0);
655  }
656  }
657  }
658 
659  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
660  {
661  if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed hull event: expected {nameof(Hull)}.{nameof(IEventData)}"); }
662 
663  msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
664  switch (eventData)
665  {
666  case StatusEventData statusEventData:
667  SharedStatusWrite(msg);
668  break;
669  case BackgroundSectionsEventData backgroundSectionsEventData:
670  SharedBackgroundSectionsWrite(msg, backgroundSectionsEventData);
671  break;
672  case DecalEventData decalEventData:
673  var decal = decalEventData.Decal;
674  int decalIndex = decals.IndexOf(decal);
675  msg.WriteByte((byte)(decalIndex < 0 ? 255 : decalIndex));
676  msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8);
677  break;
678  default:
679  throw new Exception($"Malformed hull event: did not expect {eventData.GetType().Name}");
680  }
681  }
682 
683  public void ClientEventRead(IReadMessage msg, float sendingTime)
684  {
685  EventType eventType = (EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
686  switch (eventType)
687  {
688  case EventType.Status:
689  remoteOxygenPercentage = msg.ReadRangedSingle(0.0f, 100.0f, 8);
690 
691  SharedStatusRead(
692  msg,
693  out float newWaterVolume,
694  out NetworkFireSource[] newFireSources);
695 
696  remoteWaterVolume = newWaterVolume;
697  remoteFireSources = newFireSources;
698  break;
699  case EventType.BackgroundSections:
700  SharedBackgroundSectionRead(
701  msg,
702  bsnu =>
703  {
704  int i = bsnu.SectionIndex;
705  Color color = bsnu.Color;
706  float colorStrength = bsnu.ColorStrength;
707 
708  var remoteBackgroundSection = remoteBackgroundSections.Find(s => s.Index == i);
709  if (remoteBackgroundSection != null)
710  {
711  remoteBackgroundSection.SetColorStrength(colorStrength);
712  remoteBackgroundSection.SetColor(color);
713  }
714  else
715  {
716  remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), (ushort)i, colorStrength, color, 0));
717  }
718  }, out _);
719  paintAmount = BackgroundSections.Sum(s => s.ColorStrength);
720  break;
721  case EventType.Decal:
722  int decalCount = msg.ReadRangedInteger(0, MaxDecalsPerHull);
723  if (decalCount == 0) { decals.Clear(); }
724  remoteDecals.Clear();
725  for (int i = 0; i < decalCount; i++)
726  {
727  UInt32 decalId = msg.ReadUInt32();
728  int spriteIndex = msg.ReadByte();
729  float normalizedXPos = msg.ReadRangedSingle(0.0f, 1.0f, 8);
730  float normalizedYPos = msg.ReadRangedSingle(0.0f, 1.0f, 8);
731  float decalScale = msg.ReadRangedSingle(0.0f, 2.0f, 12);
732  remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale));
733  }
734  break;
735  case EventType.BallastFlora:
736  BallastFloraBehavior.NetworkHeader header = (BallastFloraBehavior.NetworkHeader) msg.ReadByte();
737  if (header == BallastFloraBehavior.NetworkHeader.Spawn)
738  {
739  Identifier identifier = msg.ReadIdentifier();
740  float x = msg.ReadSingle();
741  float y = msg.ReadSingle();
742  BallastFlora = new BallastFloraBehavior(this, BallastFloraPrefab.Find(identifier), new Vector2(x, y), firstGrowth: true)
743  {
744  PowerConsumptionTimer = msg.ReadSingle()
745  };
746  }
747  else
748  {
749  BallastFlora?.ClientRead(msg, header);
750  }
751  break;
752  default:
753  throw new Exception($"Malformed incoming hull event: {eventType} is not a supported event type");
754  }
755 
756  if (serverUpdateDelay > 0.0f) { return; }
757 
758  ApplyRemoteState();
759  }
760 
761  private void ApplyRemoteState()
762  {
763  foreach (BackgroundSection remoteBackgroundSection in remoteBackgroundSections)
764  {
765  float prevColorStrength = BackgroundSections[remoteBackgroundSection.Index].ColorStrength;
766  BackgroundSections[remoteBackgroundSection.Index].SetColor(remoteBackgroundSection.Color);
767  BackgroundSections[remoteBackgroundSection.Index].SetColorStrength(remoteBackgroundSection.ColorStrength);
768  paintAmount = Math.Max(0, paintAmount + (BackgroundSections[remoteBackgroundSection.Index].ColorStrength - prevColorStrength) / BackgroundSections.Count);
769  }
770  remoteBackgroundSections.Clear();
771 
772  if (remoteDecals.Count > 0)
773  {
774  decals.Clear();
775  foreach (RemoteDecal remoteDecal in remoteDecals)
776  {
777  float decalPosX = MathHelper.Lerp(rect.X, rect.Right, remoteDecal.NormalizedPos.X);
778  float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, remoteDecal.NormalizedPos.Y);
779  if (Submarine != null)
780  {
781  decalPosX += Submarine.Position.X;
782  decalPosY += Submarine.Position.Y;
783  }
784  AddDecal(remoteDecal.DecalId, new Vector2(decalPosX, decalPosY), remoteDecal.Scale, isNetworkEvent: true, spriteIndex: remoteDecal.SpriteIndex);
785  }
786  remoteDecals.Clear();
787  }
788 
789  if (remoteFireSources is null) { return; }
790 
791  WaterVolume = remoteWaterVolume;
792  OxygenPercentage = remoteOxygenPercentage;
793 
794  for (int i = 0; i < remoteFireSources.Length; i++)
795  {
796  Vector2 pos = remoteFireSources[i].Position;
797  float size = remoteFireSources[i].Size;
798 
799  var newFire = i < FireSources.Count ?
800  FireSources[i] :
801  new FireSource(Submarine == null ? pos : pos + Submarine.Position, sourceCharacter: null, isNetworkMessage: true);
802  newFire.Position = pos;
803  newFire.Size = new Vector2(size, newFire.Size.Y);
804 
805  //ignore if the fire wasn't added to this room (invalid position)?
806  if (!FireSources.Contains(newFire))
807  {
808  newFire.Remove();
809  continue;
810  }
811  }
812 
813  for (int i = FireSources.Count - 1; i >= remoteFireSources.Length; i--)
814  {
815  FireSources[i].Remove();
816  if (i < FireSources.Count)
817  {
818  FireSources.RemoveAt(i);
819  }
820  }
821  remoteFireSources = null;
822  }
823  }
824 }
static BallastFloraPrefab Find(Identifier identifier)
float? Zoom
Definition: Camera.cs:78
Matrix Transform
Definition: Camera.cs:136
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
Rectangle WorldView
Definition: Camera.cs:123
void Draw(SpriteBatch spriteBatch, Hull hull, float depth)
IEnumerable< MapEntity > GetAllEntities()
Definition: EntityGrid.cs:119
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
RectTransform RectTransform
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static bool DebugDraw
Definition: GameMain.cs:29
static GameClient Client
Definition: GameMain.cs:188
void ClientEventRead(IReadMessage msg, float sendingTime)
void DrawSectionColors(SpriteBatch spriteBatch)
static void UpdateCheats(float deltaTime, Camera cam)
override bool IsMouseOn(Vector2 position)
override void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
override void UpdateEditing(Camera cam, float deltaTime)
override bool IsVisible(Rectangle worldView)
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
List< DummyFireSource > FakeFireSources
static readonly List< EntityGrid > EntityGrids
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static void UpdateVertices(Camera cam, WaterRenderer renderer)
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
List< BackgroundSection > BackgroundSections
static IEnumerable< MapEntity > HighlightedEntities
static bool KeyDown(InputType inputType)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
static bool IsLayerVisible(MapEntity entity)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
VertexPositionTexture[] vertices
readonly WaterVertexData IndoorsSurfaceBottomColor
Dictionary< EntityGrid, int > PositionInIndoorsBuffer
Dictionary< EntityGrid, VertexPositionColorTexture[]> IndoorsVertices
readonly WaterVertexData IndoorsWaterColor
readonly WaterVertexData IndoorsSurfaceTopColor
Interface for entities that the clients can send events to the server
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)
void WriteRangedSingle(Single val, Single min, Single max, int bitCount)