From 14b401f9b372641f31c5fa778d50b9c2e92b4cf7 Mon Sep 17 00:00:00 2001 From: Ygg01 Date: Wed, 27 Oct 2021 09:24:18 +0100 Subject: [PATCH] Puddle refactor 2: Spillastic boogaloo (#4784) * Refactor PuddleComponent * Move puddle effects into separate RSIs Basically egg/tomato/powder puddle will be moved into separate /Textures/Fluids RSIs * Fix YAML for puddles * Fix issues sloth pointed out. * Ensure Puddle Component are properly added when spawned * Remove unnecessary method init puddle with starting maxVolume * Addressed ElectroSr comments * Add Resolves * Try fix error in ensureSolution * Puddle unanchoring * Address some issues with puddles * Fix continue -> return --- Content.Client/Entry/IgnoredComponents.cs | 1 + Content.Client/Fluids/PuddleVisualizer.cs | 73 ++++ .../Tests/Fluids/PuddleTest.cs | 55 +-- .../Fluids/Components/EvaporationComponent.cs | 46 ++ .../Fluids/Components/MopComponent.cs | 15 +- .../Fluids/Components/PuddleComponent.cs | 407 +----------------- .../Fluids/Components/SpillExtensions.cs | 9 +- .../Fluids/EntitySystems/EvaporationSystem.cs | 58 +++ .../Fluids/EntitySystems/PuddleSystem.cs | 334 ++++++++++++++ Content.Server/Fluids/PuddleSystem.cs | 85 ---- .../EntitySystems/SolutionContainerSystem.cs | 19 +- Content.Shared/Fluids/PuddleVisuals.cs | 12 + .../Prototypes/Entities/Effects/puddle.yml | 72 +++- .../Entities/Objects/Consumable/Food/egg.yml | 15 +- .../Objects/Consumable/Food/ingredients.yml | 18 +- .../Objects/Consumable/Food/produce.yml | 15 +- .../egg_splat.rsi}/egg-0.png | Bin .../egg_splat.rsi}/egg-1.png | Bin .../egg_splat.rsi}/egg-2.png | Bin .../egg_splat.rsi}/egg-3.png | Bin .../Textures/Fluids/egg_splat.rsi/meta.json | 23 + .../Textures/Fluids/powder.rsi/meta.json | 14 + .../powder.rsi/powder.png} | Bin .../Fluids/tomato_splat.rsi/meta.json | 20 + .../tomato_splat.rsi}/puddle-0.png | Bin .../tomato_splat.rsi}/puddle-1.png | Bin .../tomato_splat.rsi}/puddle-2.png | Bin .../Objects/Consumable/Food/egg.rsi/meta.json | 12 - .../Consumable/Food/ingredients.rsi/meta.json | 3 - .../Specific/Hydroponics/tomato.rsi/meta.json | 11 +- 30 files changed, 753 insertions(+), 564 deletions(-) create mode 100644 Content.Client/Fluids/PuddleVisualizer.cs create mode 100644 Content.Server/Fluids/Components/EvaporationComponent.cs create mode 100644 Content.Server/Fluids/EntitySystems/EvaporationSystem.cs create mode 100644 Content.Server/Fluids/EntitySystems/PuddleSystem.cs delete mode 100644 Content.Server/Fluids/PuddleSystem.cs create mode 100644 Content.Shared/Fluids/PuddleVisuals.cs rename Resources/Textures/{Objects/Consumable/Food/egg.rsi => Fluids/egg_splat.rsi}/egg-0.png (100%) rename Resources/Textures/{Objects/Consumable/Food/egg.rsi => Fluids/egg_splat.rsi}/egg-1.png (100%) rename Resources/Textures/{Objects/Consumable/Food/egg.rsi => Fluids/egg_splat.rsi}/egg-2.png (100%) rename Resources/Textures/{Objects/Consumable/Food/egg.rsi => Fluids/egg_splat.rsi}/egg-3.png (100%) create mode 100644 Resources/Textures/Fluids/egg_splat.rsi/meta.json create mode 100644 Resources/Textures/Fluids/powder.rsi/meta.json rename Resources/Textures/{Objects/Consumable/Food/ingredients.rsi/powder-0.png => Fluids/powder.rsi/powder.png} (100%) create mode 100644 Resources/Textures/Fluids/tomato_splat.rsi/meta.json rename Resources/Textures/{Objects/Specific/Hydroponics/tomato.rsi => Fluids/tomato_splat.rsi}/puddle-0.png (100%) rename Resources/Textures/{Objects/Specific/Hydroponics/tomato.rsi => Fluids/tomato_splat.rsi}/puddle-1.png (100%) rename Resources/Textures/{Objects/Specific/Hydroponics/tomato.rsi => Fluids/tomato_splat.rsi}/puddle-2.png (100%) diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index b1cf404d07..2e805767db 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -254,6 +254,7 @@ namespace Content.Client.Entry "Repairable", "GasGenerator", "SolutionTransfer", + "Evaporation", "Shovel", "ReagentTank", "UtilityAI", diff --git a/Content.Client/Fluids/PuddleVisualizer.cs b/Content.Client/Fluids/PuddleVisualizer.cs new file mode 100644 index 0000000000..f6724e406f --- /dev/null +++ b/Content.Client/Fluids/PuddleVisualizer.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using Content.Shared.Fluids; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager.Attributes; + + +namespace Content.Client.Fluids +{ + [UsedImplicitly] + public class PuddleVisualizer : AppearanceVisualizer + { + [Dependency] private readonly IRobustRandom _random = default!; + + // Whether the underlying solution color should be used + [DataField("recolor")] public bool Recolor; + + public override void InitializeEntity(IEntity entity) + { + base.InitializeEntity(entity); + + if (!entity.TryGetComponent(out SpriteComponent? spriteComponent)) + { + Logger.Warning($"Missing SpriteComponent for PuddleVisualizer on entityUid = {entity.Uid}"); + return; + } + + IoCManager.InjectDependencies(this); + + var maxStates = spriteComponent.BaseRSI?.ToArray(); + + if (maxStates is not { Length: > 0 }) return; + + var variant = _random.Next(0, maxStates.Length - 1); + spriteComponent.LayerSetState(0, maxStates[variant].StateId); + spriteComponent.Rotation = Angle.FromDegrees(_random.Next(0, 359)); + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.TryGetData(PuddleVisuals.VolumeScale, out var volumeScale) && + component.Owner.TryGetComponent(out var spriteComponent)) + { + var cappedScale = Math.Min(1.0f, volumeScale * 0.75f +0.25f); + UpdateVisual(component, spriteComponent, cappedScale); + } + } + + private void UpdateVisual(AppearanceComponent component, SpriteComponent spriteComponent, float cappedScale) + { + Color newColor; + if (Recolor && component.TryGetData(PuddleVisuals.SolutionColor, out var solutionColor)) + { + newColor = solutionColor.WithAlpha(cappedScale); + } + else + { + newColor = spriteComponent.Color.WithAlpha(cappedScale); + } + + spriteComponent.Color = newColor; + } + } + +} \ No newline at end of file diff --git a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs index d6f2a9db99..bf1c95f85a 100644 --- a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs @@ -146,63 +146,60 @@ namespace Content.IntegrationTests.Tests.Fluids Assert.True(sGridEntity.Paused); }); - float sEvaporateTime = default; - PuddleComponent sPuddle = null; - Solution solution = null; - ReagentUnit sPuddleStartingVolume = default; + float evaporateTime = default; + PuddleComponent puddle = null; + EvaporationComponent evaporation; + var amount = 2; // Spawn a puddle await server.WaitAssertion(() => { - var solution = new Solution("water", ReagentUnit.New(20)); - sPuddle = solution.SpillAt(sCoordinates, "PuddleSmear"); + var solution = new Solution("water", ReagentUnit.New(amount)); + puddle = solution.SpillAt(sCoordinates, "PuddleSmear"); // Check that the puddle was created - Assert.NotNull(sPuddle); + Assert.NotNull(puddle); - sPuddle.Owner.Paused = true; // See https://github.com/space-wizards/RobustToolbox/issues/1445 + evaporation = puddle.Owner.GetComponent(); - Assert.True(sPuddle.Owner.Paused); + puddle.Owner.Paused = true; // See https://github.com/space-wizards/RobustToolbox/issues/1445 + + Assert.True(puddle.Owner.Paused); // Check that the puddle is going to evaporate - Assert.Positive(sPuddle.EvaporateTime); + Assert.Positive(evaporation.EvaporateTime); // Should have a timer component added to it for evaporation - Assert.True(sPuddle.Owner.TryGetComponent(out TimerComponent _)); + Assert.That(evaporation.Accumulator, Is.EqualTo(0f)); - sEvaporateTime = sPuddle.EvaporateTime; - sPuddleStartingVolume = sPuddle.CurrentVolume; + evaporateTime = evaporation.EvaporateTime; }); // Wait enough time for it to evaporate if it was unpaused - var sTimeToWait = (5 + (int) Math.Ceiling(sEvaporateTime * sGameTiming.TickRate)) * 2; + var sTimeToWait = (5 + (int)Math.Ceiling(amount * evaporateTime * sGameTiming.TickRate)); await server.WaitRunTicks(sTimeToWait); // No evaporation due to being paused await server.WaitAssertion(() => { - Assert.True(sPuddle.Owner.Paused); - Assert.True(sPuddle.Owner.TryGetComponent(out TimerComponent _)); + Assert.True(puddle.Owner.Paused); // Check that the puddle still exists - Assert.False(sPuddle.Owner.Deleted); + Assert.False(puddle.Owner.Deleted); }); // Unpause the map - await server.WaitPost(() => - { - sPauseManager.SetMapPaused(sMapId, false); - }); + await server.WaitPost(() => { sPauseManager.SetMapPaused(sMapId, false); }); // Check that the map, grid and puddle are unpaused await server.WaitAssertion(() => { Assert.False(sPauseManager.IsMapPaused(sMapId)); Assert.False(sPauseManager.IsGridPaused(sGridId)); - Assert.False(sPuddle.Owner.Paused); + Assert.False(puddle.Owner.Paused); // Check that the puddle still exists - Assert.False(sPuddle.Owner.Deleted); + Assert.False(puddle.Owner.Deleted); }); // Wait enough time for it to evaporate @@ -212,16 +209,10 @@ namespace Content.IntegrationTests.Tests.Fluids await server.WaitAssertion(() => { // Check that the puddle is unpaused - Assert.False(sPuddle.Owner.Paused); + Assert.False(puddle.Owner.Paused); - // Check that the puddle has evaporated some of its volume - Assert.That(sPuddle.CurrentVolume, Is.LessThan(sPuddleStartingVolume)); - - // If its new volume is zero it should have been deleted - if (sPuddle.CurrentVolume == ReagentUnit.Zero) - { - Assert.True(sPuddle.Deleted); - } + // Check that puddle has been deleted + Assert.True(puddle.Deleted); }); } } diff --git a/Content.Server/Fluids/Components/EvaporationComponent.cs b/Content.Server/Fluids/Components/EvaporationComponent.cs new file mode 100644 index 0000000000..0e9f089c36 --- /dev/null +++ b/Content.Server/Fluids/Components/EvaporationComponent.cs @@ -0,0 +1,46 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Fluids.Components +{ + [RegisterComponent] + [Friend(typeof(EvaporationSystem))] + public sealed class EvaporationComponent : Component + { + public override string Name => "Evaporation"; + + /// + /// The time that it will take this puddle to lose one reagent unit of solution, in seconds. + /// + [DataField("evaporateTime")] + public float EvaporateTime { get; set; } = 5f; + + /// + /// Name of referenced solution. Defaults to + /// + [DataField("solution")] + public string SolutionName { get; set; } = PuddleComponent.DefaultSolutionName; + + /// + /// Lower limit below which puddle won't evaporate. Useful when wanting to leave a stain. + /// Defaults to evaporate completely. + /// + [DataField("lowerLimit")] + public ReagentUnit LowerLimit = ReagentUnit.Zero; + + /// + /// Upper limit below which puddle won't evaporate. Useful when wanting to make sure large puddle will + /// remain forever. Defaults to . + /// + [DataField("upperLimit")] + public ReagentUnit UpperLimit = PuddleComponent.DefaultOverflowVolume; + + /// + /// The time accumulated since the start. + /// + public float Accumulator = 0f; + } +} diff --git a/Content.Server/Fluids/Components/MopComponent.cs b/Content.Server/Fluids/Components/MopComponent.cs index 72545604b6..457b86f6f2 100644 --- a/Content.Server/Fluids/Components/MopComponent.cs +++ b/Content.Server/Fluids/Components/MopComponent.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Content.Server.DoAfter; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; @@ -138,11 +139,11 @@ namespace Content.Server.Fluids.Components var transferAmount = ReagentUnit.Min(ReagentUnit.New(5), puddleComponent.CurrentVolume, CurrentVolume); var puddleCleaned = puddleComponent.CurrentVolume - transferAmount <= 0; + var puddleSystem = EntitySystem.Get(); + var solutionSystem = EntitySystem.Get(); if (transferAmount == 0) { - if ( - puddleComponent - .EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent. + if (puddleSystem.EmptyHolder(puddleComponent.Owner.Uid, puddleComponent)) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent. { puddleComponent.Owner.Delete(); transferAmount = ReagentUnit.Min(ReagentUnit.New(5), CurrentVolume); @@ -155,13 +156,13 @@ namespace Content.Server.Fluids.Components } else { - puddleComponent.SplitSolution(transferAmount); + if (solutionSystem.TryGetSolution(eventArgs.Target, puddleComponent.SolutionName, out var puddleSolution)) + solutionSystem.SplitSolution(eventArgs.Target.Uid, puddleSolution, transferAmount); } - if ( - puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly. + if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly. { - EntitySystem.Get().SplitSolution(Owner.Uid, contents, transferAmount) + solutionSystem.SplitSolution(Owner.Uid, contents, transferAmount) .SpillAt(eventArgs.ClickLocation, "PuddleSmear"); } else diff --git a/Content.Server/Fluids/Components/PuddleComponent.cs b/Content.Server/Fluids/Components/PuddleComponent.cs index 7cae41efe8..3a62bfd3b6 100644 --- a/Content.Server/Fluids/Components/PuddleComponent.cs +++ b/Content.Server/Fluids/Components/PuddleComponent.cs @@ -1,25 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.EntitySystems; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Directions; -using Content.Shared.Maps; -using Content.Shared.Physics; -using Content.Shared.Slippery; using Content.Shared.Sound; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Physics; -using Robust.Shared.Player; -using Robust.Shared.Random; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -29,8 +12,16 @@ namespace Content.Server.Fluids.Components /// Puddle on a floor /// [RegisterComponent] - public class PuddleComponent : Component, IMapInit + [Friend(typeof(PuddleSystem))] + public sealed class PuddleComponent : Component { + public const string DefaultSolutionName = "puddle"; + private static readonly ReagentUnit DefaultSlipThreshold = ReagentUnit.New(3); + public static readonly ReagentUnit DefaultOverflowVolume = ReagentUnit.New(20); + + public override string Name => "Puddle"; + + // Current design: Something calls the SpillHelper.Spill, that will either // A) Add to an existing puddle at the location (normalised to tile-center) or // B) add a new one @@ -43,381 +34,25 @@ namespace Content.Server.Fluids.Components // based on behaviour (e.g. someone being punched vs slashed with a sword would have different blood sprite) // to check for low volumes for evaporation or whatever - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - public override string Name => "Puddle"; - public const string DefaultSolutionName = "puddle"; + [DataField("slipThreshold")] public ReagentUnit SlipThreshold = DefaultSlipThreshold; - private CancellationTokenSource? _evaporationToken; - - [DataField("evaporate_threshold")] private ReagentUnit - _evaporateThreshold = - ReagentUnit.New(20); // How few we can hold prior to self-destructing - - public ReagentUnit EvaporateThreshold - { - get => _evaporateThreshold; - set => _evaporateThreshold = value; - } - - private ReagentUnit _slipThreshold = ReagentUnit.New(3); - - public ReagentUnit SlipThreshold - { - get => _slipThreshold; - set => _slipThreshold = value; - } - - /// - /// The time that it will take this puddle to evaporate, in seconds. - /// - [DataField("evaporate_time")] - public float EvaporateTime { get; private set; } = 5f; - - [DataField("spill_sound")] - private SoundSpecifier _spillSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg"); + [DataField("spillSound")] + public SoundSpecifier SpillSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg"); /// /// Whether or not this puddle is currently overflowing onto its neighbors /// - private bool _overflown; + public bool Overflown; - private SpriteComponent _spriteComponent = default!; + [ViewVariables(VVAccess.ReadOnly)] + public ReagentUnit CurrentVolume => EntitySystem.Get().CurrentVolume(Owner.Uid); - public ReagentUnit MaxVolume - { - get => PuddleSolution?.MaxVolume ?? ReagentUnit.Zero; - set - { - if (PuddleSolution != null) - { - PuddleSolution.MaxVolume = value; - } - } - } + [ViewVariables] [DataField("overflowVolume")] + public ReagentUnit OverflowVolume = DefaultOverflowVolume; - [ViewVariables] public ReagentUnit CurrentVolume => PuddleSolution?.CurrentVolume ?? ReagentUnit.Zero; + public ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume; - // Volume at which the fluid will try to spill to adjacent components - // Currently a random number, potentially change - public ReagentUnit OverflowVolume => _overflowVolume; - - [ViewVariables] [DataField("overflow_volume")] - private ReagentUnit _overflowVolume = ReagentUnit.New(20); - - private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume; - - public bool EmptyHolder => PuddleSolution?.Contents.Count == 0; - - [DataField("variants")] private int _spriteVariants = 1; - - // Whether the underlying solution color should be used - [DataField("recolor")] private bool _recolor = default; - - [DataField("state")] private string _spriteState = "puddle"; - - private Solution? PuddleSolution => EntitySystem.Get().EnsureSolution(Owner, DefaultSolutionName); - - protected override void Initialize() - { - base.Initialize(); - - // Smaller than 1m^3 for now but realistically this shouldn't be hit - MaxVolume = ReagentUnit.New(1000); - - // Random sprite state set server-side so it's consistent across all clients - _spriteComponent = Owner.EnsureComponent(); - - var randomVariant = _random.Next(0, _spriteVariants - 1); - - if (_spriteComponent.BaseRSIPath != null) - { - _spriteComponent.LayerSetState(0, $"{_spriteState}-{randomVariant}"); - } - - // UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here - - UpdateStatus(); - } - - void IMapInit.MapInit() - { - var robustRandom = IoCManager.Resolve(); - _spriteComponent.Rotation = Angle.FromDegrees(robustRandom.Next(0, 359)); - } - - /// - /// Whether adding this solution to this puddle would overflow. - /// - /// - /// - public bool WouldOverflow(Solution solution) - { - return (CurrentVolume + solution.TotalVolume > _overflowVolume); - } - - // Flow rate should probably be controlled globally so this is it for now - internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true, - bool checkForOverflow = true) - { - if (solution.TotalVolume == 0) - { - return false; - } - - var result = EntitySystem.Get().TryAddSolution(Owner.Uid, PuddleSolution, solution); - if (!result) - { - return false; - } - - UpdateStatus(); - - if (checkForOverflow) - { - CheckOverflow(); - } - - if (checkForEvaporate) - { - CheckEvaporate(); - } - - UpdateAppearance(); - if (!sound) - { - return true; - } - - SoundSystem.Play(Filter.Pvs(Owner), _spillSound.GetSound(), Owner); - return true; - } - - internal void SplitSolution(ReagentUnit quantity) - { - if (PuddleSolution != null) - { - EntitySystem.Get().SplitSolution(Owner.Uid, PuddleSolution, quantity); - CheckEvaporate(); - UpdateAppearance(); - } - - } - - public void CheckEvaporate() - { - if (CurrentVolume == 0) - { - Owner.Delete(); - } - } - - public void Evaporate() - { - if (PuddleSolution != null) - { - EntitySystem.Get().SplitSolution(Owner.Uid, PuddleSolution, - ReagentUnit.Min(ReagentUnit.New(1), PuddleSolution.CurrentVolume)); - } - - if (CurrentVolume == 0) - { - Owner.Delete(); - } - else - { - UpdateStatus(); - } - } - - public void UpdateStatus() - { - _evaporationToken?.Cancel(); - if (Owner.Deleted) return; - - UpdateAppearance(); - UpdateSlip(); - - if (_evaporateThreshold == ReagentUnit.New(-1) || CurrentVolume > _evaporateThreshold) - { - return; - } - - _evaporationToken = new CancellationTokenSource(); - - // KYS to evaporate - Owner.SpawnTimer(TimeSpan.FromSeconds(EvaporateTime), Evaporate, _evaporationToken.Token); - } - - private void UpdateSlip() - { - if ((_slipThreshold == ReagentUnit.New(-1) || CurrentVolume < _slipThreshold) && - Owner.TryGetComponent(out SlipperyComponent? oldSlippery)) - { - oldSlippery.Slippery = false; - } - else if (CurrentVolume >= _slipThreshold) - { - var newSlippery = Owner.EnsureComponent(); - newSlippery.Slippery = true; - } - } - - private void UpdateAppearance() - { - if (Owner.Deleted || EmptyHolder) - { - return; - } - - // Opacity based on level of fullness to overflow - // Hard-cap lower bound for visibility reasons - var volumeScale = (CurrentVolume.Float() / OverflowVolume.Float()) * 0.75f + 0.25f; - var cappedScale = Math.Min(1.0f, volumeScale); - // Color based on the underlying solutioncomponent - Color newColor; - if (_recolor && PuddleSolution != null) - { - newColor = PuddleSolution.Color.WithAlpha(cappedScale); - } - else - { - newColor = _spriteComponent.Color.WithAlpha(cappedScale); - } - - _spriteComponent.Color = newColor; - - _spriteComponent.Dirty(); - } - - /// - /// Will overflow this entity to neighboring entities if required - /// - private void CheckOverflow() - { - if (PuddleSolution == null || CurrentVolume <= _overflowVolume || _overflown) - return; - - var nextPuddles = new List() { this }; - var overflownPuddles = new List(); - - while (OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0) - { - foreach (var next in nextPuddles.ToArray()) - { - nextPuddles.Remove(next); - - next._overflown = true; - overflownPuddles.Add(next); - - var adjacentPuddles = next.GetAllAdjacentOverflow().ToArray(); - if (OverflowLeft <= ReagentUnit.Epsilon * adjacentPuddles.Length) - { - break; - } - - if (adjacentPuddles.Length == 0) - { - continue; - } - - var numberOfAdjacent = ReagentUnit.New(adjacentPuddles.Length); - var overflowSplit = OverflowLeft / numberOfAdjacent; - foreach (var adjacent in adjacentPuddles) - { - var adjacentPuddle = adjacent(); - var quantity = ReagentUnit.Min(overflowSplit, adjacentPuddle.OverflowVolume); - var spillAmount = EntitySystem.Get().SplitSolution(Owner.Uid, PuddleSolution, quantity); - - adjacentPuddle.TryAddSolution(spillAmount, false, false, false); - nextPuddles.Add(adjacentPuddle); - } - } - } - - foreach (var puddle in overflownPuddles) - { - puddle._overflown = false; - } - } - - /// - /// Tries to get an adjacent coordinate to overflow to, unless it is blocked by a wall on the - /// same tile or the tile is empty - /// - /// The direction to get the puddle from, respective to this one - /// The puddle that was found or is to be created, or null if there - /// is a wall in the way - /// true if a puddle was found or created, false otherwise - private bool TryGetAdjacentOverflow(Direction direction, [NotNullWhen(true)] out Func? puddle) - { - puddle = default; - - // We're most likely in space, do nothing. - if (!Owner.Transform.GridID.IsValid()) - return false; - - var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); - var coords = Owner.Transform.Coordinates; - - if (!coords.Offset(direction).TryGetTileRef(out var tile)) - { - return false; - } - - // If space return early, let that spill go out into the void - if (tile.Value.Tile.IsEmpty) - { - return false; - } - - if (!Owner.Transform.Anchored) - return false; - - foreach (var entity in mapGrid.GetInDir(coords, direction)) - { - if (Owner.EntityManager.TryGetComponent(entity, out IPhysBody? physics) && - (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0) - { - puddle = default; - return false; - } - - if (Owner.EntityManager.TryGetComponent(entity, out PuddleComponent? existingPuddle)) - { - if (existingPuddle._overflown) - { - return false; - } - - puddle = () => existingPuddle; - } - } - - if (puddle == default) - { - puddle = () => - Owner.EntityManager.SpawnEntity(Owner.Prototype?.ID, mapGrid.DirectionToGrid(coords, direction)) - .GetComponent(); - } - - return true; - } - - /// - /// Finds or creates adjacent puddles in random directions from this one - /// - /// Enumerable of the puddles found or to be created - private IEnumerable> GetAllAdjacentOverflow() - { - foreach (var direction in SharedDirectionExtensions.RandomDirections()) - { - if (TryGetAdjacentOverflow(direction, out var puddle)) - { - yield return puddle; - } - } - } + [DataField("solution")] public string SolutionName { get; set; } = DefaultSolutionName; } } diff --git a/Content.Server/Fluids/Components/SpillExtensions.cs b/Content.Server/Fluids/Components/SpillExtensions.cs index 9fa4f8c263..bc160842f0 100644 --- a/Content.Server/Fluids/Components/SpillExtensions.cs +++ b/Content.Server/Fluids/Components/SpillExtensions.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Coordinates.Helpers; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; @@ -138,13 +139,15 @@ namespace Content.Server.Fluids.Components } } + var puddleSystem = EntitySystem.Get(); + foreach (var spillEntity in spillEntities) { if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent)) continue; - if (!overflow && puddleComponent.WouldOverflow(solution)) return null; + if (!overflow && puddleSystem.WouldOverflow(puddleComponent.Owner.Uid, solution, puddleComponent)) return null; - if (!puddleComponent.TryAddSolution(solution, sound)) continue; + if (!puddleSystem.TryAddSolution(puddleComponent.Owner.Uid, solution, sound)) continue; puddle = puddleComponent; spilt = true; @@ -157,7 +160,7 @@ namespace Content.Server.Fluids.Components var puddleEnt = serverEntityManager.SpawnEntity(prototype, spillGridCoords); var newPuddleComponent = puddleEnt.GetComponent(); - newPuddleComponent.TryAddSolution(solution, sound); + puddleSystem.TryAddSolution(newPuddleComponent.Owner.Uid, solution, sound); return newPuddleComponent; } diff --git a/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs b/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs new file mode 100644 index 0000000000..ec95d5c32f --- /dev/null +++ b/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs @@ -0,0 +1,58 @@ +using Content.Server.Fluids.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Utility; + +namespace Content.Server.Fluids.EntitySystems +{ + [UsedImplicitly] + public sealed class EvaporationSystem : EntitySystem + { + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + var queueDelete = new RemQueue(); + foreach (var evaporationComponent in EntityManager.EntityQuery()) + { + var uid = evaporationComponent.Owner.Uid; + evaporationComponent.Accumulator += frameTime; + + if (!_solutionContainerSystem.TryGetSolution(uid, evaporationComponent.SolutionName, out var solution)) + { + // If no solution, delete the entity + queueDelete.Add(evaporationComponent); + continue; + } + + if (evaporationComponent.Accumulator < evaporationComponent.EvaporateTime) + continue; + + evaporationComponent.Accumulator -= evaporationComponent.EvaporateTime; + + + _solutionContainerSystem.SplitSolution(uid, solution, + ReagentUnit.Min(ReagentUnit.New(1), solution.CurrentVolume)); + + if (solution.CurrentVolume == 0) + { + EntityManager.QueueDeleteEntity(uid); + } + else if (solution.CurrentVolume <= evaporationComponent.LowerLimit + || solution.CurrentVolume >= evaporationComponent.UpperLimit) + { + queueDelete.Add(evaporationComponent); + } + } + + foreach (var evaporationComponent in queueDelete) + { + EntityManager.RemoveComponent(evaporationComponent.Owner.Uid, evaporationComponent); + } + } + } +} diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs new file mode 100644 index 0000000000..7087191037 --- /dev/null +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Construction.Components; +using Content.Server.Fluids.Components; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Directions; +using Content.Shared.Examine; +using Content.Shared.Fluids; +using Content.Shared.Maps; +using Content.Shared.Physics; +using Content.Shared.Slippery; +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Player; + +namespace Content.Server.Fluids.EntitySystems +{ + [UsedImplicitly] + public sealed class PuddleSystem : EntitySystem + { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUnanchored); + SubscribeLocalEvent(AddSpillVerb); + SubscribeLocalEvent(HandlePuddleExamined); + SubscribeLocalEvent(OnUpdate); + SubscribeLocalEvent(OnInit); + } + + private void OnInit(EntityUid uid, PuddleComponent component, ComponentInit args) + { + var solution = _solutionContainerSystem.EnsureSolution(uid, component.SolutionName); + solution.MaxVolume = ReagentUnit.New(1000); + } + + private void OnUpdate(EntityUid uid, PuddleComponent component, SolutionChangedEvent args) + { + UpdateSlip(uid, component); + UpdateVisuals(uid, component); + } + + private void UpdateVisuals(EntityUid uid, PuddleComponent puddleComponent) + { + if (puddleComponent.Owner.Deleted || EmptyHolder(uid, puddleComponent) || + !EntityManager.TryGetComponent(uid, out var appearanceComponent)) + { + return; + } + + // Opacity based on level of fullness to overflow + // Hard-cap lower bound for visibility reasons + var volumeScale = puddleComponent.CurrentVolume.Float() / puddleComponent.OverflowVolume.Float(); + var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName); + + appearanceComponent.SetData(PuddleVisuals.VolumeScale, volumeScale); + appearanceComponent.SetData(PuddleVisuals.SolutionColor, puddleSolution.Color); + } + + private void UpdateSlip(EntityUid entityUid, PuddleComponent puddleComponent) + { + if ((puddleComponent.SlipThreshold == ReagentUnit.New(-1) || + puddleComponent.CurrentVolume < puddleComponent.SlipThreshold) && + EntityManager.TryGetComponent(entityUid, out SlipperyComponent? oldSlippery)) + { + oldSlippery.Slippery = false; + } + else if (puddleComponent.CurrentVolume >= puddleComponent.SlipThreshold) + { + var newSlippery = EntityManager.EnsureComponent(entityUid); + newSlippery.Slippery = true; + } + } + + private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution)) + return; + + if (solution.DrainAvailable == ReagentUnit.Zero) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("spill-target-verb-get-data-text"); + // TODO VERB ICONS spill icon? pouring out a glass/beaker? + verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target.Uid, + solution, solution.DrainAvailable).SpillAt(args.Target.Transform.Coordinates, "PuddleSmear"); + args.Verbs.Add(verb); + } + + private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args) + { + if (EntityManager.TryGetComponent(uid, out var slippery) && slippery.Slippery) + { + args.PushText(Loc.GetString("puddle-component-examine-is-slipper-text")); + } + } + + private void OnUnanchored(EntityUid uid, PuddleComponent puddle, UnanchoredEvent unanchoredEvent) + { + if (!puddle.Owner.Transform.Anchored) + return; + + puddle.Owner.QueueDelete(); + } + + /// + /// Whether adding this solution to this puddle would overflow. + /// + /// Uid of owning entity + /// Puddle to which we are adding solution + /// Solution we intend to add + /// + public bool WouldOverflow(EntityUid uid, Solution solution, PuddleComponent? puddle = null) + { + if (!Resolve(uid, ref puddle)) + return false; + + return puddle.CurrentVolume + solution.TotalVolume > puddle.OverflowVolume; + } + + public bool EmptyHolder(EntityUid uid, PuddleComponent? puddleComponent = null) + { + if (!Resolve(uid, ref puddleComponent)) + return true; + + return !_solutionContainerSystem.TryGetSolution(puddleComponent.Owner.Uid, puddleComponent.SolutionName, + out var solution) + || solution.Contents.Count == 0; + } + + public ReagentUnit CurrentVolume(EntityUid uid, PuddleComponent? puddleComponent = null) + { + if (!Resolve(uid, ref puddleComponent)) + return ReagentUnit.Zero; + + return _solutionContainerSystem.TryGetSolution(puddleComponent.Owner.Uid, puddleComponent.SolutionName, + out var solution) + ? solution.CurrentVolume + : ReagentUnit.Zero; + } + + public bool TryAddSolution(EntityUid uid, Solution solution, + bool sound = true, + bool checkForOverflow = true, + PuddleComponent? puddleComponent = null) + { + if (!Resolve(uid, ref puddleComponent)) + return false; + + if (solution.TotalVolume == 0 || + !_solutionContainerSystem.TryGetSolution(puddleComponent.Owner.Uid, puddleComponent.SolutionName, + out var puddleSolution)) + { + return false; + } + + + var result = _solutionContainerSystem + .TryAddSolution(puddleComponent.Owner.Uid, puddleSolution, solution); + if (!result) + { + return false; + } + + RaiseLocalEvent(puddleComponent.Owner.Uid, new SolutionChangedEvent()); + + if (checkForOverflow) + { + CheckOverflow(puddleComponent); + } + + if (!sound) + { + return true; + } + + SoundSystem.Play(Filter.Pvs(puddleComponent.Owner), puddleComponent.SpillSound.GetSound(), + puddleComponent.Owner); + return true; + } + + /// + /// Will overflow this entity to neighboring entities if required + /// + private void CheckOverflow(PuddleComponent puddleComponent) + { + if (puddleComponent.CurrentVolume <= puddleComponent.OverflowVolume + || puddleComponent.Overflown) + return; + + var nextPuddles = new List() { puddleComponent }; + var overflownPuddles = new List(); + + while (puddleComponent.OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0) + { + foreach (var next in nextPuddles.ToArray()) + { + nextPuddles.Remove(next); + + next.Overflown = true; + overflownPuddles.Add(next); + + var adjacentPuddles = GetAllAdjacentOverflow(next).ToArray(); + if (puddleComponent.OverflowLeft <= ReagentUnit.Epsilon * adjacentPuddles.Length) + { + break; + } + + if (adjacentPuddles.Length == 0) + { + continue; + } + + var numberOfAdjacent = ReagentUnit.New(adjacentPuddles.Length); + var overflowSplit = puddleComponent.OverflowLeft / numberOfAdjacent; + foreach (var adjacent in adjacentPuddles) + { + var adjacentPuddle = adjacent(); + var quantity = ReagentUnit.Min(overflowSplit, adjacentPuddle.OverflowVolume); + var puddleSolution = _solutionContainerSystem.EnsureSolution(puddleComponent.Owner.Uid, + puddleComponent.SolutionName); + var spillAmount = _solutionContainerSystem.SplitSolution(puddleComponent.Owner.Uid, + puddleSolution, quantity); + + TryAddSolution(adjacentPuddle.Owner.Uid, spillAmount, false, false); + nextPuddles.Add(adjacentPuddle); + } + } + } + + foreach (var puddle in overflownPuddles) + { + puddle.Overflown = false; + } + } + + /// + /// Finds or creates adjacent puddles in random directions from this one + /// + /// Enumerable of the puddles found or to be created + private IEnumerable> GetAllAdjacentOverflow(PuddleComponent puddleComponent) + { + foreach (var direction in SharedDirectionExtensions.RandomDirections()) + { + if (TryGetAdjacentOverflow(puddleComponent, direction, out var puddle)) + { + yield return puddle; + } + } + } + + /// + /// Tries to get an adjacent coordinate to overflow to, unless it is blocked by a wall on the + /// same tile or the tile is empty + /// + /// + /// The direction to get the puddle from, respective to this one + /// The puddle that was found or is to be created, or null if there + /// is a wall in the way + /// true if a puddle was found or created, false otherwise + private bool TryGetAdjacentOverflow(PuddleComponent puddleComponent, Direction direction, + [NotNullWhen(true)] out Func? puddle) + { + puddle = default; + + // We're most likely in space, do nothing. + if (!puddleComponent.Owner.Transform.GridID.IsValid()) + return false; + + var mapGrid = _mapManager.GetGrid(puddleComponent.Owner.Transform.GridID); + var coords = puddleComponent.Owner.Transform.Coordinates; + + if (!coords.Offset(direction).TryGetTileRef(out var tile)) + { + return false; + } + + // If space return early, let that spill go out into the void + if (tile.Value.Tile.IsEmpty) + { + return false; + } + + if (!puddleComponent.Owner.Transform.Anchored) + return false; + + foreach (var entity in mapGrid.GetInDir(coords, direction)) + { + if (EntityManager.TryGetComponent(entity, out IPhysBody? physics) && + (physics.CollisionLayer & (int)CollisionGroup.Impassable) != 0) + { + puddle = default; + return false; + } + + if (EntityManager.TryGetComponent(entity, out PuddleComponent? existingPuddle)) + { + if (existingPuddle.Overflown) + { + return false; + } + + puddle = () => existingPuddle; + } + } + + puddle ??= () => + puddleComponent.Owner.EntityManager.SpawnEntity(puddleComponent.Owner.Prototype?.ID, + mapGrid.DirectionToGrid(coords, direction)) + .GetComponent(); + + return true; + } + } +} diff --git a/Content.Server/Fluids/PuddleSystem.cs b/Content.Server/Fluids/PuddleSystem.cs deleted file mode 100644 index b526b7825c..0000000000 --- a/Content.Server/Fluids/PuddleSystem.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Content.Server.Fluids.Components; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Verbs; -using Content.Shared.Examine; -using Content.Shared.Slippery; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Map; - -namespace Content.Server.Fluids -{ - [UsedImplicitly] - internal sealed class PuddleSystem : EntitySystem - { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - - public override void Initialize() - { - base.Initialize(); - _mapManager.TileChanged += HandleTileChanged; - - SubscribeLocalEvent(AddSpillVerb); - SubscribeLocalEvent(HandlePuddleExamined); - } - - public override void Shutdown() - { - base.Shutdown(); - _mapManager.TileChanged -= HandleTileChanged; - } - - private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution)) - return; - - if (solution.DrainAvailable == ReagentUnit.Zero) - return; - - Verb verb = new(); - verb.Text = Loc.GetString("spill-target-verb-get-data-text"); - // TODO VERB ICONS spill icon? pouring out a glass/beaker? - verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target.Uid, - solution, solution.DrainAvailable).SpillAt(args.Target.Transform.Coordinates, "PuddleSmear"); - args.Verbs.Add(verb); - } - - private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args) - { - if (EntityManager.TryGetComponent(uid, out var slippery) && slippery.Slippery) - { - args.PushText(Loc.GetString("puddle-component-examine-is-slipper-text")); - } - } - - //TODO: Replace all this with an Unanchored event that deletes the puddle - private void HandleTileChanged(object? sender, TileChangedEventArgs eventArgs) - { - // If this gets hammered you could probably queue up all the tile changes every tick but I doubt that would ever happen. - foreach (var puddle in EntityManager.EntityQuery(true)) - { - // If the tile becomes space then delete it (potentially change by design) - var puddleTransform = puddle.Owner.Transform; - if(!puddleTransform.Anchored) - continue; - - var grid = _mapManager.GetGrid(puddleTransform.GridID); - if (eventArgs.NewTile.GridIndex == puddle.Owner.Transform.GridID && - grid.TileIndicesFor(puddleTransform.Coordinates) == eventArgs.NewTile.GridIndices && - eventArgs.NewTile.Tile.IsEmpty) - { - puddle.Owner.QueueDelete(); - break; // Currently it's one puddle per tile, if that changes remove this - } - } - } - } -} diff --git a/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs index db9a8a48da..9b87acdcae 100644 --- a/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -241,7 +241,24 @@ namespace Content.Shared.Chemistry.EntitySystems /// solution public Solution EnsureSolution(IEntity owner, string name) { - var solutionsMgr = owner.EnsureComponent(); + return EnsureSolution(owner.Uid, name); + } + + /// + /// Will ensure a solution is added to given entity even if it's missing solutionContainerManager + /// + /// EntityUid to which to add solution + /// name for the solution + /// solution components used in resolves + /// solution + public Solution EnsureSolution(EntityUid uid, string name, + SolutionContainerManagerComponent? solutionsMgr = null) + { + if (!Resolve(uid, ref solutionsMgr, false)) + { + solutionsMgr = EntityManager.EnsureComponent(uid); + } + if (!solutionsMgr.Solutions.ContainsKey(name)) { var newSolution = new Solution(); diff --git a/Content.Shared/Fluids/PuddleVisuals.cs b/Content.Shared/Fluids/PuddleVisuals.cs new file mode 100644 index 0000000000..976ef1832f --- /dev/null +++ b/Content.Shared/Fluids/PuddleVisuals.cs @@ -0,0 +1,12 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.Fluids +{ + [Serializable, NetSerializable] + public enum PuddleVisuals : byte + { + VolumeScale, + SolutionColor + } +} \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index a7a28c657e..2c34619b30 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -9,7 +9,7 @@ drawdepth: FloorObjects - type: SolutionContainerManager - type: Puddle - spill_sound: + spillSound: path: /Audio/Effects/Fluids/splat.ogg recolor: true - type: Clickable @@ -24,6 +24,10 @@ mask: - SmallImpassable hard: false + - type: Appearance + visuals: + - type: PuddleVisualizer + recolor: true - type: entity name: puddle @@ -39,9 +43,17 @@ - type: Sprite sprite: Fluids/gibblet.rsi # Placeholder state: gibblet-0 + netsync: false - type: Puddle - variants: 5 - state: gibblet + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: Water + Quantity: 10 + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: puddle @@ -52,9 +64,13 @@ - type: Sprite sprite: Fluids/smear.rsi # Placeholder state: smear-0 + netsync: false - type: Puddle - variants: 7 - state: smear + - type: Evaporation + - type: Appearance + visuals: + - type: PuddleVisualizer + recolor: true - type: entity name: puddle @@ -65,9 +81,12 @@ - type: Sprite sprite: Fluids/splatter.rsi # Placeholder state: splatter-0 + netsync: false - type: Puddle - variants: 6 - state: splatter + - type: Evaporation + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: vomit @@ -78,11 +97,19 @@ - type: Sprite sprite: Fluids/vomit.rsi state: vomit-0 + netsync: false - type: Puddle - variants: 4 - recolor: false - evaporate_threshold: -1 - state: vomit + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: Nutriment + Quantity: 5 + - ReagentId: Water + Quantity: 5 + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: toxins vomit @@ -93,10 +120,19 @@ - type: Sprite sprite: Fluids/vomit_toxin.rsi state: vomit_toxin-0 + netsync: false - type: Puddle - variants: 4 - recolor: false - state: vomit_toxin + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: Toxin + Quantity: 5 + - ReagentId: Water + Quantity: 5 + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: writing @@ -107,6 +143,10 @@ - type: Sprite sprite: Fluids/writing.rsi # Placeholder state: writing-0 + netsync: false - type: Puddle - variants: 5 - state: writing + - type: Evaporation + evaporateTime: 10 + - type: Appearance + visuals: + - type: PuddleVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index 9097331f07..72e48f4447 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -70,11 +70,20 @@ description: If the floor was a little hotter this would fry. components: - type: Sprite - sprite: Objects/Consumable/Food/egg.rsi + sprite: Fluids/egg_splat.rsi state: egg-0 + netsync: false - type: Puddle - variants: 4 - state: egg + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: Egg + Quantity: 2 + - type: Evaporation + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: eggshells diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml index 14a6020e28..a207070c35 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -9,11 +9,21 @@ description: Call the janitor. components: - type: Sprite - sprite: Objects/Consumable/Food/ingredients.rsi - state: powder-0 - color: white - - type: Puddle + sprite: Fluids/powder.rsi state: powder + color: white + netsync: false + - type: Puddle + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: Flour + Quantity: 10 + - type: Evaporation + - type: Appearance + visuals: + - type: PuddleVisualizer # Reagent Containers diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index adf6b086a7..b639355804 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -313,10 +313,21 @@ description: Splat. components: - type: Sprite - sprite: Objects/Specific/Hydroponics/tomato.rsi + sprite: Fluids/tomato_splat.rsi state: puddle-0 + netsync: false - type: Puddle - variants: 3 + - type: SolutionContainerManager + solutions: + puddle: + reagents: + - ReagentId: JuiceTomato + Quantity: 10 + - type: Evaporation + lowerLimit: 2 + - type: Appearance + visuals: + - type: PuddleVisualizer - type: entity name: eggplant diff --git a/Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-0.png b/Resources/Textures/Fluids/egg_splat.rsi/egg-0.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-0.png rename to Resources/Textures/Fluids/egg_splat.rsi/egg-0.png diff --git a/Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-1.png b/Resources/Textures/Fluids/egg_splat.rsi/egg-1.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-1.png rename to Resources/Textures/Fluids/egg_splat.rsi/egg-1.png diff --git a/Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-2.png b/Resources/Textures/Fluids/egg_splat.rsi/egg-2.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-2.png rename to Resources/Textures/Fluids/egg_splat.rsi/egg-2.png diff --git a/Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-3.png b/Resources/Textures/Fluids/egg_splat.rsi/egg-3.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Food/egg.rsi/egg-3.png rename to Resources/Textures/Fluids/egg_splat.rsi/egg-3.png diff --git a/Resources/Textures/Fluids/egg_splat.rsi/meta.json b/Resources/Textures/Fluids/egg_splat.rsi/meta.json new file mode 100644 index 0000000000..c30ab7444c --- /dev/null +++ b/Resources/Textures/Fluids/egg_splat.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/9c980cb9bc84d07b1c210c5447798af525185f80/icons/obj/food.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "egg-0" + }, + { + "name": "egg-1" + }, + { + "name": "egg-2" + }, + { + "name": "egg-3" + } + ] +} diff --git a/Resources/Textures/Fluids/powder.rsi/meta.json b/Resources/Textures/Fluids/powder.rsi/meta.json new file mode 100644 index 0000000000..18ba855b86 --- /dev/null +++ b/Resources/Textures/Fluids/powder.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation and baystation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24 and https://github.com/Baystation12/Baystation12/commit/a6067826de7fd8f698793f6d84e6c2f1f9b1f188", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "powder" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Food/ingredients.rsi/powder-0.png b/Resources/Textures/Fluids/powder.rsi/powder.png similarity index 100% rename from Resources/Textures/Objects/Consumable/Food/ingredients.rsi/powder-0.png rename to Resources/Textures/Fluids/powder.rsi/powder.png diff --git a/Resources/Textures/Fluids/tomato_splat.rsi/meta.json b/Resources/Textures/Fluids/tomato_splat.rsi/meta.json new file mode 100644 index 0000000000..ff138bb90f --- /dev/null +++ b/Resources/Textures/Fluids/tomato_splat.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1dbcf389b0ec6b2c51b002df5fef8dd1519f8068", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "puddle-0" + }, + { + "name": "puddle-1" + }, + { + "name": "puddle-2" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-0.png b/Resources/Textures/Fluids/tomato_splat.rsi/puddle-0.png similarity index 100% rename from Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-0.png rename to Resources/Textures/Fluids/tomato_splat.rsi/puddle-0.png diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-1.png b/Resources/Textures/Fluids/tomato_splat.rsi/puddle-1.png similarity index 100% rename from Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-1.png rename to Resources/Textures/Fluids/tomato_splat.rsi/puddle-1.png diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-2.png b/Resources/Textures/Fluids/tomato_splat.rsi/puddle-2.png similarity index 100% rename from Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/puddle-2.png rename to Resources/Textures/Fluids/tomato_splat.rsi/puddle-2.png diff --git a/Resources/Textures/Objects/Consumable/Food/egg.rsi/meta.json b/Resources/Textures/Objects/Consumable/Food/egg.rsi/meta.json index f1101d1b1e..0f2b681beb 100644 --- a/Resources/Textures/Objects/Consumable/Food/egg.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Food/egg.rsi/meta.json @@ -140,18 +140,6 @@ "name": "red-inhand-left", "directions": 4 }, - { - "name": "egg-0" - }, - { - "name": "egg-1" - }, - { - "name": "egg-2" - }, - { - "name": "egg-3" - }, { "name": "yellow" }, diff --git a/Resources/Textures/Objects/Consumable/Food/ingredients.rsi/meta.json b/Resources/Textures/Objects/Consumable/Food/ingredients.rsi/meta.json index d85acfcd63..cd099532ca 100644 --- a/Resources/Textures/Objects/Consumable/Food/ingredients.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Food/ingredients.rsi/meta.json @@ -64,9 +64,6 @@ { "name": "pizzabread" }, - { - "name": "powder-0" - }, { "name": "rice-big" }, diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/meta.json b/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/meta.json index cdfcdbacc5..fd70b99058 100644 --- a/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/meta.json @@ -19,15 +19,6 @@ { "name": "seed" }, - { - "name": "puddle-0" - }, - { - "name": "puddle-1" - }, - { - "name": "puddle-2" - }, { "name": "stage-1" }, @@ -47,4 +38,4 @@ "name": "stage-6" } ] -} \ No newline at end of file +}