From 25869cd501a4a4b0ba9781cfb25599945e6a8bdd Mon Sep 17 00:00:00 2001 From: Flipp Syder <76629141+vulppine@users.noreply.github.com> Date: Thu, 23 Jun 2022 19:26:54 -0700 Subject: [PATCH] Solution spiking (#8984) --- .../Components/SolutionSpikerComponent.cs | 30 ++++++ .../EntitySystems/SolutionContainerSystem.cs | 2 +- .../EntitySystems/SolutionSpikableSystem.cs | 94 +++++++++++++++++++ .../Fluids/EntitySystems/SpillableSystem.cs | 11 +++ .../components/solution-spike-component.ftl | 3 + .../Entities/Objects/Consumable/Food/egg.yml | 7 +- .../Entities/Objects/Specific/chemistry.yml | 3 + 7 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 Content.Server/Chemistry/Components/SolutionSpikerComponent.cs create mode 100644 Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs create mode 100644 Resources/Locale/en-US/chemistry/components/solution-spike-component.ftl diff --git a/Content.Server/Chemistry/Components/SolutionSpikerComponent.cs b/Content.Server/Chemistry/Components/SolutionSpikerComponent.cs new file mode 100644 index 0000000000..16ce3a36fb --- /dev/null +++ b/Content.Server/Chemistry/Components/SolutionSpikerComponent.cs @@ -0,0 +1,30 @@ +namespace Content.Server.Chemistry.Components; + +[RegisterComponent] +public sealed class SolutionSpikerComponent : Component +{ + /// + /// The source solution to take the reagents from in order + /// to spike the other solution container. + /// + [DataField("sourceSolution")] + public string SourceSolution { get; } = string.Empty; + + /// + /// If spiking with this entity should ignore empty containers or not. + /// + [DataField("ignoreEmpty")] + public bool IgnoreEmpty { get; } + + /// + /// What should pop up when spiking with this entity. + /// + [DataField("popup")] + public string Popup { get; } = "spike-solution-generic"; + + /// + /// What should pop up when spiking fails because the container was empty. + /// + [DataField("popupEmpty")] + public string PopupEmpty { get; } = "spike-solution-empty-generic"; +} diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs index 3cc86919f4..979cf15456 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -96,7 +96,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem || !Resolve(uid, ref appearanceComponent, false)) return; - var filledVolumePercent = solution.CurrentVolume.Float() / solution.MaxVolume.Float(); + var filledVolumePercent = Math.Min(1.0f, solution.CurrentVolume.Float() / solution.MaxVolume.Float()); appearanceComponent.SetData(SolutionContainerVisuals.VisualState, new SolutionContainerVisualState(solution.Color, filledVolumePercent)); } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs new file mode 100644 index 0000000000..b36a5bb336 --- /dev/null +++ b/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs @@ -0,0 +1,94 @@ +using Content.Server.Chemistry.Components; +using Content.Server.Chemistry.Components.SolutionManager; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Popups; +using Content.Shared.Chemistry.Components; +using Content.Shared.FixedPoint; +using Content.Shared.Interaction; +using Robust.Shared.Player; + +namespace Content.Server.Chemistry.EntitySystems; + +/// +/// Entity system used to handle when solution containers are 'spiked' +/// with another entity. Triggers the source entity afterwards. +/// Uses refillable solution as the target solution, as that indicates +/// 'easy' refills. +/// +/// Examples of spikable entity interactions include pills being dropped into glasses, +/// eggs being cracked into bowls, and so on. +/// +public sealed class SolutionSpikableSystem : EntitySystem +{ + [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly TriggerSystem _triggerSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, RefillableSolutionComponent target, InteractUsingEvent args) + { + TrySpike(args.Used, args.Target, args.User, target); + } + + /// + /// Immediately transfer all reagents from this entity, to the other entity. + /// The source entity will then be acted on by TriggerSystem. + /// + /// Source of the solution. + /// Target to spike with the solution from source. + /// User spiking the target solution. + private void TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null, + SolutionSpikerComponent? spikableSource = null, + SolutionContainerManagerComponent? managerSource = null, + SolutionContainerManagerComponent? managerTarget = null) + { + if (!Resolve(source, ref spikableSource, ref managerSource, false) + || !Resolve(target, ref spikableTarget, ref managerTarget, false) + || !_solutionSystem.TryGetRefillableSolution(target, out var targetSolution, managerTarget, spikableTarget) + || !managerSource.Solutions.TryGetValue(spikableSource.SourceSolution, out var sourceSolution)) + { + return; + } + + if (targetSolution.CurrentVolume == 0 && !spikableSource.IgnoreEmpty) + { + _popupSystem.PopupEntity(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, Filter.Entities(user)); + return; + } + + if (_solutionSystem.TryMixAndOverflow(target, + targetSolution, + sourceSolution, + targetSolution.MaxVolume, + out var overflow)) + { + if (overflow.TotalVolume > 0) + { + RaiseLocalEvent(target, new SolutionSpikeOverflowEvent(overflow)); + } + + _popupSystem.PopupEntity(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, Filter.Entities(user)); + + sourceSolution.RemoveAllSolution(); + + _triggerSystem.Trigger(source); + } + } +} + +public sealed class SolutionSpikeOverflowEvent : HandledEntityEventArgs +{ + /// + /// The solution that's been overflowed from the spike. + /// + public Solution Overflow { get; } + + public SolutionSpikeOverflowEvent(Solution overflow) + { + Overflow = overflow; + } +} diff --git a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs index 487b6aa98c..2017885667 100644 --- a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs @@ -33,6 +33,17 @@ public sealed class SpillableSystem : EntitySystem SubscribeLocalEvent(SpillOnLand); SubscribeLocalEvent>(AddSpillVerb); SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnSpikeOverflow); + } + + private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args) + { + if (!args.Handled) + { + SpillAt(args.Overflow, Transform(uid).Coordinates, "PuddleSmear"); + } + + args.Handled = true; } private void OnGotEquipped(EntityUid uid, SpillableComponent component, GotEquippedEvent args) diff --git a/Resources/Locale/en-US/chemistry/components/solution-spike-component.ftl b/Resources/Locale/en-US/chemistry/components/solution-spike-component.ftl new file mode 100644 index 0000000000..30603bfc37 --- /dev/null +++ b/Resources/Locale/en-US/chemistry/components/solution-spike-component.ftl @@ -0,0 +1,3 @@ +spike-solution-generic = You spike {THE($spiked-entity)} with {THE($spike-entity)}. +spike-solution-empty-generic = {THE($spike-entity)} fails to dissolve in {THE($spiked-entity)}. +spike-solution-egg = You crack {THE($spike-entity)} into {THE($spiked-entity)}. diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index adfcd6241b..4ed4efd412 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -23,7 +23,12 @@ maxVol: 6 reagents: - ReagentId: Egg - Quantity: 5 + Quantity: 6 + - type: SolutionSpiker + sourceSolution: food + ignoreEmpty: true + popup: spike-solution-egg + - type: DeleteOnTrigger - type: DamageOnLand damage: types: diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 00daaf7be6..482f2c9e60 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -273,6 +273,9 @@ solutions: food: maxVol: 50 + - type: SolutionSpiker + sourceSolution: food + - type: DeleteOnTrigger - type: Extractable grindableSolutionName: food