From 3f0e9d696223286014bb5e5ca1871b409d0a40a8 Mon Sep 17 00:00:00 2001 From: Kyle Tyo <36606155+VerinSenpai@users.noreply.github.com> Date: Sat, 4 Oct 2025 07:57:24 -0400 Subject: [PATCH] VomitSystem, Predict! (#39921) * commit * Update AdminVerbSystem.Smites.cs * brrrrr * reeeee * skeet * Update VomitSystem.cs * didn't know i could do this. * Update SharedForensicsSystem.cs * Update SharedForensicsSystem.cs * Update SharedForensicsSystem.cs * Update ForensicsSystem.cs * requested changes. * Update VomitSystem.cs * lets try this. --- .../Systems/AdminVerbSystem.Smites.cs | 2 +- .../Thresholds/Behaviors/VomitBehavior.cs | 2 +- .../EntityEffects/EntityEffectSystem.cs | 2 +- Content.Server/Medical/VomitSystem.cs | 112 -------------- Content.Shared/Medical/VomitSystem.cs | 140 ++++++++++++++++++ 5 files changed, 143 insertions(+), 115 deletions(-) delete mode 100644 Content.Server/Medical/VomitSystem.cs create mode 100644 Content.Shared/Medical/VomitSystem.cs diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 02c47691fd..d03b799ff2 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -5,7 +5,6 @@ using Content.Server.Body.Systems; using Content.Server.Electrocution; using Content.Server.Explosion.EntitySystems; using Content.Server.GhostKick; -using Content.Server.Medical; using Content.Server.Nutrition.EntitySystems; using Content.Server.Physics.Components; using Content.Server.Pointing.Components; @@ -32,6 +31,7 @@ using Content.Shared.Electrocution; using Content.Shared.Gravity; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; +using Content.Shared.Medical; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; diff --git a/Content.Server/Destructible/Thresholds/Behaviors/VomitBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/VomitBehavior.cs index 067e7d4565..cba8576213 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/VomitBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/VomitBehavior.cs @@ -1,4 +1,4 @@ -using Content.Server.Medical; +using Content.Shared.Medical; namespace Content.Server.Destructible.Thresholds.Behaviors; diff --git a/Content.Server/EntityEffects/EntityEffectSystem.cs b/Content.Server/EntityEffects/EntityEffectSystem.cs index 3a86941a34..238ef4849d 100644 --- a/Content.Server/EntityEffects/EntityEffectSystem.cs +++ b/Content.Server/EntityEffects/EntityEffectSystem.cs @@ -11,7 +11,6 @@ using Content.Server.Emp; using Content.Server.Explosion.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Ghost.Roles.Components; -using Content.Server.Medical; using Content.Server.Polymorph.Components; using Content.Server.Polymorph.Systems; using Content.Server.Speech.Components; @@ -29,6 +28,7 @@ using Content.Shared.EntityEffects.Effects; using Content.Shared.EntityEffects; using Content.Shared.Flash; using Content.Shared.Maps; +using Content.Shared.Medical; using Content.Shared.Mind.Components; using Content.Shared.Popups; using Content.Shared.Random; diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs deleted file mode 100644 index 235cc17331..0000000000 --- a/Content.Server/Medical/VomitSystem.cs +++ /dev/null @@ -1,112 +0,0 @@ -using Content.Server.Body.Systems; -using Content.Server.Fluids.EntitySystems; -using Content.Server.Forensics; -using Content.Server.Popups; -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.Mobs.Systems; -using Content.Shared.Movement.Systems; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Robust.Server.Audio; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; - -namespace Content.Server.Medical -{ - public sealed class VomitSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly BloodstreamSystem _bloodstream = default!; - [Dependency] private readonly BodySystem _body = default!; - [Dependency] private readonly ForensicsSystem _forensics = default!; - [Dependency] private readonly HungerSystem _hunger = default!; - [Dependency] private readonly MobStateSystem _mobstate = default!; - [Dependency] private readonly MovementModStatusSystem _movementMod = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly PuddleSystem _puddle = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; - [Dependency] private readonly ThirstSystem _thirst = default!; - - private static readonly ProtoId VomitCollection = "Vomit"; - - private readonly SoundSpecifier _vomitSound = new SoundCollectionSpecifier(VomitCollection, - AudioParams.Default.WithVariation(0.2f).WithVolume(-4f)); - - /// - /// Make an entity vomit, if they have a stomach. - /// - public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = -40f, bool force = false) - { - // Main requirement: You have a stomach - var stomachList = _body.GetBodyOrganEntityComps(uid); - if (stomachList.Count == 0) - return; - - // Vomit only if entity is alive - // Ignore condition if force was set to true - if (!force && _mobstate.IsDead(uid)) - return; - - // Vomiting makes you hungrier and thirstier - if (TryComp(uid, out var hunger)) - _hunger.ModifyHunger(uid, hungerAdded, hunger); - - if (TryComp(uid, out var thirst)) - _thirst.ModifyThirst(uid, thirst, thirstAdded); - - // It fully empties the stomach, this amount from the chem stream is relatively small - var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6; - // Apply a bit of slowdown - _movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f); - - // TODO: Need decals - var solution = new Solution(); - - // Empty the stomach out into it - foreach (var stomach in stomachList) - { - if (_solutionContainer.ResolveSolution(stomach.Owner, StomachSystem.DefaultSolutionName, ref stomach.Comp1.Solution, out var sol)) - { - solution.AddSolution(sol, _proto); - sol.RemoveAllSolution(); - _solutionContainer.UpdateChemicals(stomach.Comp1.Solution.Value); - } - } - // Adds a tiny amount of the chem stream from earlier along with vomit - if (TryComp(uid, out var bloodStream)) - { - const float chemMultiplier = 0.1f; - - var vomitAmount = solutionSize; - - // Takes 10% of the chemicals removed from the chem stream - if (_solutionContainer.ResolveSolution(uid, bloodStream.ChemicalSolutionName, ref bloodStream.ChemicalSolution)) - { - var vomitChemstreamAmount = _solutionContainer.SplitSolution(bloodStream.ChemicalSolution.Value, vomitAmount); - vomitChemstreamAmount.ScaleSolution(chemMultiplier); - solution.AddSolution(vomitChemstreamAmount, _proto); - - vomitAmount -= (float)vomitChemstreamAmount.Volume; - } - - // Makes a vomit solution the size of 90% of the chemicals removed from the chemstream - solution.AddReagent(new ReagentId("Vomit", _bloodstream.GetEntityBloodData(uid)), vomitAmount); // TODO: Dehardcode vomit prototype - } - - if (_puddle.TrySpillAt(uid, solution, out var puddle, false)) - { - _forensics.TransferDna(puddle, uid, false); - } - - // Force sound to play as spill doesn't work if solution is empty. - _audio.PlayPvs(_vomitSound, uid); - _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); - } - } -} diff --git a/Content.Shared/Medical/VomitSystem.cs b/Content.Shared/Medical/VomitSystem.cs new file mode 100644 index 0000000000..d398faef20 --- /dev/null +++ b/Content.Shared/Medical/VomitSystem.cs @@ -0,0 +1,140 @@ +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.Fluids; +using Content.Shared.Forensics.Systems; +using Content.Shared.IdentityManagement; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Popups; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical; + +public sealed class VomitSystem : EntitySystem +{ + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + [Dependency] private readonly ThirstSystem _thirst = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly SharedForensicsSystem _forensics = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPuddleSystem _puddle = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(TryBodyVomitSolution); + } + + private const float ChemMultiplier = 0.1f; + + private static readonly ProtoId VomitCollection = "Vomit"; + + private static readonly ProtoId VomitPrototype = "Vomit"; // TODO: Dehardcode vomit prototype + + private readonly SoundSpecifier _vomitSound = new SoundCollectionSpecifier(VomitCollection, + AudioParams.Default.WithVariation(0.2f).WithVolume(-4f)); + + private void TryBodyVomitSolution(Entity ent, ref TryVomitEvent args) + { + if (args.Handled) + return; + + // Main requirement: You have a stomach + var stomachList = _body.GetBodyOrganEntityComps((ent, null)); + if (stomachList.Count == 0) + return; + + // Empty the stomach out into it + foreach (var stomach in stomachList) + { + if (_solutionContainer.ResolveSolution(stomach.Owner, StomachSystem.DefaultSolutionName, ref stomach.Comp1.Solution, out var sol)) + _solutionContainer.TryTransferSolution(stomach.Comp1.Solution.Value, args.Sol, sol.AvailableVolume); + } + + args.Handled = true; + } + + /// + /// Make an entity vomit, if they have a stomach. + /// + public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = -40f, bool force = false) + { + // Vomit only if entity is alive + // Ignore condition if force was set to true + if (!force && _mobState.IsDead(uid)) + return; + + // TODO: Need decals + var solution = new Solution(); + + var ev = new TryVomitEvent(solution, force); + RaiseLocalEvent(uid, ref ev); + + if (!ev.Handled) + return; + + // Vomiting makes you hungrier and thirstier + if (TryComp(uid, out var hunger)) + _hunger.ModifyHunger(uid, hungerAdded, hunger); + + if (TryComp(uid, out var thirst)) + _thirst.ModifyThirst(uid, thirst, thirstAdded); + + // It fully empties the stomach, this amount from the chem stream is relatively small + var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6; + + // Apply a bit of slowdown + _movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f); + + // Adds a tiny amount of the chem stream from earlier along with vomit + if (TryComp(uid, out var bloodStream)) + { + var vomitAmount = solutionSize; + + // Takes 10% of the chemicals removed from the chem stream + if (_solutionContainer.ResolveSolution(uid, bloodStream.ChemicalSolutionName, ref bloodStream.ChemicalSolution)) + { + var vomitChemstreamAmount = _solutionContainer.SplitSolution(bloodStream.ChemicalSolution.Value, vomitAmount); + vomitChemstreamAmount.ScaleSolution(ChemMultiplier); + solution.AddSolution(vomitChemstreamAmount, _proto); + + vomitAmount -= (float)vomitChemstreamAmount.Volume; + } + + // Makes a vomit solution the size of 90% of the chemicals removed from the chemstream + solution.AddReagent(new ReagentId(VomitPrototype, _bloodstream.GetEntityBloodData(uid)), vomitAmount); + } + + if (_puddle.TrySpillAt(uid, solution, out var puddle, false)) + { + _forensics.TransferDna(puddle, uid, false); + } + + + if (!_netManager.IsServer) + return; + + // Force sound to play as spill doesn't work if solution is empty. + _audio.PlayPvs(_vomitSound, uid); + _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); + } +} + +[ByRefEvent] +public record struct TryVomitEvent(Solution Sol, bool Forced = false, bool Handled = false);