diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsComponent.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsComponent.cs index da21106476..d84e838c2b 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsComponent.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsComponent.cs @@ -36,6 +36,13 @@ namespace Content.Client.Chemistry.Visualizers [DataField("metamorphicNameFull")] public string MetamorphicNameFull = "transformable-container-component-glass"; + /// + /// Which solution of the SolutionContainerManagerComponent to represent. + /// If not set, will work as default. + /// + [DataField("solutionName")] + public string SolutionName = ""; + public string InitialName = string.Empty; public string InitialDescription = string.Empty; } diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index 3eea146050..6f5c14852e 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs @@ -25,6 +25,16 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem(uid, SolutionContainerVisuals.SolutionName, out var name, + args.Component) && name != component.SolutionName) + { + return; + } + } + if (!AppearanceSystem.TryGetData(uid, SolutionContainerVisuals.FillFraction, out var fraction, args.Component)) return; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs index bd6ea0d1ac..fa33309f58 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -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) { diff --git a/Content.Server/Fluids/EntitySystems/DrainSystem.cs b/Content.Server/Fluids/EntitySystems/DrainSystem.cs index 50f359ddc5..8d415599f5 100644 --- a/Content.Server/Fluids/EntitySystems/DrainSystem.cs +++ b/Content.Server/Fluids/EntitySystems/DrainSystem.cs @@ -1,101 +1,265 @@ 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() { - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; + base.Initialize(); + SubscribeLocalEvent>(AddEmptyVerb); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnDoAfter); + } - public override void Update(float frameTime) + private void AddEmptyVerb(EntityUid uid, DrainComponent component, GetVerbsEvent 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() { - base.Update(frameTime); - var managerQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var puddleQuery = GetEntityQuery(); - var puddles = new ValueList<(EntityUid Entity, string Solution)>(); - - foreach (var drain in EntityQuery()) + Text = Loc.GetString("drain-component-empty-verb-inhand", ("object", Name(args.Using.Value))), + Act = () => { - drain.Accumulator += frameTime; - if (drain.Accumulator < drain.DrainFrequency) + 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) + { + base.Update(frameTime); + var managerQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var puddleQuery = GetEntityQuery(); + var puddles = new ValueList<(EntityUid Entity, string Solution)>(); + + foreach (var drain in EntityQuery()) + { + drain.Accumulator += frameTime; + if (drain.Accumulator < drain.DrainFrequency) + { + continue; + } + 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; + + // Best to do this one every second rather than once every tick... + _solutionSystem.TryGetSolution(drain.Owner, DrainComponent.SolutionName, out var drainSolution, manager); + + 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)); + + // This will ensure that UnitsPerSecond is per second... + var amount = drain.UnitsPerSecond * drain.DrainFrequency; + + if (!xformQuery.TryGetComponent(drain.Owner, out var xform)) + continue; + + puddles.Clear(); + + foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, drain.Range)) + { + // No InRangeUnobstructed because there's no collision group that fits right now + // and these are placed by mappers and not buildable/movable so shouldnt really be a problem... + if (puddleQuery.TryGetComponent(entity, out var puddle)) { - continue; + puddles.Add((entity, puddle.SolutionName)); } - drain.Accumulator -= drain.DrainFrequency; + } - if (!managerQuery.TryGetComponent(drain.Owner, out var manager)) - continue; + if (puddles.Count == 0) + { + _ambientSoundSystem.SetAmbience(drain.Owner, false); + continue; + } - // Best to do this one every second rather than once every tick... - _solutionSystem.TryGetSolution(drain.Owner, DrainComponent.SolutionName, out var drainSolution, manager); + _ambientSoundSystem.SetAmbience(drain.Owner, true); - if (drainSolution is null) - continue; + amount /= puddles.Count; - // Remove a bit from the buffer - _solutionSystem.SplitSolution(drain.Owner, drainSolution, (drain.UnitsDestroyedPerSecond * drain.DrainFrequency)); - - // This will ensure that UnitsPerSecond is per second... - var amount = drain.UnitsPerSecond * drain.DrainFrequency; - - if (!xformQuery.TryGetComponent(drain.Owner, out var xform)) - continue; - - puddles.Clear(); - - foreach (var entity in _lookup.GetEntitiesInRange(xform.MapPosition, drain.Range)) + foreach (var (puddle, solution) in puddles) + { + // Queue the solution deletion if it's empty. EvaporationSystem might also do this + // but queuedelete should be pretty safe. + if (!_solutionSystem.TryGetSolution(puddle, solution, out var puddleSolution)) { - // No InRangeUnobstructed because there's no collision group that fits right now - // and these are placed by mappers and not buildable/movable so shouldnt really be a problem... - if (puddleQuery.TryGetComponent(entity, out var puddle)) - { - puddles.Add((entity, puddle.SolutionName)); - } - } - - if (puddles.Count == 0) - { - _ambientSoundSystem.SetAmbience(drain.Owner, false); + EntityManager.QueueDeleteEntity(puddle); continue; } - _ambientSoundSystem.SetAmbience(drain.Owner, true); + // Removes the lowest of: + // the drain component's units per second adjusted for # of puddles + // the puddle's remaining volume (making it cleanly zero) + // the drain's remaining volume in its buffer. + var transferSolution = _solutionSystem.SplitSolution(puddle, puddleSolution, + FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume)); - amount /= puddles.Count; + _solutionSystem.TryAddSolution(drain.Owner, drainSolution, transferSolution); - foreach (var (puddle, solution) in puddles) + if (puddleSolution.Volume <= 0) { - // Queue the solution deletion if it's empty. EvaporationSystem might also do this - // but queuedelete should be pretty safe. - if (!_solutionSystem.TryGetSolution(puddle, solution, out var puddleSolution)) - { - EntityManager.QueueDeleteEntity(puddle); - continue; - } - - // Removes the lowest of: - // the drain component's units per second adjusted for # of puddles - // the puddle's remaining volume (making it cleanly zero) - // the drain's remaining volume in its buffer. - var transferSolution = _solutionSystem.SplitSolution(puddle, puddleSolution, - FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume)); - - _solutionSystem.TryAddSolution(drain.Owner, drainSolution, transferSolution); - - if (puddleSolution.Volume <= 0) - { - QueueDel(puddle); - } + QueueDel(puddle); } } } } + + 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); + } } diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Transfers.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Transfers.cs index 4ecdefa7f5..c73685c288 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Transfers.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Transfers.cs @@ -1,3 +1,4 @@ +using Content.Server.Fluids.Components; using Content.Shared.Chemistry.Components; using Content.Shared.DragDrop; using Content.Shared.FixedPoint; diff --git a/Content.Shared/Chemistry/SolutionVisuals.cs b/Content.Shared/Chemistry/SolutionVisuals.cs index a0ac0ea403..f24be50364 100644 --- a/Content.Shared/Chemistry/SolutionVisuals.cs +++ b/Content.Shared/Chemistry/SolutionVisuals.cs @@ -8,6 +8,7 @@ namespace Content.Shared.Chemistry Color, FillFraction, BaseOverride, + SolutionName } public enum SolutionContainerLayers : byte diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index 0ab3eb40b7..8903e23b89 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -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) diff --git a/Content.Shared/Fluids/Components/DrainComponent.cs b/Content.Shared/Fluids/Components/DrainComponent.cs index b456a87fad..f7a88c80e4 100644 --- a/Content.Shared/Fluids/Components/DrainComponent.cs +++ b/Content.Shared/Fluids/Components/DrainComponent.cs @@ -1,15 +1,31 @@ -using Robust.Shared.GameStates; +using Robust.Shared.Audio; namespace Content.Shared.Fluids.Components; -[RegisterComponent, NetworkedComponent] +/// +/// 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. +/// +[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; + /// + /// Does this drain automatically absorb surrouding puddles? Or is it a drain designed to empty + /// solutions in it manually? + /// + [DataField("autoDrain"), ViewVariables(VVAccess.ReadOnly)] + public bool AutoDrain = true; + /// /// 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. /// - [DataField("range")] + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] public float Range = 2f; /// @@ -37,4 +53,25 @@ public sealed class DrainComponent : Component /// [DataField("drainFrequency")] public float DrainFrequency = 1f; + + /// + /// How much time it takes to unclog it with a plunger + /// + [DataField("unclogDuration"), ViewVariables(VVAccess.ReadWrite)] + public float UnclogDuration = 1f; + + /// + /// What's the probability of uncloging on each try + /// + [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"); } diff --git a/Content.Shared/Fluids/SharedDrainSystem.cs b/Content.Shared/Fluids/SharedDrainSystem.cs new file mode 100644 index 0000000000..43df0c352b --- /dev/null +++ b/Content.Shared/Fluids/SharedDrainSystem.cs @@ -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 + { + } +} diff --git a/Resources/Audio/Effects/Fluids/ATTRIBUTION.txt b/Resources/Audio/Effects/Fluids/ATTRIBUTION.txt deleted file mode 100644 index 78c9a1114a..0000000000 --- a/Resources/Audio/Effects/Fluids/ATTRIBUTION.txt +++ /dev/null @@ -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/ \ No newline at end of file diff --git a/Resources/Audio/Effects/Fluids/attributions.yml b/Resources/Audio/Effects/Fluids/attributions.yml new file mode 100644 index 0000000000..4e28c99252 --- /dev/null +++ b/Resources/Audio/Effects/Fluids/attributions.yml @@ -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/" diff --git a/Resources/Audio/Effects/Fluids/glug.ogg b/Resources/Audio/Effects/Fluids/glug.ogg new file mode 100644 index 0000000000..5828bb0568 Binary files /dev/null and b/Resources/Audio/Effects/Fluids/glug.ogg differ diff --git a/Resources/Audio/Items/Janitor/attributions.yml b/Resources/Audio/Items/Janitor/attributions.yml new file mode 100644 index 0000000000..f9cc68c82d --- /dev/null +++ b/Resources/Audio/Items/Janitor/attributions.yml @@ -0,0 +1,4 @@ +- files: ["plunger.ogg"] + license: "CC-BY-4.0" + copyright: "Created by tosha73" + source: "https://freesound.org/people/tosha73/sounds/540784/" diff --git a/Resources/Audio/Items/Janitor/plunger.ogg b/Resources/Audio/Items/Janitor/plunger.ogg new file mode 100644 index 0000000000..f0605b8aee Binary files /dev/null and b/Resources/Audio/Items/Janitor/plunger.ogg differ diff --git a/Resources/Locale/en-US/fluids/components/drain-component.ftl b/Resources/Locale/en-US/fluids/components/drain-component.ftl new file mode 100644 index 0000000000..897971a788 --- /dev/null +++ b/Resources/Locale/en-US/fluids/components/drain-component.ftl @@ -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. diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml index c3674f5a27..a8880fe4f0 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_service.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_service.yml @@ -4,7 +4,7 @@ sprite: Objects/Specific/Janitorial/janitorial.rsi state: cleaner product: CrateServiceJanitorialSupplies - cost: 500 + cost: 560 category: Service group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index 87323512a8..ed6970400d 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -15,6 +15,8 @@ amount: 2 - id: TrashBag amount: 2 + - id: Plunger + amount: 2 - type: entity id: CrateServiceReplacementLights diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 7da64f2d1a..75dfcc2de4 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -71,6 +71,8 @@ amount: 2 - id: FlashlightLantern amount: 2 + - id: Plunger + amount: 2 - type: entity id: ClosetLegalFilled diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 01cb8f653b..da09e3baff 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -94,6 +94,7 @@ - SheetSteel - SheetPlastic - ResearchDisk + - Plunger chance: 0.6 offset: 0.0 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 501901a2a7..0d0d3822e9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Furniture/sink.yml b/Resources/Prototypes/Entities/Structures/Furniture/sink.yml index 0e1654f598..507134364d 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/sink.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/sink.yml @@ -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 @@ -63,9 +86,17 @@ id: SinkWide parent: Sink components: - - type: Sprite - sprite: Structures/Furniture/sink.rsi - state: sink_wide + - type: Sprite + sprite: Structures/Furniture/sink.rsi + 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 diff --git a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml index 9d476ddca1..af0d2f1794 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml @@ -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 diff --git a/Resources/Prototypes/Recipes/Lathes/janitorial.yml b/Resources/Prototypes/Recipes/Lathes/janitorial.yml index 902569e4b0..2fd153852f 100644 --- a/Resources/Prototypes/Recipes/Lathes/janitorial.yml +++ b/Resources/Prototypes/Recipes/Lathes/janitorial.yml @@ -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 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 8c712a7dcc..208e99dd1f 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -631,9 +631,13 @@ - type: Tag id: Plastic +- type: Tag + id: Plunger + - type: Tag id: PlushieGhost + - type: Tag id: Powerdrill diff --git a/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/fill-1.png new file mode 100644 index 0000000000..31540a6e02 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/icon.png b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/icon.png index de9a244bdf..f725b227bc 100644 Binary files a/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/icon.png and b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/meta.json index 7835a4c7bf..e40dbb086d 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/drain.rsi/meta.json @@ -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 + } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/meta.json index ae3103e2be..177bd8e619 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/meta.json @@ -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 } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-left.png b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-left.png new file mode 100644 index 0000000000..a1b319f401 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-right.png b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-right.png new file mode 100644 index 0000000000..09a35f6831 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger-inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger.png b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger.png new file mode 100644 index 0000000000..bc5f68be63 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/janitorial.rsi/plunger.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/cart_plunger.png b/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/cart_plunger.png new file mode 100644 index 0000000000..37779d4361 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/cart_plunger.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json index 846a3157a5..8691dc073c 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json @@ -46,6 +46,10 @@ { "name": "cart_spray", "directions": 4 + }, + { + "name": "cart_plunger", + "directions": 4 }, { "name": "cart_water-1", diff --git a/Resources/Textures/Structures/Furniture/sink.rsi/meta.json b/Resources/Textures/Structures/Furniture/sink.rsi/meta.json index e00dcab8e4..a2c289e8cf 100644 --- a/Resources/Textures/Structures/Furniture/sink.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/sink.rsi/meta.json @@ -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 } ] } diff --git a/Resources/Textures/Structures/Furniture/sink.rsi/sink-fill-1.png b/Resources/Textures/Structures/Furniture/sink.rsi/sink-fill-1.png new file mode 100644 index 0000000000..5ec7cf03e2 Binary files /dev/null and b/Resources/Textures/Structures/Furniture/sink.rsi/sink-fill-1.png differ diff --git a/Resources/Textures/Structures/Furniture/sink.rsi/sink_wide-fill-1.png b/Resources/Textures/Structures/Furniture/sink.rsi/sink_wide-fill-1.png new file mode 100644 index 0000000000..bba702a28d Binary files /dev/null and b/Resources/Textures/Structures/Furniture/sink.rsi/sink_wide-fill-1.png differ