diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index ee8cfcb1e3..6990b77fd0 100644 --- a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs @@ -1,13 +1,17 @@ #nullable enable using System; +using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Shared.Chemistry; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.Utility; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Fluids @@ -20,6 +24,8 @@ namespace Content.Server.GameObjects.Components.Fluids { public override string Name => "Bucket"; + private List _currentlyUsing = new(); + public ReagentUnit MaxVolume { get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero; @@ -51,64 +57,50 @@ namespace Content.Server.GameObjects.Components.Fluids Owner.EnsureComponentWarn(); } - private bool TryGiveToMop(MopComponent mopComponent) - { - if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) - { - return false; - } - - var mopContents = mopComponent.Contents; - - if (mopContents == null) - { - return false; - } - - // Let's fill 'er up - // If this is called the mop should be empty but just in case we'll do Max - Current - var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume); - var solution = contents.SplitSolution(transferAmount); - if (!mopContents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0) - { - return false; - } - - if (_sound == null) - { - return true; - } - - EntitySystem.Get().PlayFromEntity(_sound, Owner); - - return true; - } - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { - if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) + if (!Owner.TryGetComponent(out SolutionContainerComponent? contents) || + _currentlyUsing.Contains(eventArgs.Using.Uid) || + !eventArgs.Using.TryGetComponent(out MopComponent? mopComponent) || + mopComponent.Mopping) { return false; } - if (!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent)) + if (CurrentVolume <= 0) { + Owner.PopupMessage(eventArgs.User, Loc.GetString("Bucket is empty")); return false; } - // Give to the mop if it's empty - if (mopComponent.CurrentVolume == 0) + if (mopComponent.CurrentVolume == mopComponent.MaxVolume) { - if (!TryGiveToMop(mopComponent)) - { - return false; - } - - Owner.PopupMessage(eventArgs.User, Loc.GetString("Splish")); - return true; + Owner.PopupMessage(eventArgs.User, Loc.GetString("Mop is full")); + return false; } - var transferAmount = ReagentUnit.Min(mopComponent.CurrentVolume, MaxVolume - CurrentVolume); + _currentlyUsing.Add(eventArgs.Using.Uid); + + // IMO let em move while doing it. + var doAfterArgs = new DoAfterEventArgs(eventArgs.User, 1.0f, target: eventArgs.Target) + { + BreakOnStun = true, + BreakOnDamage = true, + }; + var result = await EntitySystem.Get().DoAfter(doAfterArgs); + + _currentlyUsing.Remove(eventArgs.Using.Uid); + + if (result == DoAfterStatus.Cancelled || + Owner.Deleted || + mopComponent.Deleted || + CurrentVolume <= 0 || + !Owner.InRangeUnobstructed(mopComponent.Owner)) + return false; + + // Top up mops solution given it needs it to annihilate puddles I guess + + var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume); if (transferAmount == 0) { return false; @@ -121,23 +113,17 @@ namespace Content.Server.GameObjects.Components.Fluids return false; } - var solution = mopContents.SplitSolution(transferAmount); - if (!contents.TryAddSolution(solution)) + var solution = contents.SplitSolution(transferAmount); + if (!mopContents.TryAddSolution(solution)) { - //This really shouldn't happen - throw new InvalidOperationException(); + return false; } - // Give some visual feedback shit's happening (for anyone who can't hear sound) - Owner.PopupMessage(eventArgs.User, Loc.GetString("Sploosh")); - - if (_sound == null) + if (_sound != null) { - return true; + EntitySystem.Get().PlayFromEntity(_sound, Owner); } - EntitySystem.Get().PlayFromEntity(_sound, Owner); - return true; } } diff --git a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs index 102438f664..c2a5774dd6 100644 --- a/Content.Server/GameObjects/Components/Fluids/MopComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/MopComponent.cs @@ -8,7 +8,9 @@ using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Serialization; using System.Threading.Tasks; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Robust.Server.GameObjects; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Fluids { @@ -20,6 +22,13 @@ namespace Content.Server.GameObjects.Components.Fluids { public override string Name => "Mop"; + public bool Mopping => _mopping; + + /// + /// Used to prevent do_after spam if we're currently mopping. + /// + private bool _mopping; + public SolutionContainerComponent? Contents => Owner.GetComponentOrNull(); public ReagentUnit MaxVolume @@ -44,7 +53,13 @@ namespace Content.Server.GameObjects.Components.Fluids public ReagentUnit PickupAmount => _pickupAmount; private ReagentUnit _pickupAmount; - private string _pickupSound = ""; + private string? _pickupSound; + + /// + /// Multiplier for the do_after delay for how fast the mop works. + /// + [ViewVariables] + private float _mopSpeed; /// public override void ExposeData(ObjectSerializer serializer) @@ -52,6 +67,7 @@ namespace Content.Server.GameObjects.Components.Fluids serializer.DataFieldCached(ref _pickupSound, "pickup_sound", "/Audio/Effects/Fluids/slosh.ogg"); // The turbo mop will pickup more serializer.DataFieldCached(ref _pickupAmount, "pickup_amount", ReagentUnit.New(5)); + serializer.DataField(ref _mopSpeed, "speed", 1.0f); } public override void Initialize() @@ -63,34 +79,69 @@ namespace Content.Server.GameObjects.Components.Fluids async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { - if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) - return false; - if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) - return false; + /* + * Functionality: + * Essentially if we click on an empty tile spill our contents there + * Otherwise, try to mop up the puddle (if it is a puddle). + * It will try to destroy solution on the mop to do so, and if it is successful + * will spill some of the mop's solution onto the puddle which will evaporate eventually. + */ - if (CurrentVolume <= 0) + if (!Owner.TryGetComponent(out SolutionContainerComponent? contents) || + _mopping || + !eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) { - return true; + return false; } + var currentVolume = CurrentVolume; + if (eventArgs.Target == null) { - // Drop the liquid on the mop on to the ground - contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear"); + if (currentVolume > 0) + { + // Drop the liquid on the mop on to the ground + contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear"); + return true; + } - return true; + return false; } if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent)) { - return true; + return false; } - // Essentially pickup either: - // - _pickupAmount, - // - whatever's left in the puddle, or - // - whatever we can still hold (whichever's smallest) + + var puddleVolume = puddleComponent.CurrentVolume; + + if (currentVolume <= 0) + { + Owner.PopupMessage(eventArgs.User, Loc.GetString("Mop needs to be wet!")); + return false; + } + + _mopping = true; + + // So if the puddle has 20 units we mop in 2 seconds. Don't just store CurrentVolume given it can change so need to re-calc it anyway. + var doAfterArgs = new DoAfterEventArgs(eventArgs.User, _mopSpeed * puddleVolume.Float() / 10.0f, target: eventArgs.Target) + { + BreakOnUserMove = true, + BreakOnStun = true, + BreakOnDamage = true, + }; + var result = await EntitySystem.Get().DoAfter(doAfterArgs); + + _mopping = false; + + if (result == DoAfterStatus.Cancelled || + Owner.Deleted || + puddleComponent.Deleted) + return false; + + // Annihilate the puddle var transferAmount = ReagentUnit.Min(ReagentUnit.New(5), puddleComponent.CurrentVolume, CurrentVolume); - bool puddleCleaned = puddleComponent.CurrentVolume - transferAmount <= 0; + var puddleCleaned = puddleComponent.CurrentVolume - transferAmount <= 0; if (transferAmount == 0) { @@ -119,9 +170,6 @@ namespace Content.Server.GameObjects.Components.Fluids contents.SplitSolution(transferAmount); } - // Give some visual feedback shit's happening (for anyone who can't hear sound) - Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish")); - if (!string.IsNullOrWhiteSpace(_pickupSound)) { EntitySystem.Get().PlayFromEntity(_pickupSound, Owner); diff --git a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml index 586d8b8a3a..22e348823b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml @@ -14,12 +14,12 @@ - type: Mop - type: SolutionContainer maxVol: 10 - - type: LoopingSound - type: entity name: mop bucket id: MopBucket description: Holds water and the tears of the janitor. + suffix: Full components: - type: Clickable - type: Sprite @@ -33,6 +33,11 @@ - type: LoopingSound - type: SolutionContainer maxVol: 500 + contents: + reagents: + - ReagentId: chem.Water + Quantity: 500 + - type: Physics mass: 5 anchored: false @@ -44,7 +49,6 @@ - Opaque layer: - Opaque - - type: Spillable - type: Pullable - type: entity