Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Map/Structure.cs
2 using Barotrauma.Lights;
4 using FarseerPhysics;
5 using FarseerPhysics.Dynamics;
6 using FarseerPhysics.Dynamics.Contacts;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 using System;
10 using System.Collections.Generic;
11 using System.Linq;
12 
13 namespace Barotrauma
14 {
15  partial class Structure : MapEntity, IDamageable, IServerSerializable
16  {
17  public static bool ShowWalls = true, ShowStructures = true;
18 
19  private List<ConvexHull> convexHulls;
20 
21  private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>();
22 
23  public readonly List<LightSource> Lights = new List<LightSource>();
24 
25  public override bool SelectableInEditor
26  {
27  get
28  {
30  {
31  return false;
32  }
33 
34  if (!SubEditorScreen.IsLayerVisible(this)) { return false; }
35 
36  return HasBody ? ShowWalls : ShowStructures;
37  }
38  }
39 
40  partial void InitProjSpecific()
41  {
42  Prefab.Sprite?.EnsureLazyLoaded();
43  Prefab.BackgroundSprite?.EnsureLazyLoaded();
44 
45  foreach (var decorativeSprite in Prefab.DecorativeSprites)
46  {
47  decorativeSprite.Sprite.EnsureLazyLoaded();
48  spriteAnimState.Add(decorativeSprite, new DecorativeSprite.State());
49  }
50 
51  UpdateSpriteStates(0.0f);
52  }
53 
54  public static Vector2 UpgradeTextureOffset(
55  Vector2 targetSize,
56  Vector2 originalTextureOffset,
57  SubmarineInfo submarineInfo,
58  Rectangle sourceRect,
59  Vector2 scale,
60  bool flippedX,
61  bool flippedY)
62  {
64  {
65  // Tiled sprite rendering was significantly changed after v1.2.3.0:
66  // Rendering flipped, scaled and offset textures was completely broken,
67  // but some existing community submarines depend on that old behavior,
68  // so let's redo some of the broken logic here if the sub is old enough
69 
70  Vector2 flipper = (flippedX ? -1f : 1f, flippedY ? -1f : 1f);
71 
72  var textureOffset = originalTextureOffset * flipper;
73 
74  textureOffset = new Vector2(
75  MathUtils.PositiveModulo((int)-textureOffset.X, sourceRect.Width),
76  MathUtils.PositiveModulo((int)-textureOffset.Y, sourceRect.Height));
77 
78  textureOffset.X = (textureOffset.X / scale.X) % sourceRect.Width;
79  textureOffset.Y = (textureOffset.Y / scale.Y) % sourceRect.Height;
80 
81  Vector2 flippedDrawOffset = Vector2.Zero;
82  if (flippedX)
83  {
84  float diff = targetSize.X % (sourceRect.Width * scale.X);
85  flippedDrawOffset.X = (sourceRect.Width * scale.X - diff) / scale.X;
86  flippedDrawOffset.X =
87  MathUtils.NearlyEqual(flippedDrawOffset.X, MathF.Round(flippedDrawOffset.X)) ?
88  MathF.Round(flippedDrawOffset.X) : flippedDrawOffset.X;
89  }
90  if (flippedY)
91  {
92  float diff = targetSize.Y % (sourceRect.Height * scale.Y);
93  flippedDrawOffset.Y = (sourceRect.Height * scale.Y - diff) / scale.Y;
94  flippedDrawOffset.Y =
95  MathUtils.NearlyEqual(flippedDrawOffset.Y, MathF.Round(flippedDrawOffset.Y)) ?
96  MathF.Round(flippedDrawOffset.Y) : flippedDrawOffset.Y;
97  }
98 
99  var textureOffsetPlusFlipBs = textureOffset + flippedDrawOffset;
100 
101  if (textureOffsetPlusFlipBs.X > sourceRect.Width)
102  {
103  var diff = textureOffsetPlusFlipBs.X - sourceRect.Width;
104  textureOffset.X = (textureOffset.X + diff * (scale.X - 1f)) % sourceRect.Width;
105  }
106  if (textureOffsetPlusFlipBs.Y > sourceRect.Height)
107  {
108  var diff = textureOffsetPlusFlipBs.Y - sourceRect.Height;
109  textureOffset.Y = (textureOffset.Y + diff * (scale.Y - 1f)) % sourceRect.Height;
110  }
111 
112  textureOffset *= scale * flipper;
113 
114  return -textureOffset;
115  }
116 
117  return originalTextureOffset;
118  }
119 
120  partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation)
121  {
122  if (!CastShadow) { return; }
123 
124  convexHulls ??= new List<ConvexHull>();
125 
126  //if the convex hull is longer than this, we need to split it to multiple parts
127  //very large convex hulls don't play nicely with the lighting or LOS, because the shadow cast
128  //by the convex hull would need to be extruded very far to cover the whole screen
129  const float MaxConvexHullLength = 1024.0f;
130  float length = IsHorizontal ? size.X : size.Y;
131  int convexHullCount = (int)Math.Max(1, Math.Ceiling(length / MaxConvexHullLength));
132 
133  Vector2 sectionSize = size;
134  if (convexHullCount > 1)
135  {
136  if (IsHorizontal)
137  {
138  sectionSize.X = length / convexHullCount;
139  }
140  else
141  {
142  sectionSize.Y = length / convexHullCount;
143  }
144  }
145 
146  for (int i = 0; i < convexHullCount; i++)
147  {
148  Vector2 offset =
149  (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) *
150  (i * length / convexHullCount);
151 
152  var h = new ConvexHull(
153  new Rectangle((position - size / 2 + offset).ToPoint(), sectionSize.ToPoint()),
154  IsHorizontal,
155  this);
156  if (Math.Abs(rotation) > 0.001f)
157  {
158  h.Rotate(position, rotation);
159  }
160  convexHulls.Add(h);
161  }
162  }
163 
164  public override void UpdateEditing(Camera cam, float deltaTime)
165  {
166  if (editingHUD == null || editingHUD.UserData as Structure != this)
167  {
169  }
170  }
171 
172  private void SetLightTextureOffset()
173  {
174  Vector2 textOffset = textureOffset;
175  if (FlippedX) { textOffset.X = -textOffset.X; }
176  if (FlippedY) { textOffset.Y = -textOffset.Y; }
177 
178  foreach (LightSource light in Lights)
179  {
180  Vector2 bgOffset = new Vector2(
181  MathUtils.PositiveModulo(-textOffset.X, light.texture.Width),
182  MathUtils.PositiveModulo(-textOffset.Y, light.texture.Height));
183 
184  light.LightTextureOffset = bgOffset;
185  }
186  }
187 
188  public GUIComponent CreateEditingHUD(bool inGame = false)
189  {
190  int heightScaled = (int)(20 * GUI.Scale);
191  editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
192  GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
193  {
194  CanTakeKeyBoardFocus = false
195  };
196  var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont) { UserData = this };
197 
198  if (editor.Fields.TryGetValue(nameof(Scale).ToIdentifier(), out GUIComponent[] scaleFields) &&
199  scaleFields.FirstOrDefault() is GUINumberInput scaleInput)
200  {
201  //texture offset needs to be adjusted when scaling the entity to keep the look of the entity unchanged
202  scaleInput.OnValueChanged += (GUINumberInput numberInput) =>
203  {
205  };
206  }
207 
208  if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule)
209  {
210  GUITickBox tickBox = new GUITickBox(new RectTransform(new Point(listBox.Content.Rect.Width, 10)), TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.name"))
211  {
212  Font = GUIStyle.SmallFont,
214  ToolTip = TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.description"),
215  OnSelected = (tickBox) =>
216  {
218  return true;
219  }
220  };
221  editor.AddCustomContent(tickBox, 1);
222  }
223 
224  if (!Layer.IsNullOrEmpty())
225  {
226  var layerText = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)) { MinSize = new Point(0, heightScaled) }, TextManager.AddPunctuation(':', TextManager.Get("editor.layer"), Layer));
227  editor.AddCustomContent(layerText, 1);
228  }
229 
230  var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
231  {
232  Stretch = true,
233  RelativeSpacing = 0.01f
234  };
235 
236  var mirrorX = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
237  {
238  ToolTip = TextManager.Get("MirrorEntityXToolTip"),
239  OnClicked = (button, data) =>
240  {
241  foreach (MapEntity me in SelectedList)
242  {
243  me.FlipX(relativeToSub: false);
244  }
245  if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
246  ColorFlipButton(button, FlippedX);
247  return true;
248  }
249  };
250  ColorFlipButton(mirrorX, FlippedX);
251  var mirrorY = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall")
252  {
253  ToolTip = TextManager.Get("MirrorEntityYToolTip"),
254  OnClicked = (button, data) =>
255  {
256  foreach (MapEntity me in SelectedList)
257  {
258  me.FlipY(relativeToSub: false);
259  }
260  if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
261  ColorFlipButton(button, FlippedY);
262  return true;
263  }
264  };
265  ColorFlipButton(mirrorY, FlippedY);
266  new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall")
267  {
268  OnClicked = (button, data) =>
269  {
270  Sprite.ReloadXML();
272  return true;
273  }
274  };
275  new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ResetToPrefab"), style: "GUIButtonSmall")
276  {
277  OnClicked = (button, data) =>
278  {
279  foreach (MapEntity me in SelectedList)
280  {
281  (me as Item)?.Reset();
282  (me as Structure)?.Reset();
283  }
284  if (!SelectedList.Contains(this)) { Reset(); }
286  return true;
287  }
288  };
289  buttonContainer.RectTransform.Resize(new Point(buttonContainer.Rect.Width, buttonContainer.RectTransform.Children.Max(c => c.MinSize.Y)));
290  buttonContainer.RectTransform.IsFixedSize = true;
291  GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(b => ((GUIButton)b).TextBlock));
292  editor.AddCustomContent(buttonContainer, editor.ContentCount);
293 
295 
296  return editingHUD;
297  }
298 
299  partial void OnImpactProjSpecific(Fixture f1, Fixture f2, Contact contact)
300  {
301  if (!Prefab.Platform && Prefab.StairDirection == Direction.None)
302  {
303  Vector2 pos = ConvertUnits.ToDisplayUnits(f2.Body.Position);
304 
305  int section = FindSectionIndex(pos);
306  if (section > -1)
307  {
308  Vector2 normal = contact.Manifold.LocalNormal;
309 
310  float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal) * f2.Body.Mass * 0.1f;
311  if (impact > 10.0f)
312  {
313  SoundPlayer.PlayDamageSound("StructureBlunt", impact, SectionPosition(section, true), tags: Tags);
314  }
315  }
316  }
317  }
318 
319  public override bool IsVisible(Rectangle worldView)
320  {
321  RectangleF worldRect = Quad2D.FromSubmarineRectangle(WorldRect).Rotated(
322  FlippedX != FlippedY
323  ? rotationRad
324  : -rotationRad).BoundingAxisAlignedRectangle;
325  Vector2 worldPos = WorldPosition;
326 
327  Vector2 min = new Vector2(worldRect.X, worldRect.Y);
328  Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
329  foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
330  {
331  float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
332  min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale, min.X);
333  max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale, max.X);
334  min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
335  max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
336  }
337  Vector2 offset = GetCollapseEffectOffset();
338  min += offset;
339  max += offset;
340 
341  if (min.X > worldView.Right || max.X < worldView.X) { return false; }
342  if (min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
343 
344  return true;
345  }
346 
347  public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
348  {
349  if (Prefab.Sprite == null) { return; }
350 
351  if (editing)
352  {
353  if (!SubEditorScreen.IsLayerVisible(this)) { return; }
354  if (!HasBody && !ShowStructures) { return; }
355  if (HasBody && !ShowWalls) { return; }
356  }
357 
358  Draw(spriteBatch, editing, back, null);
359  }
360 
361  public void DrawDamage(SpriteBatch spriteBatch, Effect damageEffect, bool editing)
362  {
363  Draw(spriteBatch, editing, false, damageEffect);
364  }
365 
366  private float GetRealDepth()
367  {
368  return SpriteDepthOverrideIsSet ? SpriteOverrideDepth : Prefab.Sprite.Depth;
369  }
370 
371  public float GetDrawDepth()
372  {
373  return GetDrawDepth(GetRealDepth(), Prefab.Sprite);
374  }
375 
376  private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null)
377  {
378  if (Prefab.Sprite == null) { return; }
379  if (editing)
380  {
381  if (!SubEditorScreen.IsLayerVisible(this)) { return; }
382  if (!HasBody && !ShowStructures) { return; }
383  if (HasBody && !ShowWalls) { return; }
384  }
385  else if (IsHidden)
386  {
387  return;
388  }
389 
390  Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : IsHighlighted ? GUIStyle.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor;
391 
392  if (IsSelected && editing)
393  {
394  //color = Color.Lerp(color, Color.Gold, 0.5f);
395  color = spriteColor;
396 
397  Vector2 rectSize = rect.Size.ToVector2();
398  if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
399  if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
400 
401  Vector2 bodyPos = WorldPosition + BodyOffset * Scale;
402 
403  GUI.DrawRectangle(sb: spriteBatch,
404  center: new Vector2(bodyPos.X, -bodyPos.Y),
405  width: rectSize.X,
406  height: rectSize.Y,
407  rotation: BodyRotation,
408  clr: Color.White,
409  thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
410  }
411 
412  bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
413 
414  if (isWiringMode) { color *= 0.15f; }
415 
416  Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
417  drawOffset += GetCollapseEffectOffset();
418 
419  float depth = GetDrawDepth();
420 
421  Vector2 textureOffset = this.textureOffset;
422 
423  if (back && damageEffect == null && !isWiringMode)
424  {
425  if (Prefab.BackgroundSprite != null)
426  {
427  Vector2 dropShadowOffset = Vector2.Zero;
428  if (UseDropShadow)
429  {
430  dropShadowOffset = DropShadowOffset;
431  if (dropShadowOffset == Vector2.Zero)
432  {
433  if (Submarine == null)
434  {
435  dropShadowOffset = Vector2.UnitY * 10.0f;
436  }
437  else
438  {
439  dropShadowOffset = IsHorizontal ?
440  new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) :
441  new Vector2(Math.Sign(Submarine.HiddenSubPosition.X - Position.X) * 10.0f, 0.0f);
442  }
443  }
444  dropShadowOffset.Y = -dropShadowOffset.Y;
445  }
446 
447  Vector2 backGroundOffset = new Vector2(
448  MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale),
449  MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale));
450 
451  float rotationRad = rotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
452 
454  spriteBatch,
455  new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)),
456  new Vector2(rect.Width, rect.Height),
457  rotation: rotationRad,
458  origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
461  startOffset: backGroundOffset,
462  depth: Math.Max(GetDrawDepth(Prefab.BackgroundSprite.Depth, Prefab.BackgroundSprite), depth + 0.000001f),
463  spriteEffects: Prefab.BackgroundSprite.effects ^ SpriteEffects);
464 
465  if (UseDropShadow)
466  {
468  spriteBatch,
469  new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)) + dropShadowOffset,
470  new Vector2(rect.Width, rect.Height),
471  rotation: rotationRad,
472  origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
473  color: Color.Black * 0.5f,
475  startOffset: backGroundOffset,
476  depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f,
477  spriteEffects: Prefab.BackgroundSprite.effects ^ SpriteEffects);
478  }
479  }
480  }
481 
482  if (back == GetRealDepth() > 0.5f)
483  {
484  Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY();
485  Vector2 advanceY = advanceX.YX().FlipX();
486  if (FlippedX != FlippedY)
487  {
488  advanceX = advanceX.FlipY();
489  advanceY = advanceY.FlipX();
490  }
491 
492  float sectionSpriteRotationRad = rotationForSprite(this.rotationRad, Prefab.Sprite);
493 
494  for (int i = 0; i < Sections.Length; i++)
495  {
496  Rectangle drawSection = Sections[i].rect;
497  if (damageEffect != null)
498  {
499  float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
500 
501  if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
502  {
503  damageEffect.Parameters["aCutoff"].SetValue(newCutoff);
504  damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f);
505  damageEffect.Parameters["inColor"].SetValue(color.ToVector4());
506 
507  damageEffect.CurrentTechnique.Passes[0].Apply();
508 
509  Submarine.DamageEffectCutoff = newCutoff;
511  }
512  }
513  if (!HasDamage && i == 0)
514  {
515  drawSection = new Rectangle(
516  drawSection.X,
517  drawSection.Y,
518  Sections[Sections.Length - 1].rect.Right - drawSection.X,
519  drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height));
520  i = Sections.Length;
521  }
522 
523  Vector2 sectionOffset = new Vector2(
524  Math.Abs(rect.Location.X - drawSection.Location.X),
525  Math.Abs(rect.Location.Y - drawSection.Location.Y));
526 
527  if (FlippedX && IsHorizontal) { sectionOffset.X = rect.Right - drawSection.Right; }
528  if (FlippedY && !IsHorizontal) { sectionOffset.Y = (drawSection.Y - drawSection.Height) - (rect.Y - rect.Height); }
529 
530  sectionOffset.X += MathUtils.PositiveModulo(-textureOffset.X, Prefab.Sprite.SourceRect.Width * TextureScale.X * Scale);
531  sectionOffset.Y += MathUtils.PositiveModulo(-textureOffset.Y, Prefab.Sprite.SourceRect.Height * TextureScale.Y * Scale);
532 
533  Vector2 pos = new Vector2(drawSection.X, drawSection.Y);
534  pos -= rect.Location.ToVector2();
535  pos = advanceX * pos.X + advanceY * pos.Y;
536  pos += rect.Location.ToVector2();
537  pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y));
538 
540  spriteBatch,
541  pos,
542  new Vector2(drawSection.Width, drawSection.Height),
543  rotation: sectionSpriteRotationRad,
544  origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
545  color: color,
546  startOffset: sectionOffset,
547  depth: depth,
549  spriteEffects: Prefab.Sprite.effects ^ SpriteEffects);
550  }
551 
552  foreach (var decorativeSprite in Prefab.DecorativeSprites)
553  {
554  if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
555  float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad;
556  Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
557  Vector2 drawPos = DrawPosition + MathUtils.RotatePoint(offset, -this.rotationRad);
558  decorativeSprite.Sprite.Draw(
559  spriteBatch: spriteBatch,
560  pos: drawPos.FlipY(),
561  color: color,
562  rotate: rotation,
563  scale: decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
564  spriteEffect: Prefab.Sprite.effects ^ SpriteEffects,
565  depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - Prefab.Sprite.Depth), 0.999f));
566  }
567  }
568 
569  static float rotationForSprite(float rotationRad, Sprite sprite)
570  {
571  if (sprite.effects.HasFlag(SpriteEffects.FlipHorizontally) != sprite.effects.HasFlag(SpriteEffects.FlipVertically))
572  {
574  }
575  return rotationRad;
576  }
577 
578  if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.5f)
579  {
580  if (Bodies != null)
581  {
582  foreach (var body in Bodies)
583  {
584  Vector2 pos = ConvertUnits.ToDisplayUnits(body.Position);
585  if (Submarine != null) { pos += Submarine.DrawPosition; }
586  pos.Y = -pos.Y;
587  var dimensions = bodyDimensions[body];
588  GUI.DrawRectangle(spriteBatch,
589  pos,
590  ConvertUnits.ToDisplayUnits(dimensions.X),
591  ConvertUnits.ToDisplayUnits(dimensions.Y),
592  -body.Rotation, Color.White);
593  }
594  }
595 
596  if (SectionCount > 0 && HasBody)
597  {
598  for (int i = 0; i < SectionCount; i++)
599  {
600  if (GetSection(i).damage > 0)
601  {
602  var textPos = SectionPosition(i, true);
603  textPos.Y = -textPos.Y;
604  GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / MaxHealth) * 100f) + "%", Color.Yellow);
605  }
606  }
607  }
608  }
609  }
610 
611  public void UpdateSpriteStates(float deltaTime)
612  {
613  if (Prefab.DecorativeSpriteGroups.Count == 0) { return; }
614  DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
615  foreach (int spriteGroup in Prefab.DecorativeSpriteGroups.Keys)
616  {
617  for (int i = 0; i < Prefab.DecorativeSpriteGroups[spriteGroup].Length; i++)
618  {
619  var decorativeSprite = Prefab.DecorativeSpriteGroups[spriteGroup][i];
620  if (decorativeSprite == null) { continue; }
621  if (spriteGroup > 0)
622  {
623  int activeSpriteIndex = ID % Prefab.DecorativeSpriteGroups[spriteGroup].Length;
624  if (i != activeSpriteIndex)
625  {
626  spriteAnimState[decorativeSprite].IsActive = false;
627  continue;
628  }
629  }
630 
631  //check if the sprite is active (whether it should be drawn or not)
632  var spriteState = spriteAnimState[decorativeSprite];
633  spriteState.IsActive = true;
634  foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
635  {
636  if (!ConditionalMatches(conditional))
637  {
638  spriteState.IsActive = false;
639  break;
640  }
641  }
642  if (!spriteState.IsActive) { continue; }
643 
644  //check if the sprite should be animated
645  bool animate = true;
646  foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
647  {
648  if (!ConditionalMatches(conditional)) { animate = false; break; }
649  }
650  if (!animate) { continue; }
651  spriteState.OffsetState += deltaTime;
652  spriteState.RotationState += deltaTime;
653  }
654  }
655  }
656 
657  private bool ConditionalMatches(PropertyConditional conditional)
658  {
659  if (!string.IsNullOrEmpty(conditional.TargetItemComponent))
660  {
661  return false;
662  }
663  else
664  {
665  if (!conditional.Matches(this)) { return false; }
666  }
667  return true;
668  }
669 
670  public void ClientEventRead(IReadMessage msg, float sendingTime)
671  {
672  byte sectionCount = msg.ReadByte();
673 
674  bool invalidMessage = false;
675  if (sectionCount != Sections.Length)
676  {
677  invalidMessage = true;
678  string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
679  GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
680  throw new Exception(errorMsg);
681  }
682 
683  for (int i = 0; i < sectionCount; i++)
684  {
685  float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
686  if (!invalidMessage && i < Sections.Length)
687  {
688  SetDamage(i, damage, isNetworkEvent: true);
689  }
690  }
691  }
692  }
693 }
static void UpdateSpriteStates(ImmutableDictionary< int, ImmutableArray< DecorativeSprite >> spriteGroups, Dictionary< DecorativeSprite, State > animStates, int entityID, float deltaTime, Func< PropertyConditional, bool > checkConditional)
float GetScale(float randomScaleModifier)
virtual Vector2 DrawPosition
Definition: Entity.cs:51
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
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
override bool Selected
Definition: GUITickBox.cs:18
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
virtual void FlipX(bool relativeToSub)
Flip the entity horizontally
virtual void FlipY(bool relativeToSub)
Flip the entity vertically
static void ColorFlipButton(GUIButton btn, bool flip)
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
bool Matches(ISerializableEntity? target)
readonly string TargetItemComponent
If set to the name of one of the target's ItemComponents, the conditionals defined by this element ch...
static readonly Version LastBrokenTiledSpriteGameVersion
Last version of the game that had broken handling of sprites that were scaled, flipped and offset
void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation=0f, Vector2? origin=null, Color? color=null, Vector2? startOffset=null, Vector2? textureScale=null, float? depth=null, SpriteEffects? spriteEffects=null)
void ReloadXML()
Works only if there is a name attribute defined for the sprite. For items and structures,...
override void FlipX(bool relativeToSub)
Flip the entity horizontally
void ClientEventRead(IReadMessage msg, float sendingTime)
float BodyRotation
In radians, takes flipping into account
Vector2 BodyOffset
Offset of the physics body from the center of the structure. Takes flipping into account.
Structure(Rectangle rectangle, StructurePrefab sp, Submarine submarine, ushort id=Entity.NullEntityID, XElement element=null)
void SetDamage(int sectionIndex, float damage, Character attacker=null, bool createNetworkEvent=true, bool isNetworkEvent=true, bool createExplosionEffect=true, bool createWallDamageProjectiles=false)
Vector2 SectionPosition(int sectionIndex, bool world=false)
override bool IsVisible(Rectangle worldView)
void DrawDamage(SpriteBatch spriteBatch, Effect damageEffect, bool editing)
static Vector2 UpgradeTextureOffset(Vector2 targetSize, Vector2 originalTextureOffset, SubmarineInfo submarineInfo, Rectangle sourceRect, Vector2 scale, bool flippedX, bool flippedY)
override void FlipY(bool relativeToSub)
Flip the entity vertically
override void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
GUIComponent CreateEditingHUD(bool inGame=false)
override void UpdateEditing(Camera cam, float deltaTime)
readonly ImmutableArray< DecorativeSprite > DecorativeSprites
static bool IsLayerVisible(MapEntity entity)
bool IsSubcategoryHidden(string subcategory)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Interface for entities that the server can send events to the clients