Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Repairable.cs
3 using Barotrauma.Sounds;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 
10 {
11  partial class Repairable : ItemComponent, IDrawableComponent
12  {
13  public GUIButton RepairButton { get; private set; }
14 
15  public GUIButton SabotageButton { get; private set; }
16 
17  public GUIButton TinkerButton { get; private set; }
18 
19  private GUIProgressBar progressBar;
20 
21  private GUITextBlock progressBarOverlayText;
22 
23  private GUILayoutGroup extraButtonContainer;
24 
25  private GUIComponent skillTextContainer;
26 
27  private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
28  //the corresponding particle emitter is active when the condition is within this range
29  private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
30 
31  private SoundChannel repairSoundChannel;
32 
33  private LocalizedString repairButtonText, repairingText;
34  private LocalizedString sabotageButtonText, sabotagingText;
35  private LocalizedString tinkerButtonText, tinkeringText;
36 
37  private FixActions requestStartFixAction;
38 
39  private bool qteSuccess;
40 
41  private float qteTimer;
42  private const float QteDuration = 0.5f;
43  private float qteCooldown;
44  private const float QteCooldownDuration = 0.5f;
45 
46  public float FakeBrokenTimer;
47 
48  [Serialize("", IsPropertySaveable.No, description: "An optional description of the needed repairs displayed in the repair interface.")]
49  public string Description
50  {
51  get;
52  set;
53  }
54 
55  public Vector2 DrawSize
56  {
57  //use the extents of the item as the draw size
58  get { return Vector2.Zero; }
59  }
60 
61  public override bool ShouldDrawHUD(Character character)
62  {
63  if (item.IsHidden) { return false; }
64  if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
65  if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
67 
68  if (CurrentFixer == character)
69  {
71  {
72  return true;
73  }
74  }
75  if (IsTinkerable(character)) { return true; }
76 
77  return false;
78  }
79 
80  partial void InitProjSpecific(ContentXElement element)
81  {
82  CreateGUI();
83  foreach (var subElement in element.Elements())
84  {
85  switch (subElement.Name.ToString().ToLowerInvariant())
86  {
87  case "emitter":
88  case "particleemitter":
89  particleEmitters.Add(new ParticleEmitter(subElement));
90  float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f);
91  float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f);
92 
93  if (maxCondition < minCondition)
94  {
95  DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition.");
96  float temp = maxCondition;
97  maxCondition = minCondition;
98  minCondition = temp;
99  }
100  particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition));
101 
102  break;
103  }
104  }
105  }
106 
107  private void RecreateGUI()
108  {
109  if (GuiFrame != null)
110  {
113  CreateGUI();
114  }
115  }
116 
117  protected override void CreateGUI()
118  {
119  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
120  {
121  Stretch = true,
122  RelativeSpacing = 0.05f,
123  CanBeFocused = true
124  };
125 
126  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform),
127  header, textAlignment: Alignment.TopCenter, font: GUIStyle.LargeFont);
128 
129  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
130  Description, font: GUIStyle.SmallFont, wrap: true);
131 
132  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
133  TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont);
134  skillTextContainer = paddedFrame;
135  for (int i = 0; i < RequiredSkills.Count; i++)
136  {
137  var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform),
138  " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + RequiredSkills[i].Identifier), ((int) Math.Round(RequiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
139  font: GUIStyle.SmallFont)
140  {
141  UserData = RequiredSkills[i]
142  };
143  }
144 
145  var progressBarHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
146  {
147  Stretch = true,
148  RelativeSpacing = 0.02f
149  };
150 
151  progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
152  color: GUIStyle.Green, barSize: 0.0f, style: "DeviceProgressBar");
153 
154  progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)
155  {
156  IgnoreLayoutGroups = true
157  };
158 
159  qteTimer = QteDuration;
160 
161  repairButtonText = TextManager.Get("RepairButton");
162  repairingText = TextManager.Get("Repairing");
163  RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
164  {
165  UserData = UIHighlightAction.ElementId.RepairButton,
166  OnClicked = (btn, obj) =>
167  {
168  requestStartFixAction = FixActions.Repair;
169  item.CreateClientEvent(this);
170  return true;
171  },
172  OnButtonDown = () =>
173  {
174  QTEAction();
175  return true;
176  }
177  };
179  progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize;
181 
182  extraButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), isHorizontal: true)
183  {
184  IgnoreLayoutGroups = true,
185  Stretch = true,
186  AbsoluteSpacing = GUI.IntScale(5)
187  };
188 
189  sabotageButtonText = TextManager.Get("SabotageButton");
190  sabotagingText = TextManager.Get("Sabotaging");
191  SabotageButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), sabotageButtonText, style: "GUIButtonSmall")
192  {
193  IgnoreLayoutGroups = true,
194  Visible = false,
195  OnClicked = (btn, obj) =>
196  {
197  requestStartFixAction = FixActions.Sabotage;
198  item.CreateClientEvent(this);
199  return true;
200  },
201  OnButtonDown = () =>
202  {
203  QTEAction();
204  return true;
205  }
206  };
207 
208  tinkerButtonText = TextManager.Get("TinkerButton").Fallback("Tinker");
209  tinkeringText = TextManager.Get("Tinkering").Fallback("Tinkering");
210  TinkerButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), tinkerButtonText, style: "GUIButtonSmall")
211  {
212  IgnoreLayoutGroups = true,
213  Visible = false,
214  OnClicked = (btn, obj) =>
215  {
216  requestStartFixAction = FixActions.Tinker;
217  item.CreateClientEvent(this);
218  return true;
219  }
220  };
221 
222  extraButtonContainer.RectTransform.MinSize = new Point(0, SabotageButton.RectTransform.MinSize.Y);
223  }
224 
225  partial void UpdateProjSpecific(float deltaTime)
226  {
227  if (item.IsHidden) { return; }
228  if (FakeBrokenTimer > 0.0f)
229  {
230  item.FakeBroken = true;
231  if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
232  {
233  FakeBrokenTimer = 0.0f;
234  }
235  else
236  {
237  FakeBrokenTimer -= deltaTime;
238  }
239  }
240  else
241  {
242  item.FakeBroken = false;
243  }
244 
245 
246  if (!GameMain.IsMultiplayer)
247  {
248  switch (requestStartFixAction)
249  {
250  case FixActions.Repair:
251  case FixActions.Sabotage:
252  case FixActions.Tinker:
253  StartRepairing(Character.Controlled, requestStartFixAction);
254  requestStartFixAction = FixActions.None;
255  break;
256  default:
257  requestStartFixAction = FixActions.None;
258  break;
259  }
260  }
261 
262  float conditionPercentage = item.ConditionPercentageRelativeToDefaultMaxCondition;
263 
264  for (int i = 0; i < particleEmitters.Count; i++)
265  {
266  if ((conditionPercentage >= particleEmitterConditionRanges[i].X && conditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f)
267  {
268  particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull);
269  }
270  }
271 
272  if (CurrentFixer != null && CurrentFixer.SelectedItem == item)
273  {
274  if (repairSoundChannel == null || !repairSoundChannel.IsPlaying)
275  {
276  repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
277  }
278 
279  if (qteCooldown > 0.0f)
280  {
281  qteCooldown -= deltaTime;
282  if (qteCooldown <= 0.0f)
283  {
284  qteTimer = QteDuration;
285  }
286  }
287  else
288  {
289  qteTimer -= deltaTime * (qteTimer / QteDuration);
290  if (qteTimer < 0.0f) { qteTimer = QteDuration; }
291  }
292  }
293  else
294  {
295  repairSoundChannel?.FadeOutAndDispose();
296  repairSoundChannel = null;
297  }
298  }
299 
300  public override void DrawHUD(SpriteBatch spriteBatch, Character character)
301  {
302  IsActive = true;
303 
304  float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier);
305 
306  progressBar.BarSize = item.Condition / defaultMaxCondition;
307  progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green);
308 
309  Rectangle sliderRect = progressBar.GetSliderRect(1.0f);
310  Color qteSliderColor = Color.White;
311  if (qteCooldown > 0.0f)
312  {
313  qteSliderColor = qteSuccess ? GUIStyle.Green : GUIStyle.Red * 0.5f;
314  progressBar.Color = ToolBox.GradientLerp(qteCooldown / QteCooldownDuration, progressBar.Color, qteSliderColor, Color.White);
315  }
316  else
317  {
318  if (qteTimer / QteDuration <= item.Condition / item.MaxCondition)
319  {
320  qteSliderColor = Color.Lerp(qteSliderColor, GUIStyle.Green, 0.5f);
321  }
322  }
323 
324  progressBar.Parent.Parent.Parent.DrawManually(spriteBatch, true);
325  GUI.DrawRectangle(spriteBatch,
326  new Rectangle(sliderRect.X + (int)((qteTimer / QteDuration) * sliderRect.Width), sliderRect.Y - 5, 2, sliderRect.Height + 10),
327  qteSliderColor, true);
328 
329  if (item.Condition > defaultMaxCondition)
330  {
331  float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f);
332  progressBar.Color = ToolBox.GradientLerp((item.Condition - defaultMaxCondition) / extraCondition, GUIStyle.ColorReputationHigh, GUIStyle.ColorReputationVeryHigh);
333  progressBarOverlayText.Visible = true;
334  progressBarOverlayText.Text = $"{(int)Math.Round((item.Condition / defaultMaxCondition) * 100)}%";
335  }
336  else
337  {
338  progressBarOverlayText.Visible = false;
339  }
340 
341  RepairButton.Enabled = (currentFixerAction == FixActions.None || CurrentFixer == character) && !item.IsFullCondition;
342  RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
343  repairButtonText :
344  repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
345 
346  SabotageButton.Visible = character.IsTraitor;
348  SabotageButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Sabotage)) && character.IsTraitor && item.ConditionPercentage > MinSabotageCondition;
349  SabotageButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Sabotage || !character.IsTraitor) ?
350  sabotageButtonText :
351  sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
352 
353  TinkerButton.Visible = IsTinkerable(character);
355  TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character);
356  TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ?
357  tinkerButtonText :
358  tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
359 
360  //System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
361 
362  extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible;
363  extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible;
364 
365  foreach (GUIComponent c in skillTextContainer.Children)
366  {
367  if (c.UserData is not Skill skill) { continue; }
368  GUITextBlock textBlock = (GUITextBlock)c;
369  textBlock.TextColor = character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier) ? GUIStyle.Red : GUIStyle.TextColorNormal;
370  }
371  }
372 
373  public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
374  {
376  {
377  bool paused = !ShouldDeteriorate() && ForceDeteriorationTimer <= 0.0f;
378  if (ForceDeteriorationTimer > 0.0f)
379  {
380  GUI.DrawString(spriteBatch,
381  new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Forced deterioration for " + ((int)ForceDeteriorationTimer) + " s",
382  Color.Red, Color.Black * 0.5f);
383 
384  }
385  else if (deteriorationTimer > 0.0f)
386  {
387  GUI.DrawString(spriteBatch,
388  new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""),
389  paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f);
390  }
391  else
392  {
393  GUI.DrawString(spriteBatch,
394  new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""),
395  paused ? Color.Cyan : GUIStyle.Red, Color.Black * 0.5f);
396  }
397  GUI.DrawString(spriteBatch,
398  new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
399  GUIStyle.Orange);
401  {
402  GUI.DrawString(spriteBatch,
403  new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 40), "Stress multiplier: " + StressDeteriorationMultiplier.ToString("0.00"),
404  GUIStyle.Red);
405  }
406  }
407  }
408 
409  protected override void RemoveComponentSpecific()
410  {
411  base.RemoveComponentSpecific();
412  repairSoundChannel?.FadeOutAndDispose();
413  repairSoundChannel = null;
414  }
415 
416  private void QTEAction()
417  {
418  if (currentFixerAction == FixActions.Repair)
419  {
420  float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
421  qteSuccess = qteCooldown <= 0.0f && qteTimer / QteDuration <= item.Condition / defaultMaxCondition;
422  }
423  else
424  {
425  return;
426  }
427 
428  if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); }
429 
430  SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.Increase : GUISoundType.Decrease);
431 
432  //on failure during cooldown reset cursor to beginning
433  if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = QteDuration; }
434  qteCooldown = QteCooldownDuration;
435  //this will be set on button down so we can reset it here
436  requestStartFixAction = FixActions.None;
437  item.CreateClientEvent(this);
438  }
439 
440  public void ClientEventRead(IReadMessage msg, float sendingTime)
441  {
442  deteriorationTimer = msg.ReadSingle();
444  tinkeringDuration = msg.ReadSingle();
445  tinkeringStrength = msg.ReadSingle();
446  tinkeringPowersDevices = msg.ReadBoolean();
447  ushort currentFixerID = msg.ReadUInt16();
448  currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
449  CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
450 
451  if (CurrentFixer is null)
452  {
453  qteTimer = QteDuration;
454  qteCooldown = 0.0f;
455  }
456  else
457  {
458  item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
459  }
460  }
461 
462  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
463  {
464  msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
465  msg.WriteBoolean(qteSuccess);
466  }
467  }
468 }
virtual float Strength
Definition: Affliction.cs:31
Affliction GetAffliction(string identifier, bool allowLimbAfflictions=true)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
IEnumerable< ContentXElement > Elements()
virtual Vector2 DrawPosition
Definition: Entity.cs:51
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
override bool Enabled
Definition: GUIButton.cs:27
GUITextBlock TextBlock
Definition: GUIButton.cs:11
LocalizedString Text
Definition: GUIButton.cs:138
virtual void ClearChildren()
virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren=false, bool recursive=true)
By default, all the gui elements are drawn automatically in the same order they appear on the update ...
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
Rectangle GetSliderRect(float fillAmount)
Get the area the slider should be drawn inside
bool AutoScaleHorizontal
When enabled, the text is automatically scaled down to fit the textblock horizontally.
static bool IsMultiplayer
Definition: GameMain.cs:35
static bool DebugDraw
Definition: GameMain.cs:29
float ConditionPercentageRelativeToDefaultMaxCondition
Condition percentage disregarding MaxRepairConditionMultiplier (i.e. this can go above 100% if the it...
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
override void DrawHUD(SpriteBatch spriteBatch, Character character)
void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth=-1, Color? overrideColor=null)
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Point?? MinSize
Min size in pixels. Does not affect scaling.
Highlights an UI element of some kind. Generally used in tutorials.
int ReadRangedInteger(int min, int max)
void WriteRangedInteger(int val, int min, int max)
GUISoundType
Definition: GUI.cs:21