diff --git a/Content.Server/Nutrition/Components/MessyDrinkerComponent.cs b/Content.Server/Nutrition/Components/MessyDrinkerComponent.cs new file mode 100644 index 0000000000..6a1a3a0319 --- /dev/null +++ b/Content.Server/Nutrition/Components/MessyDrinkerComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.FixedPoint; + +namespace Content.Server.Nutrition.Components; + +/// +/// Entities with this component occasionally spill some of their drink when drinking. +/// +[RegisterComponent] +public sealed partial class MessyDrinkerComponent : Component +{ + [DataField] + public float SpillChance = 0.2f; + + /// + /// The amount of solution that is spilled when procs. + /// + [DataField] + public FixedPoint2 SpillAmount = 1.0; + + [DataField] + public LocId? SpillMessagePopup; +} diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index deb49ea668..6e1824c843 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Systems; using Content.Server.Fluids.EntitySystems; using Content.Server.Forensics; using Content.Server.Inventory; +using Content.Server.Nutrition.Events; using Content.Server.Popups; using Content.Shared.Administration.Logs; using Content.Shared.Body.Components; @@ -242,11 +243,18 @@ public sealed class DrinkSystem : SharedDrinkSystem _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-2f).WithVariation(0.25f)); - _reaction.DoEntityReaction(args.Target.Value, solution, ReactionMethod.Ingestion); - _stomach.TryTransferSolution(firstStomach.Value.Owner, drained, firstStomach.Value.Comp1); + var beforeDrinkEvent = new BeforeIngestDrinkEvent(entity.Owner, drained, forceDrink); + RaiseLocalEvent(args.Target.Value, ref beforeDrinkEvent); _forensics.TransferDna(entity, args.Target.Value); + _reaction.DoEntityReaction(args.Target.Value, solution, ReactionMethod.Ingestion); + + if (drained.Volume == 0) + return; + + _stomach.TryTransferSolution(firstStomach.Value.Owner, drained, firstStomach.Value.Comp1); + if (!forceDrink && solution.Volume > 0) args.Repeat = true; } diff --git a/Content.Server/Nutrition/EntitySystems/MessyDrinkerSystem.cs b/Content.Server/Nutrition/EntitySystems/MessyDrinkerSystem.cs new file mode 100644 index 0000000000..f92318d0f7 --- /dev/null +++ b/Content.Server/Nutrition/EntitySystems/MessyDrinkerSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.Events; +using Content.Shared.Popups; +using Robust.Shared.Random; + +namespace Content.Server.Nutrition.EntitySystems; + +public sealed class MessyDrinkerSystem : EntitySystem +{ + [Dependency] private readonly PuddleSystem _puddle = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBeforeIngestDrink); + } + + private void OnBeforeIngestDrink(Entity ent, ref BeforeIngestDrinkEvent ev) + { + if (ev.Solution.Volume <= ent.Comp.SpillAmount) + return; + + // Cannot spill if you're being forced to drink. + if (ev.Forced) + return; + + if (!_random.Prob(ent.Comp.SpillChance)) + return; + + if (ent.Comp.SpillMessagePopup != null) + _popup.PopupEntity(Loc.GetString(ent.Comp.SpillMessagePopup), ent, ent, PopupType.MediumCaution); + + var split = ev.Solution.SplitSolution(ent.Comp.SpillAmount); + + _puddle.TrySpillAt(ent, split, out _); + } +} diff --git a/Content.Server/Nutrition/Events/DrinkEvents.cs b/Content.Server/Nutrition/Events/DrinkEvents.cs new file mode 100644 index 0000000000..b7a7403105 --- /dev/null +++ b/Content.Server/Nutrition/Events/DrinkEvents.cs @@ -0,0 +1,12 @@ +using Content.Shared.Chemistry.Components; + +namespace Content.Server.Nutrition.Events; + +/// +/// Raised on the entity drinking. This is right before they actually transfer the solution into the stomach. +/// +/// The drink that is being drank. +/// The solution that will be digested. +/// Whether the target was forced to drink the solution by somebody else. +[ByRefEvent] +public record struct BeforeIngestDrinkEvent(EntityUid Drink, Solution Solution, bool Forced); diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 41a66d2ef0..920468605f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2859,6 +2859,7 @@ - type: HTN rootTask: task: RuminantHostileCompound + - type: MessyDrinker - type: Tag tags: - VimPilot @@ -2905,6 +2906,7 @@ gender: epicene - type: MobPrice price: 200 + - type: MessyDrinker - type: entity parent: MobCorgiBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 75aacadd35..dcfd6b41fc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -370,6 +370,7 @@ - VimPilot - type: StealTarget stealGroup: AnimalMcGriff + - type: MessyDrinker - type: entity name: Paperwork @@ -466,6 +467,7 @@ - type: Speech speechVerb: Canine speechSounds: Dog + - type: MessyDrinker - type: entity name: Morty