Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Door.cs
2 using Barotrauma.Lights;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Linq;
8 
10 {
11  partial class Door : Pickable, IDrawableComponent, IServerSerializable
12  {
13  private ConvexHull convexHull;
14  private ConvexHull convexHull2;
15 
16  private float shake;
17  private float shakeTimer;
18  private Vector2 shakePos;
19 
20  //openState when the vertices of the convex hull were last calculated
21  private float lastConvexHullState;
22 
23  [Serialize("1,1", IsPropertySaveable.No, description: "The scale of the shadow-casting area of the door (relative to the actual size of the door).")]
24  public Vector2 ShadowScale
25  {
26  get;
27  set;
28  }
29 
30  public Vector2 DrawSize
31  {
32  //use the extents of the item as the draw size
33  get { return Vector2.Zero; }
34  }
35 
36  private Vector2[] GetConvexHullCorners(Rectangle rect)
37  {
38  Point shadowSize = rect.Size.Multiply(ShadowScale);
39  Vector2 center = new Vector2(rect.Center.X, rect.Y - rect.Height / 2);
40 
41  Vector2[] corners = new Vector2[4];
42  corners[0] = center + new Vector2(-shadowSize.X, -shadowSize.Y) / 2;
43  corners[1] = center + new Vector2(-shadowSize.X, shadowSize.Y) / 2;
44  corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2;
45  corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2;
46 
47  if (IsHorizontal)
48  {
49  if (item.FlippedX)
50  {
51  Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
52  for (int i = 0; i < corners.Length; i++)
53  {
54  corners[i].X = itemCenter.X * 2 - corners[i].X;
55  }
56  Array.Reverse(corners);
57  }
58  }
59  else
60  {
61  if (item.FlippedY)
62  {
63  Vector2 itemCenter = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2);
64  for (int i = 0; i < corners.Length; i++)
65  {
66  corners[i].Y = itemCenter.Y * 2 - corners[i].Y;
67  }
68  Array.Reverse(corners);
69  }
70  }
71 
72  return corners;
73  }
74 
75  private void UpdateConvexHulls()
76  {
77  if (item.Removed) { return; }
78  if (doorSprite == null) { return; }
79 
80  doorRect = new Rectangle(
81  item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
82  item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
83  (int)(doorSprite.size.X * item.Scale),
84  (int)(doorSprite.size.Y * item.Scale));
85 
86  Rectangle rect = doorRect;
88  {
89  rect.Width = (int)(rect.Width * (1.0f - openState));
90  }
91  else
92  {
93  rect.Height = (int)(rect.Height * (1.0f - openState));
94  }
95 
96  if (Window.Height > 0 && Window.Width > 0)
97  {
99  {
100  rect.Width = (int)(Window.X * item.Scale);
101  rect.X -= (int)(doorRect.Width * openState);
102  rect.Width = Math.Max(rect.Width - (doorRect.X - rect.X), 0);
103  rect.X = Math.Max(doorRect.X, rect.X);
104  if (convexHull2 != null)
105  {
106  Rectangle rect2 = doorRect;
107  rect2.X += (int)(Window.Right * item.Scale);
108  rect2.X -= (int)(doorRect.Width * openState);
109  rect2.X = Math.Max(doorRect.X, rect2.X);
110  rect2.Width = doorRect.Right - (int)(doorRect.Width * openState) - rect2.X;
111  if (rect2.Width == 0)
112  {
113  convexHull2.Enabled = false;
114  }
115  else
116  {
117  convexHull2.Enabled = true;
118  SetVertices(convexHull2, rect2);
119  }
120  }
121  }
122  else
123  {
124  rect.Height = -(int)(Window.Y * item.Scale);
125  rect.Y += (int)(doorRect.Height * openState);
126  rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
127  rect.Y = Math.Min(doorRect.Y, rect.Y);
128  if (convexHull2 != null)
129  {
130  Rectangle rect2 = doorRect;
131  rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale);
132  rect2.Y += (int)(doorRect.Height * openState);
133  rect2.Y = Math.Min(doorRect.Y, rect2.Y);
134  rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
135  if (rect2.Height == 0)
136  {
137  convexHull2.Enabled = false;
138  }
139  else
140  {
141  convexHull2.Enabled = true;
142  SetVertices(convexHull2, rect2);
143  }
144  }
145  }
146  }
147 
148  if (convexHull == null) { return; }
149 
150  if (rect.Height == 0 || rect.Width == 0)
151  {
152  convexHull.Enabled = false;
153  }
154  else
155  {
156  convexHull.Enabled = true;
157  SetVertices(convexHull, rect);
158  }
159  }
160 
161 
162  private void SetVertices(ConvexHull convexHull, Rectangle rect)
163  {
164  var verts = GetConvexHullCorners(rect);
165  Vector2 center = (verts[0] + verts[2]) / 2;
166  convexHull.SetVertices(
167  verts,
169  new Vector2[] { new Vector2(verts[0].X, center.Y), new Vector2(verts[2].X, center.Y) } :
170  new Vector2[] { new Vector2(center.X, verts[0].Y), new Vector2(center.X, verts[2].Y) });
171  convexHull.MaxMergeLosVerticesDist = 35.0f;
172  }
173 
174  partial void UpdateProjSpecific(float deltaTime)
175  {
176  if (shakeTimer > 0.0f)
177  {
178  shakeTimer -= deltaTime;
179  Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0.5f) - 0.5f);
180  shakePos = noisePos * shake * 2.0f;
181  shake = Math.Min(shake, shakeTimer * 10.0f);
182  }
183  else
184  {
185  shakePos = Vector2.Zero;
186  }
187  }
188 
189  public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
190  {
191  Color color = overrideColor ?? item.GetSpriteColor(withHighlight: true);
192  if (brokenSprite == null)
193  {
194  //broken doors turn black if no broken sprite has been configured
195  color = color.Multiply(item.Condition / item.MaxCondition);
196  color.A = 255;
197  }
198 
199  if (stuck > 0.0f && weldedSprite != null)
200  {
201  Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos;
202  if (item.Submarine != null) { weldSpritePos += item.Submarine.DrawPosition; }
203  weldSpritePos.Y = -weldSpritePos.Y;
204 
205  weldedSprite.Draw(spriteBatch,
206  weldSpritePos, overrideColor ?? (item.SpriteColor * (stuck / 100.0f)), scale: item.Scale);
207  }
208 
209  if (openState >= 1.0f) { return; }
210 
211  Vector2 pos;
212  if (IsHorizontal)
213  {
214  pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2);
215  if (item.FlippedX) { pos.X += (int)(doorSprite.size.X * item.Scale * openState); }
216  }
217  else
218  {
219  pos = new Vector2(item.Rect.Center.X, item.Rect.Y);
220  if (item.FlippedY) { pos.Y -= (int)(doorSprite.size.Y * item.Scale * openState); }
221  }
222 
223  pos += shakePos;
224  if (item.Submarine != null) { pos += item.Submarine.DrawPosition; }
225  pos.Y = -pos.Y;
226 
227  if (brokenSprite == null || !IsBroken)
228  {
229  if (doorSprite?.Texture != null)
230  {
231  spriteBatch.Draw(doorSprite.Texture, pos,
232  getSourceRect(doorSprite, openState, IsHorizontal),
233  color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
234  }
235  }
236 
237  float maxCondition = item.Repairables.Any() ?
238  item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition :
240  float healthRatio = item.Health / maxCondition;
241  if (brokenSprite?.Texture != null && healthRatio < 1.0f)
242  {
243  Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One;
244  if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; }
245  float alpha = fadeBrokenSprite ? 1.0f - healthRatio : 1.0f;
246  spriteBatch.Draw(brokenSprite.Texture, pos,
247  getSourceRect(brokenSprite, openState, IsHorizontal),
248  color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects,
249  brokenSprite.Depth);
250  }
251 
252  static Rectangle getSourceRect(Sprite sprite, float openState, bool horizontal)
253  {
254  if (horizontal)
255  {
256  return new Rectangle(
257  (int)(sprite.SourceRect.X + sprite.size.X * openState),
258  sprite.SourceRect.Y,
259  (int)(sprite.size.X * (1.0f - openState)),
260  (int)sprite.size.Y);
261  }
262  else
263  {
264  return new Rectangle(
265  sprite.SourceRect.X,
266  (int)(sprite.SourceRect.Y + sprite.size.Y * openState),
267  (int)sprite.size.X,
268  (int)(sprite.size.Y * (1.0f - openState)));
269  }
270  }
271  }
272 
273  partial void OnFailedToOpen()
274  {
275  if (shakeTimer <= 0.0f)
276  {
277  PlaySound(ActionType.OnFailure);
278  shake = 5.0f;
279  shakeTimer = 1.0f;
280  }
281  }
282 
283  partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
284  {
285  if ((IsStuck && !isNetworkMessage) ||
286  (PredictedState == null && isOpen == open) ||
287  (PredictedState != null && isOpen == PredictedState.Value && isOpen == open))
288  {
289  return;
290  }
291 
292  if (GameMain.Client != null && !isNetworkMessage)
293  {
294  bool stateChanged = open != PredictedState;
295 
296  //clients can "predict" that the door opens/closes when a signal is received
297  //the prediction will be reset after 1 second, setting the door to a state
298  //sent by the server, or reverting it back to its old state if no msg from server was received
299  PredictedState = open;
300  resetPredictionTimer = CorrectionDelay;
301  if (stateChanged && !IsBroken)
302  {
303  PlayInteractionSound();
304  }
305  }
306  else
307  {
308  bool stateChanged = open != isOpen;
309  isOpen = open;
310  if (!isNetworkMessage || open != PredictedState)
311  {
312  StopPicking(null);
313  if (!IsBroken)
314  {
315  PlayInteractionSound();
316  }
317  if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); }
318  }
319  if (stateChanged)
320  {
321  ActionType actionType = open ? ActionType.OnOpen : ActionType.OnClose;
322  item.ApplyStatusEffects(actionType, deltaTime: 1.0f);
323  }
324  }
325 
326  void PlayInteractionSound()
327  {
328  ActionType actionType = ActionType.OnUse;
329  if (forcedOpen)
330  {
331  actionType = ActionType.OnPicked;
332  }
333  else
334  {
335  if (open && HasSoundsOfType[(int)ActionType.OnOpen])
336  {
337  actionType = ActionType.OnOpen;
338  }
339  else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
340  {
341  actionType = ActionType.OnClose;
342  }
343  }
344  PlaySound(actionType);
345  }
346  }
347 
348  public override void ClientEventRead(IReadMessage msg, float sendingTime)
349  {
350  base.ClientEventRead(msg, sendingTime);
351 
352  bool open = msg.ReadBoolean();
353  bool broken = msg.ReadBoolean();
354  bool forcedOpen = msg.ReadBoolean();
355  bool isStuck = msg.ReadBoolean();
356  bool isJammed = msg.ReadBoolean();
357  SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
358  stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
359  UInt16 lastUserID = msg.ReadUInt16();
360  Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character;
361  if (user != lastUser)
362  {
363  lastUser = user;
364  toggleCooldownTimer = ToggleCoolDown;
365  }
366  this.isStuck = isStuck;
367  this.isJammed = isJammed;
368  if (isStuck) { OpenState = 0.0f; }
369  IsBroken = broken;
370  PredictedState = null;
371  }
372  }
373 }
Submarine Submarine
Definition: Entity.cs:53
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, Vector2? worldPosition=null)
Executes all StatusEffects of the specified type. Note that condition checks are ignored here: that s...
Color GetSpriteColor(Color? defaultColor=null, bool withHighlight=false)
override void ClientEventRead(IReadMessage msg, float sendingTime)
void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth=-1, Color? overrideColor=null)
void StopPicking(Character picker)
Definition: Pickable.cs:232
void SetVertices(Vector2[] points, Vector2[] losPoints, bool mergeOverlappingSegments=true, Matrix? rotationMatrix=null)
Definition: ConvexHull.cs:392
float? MaxMergeLosVerticesDist
Overrides the maximum distance a LOS vertex can be moved to make it align with a nearby LOS segment
Definition: ConvexHull.cs:104
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19