Mopping enhancements (#3298)
* Bucket starts with water * Mop has do_after * Bucket has do_after (user allowed to move while using it as long as they're in range at the end for maximum jannie skill) * Random cleanup Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -1,13 +1,17 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Content.Shared.Utility;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Fluids
|
namespace Content.Server.GameObjects.Components.Fluids
|
||||||
@@ -20,6 +24,8 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
{
|
{
|
||||||
public override string Name => "Bucket";
|
public override string Name => "Bucket";
|
||||||
|
|
||||||
|
private List<EntityUid> _currentlyUsing = new();
|
||||||
|
|
||||||
public ReagentUnit MaxVolume
|
public ReagentUnit MaxVolume
|
||||||
{
|
{
|
||||||
get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
|
||||||
@@ -51,64 +57,50 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
Owner.EnsureComponentWarn<SolutionContainerComponent>();
|
Owner.EnsureComponentWarn<SolutionContainerComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
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<AudioSystem>().PlayFromEntity(_sound, Owner);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
async Task<bool> 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent))
|
if (CurrentVolume <= 0)
|
||||||
{
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Bucket is empty"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give to the mop if it's empty
|
if (mopComponent.CurrentVolume == mopComponent.MaxVolume)
|
||||||
if (mopComponent.CurrentVolume == 0)
|
|
||||||
{
|
{
|
||||||
if (!TryGiveToMop(mopComponent))
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Mop is full"));
|
||||||
{
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Splish"));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<DoAfterSystem>().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)
|
if (transferAmount == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -121,23 +113,17 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var solution = mopContents.SplitSolution(transferAmount);
|
var solution = contents.SplitSolution(transferAmount);
|
||||||
if (!contents.TryAddSolution(solution))
|
if (!mopContents.TryAddSolution(solution))
|
||||||
{
|
{
|
||||||
//This really shouldn't happen
|
return false;
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give some visual feedback shit's happening (for anyone who can't hear sound)
|
if (_sound != null)
|
||||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Sploosh"));
|
|
||||||
|
|
||||||
if (_sound == null)
|
|
||||||
{
|
{
|
||||||
return true;
|
EntitySystem.Get<AudioSystem>().PlayFromEntity(_sound, Owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_sound, Owner);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Fluids
|
namespace Content.Server.GameObjects.Components.Fluids
|
||||||
{
|
{
|
||||||
@@ -20,6 +22,13 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
{
|
{
|
||||||
public override string Name => "Mop";
|
public override string Name => "Mop";
|
||||||
|
|
||||||
|
public bool Mopping => _mopping;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to prevent do_after spam if we're currently mopping.
|
||||||
|
/// </summary>
|
||||||
|
private bool _mopping;
|
||||||
|
|
||||||
public SolutionContainerComponent? Contents => Owner.GetComponentOrNull<SolutionContainerComponent>();
|
public SolutionContainerComponent? Contents => Owner.GetComponentOrNull<SolutionContainerComponent>();
|
||||||
|
|
||||||
public ReagentUnit MaxVolume
|
public ReagentUnit MaxVolume
|
||||||
@@ -44,7 +53,13 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
public ReagentUnit PickupAmount => _pickupAmount;
|
public ReagentUnit PickupAmount => _pickupAmount;
|
||||||
private ReagentUnit _pickupAmount;
|
private ReagentUnit _pickupAmount;
|
||||||
|
|
||||||
private string _pickupSound = "";
|
private string? _pickupSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier for the do_after delay for how fast the mop works.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private float _mopSpeed;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
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");
|
serializer.DataFieldCached(ref _pickupSound, "pickup_sound", "/Audio/Effects/Fluids/slosh.ogg");
|
||||||
// The turbo mop will pickup more
|
// The turbo mop will pickup more
|
||||||
serializer.DataFieldCached(ref _pickupAmount, "pickup_amount", ReagentUnit.New(5));
|
serializer.DataFieldCached(ref _pickupAmount, "pickup_amount", ReagentUnit.New(5));
|
||||||
|
serializer.DataField(ref _mopSpeed, "speed", 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -63,34 +79,69 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
|
|
||||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents))
|
/*
|
||||||
return false;
|
* Functionality:
|
||||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
* Essentially if we click on an empty tile spill our contents there
|
||||||
return false;
|
* 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)
|
if (eventArgs.Target == null)
|
||||||
{
|
{
|
||||||
// Drop the liquid on the mop on to the ground
|
if (currentVolume > 0)
|
||||||
contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear");
|
{
|
||||||
|
// 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))
|
if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent))
|
||||||
{
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
// Essentially pickup either:
|
|
||||||
// - _pickupAmount,
|
var puddleVolume = puddleComponent.CurrentVolume;
|
||||||
// - whatever's left in the puddle, or
|
|
||||||
// - whatever we can still hold (whichever's smallest)
|
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<DoAfterSystem>().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);
|
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)
|
if (transferAmount == 0)
|
||||||
{
|
{
|
||||||
@@ -119,9 +170,6 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
contents.SplitSolution(transferAmount);
|
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))
|
if (!string.IsNullOrWhiteSpace(_pickupSound))
|
||||||
{
|
{
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
|
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
- type: Mop
|
- type: Mop
|
||||||
- type: SolutionContainer
|
- type: SolutionContainer
|
||||||
maxVol: 10
|
maxVol: 10
|
||||||
- type: LoopingSound
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: mop bucket
|
name: mop bucket
|
||||||
id: MopBucket
|
id: MopBucket
|
||||||
description: Holds water and the tears of the janitor.
|
description: Holds water and the tears of the janitor.
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -33,6 +33,11 @@
|
|||||||
- type: LoopingSound
|
- type: LoopingSound
|
||||||
- type: SolutionContainer
|
- type: SolutionContainer
|
||||||
maxVol: 500
|
maxVol: 500
|
||||||
|
contents:
|
||||||
|
reagents:
|
||||||
|
- ReagentId: chem.Water
|
||||||
|
Quantity: 500
|
||||||
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
anchored: false
|
anchored: false
|
||||||
@@ -44,7 +49,6 @@
|
|||||||
- Opaque
|
- Opaque
|
||||||
layer:
|
layer:
|
||||||
- Opaque
|
- Opaque
|
||||||
- type: Spillable
|
|
||||||
- type: Pullable
|
- type: Pullable
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
Reference in New Issue
Block a user