From 76bc00b218ab8a65c2de42088aca7b81ce0c3994 Mon Sep 17 00:00:00 2001 From: Fortune117 Date: Tue, 2 Nov 2021 11:40:55 +1100 Subject: [PATCH] Changed drinks to use ECS (#4948) Co-authored-by: Paul Ritter Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth --- .../Nutrition/UseDrinkInInventoryOperator.cs | 6 +- .../SolutionContainerSystem.Capabilities.cs | 8 + .../Nutrition/Components/DrinkComponent.cs | 202 +----------------- .../Nutrition/EntitySystems/DrinkSystem.cs | 179 +++++++++++++++- 4 files changed, 194 insertions(+), 201 deletions(-) diff --git a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs index ff73ea4082..fac0695046 100644 --- a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs @@ -1,6 +1,7 @@ using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; using Content.Shared.Nutrition.Components; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -57,9 +58,8 @@ namespace Content.Server.AI.Operators.Nutrition return Outcome.Failed; } - if (drinkComponent.Deleted || - drinkComponent.Empty || - _owner.TryGetComponent(out ThirstComponent? thirstComponent) && + if (drinkComponent.Deleted || EntitySystem.Get().IsEmpty(drinkComponent.Owner.Uid, drinkComponent) + || _owner.TryGetComponent(out ThirstComponent? thirstComponent) && thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay]) { return Outcome.Success; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs index f58bd879eb..548c48b143 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs @@ -109,6 +109,14 @@ namespace Content.Server.Chemistry.EntitySystems return solution.CurrentVolume; } + public ReagentUnit DrainAvailable(EntityUid uid) + { + if (!TryGetDrainableSolution(uid, out var solution)) + return ReagentUnit.Zero; + + return solution.CurrentVolume; + } + public bool HasFitsInDispenser(IEntity owner) { return !owner.Deleted && owner.HasComponent(); diff --git a/Content.Server/Nutrition/Components/DrinkComponent.cs b/Content.Server/Nutrition/Components/DrinkComponent.cs index 0204b2e9a7..a3963b329e 100644 --- a/Content.Server/Nutrition/Components/DrinkComponent.cs +++ b/Content.Server/Nutrition/Components/DrinkComponent.cs @@ -1,33 +1,17 @@ -using Content.Server.Body.Behavior; -using Content.Server.Fluids.Components; -using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; -using Content.Shared.Nutrition.Components; -using Content.Shared.Popups; using Content.Shared.Sound; using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Player; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using System.Linq; -using System.Threading.Tasks; -using Content.Server.Chemistry.Components.SolutionManager; -using Content.Server.Chemistry.EntitySystems; +using Content.Server.Nutrition.EntitySystems; +using Robust.Shared.Analyzers; namespace Content.Server.Nutrition.Components { [RegisterComponent] -#pragma warning disable 618 - public class DrinkComponent : Component, IUse, IAfterInteract, IExamine -#pragma warning restore 618 + [Friend(typeof(DrinkSystem))] + public class DrinkComponent : Component { [DataField("solution")] public string SolutionName { get; set; } = DefaultSolutionName; @@ -35,14 +19,9 @@ namespace Content.Server.Nutrition.Components public override string Name => "Drink"; - int IAfterInteract.Priority => 10; - - [ViewVariables] - private bool _opened; - [ViewVariables] [DataField("useSound")] - private SoundSpecifier _useSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); + public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); [ViewVariables] [DataField("isOpen")] @@ -52,174 +31,15 @@ namespace Content.Server.Nutrition.Components public ReagentUnit TransferAmount { get; [UsedImplicitly] private set; } = ReagentUnit.New(5); [ViewVariables(VVAccess.ReadWrite)] - public bool Opened - { - get => _opened; - set - { - if (_opened == value) - { - return; - } - - _opened = value; - OnOpenedChanged(); - } - } - - [ViewVariables] public bool Empty => IsEmpty(); - - private bool IsEmpty() - { - var drainAvailable = EntitySystem.Get() - .DrainAvailable(Owner); - return drainAvailable <= 0; - } + public bool Opened; [DataField("openSounds")] - private SoundSpecifier _openSounds = new SoundCollectionSpecifier("canOpenSounds"); + public SoundSpecifier OpenSounds = new SoundCollectionSpecifier("canOpenSounds"); - [DataField("pressurized")] public bool Pressurized; + [DataField("pressurized")] + public bool Pressurized; - [DataField("burstSound")] public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg"); - - private void OnOpenedChanged() - { - var solutionSys = EntitySystem.Get(); - if (!solutionSys.TryGetSolution(Owner, SolutionName, out _)) - { - return; - } - - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(DrinkCanStateVisual.Opened, Opened); - } - - if (Opened) - { - var refillable = Owner.EnsureComponent(); - refillable.Solution = SolutionName; - var drainable = Owner.EnsureComponent(); - drainable.Solution = SolutionName; - } - else - { - Owner.RemoveComponent(); - Owner.RemoveComponent(); - } - } - - bool IUse.UseEntity(UseEntityEventArgs args) - { - if (!Opened) - { - //Do the opening stuff like playing the sounds. - SoundSystem.Play(Filter.Pvs(args.User), _openSounds.GetSound(), args.User, AudioParams.Default); - - Opened = true; - return false; - } - - if (!Owner.HasComponent() || - EntitySystem.Get().DrainAvailable(Owner) <= 0) - { - args.User.PopupMessage(Loc.GetString("drink-component-on-use-is-empty", ("owner", Owner))); - return true; - } - - return TryUseDrink(args.User, args.User); - } - - //Force feeding a drink to someone. - async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) - { - if (eventArgs.Target == null) - { - return false; - } - - return TryUseDrink(eventArgs.User, eventArgs.Target, true); - } - - public void Examine(FormattedMessage message, bool inDetailsRange) - { - if (!Opened || !inDetailsRange) - { - return; - } - - var color = Empty ? "gray" : "yellow"; - var openedText = - Loc.GetString(Empty ? "drink-component-on-examine-is-empty" : "drink-component-on-examine-is-opened"); - message.AddMarkup(Loc.GetString("drink-component-on-examine-details-text", ("colorName", color), ("text", openedText))); - } - - private bool TryUseDrink(IEntity user, IEntity target, bool forced = false) - { - if (!Opened) - { - target.PopupMessage(Loc.GetString("drink-component-try-use-drink-not-open", ("owner", Owner))); - return false; - } - - if (!EntitySystem.Get().TryGetDrainableSolution(Owner.Uid, out var interactions) || - interactions.DrainAvailable <= 0) - { - if (!forced) - { - target.PopupMessage(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", Owner))); - } - - return false; - } - - if (!target.TryGetComponent(out SharedBodyComponent? body) || - !body.TryGetMechanismBehaviors(out var stomachs)) - { - target.PopupMessage(Loc.GetString("drink-component-try-use-drink-cannot-drink", ("owner", Owner))); - return false; - } - - - if (user != target && - !user.InRangeUnobstructed(target, popup: true)) - { - return false; - } - - var solutionContainerSystem = EntitySystem.Get(); - var transferAmount = ReagentUnit.Min(TransferAmount, interactions.DrainAvailable); - var drain = solutionContainerSystem.Drain(Owner.Uid, interactions, transferAmount); - var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain)); - - // All stomach are full or can't handle whatever solution we have. - if (firstStomach == null) - { - target.PopupMessage(Loc.GetString("drink-component-try-use-drink-had-enough", ("owner", Owner))); - - if (Owner.EntityManager.TryGetEntity(Owner.Uid, out var interactionEntity) - && !interactionEntity.HasComponent()) - { - drain.SpillAt(target, "PuddleSmear"); - return false; - } - - solutionContainerSystem.Refill(Owner.Uid, interactions, drain); - return false; - } - - SoundSystem.Play(Filter.Pvs(target), _useSound.GetSound(), target, AudioParams.Default.WithVolume(-2f)); - - target.PopupMessage(Loc.GetString("drink-component-try-use-drink-success-slurp")); - - // TODO: Account for partial transfer. - - drain.DoEntityReaction(target, ReactionMethod.Ingestion); - - firstStomach.TryTransferSolution(drain); - - return true; - } + [DataField("burstSound")] + public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg"); } } diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 89bebffc91..59b6b29c9d 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -1,14 +1,23 @@ +using System.Linq; +using Content.Server.Body.Behavior; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.Fluids.Components; using Content.Server.Nutrition.Components; +using Content.Shared.Body.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Helpers; using Content.Shared.Nutrition.Components; +using Content.Shared.Popups; using Content.Shared.Throwing; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Player; using Robust.Shared.Random; @@ -27,6 +36,97 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent(OnSolutionChange); SubscribeLocalEvent(OnDrinkInit); SubscribeLocalEvent(HandleLand); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(AfterInteract); + SubscribeLocalEvent(OnExamined); + } + + public bool IsEmpty(EntityUid uid, DrinkComponent? component = null) + { + if(!Resolve(uid, ref component)) + return true; + + return _solutionContainerSystem.DrainAvailable(uid) <= 0; + } + + private void OnExamined(EntityUid uid, DrinkComponent component, ExaminedEvent args) + { + if (!component.Opened || !args.IsInDetailsRange) + { + return; + } + + var color = IsEmpty(uid, component) ? "gray" : "yellow"; + var openedText = + Loc.GetString(IsEmpty(uid, component) ? "drink-component-on-examine-is-empty" : "drink-component-on-examine-is-opened"); + args.Message.AddMarkup(Loc.GetString("drink-component-on-examine-details-text", ("colorName", color), ("text", openedText))); + } + + private void SetOpen(EntityUid uid, bool opened = false, DrinkComponent? component = null) + { + if(!Resolve(uid, ref component)) + return; + + if (opened == component.Opened) + return; + + component.Opened = opened; + + if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out _)) + { + return; + } + + if (EntityManager.TryGetComponent(uid, out var appearance)) + { + appearance.SetData(DrinkCanStateVisual.Opened, opened); + } + + if (opened) + { + EntityManager.EnsureComponent(uid).Solution= component.SolutionName; + EntityManager.EnsureComponent(uid).Solution= component.SolutionName; + } + else + { + EntityManager.RemoveComponent(uid); + EntityManager.RemoveComponent(uid); + } + + } + + private void AfterInteract(EntityUid uid, DrinkComponent component, AfterInteractEvent args) + { + if (args.Handled) + return; + + if (args.Target == null) + return; + + if (TryUseDrink(uid, args.User, args.Target, true, component)) + args.Handled = true; + } + + private void OnUse(EntityUid uid, DrinkComponent component, UseInHandEvent args) + { + if (args.Handled) return; + if (!component.Opened) + { + //Do the opening stuff like playing the sounds. + SoundSystem.Play(Filter.Pvs(args.User), component.OpenSounds.GetSound(), args.User, AudioParams.Default); + + SetOpen(uid, true, component); + return; + } + + if (_solutionContainerSystem.DrainAvailable(uid) <= 0) + { + args.User.PopupMessage(Loc.GetString("drink-component-on-use-is-empty", ("owner", EntityManager.GetEntity(uid)))); + return; + } + + if (TryUseDrink(uid, args.User, args.User, false, component)) + args.Handled = true; } private void HandleLand(EntityUid uid, DrinkComponent component, LandEvent args) @@ -50,17 +150,16 @@ namespace Content.Server.Nutrition.EntitySystems private void OnDrinkInit(EntityUid uid, DrinkComponent component, ComponentInit args) { - component.Opened = component.DefaultToOpened; + SetOpen(uid, component.DefaultToOpened, component); - var owner = EntityManager.GetEntity(uid); - if (owner.TryGetComponent(out DrainableSolutionComponent? existingDrainable)) + if (EntityManager.TryGetComponent(uid, out DrainableSolutionComponent? existingDrainable)) { // Beakers have Drink component but they should use the existing Drainable component.SolutionName = existingDrainable.Solution; } else { - _solutionContainerSystem.EnsureSolution(owner, component.SolutionName); + _solutionContainerSystem.EnsureSolution(uid, component.SolutionName); } UpdateAppearance(component); @@ -73,15 +172,81 @@ namespace Content.Server.Nutrition.EntitySystems public void UpdateAppearance(DrinkComponent component) { - if (!component.Owner.TryGetComponent(out AppearanceComponent? appearance) || - !component.Owner.HasComponent()) + if (!EntityManager.TryGetComponent(component.OwnerUid, out AppearanceComponent? appearance) || + !EntityManager.HasComponent(component.OwnerUid)) { return; } - var drainAvailable = Get().DrainAvailable(component.Owner); + var drainAvailable = _solutionContainerSystem.DrainAvailable(component.OwnerUid); appearance.SetData(FoodVisuals.Visual, drainAvailable.Float()); appearance.SetData(DrinkCanStateVisual.Opened, component.Opened); } + + private bool TryUseDrink(EntityUid uid, IEntity user, IEntity target, bool forced, DrinkComponent? component = null) + { + if(!Resolve(uid, ref component)) + return false; + + var owner = component.Owner; + + if (!component.Opened) + { + target.PopupMessage(Loc.GetString("drink-component-try-use-drink-not-open", ("owner", owner))); + return false; + } + + if (!_solutionContainerSystem.TryGetDrainableSolution(component.OwnerUid, out var interactions) || + interactions.DrainAvailable <= 0) + { + if (!forced) + { + target.PopupMessage(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", owner))); + } + + return false; + } + + if (!EntityManager.TryGetComponent(target.Uid, out SharedBodyComponent? body) || + !body.TryGetMechanismBehaviors(out var stomachs)) + { + target.PopupMessage(Loc.GetString("drink-component-try-use-drink-cannot-drink", ("owner", owner))); + return false; + } + + if (user != target && !user.InRangeUnobstructed(target, popup: true)) + return false; + + var transferAmount = ReagentUnit.Min(component.TransferAmount, interactions.DrainAvailable); + var drain = _solutionContainerSystem.Drain(owner.Uid, interactions, transferAmount); + var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain)); + + // All stomach are full or can't handle whatever solution we have. + if (firstStomach == null) + { + target.PopupMessage(Loc.GetString("drink-component-try-use-drink-had-enough", ("owner", owner))); + + if (EntityManager.HasComponent(uid)) + { + drain.SpillAt(target, "PuddleSmear"); + return false; + } + + _solutionContainerSystem.Refill(owner.Uid, interactions, drain); + return false; + } + + SoundSystem.Play(Filter.Pvs(target), component.UseSound.GetSound(), target, AudioParams.Default.WithVolume(-2f)); + + target.PopupMessage(Loc.GetString("drink-component-try-use-drink-success-slurp")); + + // TODO: Account for partial transfer. + + drain.DoEntityReaction(target, ReactionMethod.Ingestion); + + firstStomach.TryTransferSolution(drain); + + return true; + } } }