using Barotrauma.Networking; using Barotrauma.Sounds; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { enum SoundSelectionMode { Random, CharacterSpecific, ItemSpecific, All, Manual } class ItemSound { public readonly RoundSound RoundSound; public readonly ActionType Type; public Identifier VolumeProperty; public float VolumeMultiplier { get { return RoundSound.Volume; } } public float Range { get { return RoundSound.Range; } } public readonly bool Loop; public readonly bool OnlyPlayInSameSub; public ItemSound(RoundSound sound, ActionType type, bool loop = false, bool onlyPlayInSameSub = false) { this.RoundSound = sound; this.Type = type; this.Loop = loop; this.OnlyPlayInSameSub = onlyPlayInSameSub; } } partial class ItemComponent : ISerializableEntity { public bool HasSounds { get { return sounds.Count > 0; } } public bool[] HasSoundsOfType { get { return hasSoundsOfType; } } private readonly bool[] hasSoundsOfType; private readonly Dictionary> sounds; private Dictionary soundSelectionModes; /// /// Starts the timer for delayed client-side corrections () - in other words, /// the client will not attempt to read server updates for this component until the timer elapses. /// protected float correctionTimer; public float IsActiveTimer; public virtual bool RecreateGUIOnResolutionChange => false; public GUILayoutSettings DefaultLayout { get; protected set; } public GUILayoutSettings AlternativeLayout { get; protected set; } public class GUILayoutSettings { public Vector2? RelativeSize { get; private set; } public Point? AbsoluteSize { get; private set; } public Vector2? RelativeOffset { get; private set; } public Point? AbsoluteOffset { get; private set; } public Anchor? Anchor { get; private set; } public Pivot? Pivot { get; private set; } public static GUILayoutSettings Load(XElement element) { var layout = new GUILayoutSettings(); var relativeSize = XMLExtensions.GetAttributeVector2(element, "relativesize", Vector2.Zero); var absoluteSize = XMLExtensions.GetAttributePoint(element, "absolutesize", new Point(-1000, -1000)); var relativeOffset = XMLExtensions.GetAttributeVector2(element, "relativeoffset", Vector2.Zero); var absoluteOffset = XMLExtensions.GetAttributePoint(element, "absoluteoffset", new Point(-1000, -1000)); if (relativeSize.Length() > 0) { layout.RelativeSize = relativeSize; } if (absoluteSize.X > 0 && absoluteSize.Y > 0) { layout.AbsoluteSize = absoluteSize; } if (relativeOffset.Length() > 0) { layout.RelativeOffset = relativeOffset; } if (absoluteOffset.X > -1000 && absoluteOffset.Y > -1000) { layout.AbsoluteOffset = absoluteOffset; } if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "anchor", ""), out Anchor a)) { layout.Anchor = a; } if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "pivot", ""), out Pivot p)) { layout.Pivot = p; } return layout; } public void ApplyTo(RectTransform target) { if (RelativeOffset.HasValue) { target.RelativeOffset = RelativeOffset.Value; } else if (AbsoluteOffset.HasValue) { target.AbsoluteOffset = AbsoluteOffset.Value; } if (RelativeSize.HasValue) { target.RelativeSize = RelativeSize.Value; } else if (AbsoluteSize.HasValue) { target.NonScaledSize = AbsoluteSize.Value; } if (Anchor.HasValue) { target.Anchor = Anchor.Value; } if (Pivot.HasValue) { target.Pivot = Pivot.Value; } else { target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor); } target.RecalculateChildren(true, true); } } public GUIFrame GuiFrame { get; set; } /// /// Overlay (just a non-interactable sprite) drawn when the item is selected, equipped or focused to via Controllers (e.g. when operating a turret via a periscope or a camera via a monitor). /// public Sprite HUDOverlay { get; set; } public float HUDOverlayAnimSpeed { get; set; } private GUIDragHandle guiFrameDragHandle; private bool guiFrameUpdatePending; [Serialize(false, IsPropertySaveable.No)] public bool AllowUIOverlap { get; set; } [Serialize(true, IsPropertySaveable.No)] public bool CloseByClickingOutsideGUIFrame { get; set; } private ItemComponent linkToUIComponent; [Serialize("", IsPropertySaveable.No)] public string LinkUIToComponent { get; set; } [Serialize(0, IsPropertySaveable.No)] public int HudPriority { get; private set; } [Serialize(0, IsPropertySaveable.No)] public int HudLayer { get; private set; } private bool useAlternativeLayout; public bool UseAlternativeLayout { get { return useAlternativeLayout; } set { if (AlternativeLayout != null) { if (value == useAlternativeLayout) { return; } useAlternativeLayout = value; if (useAlternativeLayout) { AlternativeLayout?.ApplyTo(GuiFrame.RectTransform); } else { DefaultLayout?.ApplyTo(GuiFrame.RectTransform); } } } } private bool shouldMuffleLooping; private float lastMuffleCheckTime; private ItemSound loopingSound; private SoundChannel loopingSoundChannel; private readonly List playingOneshotSoundChannels = new List(); public ItemComponent ReplacedBy; public ItemComponent GetReplacementOrThis() { if (ReplacedBy != null && ReplacedBy != this) { return ReplacedBy.GetReplacementOrThis(); } return this; } public bool NeedsSoundUpdate() { if (hasSoundsOfType[(int)ActionType.Always]) { return true; } if (loopingSoundChannel != null && loopingSoundChannel.IsPlaying) { return true; } if (playingOneshotSoundChannels.Count > 0) { return true; } return false; } public void UpdateSounds() { if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying) { if (Timing.TotalTime > lastMuffleCheckTime + 0.2f) { shouldMuffleLooping = SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull); lastMuffleCheckTime = (float)Timing.TotalTime; } loopingSoundChannel.Muffled = shouldMuffleLooping; float targetGain = GetSoundVolume(loopingSound); float gainDiff = targetGain - loopingSoundChannel.Gain; loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f; loopingSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f); loopingSound.RoundSound.LastStreamSeekPos = loopingSoundChannel.StreamSeekPos; } for (int i = 0; i < playingOneshotSoundChannels.Count; i++) { if (!playingOneshotSoundChannels[i].IsPlaying) { playingOneshotSoundChannels[i].Dispose(); playingOneshotSoundChannels[i] = null; } } playingOneshotSoundChannels.RemoveAll(ch => ch == null); foreach (SoundChannel channel in playingOneshotSoundChannels) { channel.Position = new Vector3(item.WorldPosition, 0.0f); } } public void PlaySound(ActionType type, Character user = null) { if (!hasSoundsOfType[(int)type]) { return; } if (GameMain.Client?.MidRoundSyncing ?? false) { return; } //above the top boundary of the level (in an inactive respawn shuttle?) if (item.Submarine != null && item.Submarine.IsAboveLevel) { return; } if (loopingSound != null) { if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range || (GetSoundVolume(loopingSound)) <= 0.0001f) { if (loopingSoundChannel != null) { loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSound = null; } return; } if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound) { loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSound = null; } if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) { loopingSoundChannel = loopingSound.RoundSound.Sound.Play( new Vector3(item.WorldPosition, 0.0f), 0.01f, loopingSound.RoundSound.GetRandomFrequencyMultiplier(), SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull)); if (loopingSoundChannel != null) { loopingSoundChannel.Looping = true; item.CheckNeedsSoundUpdate(this); loopingSoundChannel.Near = loopingSound.Range * 0.4f; loopingSoundChannel.Far = loopingSound.Range; } } // Looping sound with manual selection mode should be changed if value of ManuallySelectedSound has changed // Otherwise the sound won't change until the sound condition (such as being active) is disabled and re-enabled if (loopingSoundChannel != null && loopingSoundChannel.IsPlaying && soundSelectionModes[type] == SoundSelectionMode.Manual) { var playingIndex = sounds[type].IndexOf(loopingSound); var shouldBePlayingIndex = Math.Clamp(ManuallySelectedSound, 0, sounds[type].Count); if (playingIndex != shouldBePlayingIndex) { loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSound = null; } } return; } var matchingSounds = sounds[type]; if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) { SoundSelectionMode soundSelectionMode = soundSelectionModes[type]; int index; if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && user != null) { index = user.ID % matchingSounds.Count; } else if (soundSelectionMode == SoundSelectionMode.ItemSpecific) { index = item.ID % matchingSounds.Count; } else if (soundSelectionMode == SoundSelectionMode.All) { foreach (ItemSound sound in matchingSounds) { PlaySound(sound, item.WorldPosition); } return; } else if (soundSelectionMode == SoundSelectionMode.Manual) { index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count - 1); } else { index = Rand.Int(matchingSounds.Count); } PlaySound(matchingSounds[index], item.WorldPosition); item.CheckNeedsSoundUpdate(this); } } private void PlaySound(ItemSound itemSound, Vector2 position) { if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range) { return; } if (itemSound.OnlyPlayInSameSub && item.Submarine != null && Character.Controlled != null) { if (Character.Controlled.Submarine == null || !Character.Controlled.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return; } } if (itemSound.Loop) { if (loopingSoundChannel != null && loopingSoundChannel.Sound != itemSound.RoundSound.Sound) { loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; } if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) { float volume = GetSoundVolume(itemSound); if (volume <= 0.0001f) { return; } loopingSound = itemSound; loopingSoundChannel = SoundPlayer.PlaySound(loopingSound.RoundSound, position, volume: 0.01f, hullGuess: item.CurrentHull); if (loopingSoundChannel != null) { loopingSoundChannel.Looping = true; loopingSoundChannel.Near = loopingSound.Range * 0.4f; loopingSoundChannel.Far = loopingSound.Range; if (loopingSound.RoundSound.Stream) { loopingSoundChannel.StreamSeekPos = loopingSound.RoundSound.LastStreamSeekPos; } } } } else { float volume = GetSoundVolume(itemSound); if (volume <= 0.0001f) { return; } var channel = SoundPlayer.PlaySound(itemSound.RoundSound, position, volume, hullGuess: item.CurrentHull); if (channel != null) { playingOneshotSoundChannels.Add(channel); } } } public void StopLoopingSound() { if (loopingSound == null) { return; } if (loopingSoundChannel != null) { loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSound = null; } } public void StopSounds(ActionType type) { if (loopingSound == null || loopingSound.Type != type) { return; } StopLoopingSound(); } private float GetSoundVolume(ItemSound sound) { if (sound == null) { return 0.0f; } if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; } SerializableProperty property = null; ISerializableEntity targetEntity = null; if (SerializableProperties.TryGetValue(sound.VolumeProperty, out property)) { targetEntity = this; } else if (Item.SerializableProperties.TryGetValue(sound.VolumeProperty, out property)) { targetEntity = Item; } if (property != null) { float newVolume; try { newVolume = property.GetFloatValue(targetEntity); } catch { return 0.0f; } newVolume = Math.Min(newVolume * sound.VolumeMultiplier, 1.0f); if (!MathUtils.IsValid(newVolume)) { DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); GameAnalyticsManager.AddErrorEventOnce( "ItemComponent.PlaySound:" + item.Name + GetType().ToString(), GameAnalyticsManager.ErrorSeverity.Error, "Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); return 0.0f; } return MathHelper.Clamp(newVolume, 0.0f, 1.0f); } return 0.0f; } public bool ShouldDrawHUD(Character character) { if (Character.Controlled?.SelectedItem != null) { Controller controller = item.GetComponent(); if (controller != null && controller.User == Character.Controlled && controller.HideAllItemComponentHUDs) { return false; } } return ShouldDrawHUDComponentSpecific(character); } protected virtual bool ShouldDrawHUDComponentSpecific(Character character) { return true; } public ItemComponent GetLinkUIToComponent() { if (string.IsNullOrEmpty(LinkUIToComponent)) { return null; } foreach (ItemComponent component in item.Components) { if (component.name.Equals(LinkUIToComponent, StringComparison.OrdinalIgnoreCase)) { linkToUIComponent = component; } } if (linkToUIComponent == null) { DebugConsole.ThrowError("Failed to link the component \"" + Name + "\" to \"" + LinkUIToComponent + "\" in the item \"" + item.Name + "\" - component with a matching name not found."); } return linkToUIComponent; } public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { if (HUDOverlay != null) { Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); if (HUDOverlay is SpriteSheet spriteSheet) { spriteSheet.Draw(spriteBatch, spriteIndex: spriteSheet.GetAnimatedSpriteIndex(HUDOverlayAnimSpeed), pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / spriteSheet.FrameSize.ToVector2()); } else { HUDOverlay.Draw(spriteBatch, pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / HUDOverlay.size); } } } public virtual void AddToGUIUpdateList(int order = 0) { GuiFrame?.AddToGUIUpdateList(order: order); } public void UpdateHUD(Character character, float deltaTime, Camera cam) { UpdateHUDComponentSpecific(character, deltaTime, cam); if (guiFrameUpdatePending && !PlayerInput.PrimaryMouseButtonHeld()) { //send a guiframe position update once the player stops dragging the frame guiFrameUpdatePending = false; if (SerializableProperties.TryGetValue(nameof(GuiFrameOffset).ToIdentifier(), out var property)) { GameMain.Client?.CreateEntityEvent(Item, new Item.ChangePropertyEventData(property, this)); } } } public virtual void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) { } public virtual void UpdateEditing(float deltaTime) { } public virtual void CreateEditingHUD(SerializableEntityEditor editor) { } private bool LoadElemProjSpecific(ContentXElement subElement) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "guiframe": if (subElement.GetAttribute("rect") != null) { DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead.", contentPackage: subElement.ContentPackage); break; } GuiFrameSource = subElement; ReloadGuiFrame(); break; case "hudoverlayanimated": HUDOverlay = new SpriteSheet(subElement); HUDOverlayAnimSpeed = subElement.GetAttributeFloat("animspeed", 1.0f); break; case "hudoverlay": HUDOverlay = new Sprite(subElement); break; case "alternativelayout": AlternativeLayout = GUILayoutSettings.Load(subElement); break; case "itemsound": case "sound": //TODO: this validation stuff should probably go somewhere else string filePath = subElement.GetAttributeStringUnrestricted("file", ""); if (filePath.IsNullOrEmpty()) { filePath = subElement.GetAttributeStringUnrestricted("sound", ""); } if (filePath.IsNullOrEmpty()) { DebugConsole.ThrowError( $"Error when instantiating item \"{item.Name}\" - sound with no file path set", contentPackage: subElement.ContentPackage); break; } ActionType type; string typeStr = subElement.GetAttributeString("type", ""); try { type = (ActionType)Enum.Parse(typeof(ActionType), typeStr, true); } catch (Exception e) { DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e, contentPackage: subElement.ContentPackage); break; } RoundSound sound = RoundSound.Load(subElement); if (sound == null) { break; } ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false), subElement.GetAttributeBool("onlyinsamesub", false)) { VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "") }; if (soundSelectionModes == null) { soundSelectionModes = new Dictionary(); } if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random) { soundSelectionModes[type] = subElement.GetAttributeEnum("selectionmode", SoundSelectionMode.Random); } if (!sounds.TryGetValue(itemSound.Type, out List soundList)) { soundList = new List(); sounds.Add(itemSound.Type, soundList); hasSoundsOfType[(int)itemSound.Type] = true; } soundList.Add(itemSound); break; default: return false; //unknown element } return true; //element processed } private XElement GuiFrameSource; protected void ReleaseGuiFrame() { if (GuiFrame != null) { GuiFrame.RectTransform.Parent = null; } } protected void ReloadGuiFrame() { if (GuiFrame != null) { ReleaseGuiFrame(); } Color? color = null; if (GuiFrameSource.Attribute("color") != null) { color = GuiFrameSource.GetAttributeColor("color", Color.White); } string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color); GuiFrame.RectTransform.ScreenSpaceOffset = GuiFrameOffset; TryCreateDragHandle(); DefaultLayout = GUILayoutSettings.Load(GuiFrameSource); if (GuiFrame != null) { GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged; } GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate; } protected void TryCreateDragHandle() { if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true)) { bool hideDragIcons = GuiFrameSource.GetAttributeBool("hidedragicons", false); guiFrameDragHandle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center), GuiFrame.RectTransform, style: null) { Enabled = !LockGuiFramePosition, DragArea = HUDLayoutSettings.ItemHUDArea }; int iconHeight = GUIStyle.ItemFrameMargin.Y / 4; var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), guiFrameDragHandle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) }, style: "GUIDragIndicatorHorizontal"); dragIcon.RectTransform.MinSize = new Point(0, iconHeight); guiFrameDragHandle.ValidatePosition = (RectTransform rectT) => { var activeHuds = Character.Controlled?.SelectedItem?.ActiveHUDs ?? item.ActiveHUDs; foreach (ItemComponent ic in activeHuds) { if (ic == this || ic.GuiFrame == null || !ic.CanBeSelected) { continue; } if (ic.GuiFrame.Rect.Width > GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height > GameMain.GraphicsHeight * 0.9f) { //a full-screen GUIFrame (or at least close to one) - this component is doing something weird, //an ItemContainer with no GUIFrame definition that positions itself in some other GUIFrame, some kind of an overlay? // -> allow intersecting continue; } if (dragIcon.Rect.Intersects(ic.GuiFrame.Rect)) { GuiFrame.ImmediateFlash(); return false; } } foreach (ItemComponent ic in activeHuds) { //refresh slots to ensure they're rendered at the correct position (ic as ItemContainer)?.Inventory.CreateSlots(); } GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset; guiFrameUpdatePending = true; return true; }; int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), guiFrameDragHandle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) }, style: "GUIButtonSettings") { OnClicked = (btn, userdata) => { GUIContextMenu.CreateContextMenu( new ContextMenuOption("item.resetuiposition", isEnabled: true, onSelected: () => { foreach (var ic in item.Components) { if (ic.GuiFrame != null && ic.GuiFrameOffset != Point.Zero) { ic.GuiFrameOffset = Point.Zero; ic.guiFrameUpdatePending = true; } } if (Character.Controlled?.SelectedItem != null && item != Character.Controlled.SelectedItem) { Character.Controlled.SelectedItem.ForceHUDLayoutUpdate(ignoreLocking: true); } else { item.ForceHUDLayoutUpdate(ignoreLocking: true); } }), new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () => { //ensure the offset is set to where the frame is now //(it may have been repositioned by the overlap prevention logic, which doesn't set this offset) GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset; LockGuiFramePosition = !LockGuiFramePosition; guiFrameDragHandle.Enabled = !LockGuiFramePosition; if (SerializableProperties.TryGetValue(nameof(LockGuiFramePosition).ToIdentifier(), out var property)) { GameMain.Client?.CreateEntityEvent(Item, new Item.ChangePropertyEventData(property, this)); } })); return true; } }; if (hideDragIcons) { dragIcon.Visible = false; settingsIcon.Visible = false; } } } /// /// Overload this method and implement. The method is automatically called when the resolution changes. /// protected virtual void CreateGUI() { } /// /// Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. /// Useful in cases where we a client is constantly adjusting some value, and we don't want state updates from the server to interfere with it /// (e.g. setting the value back to what a client just set it to, when the client has already modified the value further). /// protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false) { if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); } delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(buffer, sendingTime, waitForMidRoundSync)); } private IEnumerable DoDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync) { while (GameMain.Client != null && (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) { correctionTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } if (item.Removed || GameMain.Client == null) { yield return CoroutineStatus.Success; } ((IServerSerializable)this).ClientEventRead(buffer, sendingTime); correctionTimer = 0.0f; delayedCorrectionCoroutine = null; yield return CoroutineStatus.Success; } /// /// Launches when the parent of the GuiFrame is changed. /// protected void OnGUIParentChanged(RectTransform newParent) { if (newParent == null) { // Make sure to unregister. It doesn't matter if we haven't ever registered to the event. GameMain.Instance.ResolutionChanged -= OnResolutionChangedPrivate; } } protected virtual void OnResolutionChanged() { } private void OnResolutionChangedPrivate() { if (RecreateGUIOnResolutionChange) { ReloadGuiFrame(); CreateGUI(); } OnResolutionChanged(); item.ForceHUDLayoutUpdate(ignoreLocking: true); if (GuiFrame != null && GuiFrame.GetChild() is GUIDragHandle dragHandle) { dragHandle.DragArea = HUDLayoutSettings.ItemHUDArea; } } public virtual void OnPlayerSkillsChanged() { } public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { } } }