Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs
2 using Barotrauma.Sounds;
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using System;
6 using System.Collections.Generic;
7 using Barotrauma.IO;
8 using System.Linq;
9 using System.Xml.Linq;
10 
12 {
14  {
15  Random,
18  All,
19  Manual
20  }
21 
22  class ItemSound
23  {
24  public readonly RoundSound RoundSound;
25  public readonly ActionType Type;
26 
27  public Identifier VolumeProperty;
28 
29  public float VolumeMultiplier
30  {
31  get { return RoundSound.Volume; }
32  }
33 
34  public float Range
35  {
36  get { return RoundSound.Range; }
37  }
38 
39  public readonly bool Loop;
40 
41  public readonly bool OnlyPlayInSameSub;
42 
43  public ItemSound(RoundSound sound, ActionType type, bool loop = false, bool onlyPlayInSameSub = false)
44  {
45  this.RoundSound = sound;
46  this.Type = type;
47  this.Loop = loop;
48  this.OnlyPlayInSameSub = onlyPlayInSameSub;
49  }
50  }
51 
52  partial class ItemComponent : ISerializableEntity
53  {
54  public bool HasSounds
55  {
56  get { return sounds.Count > 0; }
57  }
58 
59  public bool[] HasSoundsOfType { get { return hasSoundsOfType; } }
60 
61  private readonly bool[] hasSoundsOfType;
62  private readonly Dictionary<ActionType, List<ItemSound>> sounds;
63  private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
64 
65  protected float correctionTimer;
66 
67  public float IsActiveTimer;
68 
69  public virtual bool RecreateGUIOnResolutionChange => false;
70 
71  public GUILayoutSettings DefaultLayout { get; protected set; }
72  public GUILayoutSettings AlternativeLayout { get; protected set; }
73 
74  public class GUILayoutSettings
75  {
76  public Vector2? RelativeSize { get; private set; }
77  public Point? AbsoluteSize { get; private set; }
78  public Vector2? RelativeOffset { get; private set; }
79  public Point? AbsoluteOffset { get; private set; }
80  public Anchor? Anchor { get; private set; }
81  public Pivot? Pivot { get; private set; }
82 
83  public static GUILayoutSettings Load(XElement element)
84  {
85  var layout = new GUILayoutSettings();
86  var relativeSize = XMLExtensions.GetAttributeVector2(element, "relativesize", Vector2.Zero);
87  var absoluteSize = XMLExtensions.GetAttributePoint(element, "absolutesize", new Point(-1000, -1000));
88  var relativeOffset = XMLExtensions.GetAttributeVector2(element, "relativeoffset", Vector2.Zero);
89  var absoluteOffset = XMLExtensions.GetAttributePoint(element, "absoluteoffset", new Point(-1000, -1000));
90  if (relativeSize.Length() > 0)
91  {
92  layout.RelativeSize = relativeSize;
93  }
94  if (absoluteSize.X > 0 && absoluteSize.Y > 0)
95  {
96  layout.AbsoluteSize = absoluteSize;
97  }
98  if (relativeOffset.Length() > 0)
99  {
100  layout.RelativeOffset = relativeOffset;
101  }
102  if (absoluteOffset.X > -1000 && absoluteOffset.Y > -1000)
103  {
104  layout.AbsoluteOffset = absoluteOffset;
105  }
106  if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "anchor", ""), out Anchor a))
107  {
108  layout.Anchor = a;
109  }
110  if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "pivot", ""), out Pivot p))
111  {
112  layout.Pivot = p;
113  }
114  return layout;
115  }
116 
117  public void ApplyTo(RectTransform target)
118  {
119  if (RelativeOffset.HasValue)
120  {
121  target.RelativeOffset = RelativeOffset.Value;
122  }
123  else if (AbsoluteOffset.HasValue)
124  {
125  target.AbsoluteOffset = AbsoluteOffset.Value;
126  }
127  if (RelativeSize.HasValue)
128  {
129  target.RelativeSize = RelativeSize.Value;
130  }
131  else if (AbsoluteSize.HasValue)
132  {
133  target.NonScaledSize = AbsoluteSize.Value;
134  }
135  if (Anchor.HasValue)
136  {
137  target.Anchor = Anchor.Value;
138  }
139  if (Pivot.HasValue)
140  {
141  target.Pivot = Pivot.Value;
142  }
143  else
144  {
145  target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
146  }
147  target.RecalculateChildren(true, true);
148  }
149  }
150 
151  public GUIFrame GuiFrame { get; set; }
152 
153  private GUIDragHandle guiFrameDragHandle;
154 
155  private bool guiFrameUpdatePending;
156 
157  [Serialize(false, IsPropertySaveable.No)]
158  public bool AllowUIOverlap
159  {
160  get;
161  set;
162  }
163 
164  private ItemComponent linkToUIComponent;
165  [Serialize("", IsPropertySaveable.No)]
166  public string LinkUIToComponent
167  {
168  get;
169  set;
170  }
171 
173  public int HudPriority
174  {
175  get;
176  private set;
177  }
178 
180  public int HudLayer
181  {
182  get;
183  private set;
184  }
185 
186  private bool useAlternativeLayout;
188  {
189  get { return useAlternativeLayout; }
190  set
191  {
192  if (AlternativeLayout != null)
193  {
194  if (value == useAlternativeLayout) { return; }
195  useAlternativeLayout = value;
196  if (useAlternativeLayout)
197  {
199  }
200  else
201  {
203  }
204  }
205  }
206  }
207 
208  private bool shouldMuffleLooping;
209  private float lastMuffleCheckTime;
210  private ItemSound loopingSound;
211  private SoundChannel loopingSoundChannel;
212  private readonly List<SoundChannel> playingOneshotSoundChannels = new List<SoundChannel>();
214 
216  {
217  return ReplacedBy?.GetReplacementOrThis() ?? this;
218  }
219 
220  public bool NeedsSoundUpdate()
221  {
222  if (hasSoundsOfType[(int)ActionType.Always]) { return true; }
223  if (loopingSoundChannel != null && loopingSoundChannel.IsPlaying) { return true; }
224  if (playingOneshotSoundChannels.Count > 0) { return true; }
225  return false;
226  }
227 
228  public void UpdateSounds()
229  {
230  if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying)
231  {
232  if (Timing.TotalTime > lastMuffleCheckTime + 0.2f)
233  {
234  shouldMuffleLooping = SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull);
235  lastMuffleCheckTime = (float)Timing.TotalTime;
236  }
237  loopingSoundChannel.Muffled = shouldMuffleLooping;
238  float targetGain = GetSoundVolume(loopingSound);
239  float gainDiff = targetGain - loopingSoundChannel.Gain;
240  loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f;
241  loopingSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
242  }
243  for (int i = 0; i < playingOneshotSoundChannels.Count; i++)
244  {
245  if (!playingOneshotSoundChannels[i].IsPlaying)
246  {
247  playingOneshotSoundChannels[i].Dispose();
248  playingOneshotSoundChannels[i] = null;
249  }
250  }
251  playingOneshotSoundChannels.RemoveAll(ch => ch == null);
252  foreach (SoundChannel channel in playingOneshotSoundChannels)
253  {
254  channel.Position = new Vector3(item.WorldPosition, 0.0f);
255  }
256  }
257 
258  public void PlaySound(ActionType type, Character user = null)
259  {
260  if (!hasSoundsOfType[(int)type]) { return; }
261  if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
262 
263  //above the top boundary of the level (in an inactive respawn shuttle?)
264  if (item.Submarine != null && Level.Loaded != null && item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y)
265  {
266  return;
267  }
268 
269  if (loopingSound != null)
270  {
271  if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range ||
272  (GetSoundVolume(loopingSound)) <= 0.0001f)
273  {
274  if (loopingSoundChannel != null)
275  {
276  loopingSoundChannel.FadeOutAndDispose();
277  loopingSoundChannel = null;
278  loopingSound = null;
279  }
280  return;
281  }
282 
283  if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
284  {
285  loopingSoundChannel.FadeOutAndDispose();
286  loopingSoundChannel = null;
287  loopingSound = null;
288  }
289 
290  if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
291  {
292  loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
293  new Vector3(item.WorldPosition, 0.0f),
294  0.01f,
296  SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
297  loopingSoundChannel.Looping = true;
299  //TODO: tweak
300  loopingSoundChannel.Near = loopingSound.Range * 0.4f;
301  loopingSoundChannel.Far = loopingSound.Range;
302  }
303 
304  // Looping sound with manual selection mode should be changed if value of ManuallySelectedSound has changed
305  // Otherwise the sound won't change until the sound condition (such as being active) is disabled and re-enabled
306  if (loopingSoundChannel != null && loopingSoundChannel.IsPlaying && soundSelectionModes[type] == SoundSelectionMode.Manual)
307  {
308  var playingIndex = sounds[type].IndexOf(loopingSound);
309  var shouldBePlayingIndex = Math.Clamp(ManuallySelectedSound, 0, sounds[type].Count);
310  if (playingIndex != shouldBePlayingIndex)
311  {
312  loopingSoundChannel.FadeOutAndDispose();
313  loopingSoundChannel = null;
314  loopingSound = null;
315  }
316  }
317  return;
318  }
319 
320  var matchingSounds = sounds[type];
321  if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
322  {
323  SoundSelectionMode soundSelectionMode = soundSelectionModes[type];
324  int index;
325  if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && user != null)
326  {
327  index = user.ID % matchingSounds.Count;
328  }
329  else if (soundSelectionMode == SoundSelectionMode.ItemSpecific)
330  {
331  index = item.ID % matchingSounds.Count;
332  }
333  else if (soundSelectionMode == SoundSelectionMode.All)
334  {
335  foreach (ItemSound sound in matchingSounds)
336  {
337  PlaySound(sound, item.WorldPosition);
338  }
339  return;
340  }
341  else if (soundSelectionMode == SoundSelectionMode.Manual)
342  {
343  index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count);
344  }
345  else
346  {
347  index = Rand.Int(matchingSounds.Count);
348  }
349 
350  PlaySound(matchingSounds[index], item.WorldPosition);
352  }
353  }
354  private void PlaySound(ItemSound itemSound, Vector2 position)
355  {
356  if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range)
357  {
358  return;
359  }
360 
361  if (itemSound.OnlyPlayInSameSub && item.Submarine != null && Character.Controlled != null)
362  {
363  if (Character.Controlled.Submarine == null || !Character.Controlled.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return; }
364  }
365 
366  if (itemSound.Loop)
367  {
368  if (loopingSoundChannel != null && loopingSoundChannel.Sound != itemSound.RoundSound.Sound)
369  {
370  loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
371  }
372  if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
373  {
374  float volume = GetSoundVolume(itemSound);
375  if (volume <= 0.0001f) { return; }
376  loopingSound = itemSound;
377  loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
378  new Vector3(position.X, position.Y, 0.0f),
379  0.01f,
380  freqMult: itemSound.RoundSound.GetRandomFrequencyMultiplier(),
381  muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
382  loopingSoundChannel.Looping = true;
383  //TODO: tweak
384  loopingSoundChannel.Near = loopingSound.Range * 0.4f;
385  loopingSoundChannel.Far = loopingSound.Range;
386  }
387  }
388  else
389  {
390  float volume = GetSoundVolume(itemSound);
391  if (volume <= 0.0001f) { return; }
392  var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, itemSound.RoundSound.GetRandomFrequencyMultiplier(), item.CurrentHull, ignoreMuffling: itemSound.RoundSound.IgnoreMuffling);
393  if (channel != null) { playingOneshotSoundChannels.Add(channel); }
394  }
395  }
396 
397  public void StopLoopingSound()
398  {
399  if (loopingSound == null) { return; }
400  if (loopingSoundChannel != null)
401  {
402  loopingSoundChannel.FadeOutAndDispose();
403  loopingSoundChannel = null;
404  loopingSound = null;
405  }
406  }
407 
408  public void StopSounds(ActionType type)
409  {
410  if (loopingSound == null || loopingSound.Type != type) { return; }
412  }
413 
414  private float GetSoundVolume(ItemSound sound)
415  {
416  if (sound == null) { return 0.0f; }
417  if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
418 
419  if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
420  {
421  float newVolume;
422  try
423  {
424  newVolume = property.GetFloatValue(this);
425  }
426  catch
427  {
428  return 0.0f;
429  }
430  newVolume = Math.Min(newVolume * sound.VolumeMultiplier, 1.0f);
431 
432  if (!MathUtils.IsValid(newVolume))
433  {
434  DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
435  GameAnalyticsManager.AddErrorEventOnce(
436  "ItemComponent.PlaySound:" + item.Name + GetType().ToString(),
437  GameAnalyticsManager.ErrorSeverity.Error,
438  "Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
439  return 0.0f;
440  }
441 
442  return MathHelper.Clamp(newVolume, 0.0f, 1.0f);
443  }
444 
445  return 0.0f;
446  }
447 
448  public virtual bool ShouldDrawHUD(Character character)
449  {
450  return true;
451  }
452 
454  {
455  if (string.IsNullOrEmpty(LinkUIToComponent))
456  {
457  return null;
458  }
459  foreach (ItemComponent component in item.Components)
460  {
461  if (component.name.Equals(LinkUIToComponent, StringComparison.OrdinalIgnoreCase))
462  {
463  linkToUIComponent = component;
464  }
465  }
466  if (linkToUIComponent == null)
467  {
468  DebugConsole.ThrowError("Failed to link the component \"" + Name + "\" to \"" + LinkUIToComponent + "\" in the item \"" + item.Name + "\" - component with a matching name not found.");
469  }
470  return linkToUIComponent;
471  }
472 
473  public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
474 
475  public virtual void AddToGUIUpdateList(int order = 0)
476  {
477  GuiFrame?.AddToGUIUpdateList(order: order);
478  }
479 
480  public void UpdateHUD(Character character, float deltaTime, Camera cam)
481  {
482  UpdateHUDComponentSpecific(character, deltaTime, cam);
483  if (guiFrameUpdatePending && !PlayerInput.PrimaryMouseButtonHeld())
484  {
485  //send a guiframe position update once the player stops dragging the frame
486  guiFrameUpdatePending = false;
487  if (SerializableProperties.TryGetValue(nameof(GuiFrameOffset).ToIdentifier(), out var property))
488  {
490  }
491  }
492  }
493 
494  public virtual void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) { }
495 
496  public virtual void UpdateEditing(float deltaTime) { }
497 
498  public virtual void CreateEditingHUD(SerializableEntityEditor editor)
499  {
500  }
501 
502  private bool LoadElemProjSpecific(ContentXElement subElement)
503  {
504  switch (subElement.Name.ToString().ToLowerInvariant())
505  {
506  case "guiframe":
507  if (subElement.GetAttribute("rect") != null)
508  {
509  DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead.",
510  contentPackage: subElement.ContentPackage);
511  break;
512  }
513  GuiFrameSource = subElement;
514  ReloadGuiFrame();
515  break;
516  case "alternativelayout":
517  AlternativeLayout = GUILayoutSettings.Load(subElement);
518  break;
519  case "itemsound":
520  case "sound":
521  //TODO: this validation stuff should probably go somewhere else
522  string filePath = subElement.GetAttributeStringUnrestricted("file", "");
523 
524  if (filePath.IsNullOrEmpty()) { filePath = subElement.GetAttributeStringUnrestricted("sound", ""); }
525 
526  if (filePath.IsNullOrEmpty())
527  {
528  DebugConsole.ThrowError(
529  $"Error when instantiating item \"{item.Name}\" - sound with no file path set",
530  contentPackage: subElement.ContentPackage);
531  break;
532  }
533 
534  ActionType type;
535  string typeStr = subElement.GetAttributeString("type", "");
536  try
537  {
538  type = (ActionType)Enum.Parse(typeof(ActionType), typeStr, true);
539  }
540  catch (Exception e)
541  {
542  DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e,
543  contentPackage: subElement.ContentPackage);
544  break;
545  }
546 
547  RoundSound sound = RoundSound.Load(subElement);
548  if (sound == null) { break; }
549  ItemSound itemSound = new ItemSound(sound, type,
550  subElement.GetAttributeBool("loop", false),
551  subElement.GetAttributeBool("onlyinsamesub", false))
552  {
553  VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "")
554  };
555 
556  if (soundSelectionModes == null)
557  {
558  soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
559  }
560  if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
561  {
562  soundSelectionModes[type] = subElement.GetAttributeEnum("selectionmode", SoundSelectionMode.Random);
563  }
564 
565  if (!sounds.TryGetValue(itemSound.Type, out List<ItemSound> soundList))
566  {
567  soundList = new List<ItemSound>();
568  sounds.Add(itemSound.Type, soundList);
569  hasSoundsOfType[(int)itemSound.Type] = true;
570  }
571 
572  soundList.Add(itemSound);
573  break;
574  default:
575  return false; //unknown element
576  }
577  return true; //element processed
578  }
579 
580  private XElement GuiFrameSource;
581 
582  protected void ReleaseGuiFrame()
583  {
584  if (GuiFrame != null)
585  {
587  }
588  }
589 
590  protected void ReloadGuiFrame()
591  {
592  if (GuiFrame != null)
593  {
594  ReleaseGuiFrame();
595  }
596  Color? color = null;
597  if (GuiFrameSource.Attribute("color") != null)
598  {
599  color = GuiFrameSource.GetAttributeColor("color", Color.White);
600  }
601  string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
602  GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color);
604 
606 
607  DefaultLayout = GUILayoutSettings.Load(GuiFrameSource);
608  if (GuiFrame != null)
609  {
611  }
612  GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate;
613  }
614 
615  protected void TryCreateDragHandle()
616  {
617  if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true))
618  {
619  bool hideDragIcons = GuiFrameSource.GetAttributeBool("hidedragicons", false);
620 
621  guiFrameDragHandle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center),
622  GuiFrame.RectTransform, style: null)
623  {
624  Enabled = !LockGuiFramePosition,
625  DragArea = HUDLayoutSettings.ItemHUDArea
626  };
627 
628  int iconHeight = GUIStyle.ItemFrameMargin.Y / 4;
629  var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), guiFrameDragHandle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) },
630  style: "GUIDragIndicatorHorizontal");
631  dragIcon.RectTransform.MinSize = new Point(0, iconHeight);
632 
633  guiFrameDragHandle.ValidatePosition = (RectTransform rectT) =>
634  {
636  foreach (ItemComponent ic in activeHuds)
637  {
638  if (ic == this || ic.GuiFrame == null || !ic.CanBeSelected) { continue; }
639  if (ic.GuiFrame.Rect.Width > GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height > GameMain.GraphicsHeight * 0.9f)
640  {
641  //a full-screen GUIFrame (or at least close to one) - this component is doing something weird,
642  //an ItemContainer with no GUIFrame definition that positions itself in some other GUIFrame, some kind of an overlay?
643  // -> allow intersecting
644  continue;
645  }
646  if (dragIcon.Rect.Intersects(ic.GuiFrame.Rect))
647  {
649  return false;
650  }
651  }
652  foreach (ItemComponent ic in activeHuds)
653  {
654  //refresh slots to ensure they're rendered at the correct position
656  }
658  guiFrameUpdatePending = true;
659  return true;
660  };
661 
662  int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
663  var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), guiFrameDragHandle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
664  style: "GUIButtonSettings")
665  {
666  OnClicked = (btn, userdata) =>
667  {
668  GUIContextMenu.CreateContextMenu(
669  new ContextMenuOption("item.resetuiposition", isEnabled: true, onSelected: () =>
670  {
671  foreach (var ic in item.Components)
672  {
673  if (ic.GuiFrame != null && ic.GuiFrameOffset != Point.Zero)
674  {
675  ic.GuiFrameOffset = Point.Zero;
676  ic.guiFrameUpdatePending = true;
677  }
678  }
680  {
681  Character.Controlled.SelectedItem.ForceHUDLayoutUpdate(ignoreLocking: true);
682  }
683  else
684  {
685  item.ForceHUDLayoutUpdate(ignoreLocking: true);
686  }
687  }),
688  new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
689  {
690  LockGuiFramePosition = !LockGuiFramePosition;
691  guiFrameDragHandle.Enabled = !LockGuiFramePosition;
692  if (SerializableProperties.TryGetValue(nameof(LockGuiFramePosition).ToIdentifier(), out var property))
693  {
694  GameMain.Client?.CreateEntityEvent(Item, new Item.ChangePropertyEventData(property, this));
695  }
696  }));
697  return true;
698  }
699  };
700 
701  if (hideDragIcons)
702  {
703  dragIcon.Visible = false;
704  settingsIcon.Visible = false;
705  }
706  }
707  }
708 
712  protected virtual void CreateGUI() { }
713 
714  //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
715  protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
716  {
717  if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); }
718 
719  delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(buffer, sendingTime, waitForMidRoundSync));
720  }
721 
722  private IEnumerable<CoroutineStatus> DoDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
723  {
724  while (GameMain.Client != null &&
725  (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
726  {
727  correctionTimer -= CoroutineManager.DeltaTime;
728  yield return CoroutineStatus.Running;
729  }
730 
731  if (item.Removed || GameMain.Client == null)
732  {
733  yield return CoroutineStatus.Success;
734  }
735 
736  ((IServerSerializable)this).ClientEventRead(buffer, sendingTime);
737 
738  correctionTimer = 0.0f;
739  delayedCorrectionCoroutine = null;
740 
741  yield return CoroutineStatus.Success;
742  }
743 
747  protected void OnGUIParentChanged(RectTransform newParent)
748  {
749  if (newParent == null)
750  {
751  // Make sure to unregister. It doesn't matter if we haven't ever registered to the event.
752  GameMain.Instance.ResolutionChanged -= OnResolutionChangedPrivate;
753  }
754  }
755 
756  protected virtual void OnResolutionChanged() { }
757 
758  private void OnResolutionChangedPrivate()
759  {
760  if (RecreateGUIOnResolutionChange)
761  {
762  ReloadGuiFrame();
763  CreateGUI();
764  }
765  OnResolutionChanged();
766  item.ForceHUDLayoutUpdate(ignoreLocking: true);
767  if (GuiFrame != null && GuiFrame.GetChild<GUIDragHandle>() is GUIDragHandle dragHandle)
768  {
769  dragHandle.DragArea = HUDLayoutSettings.ItemHUDArea;
770  }
771  }
772 
773  public virtual void OnPlayerSkillsChanged() { }
774 
775  public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { }
776  }
777 }
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
string? GetAttributeString(string key, string? def)
ContentPackage? ContentPackage
string GetAttributeStringUnrestricted(string key, string def)
bool GetAttributeBool(string key, bool def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
static CoroutineStatus Running
static CoroutineStatus Success
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
void ImmediateFlash(Color? color=null)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual Rectangle Rect
RectTransform RectTransform
Func< RectTransform, bool > ValidatePosition
static int GraphicsWidth
Definition: GameMain.cs:162
static int GraphicsHeight
Definition: GameMain.cs:168
Action ResolutionChanged
NOTE: Use very carefully. You need to ensure that you ALWAYS unsubscribe from this when you no longer...
Definition: GameMain.cs:133
static GameClient Client
Definition: GameMain.cs:188
static GameMain Instance
Definition: GameMain.cs:144
static Sounds.SoundManager SoundManager
Definition: GameMain.cs:80
IEnumerable< ItemComponent > ActiveHUDs
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
void CheckNeedsSoundUpdate(ItemComponent ic)
The base class for components holding the different functionalities of the item
void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync=false)
virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
int ManuallySelectedSound
Which sound should be played when manual sound selection type is selected? Not [Editable] because we ...
virtual void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
virtual void DrawHUD(SpriteBatch spriteBatch, Character character)
virtual void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
void OnGUIParentChanged(RectTransform newParent)
Launches when the parent of the GuiFrame is changed.
ItemSound(RoundSound sound, ActionType type, bool loop=false, bool onlyPlayInSameSub=false)
override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData=null)
Definition: GameClient.cs:2606
Point ScreenSpaceOffset
Screen space offset. From top left corner. In pixels.
static RectTransform Load(XElement element, RectTransform parent, Anchor defaultAnchor=Anchor.TopLeft)
void RecalculateChildren(bool resize, bool scale=true)
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
Vector2 RelativeSize
Relative to the parent rect.
Anchor Anchor
Does not automatically calculate children. Note also that if you change the anchor point with this pr...
RectTransform?? Parent
Pivot Pivot
Does not automatically calculate children. Note also that if you change the pivot point with this pro...
Vector2 RelativeOffset
Defined as portions of the parent size. Also the direction of the offset is relative,...
Action< RectTransform > ParentChanged
Point NonScaledSize
Size before scale multiplications.
readonly float Range
Definition: RoundSound.cs:14
float GetRandomFrequencyMultiplier()
Definition: RoundSound.cs:54
readonly float Volume
Definition: RoundSound.cs:13
readonly bool IgnoreMuffling
Definition: RoundSound.cs:17
virtual SoundChannel Play(float gain, float range, Vector2 position, bool muffle=false)
Definition: Sound.cs:102
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19