From 381bb9020aeba82e9cad111c77c34bee29d52c68 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 22 Feb 2024 20:09:43 -0500 Subject: [PATCH] Add prediction to Openable (#25477) * Fix formatting problem with FullOpened * Moved to Shared and networked * Revert "Fix formatting problem with FullOpened" This reverts commit f8353403da830a4402bdd457bcf24a2432a5f566. --- .../Nutrition/EntitySystems/OpenableSystem.cs | 7 + .../Nutrition/EntitySystems/OpenableSystem.cs | 173 ----------------- .../Nutrition/Components/OpenableComponent.cs | 14 +- .../EntitySystems/SharedOpenableSystem.cs | 182 ++++++++++++++++++ 4 files changed, 197 insertions(+), 179 deletions(-) create mode 100644 Content.Client/Nutrition/EntitySystems/OpenableSystem.cs rename {Content.Server => Content.Shared}/Nutrition/Components/OpenableComponent.cs (86%) diff --git a/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs b/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs new file mode 100644 index 0000000000..f8c3f7c447 --- /dev/null +++ b/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Nutrition.EntitySystems; + +namespace Content.Client.Nutrition.EntitySystems; + +public sealed class OpenableSystem : SharedOpenableSystem +{ +} diff --git a/Content.Server/Nutrition/EntitySystems/OpenableSystem.cs b/Content.Server/Nutrition/EntitySystems/OpenableSystem.cs index 373b97700f..8037b61572 100644 --- a/Content.Server/Nutrition/EntitySystems/OpenableSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/OpenableSystem.cs @@ -1,16 +1,6 @@ using Content.Server.Chemistry.EntitySystems; -using Content.Server.Fluids.EntitySystems; using Content.Shared.Nutrition.EntitySystems; -using Content.Server.Nutrition.Components; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.Nutrition.Components; -using Content.Shared.Popups; -using Content.Shared.Verbs; -using Content.Shared.Weapons.Melee.Events; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Utility; namespace Content.Server.Nutrition.EntitySystems; @@ -19,43 +9,11 @@ namespace Content.Server.Nutrition.EntitySystems; /// public sealed class OpenableSystem : SharedOpenableSystem { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnExamined, after: new[] { typeof(PuddleSystem) }); SubscribeLocalEvent(OnTransferAttempt); - SubscribeLocalEvent(HandleIfClosed); - SubscribeLocalEvent(HandleIfClosed); - SubscribeLocalEvent>(AddOpenCloseVerbs); - } - - private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args) - { - UpdateAppearance(uid, comp); - } - - private void OnUse(EntityUid uid, OpenableComponent comp, UseInHandEvent args) - { - if (args.Handled || !comp.OpenableByHand) - return; - - args.Handled = TryOpen(uid, comp); - } - - private void OnExamined(EntityUid uid, OpenableComponent comp, ExaminedEvent args) - { - if (!comp.Opened || !args.IsInDetailsRange) - return; - - var text = Loc.GetString(comp.ExamineText); - args.PushMarkup(text); } private void OnTransferAttempt(EntityUid uid, OpenableComponent comp, SolutionTransferAttemptEvent args) @@ -66,135 +24,4 @@ public sealed class OpenableSystem : SharedOpenableSystem args.Cancel(Loc.GetString("drink-component-try-use-drink-not-open", ("owner", uid))); } } - - private void HandleIfClosed(EntityUid uid, OpenableComponent comp, HandledEntityEventArgs args) - { - // prevent spilling/pouring/whatever drinks when closed - args.Handled = !comp.Opened; - } - - private void AddOpenCloseVerbs(EntityUid uid, OpenableComponent comp, GetVerbsEvent args) - { - if (args.Hands == null || !args.CanAccess || !args.CanInteract) - return; - - Verb verb; - if (comp.Opened) - { - if (!comp.Closeable) - return; - - verb = new() - { - Text = Loc.GetString(comp.CloseVerbText), - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")), - Act = () => TryClose(args.Target, comp) - }; - } - else - { - verb = new() - { - Text = Loc.GetString(comp.OpenVerbText), - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")), - Act = () => TryOpen(args.Target, comp) - }; - } - args.Verbs.Add(verb); - } - - /// - /// Returns true if the entity either does not have OpenableComponent or it is opened. - /// Drinks that don't have OpenableComponent are automatically open, so it returns true. - /// - public bool IsOpen(EntityUid uid, OpenableComponent? comp = null) - { - if (!Resolve(uid, ref comp, false)) - return true; - - return comp.Opened; - } - - /// - /// Returns true if the entity both has OpenableComponent and is not opened. - /// Drinks that don't have OpenableComponent are automatically open, so it returns false. - /// If user is not null a popup will be shown to them. - /// - public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null) - { - if (!Resolve(uid, ref comp, false)) - return false; - - if (comp.Opened) - return false; - - if (user != null) - _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value); - - return true; - } - - /// - /// Update open visuals to the current value. - /// - public void UpdateAppearance(EntityUid uid, OpenableComponent? comp = null, AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref comp)) - return; - - _appearance.SetData(uid, OpenableVisuals.Opened, comp.Opened, appearance); - } - - /// - /// Sets the opened field and updates open visuals. - /// - public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = null) - { - if (!Resolve(uid, ref comp, false) || opened == comp.Opened) - return; - - comp.Opened = opened; - - if (opened) - { - var ev = new OpenableOpenedEvent(); - RaiseLocalEvent(uid, ref ev); - } - else - { - var ev = new OpenableClosedEvent(); - RaiseLocalEvent(uid, ref ev); - } - - UpdateAppearance(uid, comp); - } - - /// - /// If closed, opens it and plays the sound. - /// - /// Whether it got opened - public bool TryOpen(EntityUid uid, OpenableComponent? comp = null) - { - if (!Resolve(uid, ref comp, false) || comp.Opened) - return false; - - SetOpen(uid, true, comp); - _audio.PlayPvs(comp.Sound, uid); - return true; - } - - /// - /// If opened, closes it and plays the close sound, if one is defined. - /// - /// Whether it got closed - public bool TryClose(EntityUid uid, OpenableComponent? comp = null) - { - if (!Resolve(uid, ref comp, false) || !comp.Opened || !comp.Closeable) - return false; - - SetOpen(uid, false, comp); - if (comp.CloseSound != null) - _audio.PlayPvs(comp.CloseSound, uid); - return true; - } } diff --git a/Content.Server/Nutrition/Components/OpenableComponent.cs b/Content.Shared/Nutrition/Components/OpenableComponent.cs similarity index 86% rename from Content.Server/Nutrition/Components/OpenableComponent.cs rename to Content.Shared/Nutrition/Components/OpenableComponent.cs index cc24bf44dc..3a230fc765 100644 --- a/Content.Server/Nutrition/Components/OpenableComponent.cs +++ b/Content.Shared/Nutrition/Components/OpenableComponent.cs @@ -1,27 +1,29 @@ -using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Server.Nutrition.Components; +namespace Content.Shared.Nutrition.Components; /// /// A drink or food that can be opened. /// Starts closed, open it with Z or E. /// -[RegisterComponent, Access(typeof(OpenableSystem))] +[NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, Access(typeof(SharedOpenableSystem))] public sealed partial class OpenableComponent : Component { /// /// Whether this drink or food is opened or not. /// Drinks can only be drunk or poured from/into when open, and food can only be eaten when open. /// - [DataField] + [DataField, AutoNetworkedField] public bool Opened; /// /// If this is false you cant press Z to open it. /// Requires an OpenBehavior damage threshold or other logic to open. /// - [DataField] + [DataField, AutoNetworkedField] public bool OpenableByHand = true; /// @@ -61,7 +63,7 @@ public sealed partial class OpenableComponent : Component /// /// Can this item be closed again after opening? /// - [DataField] + [DataField, AutoNetworkedField] public bool Closeable; /// diff --git a/Content.Shared/Nutrition/EntitySystems/SharedOpenableSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedOpenableSystem.cs index 274de89003..f3b1127578 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedOpenableSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedOpenableSystem.cs @@ -1,7 +1,189 @@ +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Nutrition.Components; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Utility; + namespace Content.Shared.Nutrition.EntitySystems; +/// +/// Provides API for openable food and drinks, handles opening on use and preventing transfer when closed. +/// public abstract partial class SharedOpenableSystem : EntitySystem { + [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; + [Dependency] protected readonly SharedAudioSystem Audio = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(HandleIfClosed); + SubscribeLocalEvent(HandleIfClosed); + SubscribeLocalEvent>(AddOpenCloseVerbs); + } + + private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args) + { + UpdateAppearance(uid, comp); + } + + private void OnUse(EntityUid uid, OpenableComponent comp, UseInHandEvent args) + { + if (args.Handled || !comp.OpenableByHand) + return; + + args.Handled = TryOpen(uid, comp, args.User); + } + + private void OnExamined(EntityUid uid, OpenableComponent comp, ExaminedEvent args) + { + if (!comp.Opened || !args.IsInDetailsRange) + return; + + var text = Loc.GetString(comp.ExamineText); + args.PushMarkup(text); + } + + private void HandleIfClosed(EntityUid uid, OpenableComponent comp, HandledEntityEventArgs args) + { + // prevent spilling/pouring/whatever drinks when closed + args.Handled = !comp.Opened; + } + + private void AddOpenCloseVerbs(EntityUid uid, OpenableComponent comp, GetVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + Verb verb; + if (comp.Opened) + { + if (!comp.Closeable) + return; + + verb = new() + { + Text = Loc.GetString(comp.CloseVerbText), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")), + Act = () => TryClose(args.Target, comp, args.User) + }; + } + else + { + verb = new() + { + Text = Loc.GetString(comp.OpenVerbText), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")), + Act = () => TryOpen(args.Target, comp, args.User) + }; + } + args.Verbs.Add(verb); + } + + /// + /// Returns true if the entity either does not have OpenableComponent or it is opened. + /// Drinks that don't have OpenableComponent are automatically open, so it returns true. + /// + public bool IsOpen(EntityUid uid, OpenableComponent? comp = null) + { + if (!Resolve(uid, ref comp, false)) + return true; + + return comp.Opened; + } + + /// + /// Returns true if the entity both has OpenableComponent and is not opened. + /// Drinks that don't have OpenableComponent are automatically open, so it returns false. + /// If user is not null a popup will be shown to them. + /// + public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null) + { + if (!Resolve(uid, ref comp, false)) + return false; + + if (comp.Opened) + return false; + + if (user != null) + Popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value); + + return true; + } + + /// + /// Update open visuals to the current value. + /// + public void UpdateAppearance(EntityUid uid, OpenableComponent? comp = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref comp)) + return; + + Appearance.SetData(uid, OpenableVisuals.Opened, comp.Opened, appearance); + } + + /// + /// Sets the opened field and updates open visuals. + /// + public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = null) + { + if (!Resolve(uid, ref comp, false) || opened == comp.Opened) + return; + + comp.Opened = opened; + Dirty(uid, comp); + + if (opened) + { + var ev = new OpenableOpenedEvent(); + RaiseLocalEvent(uid, ref ev); + } + else + { + var ev = new OpenableClosedEvent(); + RaiseLocalEvent(uid, ref ev); + } + + UpdateAppearance(uid, comp); + } + + /// + /// If closed, opens it and plays the sound. + /// + /// Whether it got opened + public bool TryOpen(EntityUid uid, OpenableComponent? comp = null, EntityUid? user = null) + { + if (!Resolve(uid, ref comp, false) || comp.Opened) + return false; + + SetOpen(uid, true, comp); + Audio.PlayPredicted(comp.Sound, uid, user); + return true; + } + + /// + /// If opened, closes it and plays the close sound, if one is defined. + /// + /// Whether it got closed + public bool TryClose(EntityUid uid, OpenableComponent? comp = null, EntityUid? user = null) + { + if (!Resolve(uid, ref comp, false) || !comp.Opened || !comp.Closeable) + return false; + + SetOpen(uid, false, comp); + if (comp.CloseSound != null) + Audio.PlayPredicted(comp.CloseSound, uid, user); + return true; + } } ///