]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Fix eating and drinking verbs showing up after a short delay and making your verb...
authorDrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
Mon, 9 Jun 2025 14:36:04 +0000 (07:36 -0700)
committerGitHub <noreply@github.com>
Mon, 9 Jun 2025 14:36:04 +0000 (10:36 -0400)
* Fix eating and drinking verbs showing up after a short delay and making your verb UI bounce

* Usings fix

* Usings fix

* Usings fix

* Usings fix

* CVar fix

* Predicted ppups

* Openable predicted popup

* Fix audio prediction

40 files changed:
Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleEui.cs
Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleWindow.xaml.cs
Content.Server/Bed/BedSystem.cs
Content.Server/Body/Systems/BloodstreamSystem.cs
Content.Server/Body/Systems/BodySystem.cs
Content.Server/Body/Systems/MetabolizerSystem.cs
Content.Server/Body/Systems/RespiratorSystem.cs
Content.Server/Crayon/CrayonSystem.cs
Content.Server/Kitchen/EntitySystems/SharpSystem.cs
Content.Server/Medical/VomitSystem.cs
Content.Server/Nutrition/Components/BadFoodComponent.cs
Content.Server/Nutrition/Components/IgnoreBadFoodComponent.cs
Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
Content.Server/Nutrition/EntitySystems/SmokingSystem.Vape.cs
Content.Server/Nutrition/EntitySystems/UtensilSystem.cs [deleted file]
Content.Shared/Body/Components/StomachComponent.cs [moved from Content.Server/Body/Components/StomachComponent.cs with 89% similarity]
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs [new file with mode: 0644]
Content.Shared/Body/Systems/StomachSystem.cs [moved from Content.Server/Body/Systems/StomachSystem.cs with 98% similarity]
Content.Shared/Burial/BurialSystem.cs
Content.Shared/CCVar/CCVars.Misc.cs
Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs
Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettings.cs
Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettingsPrototype.cs
Content.Shared/Light/Components/LightReplacerComponent.cs
Content.Shared/Medical/Cryogenics/ActiveCryoPodComponent.cs
Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
Content.Shared/Nutrition/Components/FlavorProfileComponent.cs [moved from Content.Server/Nutrition/Components/FlavorProfileComponent.cs with 74% similarity]
Content.Shared/Nutrition/Components/FoodComponent.cs [moved from Content.Server/Nutrition/Components/FoodComponent.cs with 92% similarity]
Content.Shared/Nutrition/Components/IngestionBlockerComponent.cs [moved from Content.Server/Nutrition/Components/IngestionBlockerComponent.cs with 76% similarity]
Content.Shared/Nutrition/Components/UtensilComponent.cs
Content.Shared/Nutrition/EntitySystems/FlavorProfileSystem.cs [moved from Content.Server/Nutrition/EntitySystems/FlavorProfileSystem.cs with 96% similarity]
Content.Shared/Nutrition/EntitySystems/FoodSequenceSystem.cs [moved from Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs with 96% similarity]
Content.Shared/Nutrition/EntitySystems/FoodSystem.cs [moved from Content.Server/Nutrition/EntitySystems/FoodSystem.cs with 93% similarity]
Content.Shared/Nutrition/EntitySystems/IngestionBlockerSystem.cs [moved from Content.Server/Nutrition/EntitySystems/IngestionBlockerSystem.cs with 76% similarity]
Content.Shared/Nutrition/EntitySystems/OpenableSystem.cs
Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs
Content.Shared/Nutrition/EntitySystems/SharedUtensilSystem.cs [deleted file]
Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs [new file with mode: 0644]
Content.Shared/Storage/Components/MagnetPickupComponent.cs
Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs

index 1e24d4c84c1e6912f18a10949f103862fc164da3..ab2c389f49417d119ed4f5864a60d0916f044e07 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Client.Eui;
-using Content.Server.Ghost.Roles.Raffles;
 using Content.Shared.Eui;
 using Content.Shared.Ghost.Roles;
+using Content.Shared.Ghost.Roles.Raffles;
 using JetBrains.Annotations;
 using Robust.Client.Console;
 using Robust.Client.Player;
index 6711d76b10a0b2c5f5a511872b77e4c4648d267b..5480db1dcd64fb0afeb9ba7d0b3e465087b3a63e 100644 (file)
@@ -1,6 +1,4 @@
-using System.Linq;
-using System.Numerics;
-using Content.Server.Ghost.Roles.Raffles;
+using System.Numerics;
 using Content.Shared.Ghost.Roles.Raffles;
 using Robust.Client.AutoGenerated;
 using Robust.Client.UserInterface.Controls;
index 673984c0a7f45bc262267b2c0495921b1971c89b..91b31c782be718d865e63f6463351c15f795ee3a 100644 (file)
@@ -1,11 +1,10 @@
-using Content.Server.Actions;
 using Content.Server.Bed.Components;
-using Content.Server.Body.Systems;
 using Content.Server.Power.EntitySystems;
 using Content.Shared.Bed;
 using Content.Shared.Bed.Components;
 using Content.Shared.Bed.Sleep;
 using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
 using Content.Shared.Buckle.Components;
 using Content.Shared.Damage;
 using Content.Shared.Emag.Systems;
index f4f50bf16e61bc416f983e3b92017faa5fc6c45a..9816c8c3f6b1f1d0bd0ba6c09e7973d3e73cff58 100644 (file)
@@ -1,8 +1,8 @@
 using Content.Server.Body.Components;
-using Content.Shared.EntityEffects.Effects;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Popups;
 using Content.Shared.Alert;
+using Content.Shared.Body.Events;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reaction;
@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Damage;
 using Content.Shared.Damage.Prototypes;
 using Content.Shared.Drunk;
+using Content.Shared.EntityEffects.Effects;
 using Content.Shared.FixedPoint;
 using Content.Shared.Forensics;
 using Content.Shared.Forensics.Components;
index fb6f8d6777d3b12abf4bf48cc5407acf101e4602..d2fc3d65586e999ebcf641d209baec14d9702f70 100644 (file)
@@ -1,9 +1,12 @@
+using System.Numerics;
 using Content.Server.Body.Components;
 using Content.Server.Ghost;
 using Content.Server.Humanoid;
 using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
 using Content.Shared.Body.Part;
 using Content.Shared.Body.Systems;
+using Content.Shared.Damage.Components;
 using Content.Shared.Humanoid;
 using Content.Shared.Mind;
 using Content.Shared.Mobs.Systems;
@@ -11,8 +14,6 @@ using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Systems;
 using Robust.Shared.Audio;
 using Robust.Shared.Timing;
-using System.Numerics;
-using Content.Shared.Damage.Components;
 
 namespace Content.Server.Body.Systems;
 
index 3497d4a6d785f00dfc8a128c2ca0071bd91b9b4f..5577b42a069334adc9db087f509a4c3d8b7fe41a 100644 (file)
@@ -1,9 +1,10 @@
 using Content.Server.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Events;
 using Content.Shared.Body.Organ;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Database;
 using Content.Shared.EntityEffects;
@@ -231,29 +232,4 @@ namespace Content.Server.Body.Systems
             _solutionContainerSystem.UpdateChemicals(soln.Value);
         }
     }
-
-    // TODO REFACTOR THIS
-    // This will cause rates to slowly drift over time due to floating point errors.
-    // Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
-    [ByRefEvent]
-    public readonly record struct ApplyMetabolicMultiplierEvent(
-        EntityUid Uid,
-        float Multiplier,
-        bool Apply)
-    {
-        /// <summary>
-        /// The entity whose metabolism is being modified.
-        /// </summary>
-        public readonly EntityUid Uid = Uid;
-
-        /// <summary>
-        /// What the metabolism's update rate will be multiplied by.
-        /// </summary>
-        public readonly float Multiplier = Multiplier;
-
-        /// <summary>
-        /// If true, apply the multiplier. If false, revert it.
-        /// </summary>
-        public readonly bool Apply = Apply;
-    }
 }
index e068aa9c7a24cbfc70122f8fe6ea8f396023805b..1b4cf4d69894f30ac6dc5270cdc17fb7e3de149a 100644 (file)
@@ -3,18 +3,19 @@ using Content.Server.Atmos.EntitySystems;
 using Content.Server.Body.Components;
 using Content.Server.Chat.Systems;
 using Content.Server.EntityEffects;
-using Content.Shared.EntityEffects.EffectConditions;
-using Content.Shared.EntityEffects.Effects;
-using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Alert;
 using Content.Shared.Atmos;
 using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
 using Content.Shared.Body.Prototypes;
 using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Damage;
 using Content.Shared.Database;
 using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.EffectConditions;
+using Content.Shared.EntityEffects.Effects;
 using Content.Shared.Mobs.Systems;
 using JetBrains.Annotations;
 using Robust.Shared.Prototypes;
index 4257c436c23c6d2cc11e6f29aab326ef7eb4d60e..f2597cd424682b933da94adec34d9bb8ccad7021 100644 (file)
@@ -2,18 +2,17 @@ using System.Linq;
 using System.Numerics;
 using Content.Server.Administration.Logs;
 using Content.Server.Decals;
-using Content.Server.Nutrition.EntitySystems;
 using Content.Server.Popups;
 using Content.Shared.Crayon;
 using Content.Shared.Database;
 using Content.Shared.Decals;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Nutrition.EntitySystems;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.GameStates;
-using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.Crayon;
index ab7961ba18ddf1388f24f393658ef8461dd256b4..4b4e16b5db615abb22bf6fb4eff6b09fad2b7cee 100644 (file)
@@ -1,21 +1,20 @@
 using Content.Server.Body.Systems;
 using Content.Server.Kitchen.Components;
-using Content.Server.Nutrition.EntitySystems;
-using Content.Shared.Body.Components;
 using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Components;
 using Content.Shared.Database;
-using Content.Shared.Interaction;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Popups;
-using Content.Shared.Storage;
-using Content.Shared.Verbs;
 using Content.Shared.Destructible;
 using Content.Shared.DoAfter;
-using Content.Shared.Hands.Components;
 using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
 using Content.Shared.Kitchen;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Popups;
+using Content.Shared.Storage;
+using Content.Shared.Verbs;
 using Robust.Server.Containers;
 using Robust.Server.GameObjects;
 using Robust.Shared.Random;
index a6982cea4f763712489eda9399ae7679775c23b3..9d27247a38db9509b2c88ed6edb7a55e7211be04 100644 (file)
@@ -4,8 +4,10 @@ using Content.Server.Fluids.EntitySystems;
 using Content.Server.Forensics;
 using Content.Server.Popups;
 using Content.Server.Stunnable;
-using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Nutrition.Components;
index 0924b465a4b32f7d3f4de55b3b165c9ba5da7fc7..16f90533cba7394bafaa909d7795f09cb9819b96 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
 
 namespace Content.Server.Nutrition.Components;
 
index cb30b98e1911424b6b6b1c6327a72806151eec0a..f5b3c326f32596bf94ad4a87215ee3477c6151bf 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
 
 namespace Content.Server.Nutrition.Components;
 
index 7ac5c20a7da9af79c6b8c72873acbeb83e1bfd5c..467f7b63349ce85b3e431782bb21106aa1384c07 100644 (file)
@@ -1,12 +1,11 @@
-using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
-using Content.Shared.EntityEffects.Effects;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Forensics;
 using Content.Server.Inventory;
 using Content.Server.Popups;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Components.SolutionManager;
@@ -14,7 +13,7 @@ using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Database;
 using Content.Shared.DoAfter;
-using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
 using Content.Shared.FixedPoint;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.IdentityManagement;
@@ -24,7 +23,6 @@ using Content.Shared.Mobs.Systems;
 using Content.Shared.Nutrition;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Nutrition.EntitySystems;
-using Content.Shared.Verbs;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Player;
@@ -65,7 +63,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
         // run after openable so its always open -> drink
         SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse, before: [typeof(ServerInventorySystem)], after: [typeof(OpenableSystem)]);
         SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
-        SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
         SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
     }
 
@@ -157,76 +154,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
         _appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance);
     }
 
-    /// <summary>
-    /// Tries to feed the drink item to the target entity
-    /// </summary>
-    private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
-    {
-        if (!HasComp<BodyComponent>(target))
-            return false;
-
-        if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
-            return false;
-
-        if (_openable.IsClosed(item, user))
-            return true;
-
-        if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
-        {
-            if (drink.IgnoreEmpty)
-                return false;
-
-            _popup.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
-            return true;
-        }
-
-        if (_food.IsMouthBlocked(target, user))
-            return true;
-
-        if (!_interaction.InRangeUnobstructed(user, item, popup: true))
-            return true;
-
-        var forceDrink = user != target;
-
-        if (forceDrink)
-        {
-            var userName = Identity.Entity(user, EntityManager);
-
-            _popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
-
-            // logging
-            _adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
-        }
-        else
-        {
-            // log voluntary drinking
-            _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
-        }
-
-        var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
-
-        var doAfterEventArgs = new DoAfterArgs(EntityManager,
-            user,
-            forceDrink ? drink.ForceFeedDelay : drink.Delay,
-            new ConsumeDoAfterEvent(drink.Solution, flavors),
-            eventTarget: item,
-            target: target,
-            used: item)
-        {
-            BreakOnHandChange = false,
-            BreakOnMove = forceDrink,
-            BreakOnDamage = true,
-            MovementThreshold = 0.01f,
-            DistanceThreshold = 1.0f,
-            // do-after will stop if item is dropped when trying to feed someone else
-            // or if the item started out in the user's own hands
-            NeedHand = forceDrink || _hands.IsHolding(user, item),
-        };
-
-        _doAfter.TryStartDoAfter(doAfterEventArgs);
-        return true;
-    }
-
     /// <summary>
     ///     Raised directed at a victim when someone has force fed them a drink.
     /// </summary>
@@ -241,7 +168,7 @@ public sealed class DrinkSystem : SharedDrinkSystem
         if (args.Used is null || !_solutionContainer.TryGetSolution(args.Used.Value, args.Solution, out var soln, out var solution))
             return;
 
-        if (_openable.IsClosed(args.Used.Value, args.Target.Value))
+        if (_openable.IsClosed(args.Used.Value, args.Target.Value, predicted: true))
             return;
 
         // TODO this should really be checked every tick.
@@ -330,36 +257,4 @@ public sealed class DrinkSystem : SharedDrinkSystem
         if (!forceDrink && solution.Volume > 0)
             args.Repeat = true;
     }
-
-    private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
-    {
-        if (entity.Owner == ev.User ||
-            !ev.CanInteract ||
-            !ev.CanAccess ||
-            !TryComp<BodyComponent>(ev.User, out var body) ||
-            !_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
-            return;
-
-        // Make sure the solution exists
-        if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
-            return;
-
-        // no drinking from living drinks, have to kill them first.
-        if (_mobState.IsAlive(entity))
-            return;
-
-        var user = ev.User;
-        AlternativeVerb verb = new()
-        {
-            Act = () =>
-            {
-                TryDrink(user, user, entity.Comp, entity);
-            },
-            Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
-            Text = Loc.GetString("drink-system-verb-drink"),
-            Priority = 2
-        };
-
-        ev.Verbs.Add(verb);
-    }
 }
index 48fb946135e7572e4478edf85904a10f27e11a85..72b8396051091ef89b919bfda5db0c8548d3ad42 100644 (file)
@@ -1,19 +1,16 @@
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
 using Content.Server.Body.Components;
 using Content.Server.DoAfter;
 using Content.Server.Explosion.EntitySystems;
 using Content.Server.Nutrition.Components;
 using Content.Server.Popups;
+using Content.Shared.Atmos;
 using Content.Shared.Damage;
 using Content.Shared.DoAfter;
-using Content.Shared.Emag.Components;
 using Content.Shared.Emag.Systems;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
 using Content.Shared.Nutrition;
-using System.Threading;
-using Content.Shared.Atmos;
+using Content.Shared.Nutrition.EntitySystems;
 
 /// <summary>
 /// System for vapes
diff --git a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs
deleted file mode 100644 (file)
index 766c38d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-using Content.Shared.Containers.ItemSlots;
-using Content.Server.Nutrition.Components;
-using Content.Server.Popups;
-using Content.Shared.Interaction;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Content.Shared.Tools.EntitySystems;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Random;
-
-namespace Content.Server.Nutrition.EntitySystems
-{
-    /// <summary>
-    /// Handles usage of the utensils on the food items
-    /// </summary>
-    internal sealed class UtensilSystem : SharedUtensilSystem
-    {
-        [Dependency] private readonly IRobustRandom _robustRandom = default!;
-        [Dependency] private readonly FoodSystem _foodSystem = default!;
-        [Dependency] private readonly PopupSystem _popupSystem = default!;
-        [Dependency] private readonly SharedAudioSystem _audio = default!;
-        [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
-
-            SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
-        }
-
-        /// <summary>
-        /// Clicked with utensil
-        /// </summary>
-        private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
-        {
-            if (ev.Handled || ev.Target == null || !ev.CanReach)
-                return;
-
-            var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
-            ev.Handled = result.Handled;
-        }
-
-        public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
-        {
-            if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
-                return (false, false);
-
-            //Prevents food usage with a wrong utensil
-            if ((food.Utensil & utensil.Comp.Types) == 0)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
-                return (false, true);
-            }
-
-            if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
-                return (false, true);
-
-            return _foodSystem.TryFeed(user, user, target, food);
-        }
-
-        /// <summary>
-        /// Attempt to break the utensil after interaction.
-        /// </summary>
-        /// <param name="uid">Utensil.</param>
-        /// <param name="userUid">User of the utensil.</param>
-        public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
-        {
-            if (!Resolve(uid, ref component))
-                return;
-
-            if (_robustRandom.Prob(component.BreakChance))
-            {
-                _audio.PlayPvs(component.BreakSound, userUid, AudioParams.Default.WithVolume(-2f));
-                EntityManager.DeleteEntity(uid);
-            }
-        }
-    }
-}
similarity index 89%
rename from Content.Server/Body/Components/StomachComponent.cs
rename to Content.Shared/Body/Components/StomachComponent.cs
index 4821b78f4aff54a8c9aa85823bfa63592ba680b2..b76dff0902997ea342abed9195a9c5d28c4dab64 100644 (file)
@@ -1,13 +1,14 @@
-using Content.Server.Body.Systems;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Nutrition.EntitySystems;
 using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Server.Body.Components
+namespace Content.Shared.Body.Components
 {
-    [RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
+    [RegisterComponent, NetworkedComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
     public sealed partial class StomachComponent : Component
     {
         /// <summary>
@@ -32,7 +33,7 @@ namespace Content.Server.Body.Components
         ///     What solution should this stomach push reagents into, on the body?
         /// </summary>
         [DataField]
-        public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
+        public string BodySolutionName = "chemicals";
 
         /// <summary>
         ///     Time between reagents being ingested and them being
diff --git a/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs b/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
new file mode 100644 (file)
index 0000000..dafc1e4
--- /dev/null
@@ -0,0 +1,26 @@
+namespace Content.Shared.Body.Events;
+
+// TODO REFACTOR THIS
+// This will cause rates to slowly drift over time due to floating point errors.
+// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
+[ByRefEvent]
+public readonly record struct ApplyMetabolicMultiplierEvent(
+    EntityUid Uid,
+    float Multiplier,
+    bool Apply)
+{
+    /// <summary>
+    /// The entity whose metabolism is being modified.
+    /// </summary>
+    public readonly EntityUid Uid = Uid;
+
+    /// <summary>
+    /// What the metabolism's update rate will be multiplied by.
+    /// </summary>
+    public readonly float Multiplier = Multiplier;
+
+    /// <summary>
+    /// If true, apply the multiplier. If false, revert it.
+    /// </summary>
+    public readonly bool Apply = Apply;
+}
similarity index 98%
rename from Content.Server/Body/Systems/StomachSystem.cs
rename to Content.Shared/Body/Systems/StomachSystem.cs
index 9fc7ff10e46d78f6ffe53caee0a2af26e907b586..935dda6389a0c122960590ca2d2ed0bbd2ee0030 100644 (file)
@@ -1,12 +1,13 @@
-using Content.Server.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
 using Content.Shared.Body.Organ;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.EntitySystems;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
-namespace Content.Server.Body.Systems
+namespace Content.Shared.Body.Systems
 {
     public sealed class StomachSystem : EntitySystem
     {
index 68b634efc1279e496c486611fabca61bd38a065d..5216e39dc8824f8dff8cc46fc04c42faa02253e7 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Shared.ActionBlocker;
-using Content.Shared.Burial;
 using Content.Shared.Burial.Components;
 using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
@@ -10,7 +9,7 @@ using Content.Shared.Storage.Components;
 using Content.Shared.Storage.EntitySystems;
 using Robust.Shared.Audio.Systems;
 
-namespace Content.Server.Burial.Systems;
+namespace Content.Shared.Burial;
 
 public sealed class BurialSystem : EntitySystem
 {
index 3d597c742746a2e1412701fd3607b83b588b9b23..5fda4dc2fdb6a4210d5d8405e2be59e386619584 100644 (file)
@@ -38,7 +38,7 @@ public sealed partial class CCVars
     ///     some food object won't spam a user with flavors.
     /// </summary>
     public static readonly CVarDef<int>
-        FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVERONLY);
+        FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVER | CVar.REPLICATED);
 
     public static readonly CVarDef<string> DestinationFile =
         CVarDef.Create("autogen.destination_file", "", CVar.SERVER | CVar.SERVERONLY);
index 1bc5b26cfb72ebc7b289f8613aeb003ad794c549..31294bc21fbc7d746fadeb8beb262ec2b0bded80 100644 (file)
@@ -1,8 +1,7 @@
 using Content.Shared.Body.Components;
-using Content.Shared.EntityEffects;
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.EntityEffects.EffectConditions;
+namespace Content.Shared.EntityEffects.EffectConditions;
 
 /// <summary>
 ///     Condition for if the entity is or isn't wearing internals.
index a7aa757b72a27e74e720464a3ddb5d55f3cff6e7..4f31bb7182a19b15864c4543bf215281ff873e50 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.Ghost.Roles.Raffles;
+namespace Content.Shared.Ghost.Roles.Raffles;
 
 /// <summary>
 /// Defines settings for a ghost role raffle.
index 2c6e7229d7f03071b8e17f47b05783352f1593ac..18ebff556a61a1d0a1bc50f0ae2e89e9c1ed8399 100644 (file)
@@ -1,5 +1,4 @@
-using Content.Server.Ghost.Roles.Raffles;
-using Robust.Shared.Prototypes;
+using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Ghost.Roles.Raffles;
 
index 8d7d2339275df760ddc3bbb39f3aac9664b77ac5..3ce9647c884324cc8ef889803ab4f68fc463eef3 100644 (file)
@@ -4,7 +4,7 @@ using Robust.Shared.Audio;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 
-namespace Content.Server.Light.Components;
+namespace Content.Shared.Light.Components;
 
 /// <summary>
 ///     Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
index 21c000a289c651e1f65371a3117100a6c711f450..e242fd7502c2b352b2dd620dd4d7d52476377531 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.Medical.Components;
+namespace Content.Shared.Medical.Cryogenics;
 
 /// <summary>
 /// Tracking component for an enabled cryo pod (which periodically tries to inject chemicals in the occupant, if one exists)
index 891129a2ddc0b08f870802a586db9526bb28389b..8d7eb05cd9152ca055f8c2dfe18b6e739077a2f3 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Medical.Components;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Components;
 using Content.Shared.Database;
similarity index 74%
rename from Content.Server/Nutrition/Components/FlavorProfileComponent.cs
rename to Content.Shared/Nutrition/Components/FlavorProfileComponent.cs
index a4d5c1085a5161b430a0d7ce4855b939010b04b4..a3ca92ecf2c6e187144630d3dc33eee1ca0779e3 100644 (file)
@@ -1,22 +1,24 @@
-namespace Content.Server.Nutrition.Components;
+using Robust.Shared.GameStates;
 
-[RegisterComponent]
+namespace Content.Shared.Nutrition.Components;
+
+[RegisterComponent, NetworkedComponent]
 public sealed partial class FlavorProfileComponent : Component
 {
     /// <summary>
     ///     Localized string containing the base flavor of this entity.
     /// </summary>
-    [DataField("flavors")]
+    [DataField]
     public HashSet<string> Flavors { get; private set; } = new();
 
     /// <summary>
     ///     Reagent IDs to ignore when processing this flavor profile. Defaults to nutriment.
     /// </summary>
-    [DataField("ignoreReagents")]
+    [DataField]
     public HashSet<string> IgnoreReagents { get; private set; } = new()
     {
         "Nutriment",
         "Vitamin",
-        "Protein"
+        "Protein",
     };
 }
similarity index 92%
rename from Content.Server/Nutrition/Components/FoodComponent.cs
rename to Content.Shared/Nutrition/Components/FoodComponent.cs
index cfb69f53f0c9a2decc33629f534a30ae41b62c98..ce04569fcb92e890e0a52b22c33d56b39104fa1d 100644 (file)
@@ -1,11 +1,10 @@
-using Content.Server.Body.Components;
-using Content.Shared.Nutrition.Components;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Body.Components;
 using Content.Shared.FixedPoint;
+using Content.Shared.Nutrition.EntitySystems;
 using Robust.Shared.Audio;
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.Nutrition.Components;
+namespace Content.Shared.Nutrition.Components;
 
 [RegisterComponent, Access(typeof(FoodSystem), typeof(FoodSequenceSystem))]
 public sealed partial class FoodComponent : Component
similarity index 76%
rename from Content.Server/Nutrition/Components/IngestionBlockerComponent.cs
rename to Content.Shared/Nutrition/Components/IngestionBlockerComponent.cs
index c3188d07a0f5d3c8e361f55820894a8bcfece7cc..803bf1f8b20f2071dc9413bb5a9549100302607c 100644 (file)
@@ -1,6 +1,6 @@
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
 
-namespace Content.Server.Nutrition.Components;
+namespace Content.Shared.Nutrition.Components;
 
 /// <summary>
 ///     Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
@@ -9,7 +9,7 @@ namespace Content.Server.Nutrition.Components;
 ///     In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of
 ///     masks), then this component might become redundant.
 /// </remarks>
-[RegisterComponent, Access(typeof(FoodSystem), typeof(DrinkSystem), typeof(IngestionBlockerSystem))]
+[RegisterComponent, Access(typeof(FoodSystem), typeof(SharedDrinkSystem), typeof(IngestionBlockerSystem))]
 public sealed partial class IngestionBlockerComponent : Component
 {
     /// <summary>
index 50158f1f63ca499db1bd695c012fb18500aaa0f7..e8da5881863d243bd96a92cc0196729c56112065 100644 (file)
@@ -4,7 +4,7 @@ using Robust.Shared.GameStates;
 
 namespace Content.Shared.Nutrition.Components
 {
-    [RegisterComponent, NetworkedComponent, Access(typeof(SharedUtensilSystem))]
+    [RegisterComponent, NetworkedComponent, Access(typeof(UtensilSystem))]
     public sealed partial class UtensilComponent : Component
     {
         [DataField("types")]
similarity index 96%
rename from Content.Server/Nutrition/EntitySystems/FlavorProfileSystem.cs
rename to Content.Shared/Nutrition/EntitySystems/FlavorProfileSystem.cs
index 4a18696cea1aa184fd846717a9d3247ce86336a5..31384f3a18e891896e8a0449ab44b4ece0974660 100644 (file)
@@ -1,12 +1,11 @@
-using Content.Server.Nutrition.Components;
+using System.Linq;
 using Content.Shared.CCVar;
 using Content.Shared.Chemistry.Components;
-using Content.Shared.Nutrition;
+using Content.Shared.Nutrition.Components;
 using Robust.Shared.Configuration;
 using Robust.Shared.Prototypes;
-using System.Linq;
 
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
 
 /// <summary>
 ///     Deals with flavor profiles when you eat something.
similarity index 96%
rename from Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs
rename to Content.Shared/Nutrition/EntitySystems/FoodSequenceSystem.cs
index 8953773053ed1881a8486f85b36de157b7382c4d..7b50ae2c8bfabcabd82ce7991e19e235e6b15ee5 100644 (file)
@@ -1,21 +1,17 @@
 using System.Numerics;
 using System.Text;
-using Content.Server.Nutrition.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Interaction;
 using Content.Shared.Mobs.Systems;
-using Content.Shared.Nutrition;
 using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
 using Content.Shared.Nutrition.Prototypes;
 using Content.Shared.Popups;
 using Content.Shared.Storage.Components;
 using Content.Shared.Tag;
-using Robust.Server.GameObjects;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
 
 public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
 {
@@ -26,7 +22,7 @@ public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
     [Dependency] private readonly TagSystem _tag = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly IPrototypeManager _proto = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
 
     public override void Initialize()
     {
@@ -126,7 +122,7 @@ public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
         if (start.Comp.FoodLayers.Count >= start.Comp.MaxLayers && !elementIndexed.Final || start.Comp.Finished)
         {
             if (user is not null)
-                _popup.PopupEntity(Loc.GetString("food-sequence-no-space"), start, user.Value);
+                _popup.PopupClient(Loc.GetString("food-sequence-no-space"), start, user.Value);
             return false;
         }
 
similarity index 93%
rename from Content.Server/Nutrition/EntitySystems/FoodSystem.cs
rename to Content.Shared/Nutrition/EntitySystems/FoodSystem.cs
index 3bdf3393ae8f65a4624f2beff30269b5d2621716..a4122168e4c0bf1c2b53160db926fab512c7b328 100644 (file)
@@ -1,16 +1,13 @@
-using Content.Server.Body.Components;
-using Content.Server.Body.Systems;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Server.Inventory;
-using Content.Server.Nutrition.Components;
-using Content.Shared.Nutrition.Components;
-using Content.Server.Popups;
-using Content.Server.Stack;
+using System.Linq;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Organ;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Containers.ItemSlots;
 using Content.Shared.Database;
+using Content.Shared.Destructible;
 using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using Content.Shared.Hands.Components;
@@ -21,42 +18,38 @@ using Content.Shared.Interaction.Components;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Inventory;
 using Content.Shared.Mobs.Systems;
-using Content.Shared.Nutrition;
-using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
 using Content.Shared.Stacks;
 using Content.Shared.Storage;
 using Content.Shared.Verbs;
+using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Utility;
-using System.Linq;
-using Content.Shared.Containers.ItemSlots;
-using Robust.Server.GameObjects;
-using Content.Shared.Whitelist;
-using Content.Shared.Destructible;
 
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
 
 /// <summary>
 /// Handles feeding attempts both on yourself and on the target.
 /// </summary>
 public sealed class FoodSystem : EntitySystem
 {
-    [Dependency] private readonly BodySystem _body = default!;
+    [Dependency] private readonly SharedBodySystem _body = default!;
     [Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
     [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
     [Dependency] private readonly InventorySystem _inventory = default!;
     [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly OpenableSystem _openable = default!;
-    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly ReactiveSystem _reaction = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly SharedHandsSystem _hands = default!;
     [Dependency] private readonly SharedInteractionSystem _interaction = default!;
     [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
-    [Dependency] private readonly StackSystem _stack = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedStackSystem _stack = default!;
     [Dependency] private readonly StomachSystem _stomach = default!;
     [Dependency] private readonly UtensilSystem _utensil = default!;
     [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
@@ -69,7 +62,7 @@ public sealed class FoodSystem : EntitySystem
 
         // TODO add InteractNoHandEvent for entities like mice.
         // run after openable for wrapped/peelable foods
-        SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(ServerInventorySystem) });
+        SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(InventorySystem) });
         SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
         SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
         SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
@@ -116,7 +109,7 @@ public sealed class FoodSystem : EntitySystem
         if (HasComp<UnremoveableComponent>(food))
             return (false, false);
 
-        if (_openable.IsClosed(food, user))
+        if (_openable.IsClosed(food, user, predicted: true))
             return (false, true);
 
         if (!_solutionContainer.TryGetSolution(food, foodComp.Solution, out _, out var foodSolution))
@@ -135,7 +128,7 @@ public sealed class FoodSystem : EntitySystem
         // Check for used storage on the food item
         if (TryComp<StorageComponent>(food, out var storageState) && storageState.Container.ContainedEntities.Any())
         {
-            _popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
+            _popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
             return (false, true);
         }
 
@@ -144,7 +137,7 @@ public sealed class FoodSystem : EntitySystem
         {
             if (itemSlots.Slots.Any(slot => slot.Value.HasItem))
             {
-                _popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
+                _popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
                 return (false, true);
             }
         }
@@ -153,7 +146,7 @@ public sealed class FoodSystem : EntitySystem
 
         if (GetUsesRemaining(food, foodComp) <= 0)
         {
-            _popup.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
+            _popup.PopupClient(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
             DeleteAndSpawnTrash(foodComp, food, user);
             return (false, true);
         }
@@ -171,7 +164,7 @@ public sealed class FoodSystem : EntitySystem
         if (!_transform.GetMapCoordinates(user).InRange(_transform.GetMapCoordinates(target), MaxFeedDistance))
         {
             var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
-            _popup.PopupEntity(message, user, user);
+            _popup.PopupClient(message, user, user);
             return (false, true);
         }
 
@@ -268,7 +261,7 @@ public sealed class FoodSystem : EntitySystem
         if (stomachToUse == null)
         {
             _solutionContainer.TryAddSolution(soln.Value, split);
-            _popup.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
+            _popup.PopupClient(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
             return;
         }
 
@@ -283,20 +276,20 @@ public sealed class FoodSystem : EntitySystem
             var userName = Identity.Entity(args.User, EntityManager);
             _popup.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)), entity.Owner, entity.Owner);
 
-            _popup.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
+            _popup.PopupClient(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
 
             // log successful force feed
             _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity.Owner):user} forced {ToPrettyString(args.User):target} to eat {ToPrettyString(entity.Owner):food}");
         }
         else
         {
-            _popup.PopupEntity(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
+            _popup.PopupClient(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
 
             // log successful voluntary eating
             _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(entity.Owner):food}");
         }
 
-        _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
+        _audio.PlayPredicted(entity.Comp.UseSound, args.Target.Value, args.User, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
 
         // Try to break all used utensils
         foreach (var utensil in utensils)
@@ -484,7 +477,7 @@ public sealed class FoodSystem : EntitySystem
         // If "required" field is set, try to block eating without proper utensils used
         if (component.UtensilRequired && (usedTypes & component.Utensil) != component.Utensil)
         {
-            _popup.PopupEntity(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
+            _popup.PopupClient(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
             return false;
         }
 
@@ -533,7 +526,7 @@ public sealed class FoodSystem : EntitySystem
         RaiseLocalEvent(uid, attempt, false);
         if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
         {
-            _popup.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
+            _popup.PopupClient(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
                 uid, popupUid.Value);
         }
 
similarity index 76%
rename from Content.Server/Nutrition/EntitySystems/IngestionBlockerSystem.cs
rename to Content.Shared/Nutrition/EntitySystems/IngestionBlockerSystem.cs
index 63b39fb524a2149f56736cf168ebd02e956fc29c..f9cd233948bd81d3960645d4442b8182043cfc33 100644 (file)
@@ -1,7 +1,7 @@
-using Content.Server.Nutrition.Components;
-using Content.Shared.Clothing;
+using Content.Shared.Clothing;
+using Content.Shared.Nutrition.Components;
 
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
 
 public sealed class IngestionBlockerSystem : EntitySystem
 {
index 85a94a7ec68e3b2bd4cffacf0e58f825361501a7..2f276fa93d4d9bab30093a1fdef4a6f086dfbe72 100644 (file)
@@ -1,8 +1,8 @@
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Examine;
-using Content.Shared.Lock;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Lock;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Popups;
 using Content.Shared.Verbs;
@@ -166,7 +166,7 @@ public sealed partial class OpenableSystem : EntitySystem
     /// 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)
+    public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null, bool predicted = false)
     {
         if (!Resolve(uid, ref comp, false))
             return false;
@@ -175,7 +175,12 @@ public sealed partial class OpenableSystem : EntitySystem
             return false;
 
         if (user != null)
-            _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+        {
+            if (predicted)
+                _popup.PopupClient(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+            else
+                _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+        }
 
         return true;
     }
index bf1e585fab9ad8c7009ab538589c4ef6a96e594a..a4b8e13b2fa16b19d7effd08d1cd80e55f1e5fd6 100644 (file)
@@ -1,15 +1,36 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.FixedPoint;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Mobs.Systems;
 using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.Utility;
 
 namespace Content.Shared.Nutrition.EntitySystems;
 
 public abstract partial class SharedDrinkSystem : EntitySystem
 {
-    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly SharedBodySystem _body = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+    [Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
+    [Dependency] private readonly FoodSystem _food = default!;
+    [Dependency] private readonly SharedHandsSystem _hands = default!;
+    [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+    [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly OpenableSystem _openable = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
 
     public override void Initialize()
     {
@@ -17,6 +38,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
 
         SubscribeLocalEvent<DrinkComponent, AttemptShakeEvent>(OnAttemptShake);
         SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
     }
 
     protected void OnAttemptShake(Entity<DrinkComponent> entity, ref AttemptShakeEvent args)
@@ -28,7 +50,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
     protected void OnExamined(Entity<DrinkComponent> entity, ref ExaminedEvent args)
     {
         TryComp<OpenableComponent>(entity, out var openable);
-        if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable)
+        if (_openable.IsClosed(entity.Owner, null, openable, true) || !args.IsInDetailsRange || !entity.Comp.Examinable)
             return;
 
         var empty = IsEmpty(entity, entity.Comp);
@@ -57,6 +79,38 @@ public abstract partial class SharedDrinkSystem : EntitySystem
         }
     }
 
+    private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
+    {
+        if (entity.Owner == ev.User ||
+            !ev.CanInteract ||
+            !ev.CanAccess ||
+            !TryComp<BodyComponent>(ev.User, out var body) ||
+            !_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
+            return;
+
+        // Make sure the solution exists
+        if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
+            return;
+
+        // no drinking from living drinks, have to kill them first.
+        if (_mobState.IsAlive(entity))
+            return;
+
+        var user = ev.User;
+        AlternativeVerb verb = new()
+        {
+            Act = () =>
+            {
+                TryDrink(user, user, entity.Comp, entity);
+            },
+            Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
+            Text = Loc.GetString("drink-system-verb-drink"),
+            Priority = 2
+        };
+
+        ev.Verbs.Add(verb);
+    }
+
     protected FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null)
     {
         if (!Resolve(uid, ref component))
@@ -87,4 +141,74 @@ public abstract partial class SharedDrinkSystem : EntitySystem
 
         return remainingString;
     }
+
+    /// <summary>
+    /// Tries to feed the drink item to the target entity
+    /// </summary>
+    protected bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
+    {
+        if (!HasComp<BodyComponent>(target))
+            return false;
+
+        if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
+            return false;
+
+        if (_openable.IsClosed(item, user, predicted: true))
+            return true;
+
+        if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
+        {
+            if (drink.IgnoreEmpty)
+                return false;
+
+            _popup.PopupClient(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
+            return true;
+        }
+
+        if (_food.IsMouthBlocked(target, user))
+            return true;
+
+        if (!_interaction.InRangeUnobstructed(user, item, popup: true))
+            return true;
+
+        var forceDrink = user != target;
+
+        if (forceDrink)
+        {
+            var userName = Identity.Entity(user, EntityManager);
+
+            _popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
+
+            // logging
+            _adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
+        }
+        else
+        {
+            // log voluntary drinking
+            _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
+        }
+
+        var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
+
+        var doAfterEventArgs = new DoAfterArgs(EntityManager,
+            user,
+            forceDrink ? drink.ForceFeedDelay : drink.Delay,
+            new ConsumeDoAfterEvent(drink.Solution, flavors),
+            eventTarget: item,
+            target: target,
+            used: item)
+        {
+            BreakOnHandChange = false,
+            BreakOnMove = forceDrink,
+            BreakOnDamage = true,
+            MovementThreshold = 0.01f,
+            DistanceThreshold = 1.0f,
+            // do-after will stop if item is dropped when trying to feed someone else
+            // or if the item started out in the user's own hands
+            NeedHand = forceDrink || _hands.IsHolding(user, item),
+        };
+
+        _doAfter.TryStartDoAfter(doAfterEventArgs);
+        return true;
+    }
 }
diff --git a/Content.Shared/Nutrition/EntitySystems/SharedUtensilSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedUtensilSystem.cs
deleted file mode 100644 (file)
index 656a36f..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Content.Shared.Nutrition.EntitySystems;
-
-public abstract class SharedUtensilSystem : EntitySystem
-{
-}
diff --git a/Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs b/Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs
new file mode 100644 (file)
index 0000000..e100d25
--- /dev/null
@@ -0,0 +1,73 @@
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Interaction;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
+using Content.Shared.Tools.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Shared.Nutrition.EntitySystems;
+
+public sealed class UtensilSystem : EntitySystem
+{
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly FoodSystem _foodSystem = default!;
+    [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
+    }
+
+    /// <summary>
+    /// Clicked with utensil
+    /// </summary>
+    private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
+    {
+        if (ev.Handled || ev.Target == null || !ev.CanReach)
+            return;
+
+        var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
+        ev.Handled = result.Handled;
+    }
+
+    public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
+    {
+        if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
+            return (false, false);
+
+        //Prevents food usage with a wrong utensil
+        if ((food.Utensil & utensil.Comp.Types) == 0)
+        {
+            _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
+            return (false, true);
+        }
+
+        if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
+            return (false, true);
+
+        return _foodSystem.TryFeed(user, user, target, food);
+    }
+
+    /// <summary>
+    /// Attempt to break the utensil after interaction.
+    /// </summary>
+    /// <param name="uid">Utensil.</param>
+    /// <param name="userUid">User of the utensil.</param>
+    public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
+    {
+        if (!Resolve(uid, ref component))
+            return;
+
+        if (_robustRandom.Prob(component.BreakChance))
+        {
+            _audio.PlayPredicted(component.BreakSound, userUid, userUid, AudioParams.Default.WithVolume(-2f));
+            EntityManager.DeleteEntity(uid);
+        }
+    }
+}
index 3467439a6da71eb343d8de447a8b34d0a771bd0a..90b7e83d638e8afb375366ecd612ca5154be6bef 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Shared.Inventory;
 
-namespace Content.Server.Storage.Components;
+namespace Content.Shared.Storage.Components;
 
 /// <summary>
 /// Applies an ongoing pickup area around the attached entity.
index c1fc856ff390a48a89adb3449f17e7be8b4c6679..9a0b48e65b34d4168a941a00af6b5a5949d01904 100644 (file)
@@ -1,7 +1,6 @@
-using Content.Server.Storage.Components;
 using Content.Shared.Inventory;
+using Content.Shared.Storage.Components;
 using Content.Shared.Whitelist;
-using Robust.Shared.Map;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Timing;