diff --git a/Content.Client/Fluids/PuddleVisualizer.cs b/Content.Client/Fluids/PuddleVisualizer.cs deleted file mode 100644 index f1c610126f..0000000000 --- a/Content.Client/Fluids/PuddleVisualizer.cs +++ /dev/null @@ -1,91 +0,0 @@ -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 sealed class PuddleVisualizer : AppearanceVisualizer - { - [Dependency] private readonly IRobustRandom _random = default!; - - // Whether the underlying solution color should be used - [DataField("recolor")] public bool Recolor; - - // Whether the puddle has a unique sprite we don't want to overwrite - [DataField("customPuddleSprite")] public bool CustomPuddleSprite; - - [Obsolete("Subscribe to your component being initialised instead.")] - public override void InitializeEntity(EntityUid entity) - { - base.InitializeEntity(entity); - - if (!IoCManager.Resolve().TryGetComponent(entity, out SpriteComponent? spriteComponent)) - { - Logger.Warning($"Missing SpriteComponent for PuddleVisualizer on entityUid = {entity}"); - 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)); - } - - [Obsolete("Subscribe to AppearanceChangeEvent instead.")] - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - var entities = IoCManager.Resolve(); - if (component.TryGetData(PuddleVisuals.VolumeScale, out var volumeScale) && - entities.TryGetComponent(component.Owner, out var spriteComponent)) - { - component.TryGetData(PuddleVisuals.ForceWetFloorSprite, out var forceWetFloorSprite); - var cappedScale = Math.Min(1.0f, volumeScale * 0.75f +0.25f); - UpdateVisual(component, spriteComponent, cappedScale, forceWetFloorSprite); - } - } - - private void UpdateVisual(AppearanceComponent component, SpriteComponent spriteComponent, float cappedScale, bool forceWetFloorSprite) - { - Color newColor; - if (Recolor && component.TryGetData(PuddleVisuals.SolutionColor, out var solutionColor)) - { - newColor = solutionColor.WithAlpha(cappedScale); - } - else - { - newColor = spriteComponent.Color.WithAlpha(cappedScale); - } - - spriteComponent.Color = newColor; - - if (forceWetFloorSprite) - { - //Change the puddle's sprite to the wet floor sprite - spriteComponent.LayerSetState(0, "sparkles", "Fluids/wet_floor_sparkles.rsi"); - spriteComponent.Color = spriteComponent.Color.WithAlpha(0.25f); //should be mostly transparent. - } - else if(!CustomPuddleSprite) - { - spriteComponent.LayerSetState(0, "smear-0", "Fluids/smear.rsi"); // TODO: need a way to implement the random smears again when the mop creates new puddles. - } - - } - } - -} diff --git a/Content.Client/Fluids/PuddleVisualizerComponent.cs b/Content.Client/Fluids/PuddleVisualizerComponent.cs new file mode 100644 index 0000000000..6d1bb0b2d8 --- /dev/null +++ b/Content.Client/Fluids/PuddleVisualizerComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.FixedPoint; +using Robust.Client.Graphics; + +namespace Content.Client.Fluids +{ + [RegisterComponent] + public sealed class PuddleVisualizerComponent : Component + { + // Whether the underlying solution color should be used. True in most cases. + [DataField("recolor")] public bool Recolor = true; + + // Whether the puddle has a unique sprite we don't want to overwrite + [DataField("customPuddleSprite")] public bool CustomPuddleSprite; + + // Puddles may change which RSI they use for their sprites (e.g. wet floor effects). This field will store the original RSI they used. + [DataField("originalRsi")] public RSI? OriginalRsi; + + /// + /// Puddles with volume below this threshold are able to have their sprite changed to a wet floor effect, though this is not the only factor. + /// + [DataField("wetFloorEffectThreshold")] + public FixedPoint2 WetFloorEffectThreshold = FixedPoint2.New(5); + + /// + /// Alpha (opacity) of the wet floor sparkle effect. Higher alpha = more opaque/visible. + /// + [DataField("wetFloorEffectAlpha")] + public float WetFloorEffectAlpha = 0.75f; //should be somewhat transparent by default. + } +} diff --git a/Content.Client/Fluids/PuddleVisualizerSystem.cs b/Content.Client/Fluids/PuddleVisualizerSystem.cs new file mode 100644 index 0000000000..ae37a0c08b --- /dev/null +++ b/Content.Client/Fluids/PuddleVisualizerSystem.cs @@ -0,0 +1,131 @@ +using System.Linq; +using Content.Shared.Fluids; +using Content.Shared.FixedPoint; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Random; + +namespace Content.Client.Fluids +{ + [UsedImplicitly] + public sealed class PuddleVisualizerSystem : VisualizerSystem + { + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + } + + private void OnComponentInit(EntityUid uid, PuddleVisualizerComponent puddleVisuals, ComponentInit args) + { + if (!TryComp(uid, out AppearanceComponent? appearance)) + { + return; + } + + if (!TryComp(uid, out SpriteComponent? sprite)) + { + return; + } + + puddleVisuals.OriginalRsi = sprite.BaseRSI; //Back up the original RSI upon initialization + RandomizeState(sprite, puddleVisuals.OriginalRsi); + RandomizeRotation(sprite); + } + + protected override void OnAppearanceChange(EntityUid uid, PuddleVisualizerComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + { + Logger.Warning($"Missing SpriteComponent for PuddleVisualizerSystem on entityUid = {uid}"); + return; + } + + if (args.Component.TryGetData(PuddleVisuals.VolumeScale, out float volumeScale) + && args.Component.TryGetData(PuddleVisuals.CurrentVolume, out FixedPoint2 currentVolume) + && args.Component.TryGetData(PuddleVisuals.SolutionColor, out Color solutionColor) + && args.Component.TryGetData(PuddleVisuals.IsEvaporatingVisual, out bool isEvaporating)) + { + // volumeScale is our opacity based on level of fullness to overflow. The lower bound is hard-capped for visibility reasons. + var cappedScale = Math.Min(1.0f, volumeScale * 0.75f + 0.25f); + + Color newColor; + if (component.Recolor) + { + newColor = solutionColor.WithAlpha(cappedScale); + } + else + { + newColor = args.Sprite.Color.WithAlpha(cappedScale); + } + + args.Sprite.LayerSetColor(0, newColor); + + if (component.CustomPuddleSprite) //Don't consider wet floor effects if we're using a custom sprite. + { + return; + } + + bool wetFloorEffectNeeded; + + if (isEvaporating + && currentVolume < component.WetFloorEffectThreshold) + { + wetFloorEffectNeeded = true; + } + else + wetFloorEffectNeeded = false; + + if (wetFloorEffectNeeded) + { + if (args.Sprite.LayerGetState(0) != "sparkles") // If we need the effect but don't already have it - start it + { + StartWetFloorEffect(args.Sprite, component.WetFloorEffectAlpha); + } + } + else + { + if (args.Sprite.LayerGetState(0) == "sparkles") // If we have the effect but don't need it - end it + EndWetFloorEffect(args.Sprite, component.OriginalRsi); + } + } + else + { + return; + } + } + + private void StartWetFloorEffect(SpriteComponent sprite, float alpha) + { + sprite.LayerSetState(0, "sparkles", "Fluids/wet_floor_sparkles.rsi"); + sprite.Color = sprite.Color.WithAlpha(alpha); + sprite.LayerSetAutoAnimated(0, false); + sprite.LayerSetAutoAnimated(0, true); //fixes a bug where the sparkle effect would sometimes freeze on a single frame. + } + + private void EndWetFloorEffect(SpriteComponent sprite, RSI? originalRSI) + { + RandomizeState(sprite, originalRSI); + sprite.LayerSetAutoAnimated(0, false); + } + + private void RandomizeState(SpriteComponent sprite, RSI? rsi) + { + var maxStates = rsi?.ToArray(); + if (maxStates is not { Length: > 0 }) return; + + var selectedState = _random.Next(0, maxStates.Length - 1); //randomly select an index for which RSI state to use. + sprite.LayerSetState(0, maxStates[selectedState].StateId, rsi); // sets the sprite's state via our randomly selected index. + } + + private void RandomizeRotation(SpriteComponent sprite) + { + float rotationDegrees = _random.Next(0, 359); // randomly select a rotation for our puddle sprite. + sprite.Rotation = Angle.FromDegrees(rotationDegrees); // sets the sprite's rotation to the one we randomly selected. + } + } +} diff --git a/Content.Server/Entry/IgnoredComponents.cs b/Content.Server/Entry/IgnoredComponents.cs index 559bc3b8a1..28fdb0cc55 100644 --- a/Content.Server/Entry/IgnoredComponents.cs +++ b/Content.Server/Entry/IgnoredComponents.cs @@ -15,6 +15,7 @@ namespace Content.Server.Entry "ClientEntitySpawner", "HandheldGPS", "CableVisualizer", + "PuddleVisualizer", "UIFragment", "PDABorderColor", }; diff --git a/Content.Server/Fluids/Components/PuddleComponent.cs b/Content.Server/Fluids/Components/PuddleComponent.cs index bc51df6b15..ae49b6c6a8 100644 --- a/Content.Server/Fluids/Components/PuddleComponent.cs +++ b/Content.Server/Fluids/Components/PuddleComponent.cs @@ -12,7 +12,7 @@ namespace Content.Server.Fluids.Components public sealed class PuddleComponent : Component { public const string DefaultSolutionName = "puddle"; - private static readonly FixedPoint2 DefaultSlipThreshold = FixedPoint2.New(-1); + private static readonly FixedPoint2 DefaultSlipThreshold = FixedPoint2.New(-1); //Not slippery by default. Set specific slipThresholds in YAML if you want your puddles to be slippery. Lower = more slippery, and zero means any volume can slip. public static readonly FixedPoint2 DefaultOverflowVolume = FixedPoint2.New(20); // Current design: Something calls the SpillHelper.Spill, that will either @@ -33,13 +33,6 @@ namespace Content.Server.Fluids.Components [DataField("slipThreshold")] public FixedPoint2 SlipThreshold = DefaultSlipThreshold; - /// - /// Puddles with volume below this threshold will have their sprite changed to a wet floor effect, - /// provided they can evaporate down to zero. - /// - [DataField("wetFloorEffectThreshold")] - public FixedPoint2 WetFloorEffectThreshold = FixedPoint2.New(5); - [DataField("spillSound")] public SoundSpecifier SpillSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg"); diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index ae877c0166..ee2690bc93 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -12,6 +12,8 @@ using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Random; +using Robust.Shared.GameObjects; using Solution = Content.Shared.Chemistry.Components.Solution; namespace Content.Server.Fluids.EntitySystems @@ -33,26 +35,26 @@ namespace Content.Server.Fluids.EntitySystems // Shouldn't need re-anchoring. SubscribeLocalEvent(OnAnchorChanged); SubscribeLocalEvent(HandlePuddleExamined); - SubscribeLocalEvent(OnUpdate); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnSolutionUpdate); + SubscribeLocalEvent(OnPuddleInit); } - private void OnInit(EntityUid uid, PuddleComponent component, ComponentInit args) + private void OnPuddleInit(EntityUid uid, PuddleComponent component, ComponentInit args) { var solution = _solutionContainerSystem.EnsureSolution(uid, component.SolutionName); solution.MaxVolume = FixedPoint2.New(1000); } - private void OnUpdate(EntityUid uid, PuddleComponent component, SolutionChangedEvent args) + private void OnSolutionUpdate(EntityUid uid, PuddleComponent component, SolutionChangedEvent args) { UpdateSlip(uid, component); - UpdateVisuals(uid, component); + UpdateAppearance(uid, component); } - private void UpdateVisuals(EntityUid uid, PuddleComponent puddleComponent) + private void UpdateAppearance(EntityUid uid, PuddleComponent? puddleComponent = null, AppearanceComponent? appearance = null) { - if (Deleted(puddleComponent.Owner) || EmptyHolder(uid, puddleComponent) || - !EntityManager.TryGetComponent(uid, out var appearanceComponent)) + if (!Resolve(uid, ref puddleComponent, ref appearance, false) + || EmptyHolder(uid, puddleComponent)) { return; } @@ -64,22 +66,19 @@ namespace Content.Server.Fluids.EntitySystems puddleComponent.OpacityModifier; var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName); + bool isEvaporating; - bool hasEvaporationComponent = - EntityManager.TryGetComponent(uid, out var evaporationComponent); - bool canEvaporate = (hasEvaporationComponent && - (evaporationComponent!.LowerLimit == 0 || - CurrentVolume(puddleComponent.Owner, puddleComponent) > - evaporationComponent.LowerLimit)); + if (TryComp(uid, out EvaporationComponent? evaporation) + && evaporation.EvaporationToggle)// if puddle is evaporating. + { + isEvaporating = true; + } + else isEvaporating = false; - // "Does this puddle's sprite need changing to the wet floor effect sprite?" - bool changeToWetFloor = (CurrentVolume(puddleComponent.Owner, puddleComponent) <= - puddleComponent.WetFloorEffectThreshold - && canEvaporate); - - appearanceComponent.SetData(PuddleVisuals.VolumeScale, volumeScale); - appearanceComponent.SetData(PuddleVisuals.SolutionColor, puddleSolution.Color); - appearanceComponent.SetData(PuddleVisuals.ForceWetFloorSprite, changeToWetFloor); + appearance.SetData(PuddleVisuals.VolumeScale, volumeScale); + appearance.SetData(PuddleVisuals.CurrentVolume, puddleComponent.CurrentVolume); + appearance.SetData(PuddleVisuals.SolutionColor, puddleSolution.Color); + appearance.SetData(PuddleVisuals.IsEvaporatingVisual, isEvaporating); } private void UpdateSlip(EntityUid entityUid, PuddleComponent puddleComponent) diff --git a/Content.Shared/Fluids/PuddleVisuals.cs b/Content.Shared/Fluids/PuddleVisuals.cs index 0fab6ab88f..89c0eb9238 100644 --- a/Content.Shared/Fluids/PuddleVisuals.cs +++ b/Content.Shared/Fluids/PuddleVisuals.cs @@ -6,7 +6,8 @@ namespace Content.Shared.Fluids public enum PuddleVisuals : byte { VolumeScale, + CurrentVolume, SolutionColor, - ForceWetFloorSprite + IsEvaporatingVisual } } diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index d790eeea42..0e1c59c285 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -28,14 +28,7 @@ - SlipLayer hard: false - type: Appearance - visuals: - - type: PuddleVisualizer - recolor: true - -- type: entity - name: puddle - id: PuddleGeneric - parent: PuddleSmear + - type: PuddleVisualizer - type: entity name: gibblets @@ -48,6 +41,7 @@ state: gibblet-0 netsync: false - type: Puddle + wetFloorEffectThreshold: 0 # No wet floor sparkles - type: SolutionContainerManager solutions: puddle: @@ -55,8 +49,7 @@ - ReagentId: Water Quantity: 10 - type: Appearance - visuals: - - type: PuddleVisualizer + - type: PuddleVisualizer - type: Slippery launchForwardsMultiplier: 2.0 - type: StepTrigger @@ -72,11 +65,9 @@ state: smear-0 netsync: false - type: Puddle - slipThreshold: 5 + slipThreshold: 3 - type: Appearance - visuals: - - type: PuddleVisualizer - recolor: true + - type: PuddleVisualizer - type: Slippery launchForwardsMultiplier: 2.0 - type: StepTrigger @@ -92,10 +83,9 @@ state: splatter-0 netsync: false - type: Puddle - slipThreshold: 5 + slipThreshold: 3 - type: Appearance - visuals: - - type: PuddleVisualizer + - type: PuddleVisualizer - type: Slippery launchForwardsMultiplier: 2.0 - type: StepTrigger @@ -113,16 +103,15 @@ - type: Puddle overflowVolume: 50 opacityModifier: 8 + wetFloorEffectThreshold: 0 # No wet floor sparkles - type: Evaporation evaporateTime: 400 # very slow - type: Appearance - visuals: - - type: PuddleVisualizer - recolor: true + - type: PuddleVisualizer - type: entity name: vomit - id: PuddleVomit # No parent because we don't want the visualizer + id: PuddleVomit # No parent because we don't want the VisualizerSystem to behave in the standard way description: Gross. components: - type: Transform @@ -146,6 +135,7 @@ netsync: false - type: Puddle slipThreshold: 5 + recolor: false - type: SolutionContainerManager solutions: puddle: @@ -158,9 +148,8 @@ launchForwardsMultiplier: 2.0 - type: StepTrigger - type: Appearance - visuals: - - type: PuddleVisualizer - customPuddleSprite: true + - type: PuddleVisualizer + customPuddleSprite: true - type: entity name: toxins vomit @@ -182,8 +171,8 @@ - ReagentId: Water Quantity: 5 - type: Appearance - visuals: - - type: PuddleVisualizer + - type: PuddleVisualizer + customPuddleSprite: true - type: Slippery launchForwardsMultiplier: 2.0 - type: StepTrigger @@ -201,8 +190,7 @@ - type: Puddle evaporateTime: 10 - type: Appearance - visuals: - - type: PuddleVisualizer + - type: PuddleVisualizer - type: Slippery launchForwardsMultiplier: 2.0 - type: StepTrigger diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index 844a6c81f2..08abc76662 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -79,6 +79,7 @@ state: egg-0 netsync: false - type: Puddle + wetFloorEffectThreshold: 0 # No wet floor sparkles - type: SolutionContainerManager solutions: puddle: @@ -87,8 +88,7 @@ Quantity: 2 - type: Evaporation - type: Appearance - visuals: - - type: PuddleVisualizer + - 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 c8efde12c4..dc8ffdab9e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -22,8 +22,7 @@ Quantity: 10 - type: Evaporation - type: Appearance - visuals: - - type: PuddleVisualizer + - 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 e16e356e72..ff132217e6 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -408,6 +408,7 @@ state: puddle-0 netsync: false - type: Puddle + wetFloorEffectThreshold: 0 # No wet floor sparkles - type: SolutionContainerManager solutions: puddle: @@ -417,8 +418,7 @@ - type: Evaporation lowerLimit: 0 # todo: reimplement stain behaviour, ideally in a way that doesn't use evaporation lowerLimit - type: Appearance - visuals: - - type: PuddleVisualizer + - type: PuddleVisualizer - type: entity name: eggplant diff --git a/Resources/Textures/Fluids/wet_floor_sparkles.rsi/meta.json b/Resources/Textures/Fluids/wet_floor_sparkles.rsi/meta.json index 60daf3ca47..9d7419af42 100644 --- a/Resources/Textures/Fluids/wet_floor_sparkles.rsi/meta.json +++ b/Resources/Textures/Fluids/wet_floor_sparkles.rsi/meta.json @@ -17,7 +17,7 @@ 0.14, 0.14, 0.14, - 5 + 3 ] ] }