Added sink (#14348)

This commit is contained in:
Topy
2023-07-01 00:10:47 +02:00
committed by GitHub
parent 7d89b9e253
commit b85c902189
36 changed files with 484 additions and 104 deletions

View File

@@ -36,6 +36,13 @@ namespace Content.Client.Chemistry.Visualizers
[DataField("metamorphicNameFull")]
public string MetamorphicNameFull = "transformable-container-component-glass";
/// <summary>
/// Which solution of the SolutionContainerManagerComponent to represent.
/// If not set, will work as default.
/// </summary>
[DataField("solutionName")]
public string SolutionName = "";
public string InitialName = string.Empty;
public string InitialDescription = string.Empty;
}

View File

@@ -25,6 +25,16 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
protected override void OnAppearanceChange(EntityUid uid, SolutionContainerVisualsComponent component, ref AppearanceChangeEvent args)
{
// Check if the solution that was updated is the one set as represented
if (!string.IsNullOrEmpty(component.SolutionName))
{
if (AppearanceSystem.TryGetData<string>(uid, SolutionContainerVisuals.SolutionName, out var name,
args.Component) && name != component.SolutionName)
{
return;
}
}
if (!AppearanceSystem.TryGetData<float>(uid, SolutionContainerVisuals.FillFraction, out var fraction, args.Component))
return;

View File

@@ -152,6 +152,10 @@ public sealed partial class SolutionContainerSystem : EntitySystem
_appearance.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
_appearance.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(_prototypeManager), appearanceComponent);
if (solution.Name != null)
{
_appearance.SetData(uid, SolutionContainerVisuals.SolutionName, solution.Name, appearanceComponent);
}
if (solution.GetPrimaryReagentId() is { } reagent)
{

View File

@@ -1,18 +1,106 @@
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Fluids.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Shared.FixedPoint;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Content.Shared.Fluids.Components;
using Robust.Shared.Collections;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Fluids.EntitySystems
namespace Content.Server.Fluids.EntitySystems;
public sealed class DrainSystem : SharedDrainSystem
{
public sealed class DrainSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DrainComponent, GetVerbsEvent<Verb>>(AddEmptyVerb);
SubscribeLocalEvent<DrainComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DrainComponent, AfterInteractUsingEvent>(OnInteract);
SubscribeLocalEvent<DrainComponent, DrainDoAfterEvent>(OnDoAfter);
}
private void AddEmptyVerb(EntityUid uid, DrainComponent component, GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Using == null)
return;
if (!TryComp(args.Using, out SpillableComponent? spillable) ||
!TryComp(args.Target, out DrainComponent? drain))
return;
Verb verb = new()
{
Text = Loc.GetString("drain-component-empty-verb-inhand", ("object", Name(args.Using.Value))),
Act = () =>
{
Empty(args.Using.Value, spillable, args.Target, drain);
},
Impact = LogImpact.Low,
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png"))
};
args.Verbs.Add(verb);
}
private void Empty(EntityUid container, SpillableComponent spillable, EntityUid target, DrainComponent drain)
{
// Find the solution in the container that is emptied
if (!_solutionSystem.TryGetDrainableSolution(container, out var containerSolution) ||
containerSolution.Volume == FixedPoint2.Zero)
{
_popupSystem.PopupEntity(
Loc.GetString("drain-component-empty-verb-using-is-empty-message", ("object", container)),
container);
return;
}
// try to find the drain's solution
if (!_solutionSystem.TryGetSolution(target, DrainComponent.SolutionName, out var drainSolution))
{
return;
}
// Try to transfer as much solution as possible to the drain
var transferSolution = _solutionSystem.SplitSolution(container, containerSolution,
FixedPoint2.Min(containerSolution.Volume, drainSolution.AvailableVolume));
_solutionSystem.TryAddSolution(target, drainSolution, transferSolution);
_audioSystem.PlayPvs(drain.ManualDrainSound, target);
_ambientSoundSystem.SetAmbience(target, true);
// If drain is full, spill
if (drainSolution.MaxVolume == drainSolution.Volume)
{
_puddleSystem.TrySpillAt(Transform(target).Coordinates, containerSolution, out var puddle);
_popupSystem.PopupEntity(
Loc.GetString("drain-component-empty-verb-target-is-full-message", ("object", target)),
container);
}
}
public override void Update(float frameTime)
{
@@ -31,6 +119,13 @@ namespace Content.Server.Fluids.EntitySystems
}
drain.Accumulator -= drain.DrainFrequency;
// Disable ambient sound from emptying manually
if (!drain.AutoDrain)
{
_ambientSoundSystem.SetAmbience(drain.Owner, false);
return;
}
if (!managerQuery.TryGetComponent(drain.Owner, out var manager))
continue;
@@ -40,6 +135,12 @@ namespace Content.Server.Fluids.EntitySystems
if (drainSolution is null)
continue;
if (drainSolution.AvailableVolume <= 0)
{
_ambientSoundSystem.SetAmbience(drain.Owner, false);
continue;
}
// Remove a bit from the buffer
_solutionSystem.SplitSolution(drain.Owner, drainSolution, (drain.UnitsDestroyedPerSecond * drain.DrainFrequency));
@@ -97,5 +198,68 @@ namespace Content.Server.Fluids.EntitySystems
}
}
}
private void OnExamined(EntityUid uid, DrainComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange ||
!TryComp(uid, out SolutionContainerManagerComponent? solutionComp) ||
!_solutionSystem.TryGetSolution(uid, DrainComponent.SolutionName, out var drainSolution))
{ return; }
var text = drainSolution.AvailableVolume != 0 ?
Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume)) :
Loc.GetString("drain-component-examine-hint-full");
args.Message.AddMarkup($"\n\n{text}");
}
private void OnInteract(EntityUid uid, DrainComponent component, InteractEvent args)
{
if (!args.CanReach || args.Target == null ||
!_tagSystem.HasTag(args.Used, DrainComponent.PlungerTag) ||
!_solutionSystem.TryGetSolution(args.Target.Value, DrainComponent.SolutionName, out var drainSolution))
{ return; }
if (drainSolution.AvailableVolume > 0)
{
_popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-notapplicable", ("object", args.Target.Value)), args.Target.Value);
return;
}
_audioSystem.PlayPvs(component.PlungerSound, uid);
var doAfterArgs = new DoAfterArgs(args.User, component.UnclogDuration, new DrainDoAfterEvent(),uid, args.Used)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnHandChange = true
};
_doAfterSystem.TryStartDoAfter(doAfterArgs);
}
private void OnDoAfter(EntityUid uid, DrainComponent component, DoAfterEvent args)
{
if (args.Target == null)
return;
if (!_random.Prob(component.UnclogProbability))
{
_popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-fail", ("object", args.Target.Value)), args.Target.Value);
return;
}
if (!_solutionSystem.TryGetSolution(args.Target.Value, DrainComponent.SolutionName,
out var drainSolution))
{
return;
}
_solutionSystem.RemoveAllSolution(args.Target.Value, drainSolution);
_audioSystem.PlayPvs(component.UnclogSound, args.Target.Value);
_popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-success", ("object", args.Target.Value)), args.Target.Value);
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Fluids.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.DragDrop;
using Content.Shared.FixedPoint;

View File

@@ -8,6 +8,7 @@ namespace Content.Shared.Chemistry
Color,
FillFraction,
BaseOverride,
SolutionName
}
public enum SolutionContainerLayers : byte

View File

@@ -89,7 +89,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
// I feel like this is somewhat cursed, but its the only way I can think of without having to just send
// redundant data over the network and increasing DoAfter boilerplate.
var evType = typeof(DoAfterAttemptEvent<>).MakeGenericType(args.Event.GetType());
doAfter.AttemptEvent = _factory.CreateInstance(evType, new object[] { doAfter, args.Event }, inject: false);
doAfter.AttemptEvent = _factory.CreateInstance(evType, new object[] { doAfter, args.Event });
}
if (args.EventTarget != null)

View File

@@ -1,15 +1,31 @@
using Robust.Shared.GameStates;
using Robust.Shared.Audio;
namespace Content.Shared.Fluids.Components;
[RegisterComponent, NetworkedComponent]
/// <summary>
/// A Drain allows an entity to absorb liquid in a disposal goal. Drains can be filled manually (with the Empty verb)
/// or they can absorb puddles of liquid around them when AutoDrain is set to true.
/// When the entity also has a SolutionContainerManager attached with a solution named drainBuffer, this solution
/// gets filled until the drain is full.
/// When the drain is full, it can be unclogged using a plunger (i.e. an entity with a Plunger tag attached).
/// Later this can be refactored into a proper Plunger component if needed.
/// </summary>
[RegisterComponent, Access(typeof(SharedDrainSystem))]
public sealed class DrainComponent : Component
{
public const string SolutionName = "drainBuffer";
public const string PlungerTag = "Plunger";
[DataField("accumulator")]
public float Accumulator = 0f;
/// <summary>
/// Does this drain automatically absorb surrouding puddles? Or is it a drain designed to empty
/// solutions in it manually?
/// </summary>
[DataField("autoDrain"), ViewVariables(VVAccess.ReadOnly)]
public bool AutoDrain = true;
/// <summary>
/// How many units per second the drain can absorb from the surrounding puddles.
/// Divided by puddles, so if there are 5 puddles this will take 1/5 from each puddle.
@@ -28,7 +44,7 @@ public sealed class DrainComponent : Component
/// How many (unobstructed) tiles away the drain will
/// drain puddles from.
/// </summary>
[DataField("range")]
[DataField("range"), ViewVariables(VVAccess.ReadWrite)]
public float Range = 2f;
/// <summary>
@@ -37,4 +53,25 @@ public sealed class DrainComponent : Component
/// </summary>
[DataField("drainFrequency")]
public float DrainFrequency = 1f;
/// <summary>
/// How much time it takes to unclog it with a plunger
/// </summary>
[DataField("unclogDuration"), ViewVariables(VVAccess.ReadWrite)]
public float UnclogDuration = 1f;
/// <summary>
/// What's the probability of uncloging on each try
/// </summary>
[DataField("unclogProbability"), ViewVariables(VVAccess.ReadWrite)]
public float UnclogProbability = 0.3f;
[DataField("manualDrainSound"), ViewVariables(VVAccess.ReadOnly)]
public SoundSpecifier ManualDrainSound = new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg");
[DataField("plungerSound"), ViewVariables(VVAccess.ReadOnly)]
public SoundSpecifier PlungerSound = new SoundPathSpecifier("/Audio/Items/Janitor/plunger.ogg");
[DataField("unclogSound"), ViewVariables(VVAccess.ReadOnly)]
public SoundSpecifier UnclogSound = new SoundPathSpecifier("/Audio/Effects/Fluids/glug.ogg");
}

View File

@@ -0,0 +1,12 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Fluids;
public class SharedDrainSystem : EntitySystem
{
[Serializable, NetSerializable]
public sealed class DrainDoAfterEvent : SimpleDoAfterEvent
{
}
}

View File

@@ -1,2 +0,0 @@
blood1.ogg under CC-0 from https://freesound.org/people/kyles/sounds/453769/
blood2.ogg under CC-BY 3.0 from https://freesound.org/people/EminYILDIRIM/sounds/554284/

View File

@@ -0,0 +1,14 @@
- files: ["blood1.ogg"]
license: "CC0-1.0"
copyright: "Created by Kyles"
source: "https://freesound.org/people/kyles/sounds/453769/"
- files: ["blood2.ogg"]
license: "CC-BY-4.0"
copyright: "Created by EminYILDIRIM"
source: "https://freesound.org/people/EminYILDIRIM/sounds/554284/"
- files: ["glug.ogg"]
license: "CC0-1.0"
copyright: "Created by brittmosel"
source: "https://freesound.org/people/brittmosel/sounds/529300/"

Binary file not shown.

View File

@@ -0,0 +1,4 @@
- files: ["plunger.ogg"]
license: "CC-BY-4.0"
copyright: "Created by tosha73"
source: "https://freesound.org/people/tosha73/sounds/540784/"

Binary file not shown.

View File

@@ -0,0 +1,8 @@
drain-component-empty-verb-using-is-empty-message = { CAPITALIZE(THE($object)) } is empty!
drain-component-empty-verb-target-is-full-message = { CAPITALIZE(THE($object)) } is full!
drain-component-empty-verb-inhand = Empty {$object}
drain-component-examine-hint-full = [color="blue"]It is filled to the brim. Maybe a plunger can help?[/color]
drain-component-examine-volume = [color="blue"]Remaining space - {$volume}u.[/color]
drain-component-unclog-fail = { CAPITALIZE(THE($object)) } is still full.
drain-component-unclog-success = { CAPITALIZE(THE($object)) } unclogs.
drain-component-unclog-notapplicable = { CAPITALIZE(THE($object)) } isn't clogged.

View File

@@ -4,7 +4,7 @@
sprite: Objects/Specific/Janitorial/janitorial.rsi
state: cleaner
product: CrateServiceJanitorialSupplies
cost: 500
cost: 560
category: Service
group: market

View File

@@ -15,6 +15,8 @@
amount: 2
- id: TrashBag
amount: 2
- id: Plunger
amount: 2
- type: entity
id: CrateServiceReplacementLights

View File

@@ -71,6 +71,8 @@
amount: 2
- id: FlashlightLantern
amount: 2
- id: Plunger
amount: 2
- type: entity
id: ClosetLegalFilled

View File

@@ -94,6 +94,7 @@
- SheetSteel
- SheetPlastic
- ResearchDisk
- Plunger
chance: 0.6
offset: 0.0

View File

@@ -317,6 +317,26 @@
path: /Audio/Effects/metalbreak.ogg
- type: ItemMapper
mapLayers:
cart_plunger:
whitelist:
tags:
- Plunger
cart_mop:
whitelist:
tags:
- Mop
cart_garbage:
whitelist:
tags:
- TrashBag
cart_replacer:
whitelist:
components:
- LightReplacer
cart_spray:
whitelist:
tags:
- Spray
cart_sign1: # this is like stack of floor signs
minCount: 1
whitelist:
@@ -337,26 +357,10 @@
whitelist:
tags:
- WetFloorSign
cart_spray:
whitelist:
tags:
- Spray
cart_garbage:
whitelist:
tags:
- TrashBag
cart_replacer:
whitelist:
components:
- LightReplacer
cart_bucket:
whitelist:
tags:
- Bucket
cart_mop:
whitelist:
tags:
- Mop
sprite: Objects/Specific/Janitorial/janitorial_cart.rsi
- type: Appearance
- type: SolutionContainerVisuals
@@ -392,7 +396,11 @@
- type: Sprite
drawdepth: FloorObjects
sprite: Objects/Specific/Janitorial/drain.rsi
state: icon
layers:
- state: icon
- map: [ "enum.SolutionContainerLayers.Fill" ]
state: fill-1
visible: false
- type: InteractionOutline
- type: Clickable
- type: Transform
@@ -400,13 +408,18 @@
- type: Physics
bodyType: Static
canCollide: false
- type: Drain
- type: AmbientSound
enabled: false
volume: -8
range: 8
sound:
path: /Audio/Ambience/Objects/drain.ogg
- type: Drain
- type: Appearance
- type: SolutionContainerVisuals
maxFillLevels: 1
fillBaseName: fill-
solutionName: drainBuffer
- type: SolutionContainerManager
solutions:
drainBuffer:
@@ -433,6 +446,24 @@
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/metalbreak.ogg
- type: entity
name: plunger
id: Plunger
parent: BaseItem
description: A plunger with a red plastic suction-cup and a wooden handle. Used to unclog drains.
components:
- type: Tag
tags:
- Plunger
- type: Sprite
netsync: false
sprite: Objects/Specific/Janitorial/janitorial.rsi
state: plunger
- type: Item
sprite: Objects/Specific/Janitorial/janitorial.rsi
heldPrefix: plunger
- type: ItemCooldown
- type: GuideHelp
guides:
- Janitorial

View File

@@ -10,9 +10,21 @@
- type: InteractionOutline
- type: Sprite
sprite: Structures/Furniture/sink.rsi
state: sink_stem
layers:
- state: sink_stem
- map: [ "enum.SolutionContainerLayers.Fill" ]
state: sink-fill-1
visible: false
netsync: false
- type: Appearance
- type: SolutionContainerVisuals
maxFillLevels: 1
fillBaseName: sink-fill-
solutionName: drainBuffer
- type: SolutionContainerManager
solutions:
drainBuffer:
maxVol: 100
tank:
maxVol: 500
- type: SolutionRegeneration
@@ -24,6 +36,8 @@
- type: DrainableSolution
solution: tank
- type: ReagentTank
- type: Drain
autoDrain: false
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
@@ -44,6 +58,13 @@
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/metalbreak.ogg
- type: AmbientSound
enabled: false
volume: -8
range: 8
sound:
path: /Audio/Ambience/Objects/drain.ogg
- type: entity
name: sink
@@ -53,6 +74,8 @@
components:
- type: SolutionContainerManager
solutions:
drainBuffer:
maxVol: 200
tank:
reagents:
- ReagentId: Water
@@ -65,7 +88,15 @@
components:
- type: Sprite
sprite: Structures/Furniture/sink.rsi
state: sink_wide
layers:
- state: sink_wide
- map: [ "enum.SolutionContainerLayers.Fill" ]
state: sink_wide-fill-1
visible: false
- type: SolutionContainerVisuals
maxFillLevels: 1
fillBaseName: sink_wide-fill-
solutionName: drainBuffer
#Stemless Sink
@@ -76,7 +107,11 @@
components:
- type: Sprite
sprite: Structures/Furniture/sink.rsi
state: sink
layers:
- state: sink
- map: [ "enum.SolutionContainerLayers.Fill" ]
state: sink-fill-1
visible: false
- type: entity
name: sink
@@ -86,6 +121,8 @@
components:
- type: SolutionContainerManager
solutions:
drainBuffer:
maxVol: 100
tank:
reagents:
- ReagentId: Water

View File

@@ -22,6 +22,8 @@
stash: !type:ContainerSlot {}
- type: SolutionContainerManager
solutions:
drainBuffer:
maxVol: 500
toilet:
maxVol: 250
- type: Transform
@@ -32,6 +34,12 @@
graph: Toilet
node: toilet
- type: Appearance
- type: Drain
autoDrain: false
- type: SolutionContainerVisuals
maxFillLevels: 1
fillBaseName: fill-
solutionName: drainBuffer
- type: StaticPrice
price: 25

View File

@@ -18,7 +18,7 @@
result: Bucket
completetime: 2
materials:
Plastic: 100
Steel: 100
- type: latheRecipe
id: WetFloorSign
@@ -81,6 +81,14 @@
Steel: 100
Glass: 100
- type: latheRecipe
id: Plunger
result: Plunger
completetime: 2
materials:
Plastic: 50
Wood: 200
- type: latheRecipe
id: WeaponSprayNozzle
result: WeaponSprayNozzle

View File

@@ -631,9 +631,13 @@
- type: Tag
id: Plastic
- type: Tag
id: Plunger
- type: Tag
id: PlushieGhost
- type: Tag
id: Powerdrill

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -5,11 +5,15 @@
"y":32
},
"license":"CC-BY-SA-3.0",
"copyright":"Created by EmoGarbage",
"copyright":"Created by EmoGarbage, fill-1 created by Topy for SS14",
"states":[
{
"name":"icon",
"directions": 4
},
{
"name": "fill-1",
"directions": 4
}
]
}

View File

@@ -24,6 +24,9 @@
},
{
"name": "mopbucket_water-3"
},
{
"name": "plunger"
},
{
"name": "inhand-left",
@@ -36,6 +39,14 @@
{
"name": "equipped-BELT",
"directions": 4
},
{
"name": "plunger-inhand-left",
"directions": 4
},
{
"name": "plunger-inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -46,6 +46,10 @@
{
"name": "cart_spray",
"directions": 4
},
{
"name": "cart_plunger",
"directions": 4
},
{
"name": "cart_water-1",

View File

@@ -5,7 +5,7 @@
"y": 32
},
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation https://github.com/tgstation/tgstation/commit/f01afc7edd39b28dd718407d5bbfca3a5dfe995f#diff-378d1b8f0f0a73185e7c82e4ccfdb65102561992a7abb306090ce851f8419780",
"copyright": "Taken from tgstation https://github.com/tgstation/tgstation/commit/f01afc7edd39b28dd718407d5bbfca3a5dfe995f#diff-378d1b8f0f0a73185e7c82e4ccfdb65102561992a7abb306090ce851f8419780, sink-fill-1 and sink_wide-fill-1 made by Topy for SS14",
"states": [
{
"name": "sink",
@@ -18,6 +18,14 @@
{
"name": "sink_stem",
"directions": 4
},
{
"name": "sink-fill-1",
"directions": 4
},
{
"name": "sink_wide-fill-1",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB