--- /dev/null
+using Content.Shared.Nutrition.EntitySystems;
+
+namespace Content.Client.Nutrition.EntitySystems;
+
+public sealed class OpenableSystem : SharedOpenableSystem
+{
+}
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;
/// </summary>
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<OpenableComponent, ComponentInit>(OnInit);
- SubscribeLocalEvent<OpenableComponent, UseInHandEvent>(OnUse);
- SubscribeLocalEvent<OpenableComponent, ExaminedEvent>(OnExamined, after: new[] { typeof(PuddleSystem) });
SubscribeLocalEvent<OpenableComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
- SubscribeLocalEvent<OpenableComponent, MeleeHitEvent>(HandleIfClosed);
- SubscribeLocalEvent<OpenableComponent, AfterInteractEvent>(HandleIfClosed);
- SubscribeLocalEvent<OpenableComponent, GetVerbsEvent<Verb>>(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)
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<Verb> 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);
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- public bool IsOpen(EntityUid uid, OpenableComponent? comp = null)
- {
- if (!Resolve(uid, ref comp, false))
- return true;
-
- return comp.Opened;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
-
- /// <summary>
- /// Update open visuals to the current value.
- /// </summary>
- 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);
- }
-
- /// <summary>
- /// Sets the opened field and updates open visuals.
- /// </summary>
- 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);
- }
-
- /// <summary>
- /// If closed, opens it and plays the sound.
- /// </summary>
- /// <returns>Whether it got opened</returns>
- 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;
- }
-
- /// <summary>
- /// If opened, closes it and plays the close sound, if one is defined.
- /// </summary>
- /// <returns>Whether it got closed</returns>
- 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;
- }
}
-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;
/// <summary>
/// A drink or food that can be opened.
/// Starts closed, open it with Z or E.
/// </summary>
-[RegisterComponent, Access(typeof(OpenableSystem))]
+[NetworkedComponent, AutoGenerateComponentState]
+[RegisterComponent, Access(typeof(SharedOpenableSystem))]
public sealed partial class OpenableComponent : Component
{
/// <summary>
/// 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.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public bool Opened;
/// <summary>
/// If this is false you cant press Z to open it.
/// Requires an OpenBehavior damage threshold or other logic to open.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public bool OpenableByHand = true;
/// <summary>
/// <summary>
/// Can this item be closed again after opening?
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public bool Closeable;
/// <summary>
+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;
+/// <summary>
+/// Provides API for openable food and drinks, handles opening on use and preventing transfer when closed.
+/// </summary>
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<OpenableComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<OpenableComponent, UseInHandEvent>(OnUse);
+ SubscribeLocalEvent<OpenableComponent, ExaminedEvent>(OnExamined);
+ SubscribeLocalEvent<OpenableComponent, MeleeHitEvent>(HandleIfClosed);
+ SubscribeLocalEvent<OpenableComponent, AfterInteractEvent>(HandleIfClosed);
+ SubscribeLocalEvent<OpenableComponent, GetVerbsEvent<Verb>>(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<Verb> 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);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public bool IsOpen(EntityUid uid, OpenableComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp, false))
+ return true;
+
+ return comp.Opened;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Update open visuals to the current value.
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Sets the opened field and updates open visuals.
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// If closed, opens it and plays the sound.
+ /// </summary>
+ /// <returns>Whether it got opened</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// If opened, closes it and plays the close sound, if one is defined.
+ /// </summary>
+ /// <returns>Whether it got closed</returns>
+ 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;
+ }
}
/// <summary>