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