diff --git a/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs deleted file mode 100644 index b25f36b0a3..0000000000 --- a/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Content.Shared.GameObjects.Components; -using JetBrains.Annotations; -using Robust.Client.Animations; -using Robust.Client.GameObjects; -using Robust.Client.GameObjects.Components.Animations; -using Robust.Client.Interfaces.GameObjects.Components; -using Robust.Shared.Animations; -using Robust.Shared.Maths; - -namespace Content.Client.GameObjects.Components.Atmos -{ - [UsedImplicitly] - public class ExtinguisherVisualizer : AppearanceVisualizer - { - - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - if (component.Deleted) - { - return; - } - - if (component.TryGetData(ExtinguisherVisuals.Rotation, out var degrees)) - { - SetRotation(component, Angle.FromDegrees(degrees)); - } - } - - private void SetRotation(AppearanceComponent component, Angle rotation) - { - var sprite = component.Owner.GetComponent(); - - if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation)) - { - sprite.Rotation = rotation; - return; - } - - if (animation.HasRunningAnimation("rotate")) - { - animation.Stop("rotate"); - } - - animation.Play(new Animation - { - Length = TimeSpan.FromSeconds(0.125), - AnimationTracks = - { - new AnimationTrackComponentProperty - { - ComponentType = typeof(ISpriteComponent), - Property = nameof(ISpriteComponent.Rotation), - InterpolationMode = AnimationInterpolationMode.Linear, - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0), - new AnimationTrackProperty.KeyFrame(rotation, 0.125f) - } - } - } - }, "rotate"); - } - } -} diff --git a/Content.Client/GameObjects/Components/Atmos/PumpVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/PumpVisualizer.cs index 233d3cbe90..26e86403e5 100644 --- a/Content.Client/GameObjects/Components/Atmos/PumpVisualizer.cs +++ b/Content.Client/GameObjects/Components/Atmos/PumpVisualizer.cs @@ -1,5 +1,5 @@ using System; -using Content.Shared.GameObjects.Atmos; +using Content.Shared.GameObjects.Components.Atmos; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; diff --git a/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs index 07a8c04578..bdee4169dc 100644 --- a/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs +++ b/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs @@ -1,5 +1,4 @@ -using Content.Shared.GameObjects.Atmos; -using JetBrains.Annotations; +using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Interfaces.GameObjects.Components; @@ -10,6 +9,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; using System; +using Content.Shared.GameObjects.Components.Atmos; using YamlDotNet.RepresentationModel; namespace Content.Client.GameObjects.Components.Atmos diff --git a/Content.Client/GameObjects/Components/Atmos/VaporVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/VaporVisualizer.cs new file mode 100644 index 0000000000..0a6872b9ea --- /dev/null +++ b/Content.Client/GameObjects/Components/Atmos/VaporVisualizer.cs @@ -0,0 +1,101 @@ +using System; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Animations; +using Robust.Shared.Maths; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components.Atmos +{ + [UsedImplicitly] + public class VaporVisualizer : AppearanceVisualizer + { + private const string AnimationKey = "flick_animation"; + private Animation VaporFlick; + + public override void LoadData(YamlMappingNode node) + { + base.LoadData(node); + + var delay = 0.25f; + var state = "chempuff"; + + if (node.TryGetNode("animation_time", out var delayNode)) + { + delay = delayNode.AsFloat(); + } + + if (node.TryGetNode("animation_state", out var stateNode)) + { + state = stateNode.AsString(); + } + + VaporFlick = new Animation {Length = TimeSpan.FromSeconds(delay)}; + { + var flick = new AnimationTrackSpriteFlick(); + VaporFlick.AnimationTracks.Add(flick); + flick.LayerKey = VaporVisualLayers.Base; + flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame(state, 0f)); + } + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.Deleted) + { + return; + } + + if (component.TryGetData(VaporVisuals.Rotation, out var radians)) + { + SetRotation(component, new Angle(radians)); + } + + if (component.TryGetData(VaporVisuals.Color, out var color)) + { + SetColor(component, color); + } + + if (component.TryGetData(VaporVisuals.State, out var state)) + { + SetState(component, state); + } + } + + private void SetState(AppearanceComponent component, bool state) + { + if (!state) return; + + var animPlayer = component.Owner.GetComponent(); + + if(!animPlayer.HasRunningAnimation(AnimationKey)) + animPlayer.Play(VaporFlick, AnimationKey); + } + + private void SetRotation(AppearanceComponent component, Angle rotation) + { + var sprite = component.Owner.GetComponent(); + + sprite.Rotation = rotation; + } + + private void SetColor(AppearanceComponent component, Color color) + { + var sprite = component.Owner.GetComponent(); + + sprite.Color = color; + } + } + + public enum VaporVisualLayers + { + Base + } +} diff --git a/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs index a30b2b9f9d..04c9bd2681 100644 --- a/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs +++ b/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs @@ -1,5 +1,4 @@ -using Content.Shared.GameObjects.Atmos; -using JetBrains.Annotations; +using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Interfaces.GameObjects.Components; @@ -10,6 +9,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; using System; +using Content.Shared.GameObjects.Components.Atmos; using YamlDotNet.RepresentationModel; namespace Content.Client.GameObjects.Components.Atmos diff --git a/Content.Client/GameObjects/Components/Fluids/SprayVisualizer.cs b/Content.Client/GameObjects/Components/Fluids/SprayVisualizer.cs new file mode 100644 index 0000000000..d8d23acd50 --- /dev/null +++ b/Content.Client/GameObjects/Components/Fluids/SprayVisualizer.cs @@ -0,0 +1,51 @@ +using Content.Shared.GameObjects.Components.Fluids; +using Robust.Client.GameObjects; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components.Fluids +{ + public class SprayVisualizer : AppearanceVisualizer + { + private string _safetyOnState; + private string _safetyOffState; + + public override void LoadData(YamlMappingNode node) + { + base.LoadData(node); + + if (node.TryGetNode("safety_on_state", out var safetyOn)) + { + _safetyOnState = safetyOn.AsString(); + } + + if (node.TryGetNode("safety_off_state", out var safetyOff)) + { + _safetyOffState = safetyOff.AsString(); + } + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.TryGetData(SprayVisuals.Safety, out var safety)) + { + SetSafety(component, safety); + } + } + + private void SetSafety(AppearanceComponent component, bool safety) + { + var sprite = component.Owner.GetComponent(); + + sprite.LayerSetState(SprayVisualLayers.Base, safety ? _safetyOnState : _safetyOffState); + } + } + + public enum SprayVisualLayers + { + Base + } +} diff --git a/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs b/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs index 071aa459f9..596d06c7de 100644 --- a/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs +++ b/Content.Client/GameObjects/Components/Movement/SlipperyComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.GameObjects.Components.Movement; +#nullable enable +using Content.Shared.GameObjects.Components.Movement; using Robust.Shared.GameObjects; namespace Content.Client.GameObjects.Components.Movement @@ -7,5 +8,15 @@ namespace Content.Client.GameObjects.Components.Movement [ComponentReference(typeof(SharedSlipperyComponent))] public class SlipperyComponent : SharedSlipperyComponent { + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (!(curState is SlipperyComponentState state)) return; + + Slippery = state.Slippery; + IntersectPercentage = state.IntersectPercentage; + ParalyzeTime = state.ParalyzeTime; + RequiredSlipSpeed = state.RequiredSlipSpeed; + LaunchForwardsMultiplier = state.LaunchForwardsMultiplier; + } } } diff --git a/Content.Server/Atmos/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs deleted file mode 100644 index 0f5897c6ee..0000000000 --- a/Content.Server/Atmos/GasSprayerComponent.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Content.Server.GameObjects.Components.Chemistry; -using Content.Shared.Chemistry; -using Content.Shared.GameObjects.Components; -using Content.Shared.Interfaces; -using Content.Shared.Interfaces.GameObjects.Components; -using Robust.Server.GameObjects; -using Robust.Server.GameObjects.EntitySystems; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; -using Robust.Shared.Serialization; - -namespace Content.Server.Atmos -{ - [RegisterComponent] - public class GasSprayerComponent : Component, IAfterInteract - { - [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; - - //TODO: create a function that can create a gas based on a solution mix - public override string Name => "GasSprayer"; - - private string _spraySound; - private string _sprayType; - private string _fuelType; - private string _fuelName; - private int _fuelCost; - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(ref _spraySound, "spraySound", string.Empty); - serializer.DataField(ref _sprayType, "sprayType", string.Empty); - serializer.DataField(ref _fuelType, "fuelType", string.Empty); - serializer.DataField(ref _fuelName, "fuelName", "fuel"); - serializer.DataField(ref _fuelCost, "fuelCost", 50); - } - - public void AfterInteract(AfterInteractEventArgs eventArgs) - { - if (!Owner.TryGetComponent(out SolutionContainerComponent tank)) - return; - - if (tank.Solution.GetReagentQuantity(_fuelType) == 0) - { - Owner.PopupMessage(eventArgs.User, - Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName)); - } - else - { - tank.TryRemoveReagent(_fuelType, ReagentUnit.New(_fuelCost)); - - var playerPos = eventArgs.User.Transform.Coordinates; - var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; - playerPos.Offset(direction/2); - - var spray = _serverEntityManager.SpawnEntity(_sprayType, playerPos); - spray.GetComponent() - .SetData(ExtinguisherVisuals.Rotation, direction.ToAngle().Degrees); - spray.GetComponent().StartMove(direction, 5); - - EntitySystem.Get().PlayFromEntity(_spraySound, Owner); - } - } - } -} diff --git a/Content.Server/Atmos/GasVaporComponent.cs b/Content.Server/Atmos/GasVaporComponent.cs deleted file mode 100644 index bb57f694ac..0000000000 --- a/Content.Server/Atmos/GasVaporComponent.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Content.Server.Atmos.Reactions; -using Content.Server.Interfaces; -using Content.Shared.Atmos; -using Content.Shared.Physics; -using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Components; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Atmos -{ - [RegisterComponent] - class GasVaporComponent : Component, ICollideBehavior, IGasMixtureHolder - { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - - public override string Name => "GasVapor"; - - [ViewVariables] public GasMixture Air { get; set; } - - private bool _running; - private Vector2 _direction; - private float _velocity; - private float _disspateTimer = 0; - private float _dissipationInterval; - private Gas _gas; - private float _gasVolume; - private float _gasTemperature; - private float _gasAmount; - - public override void Initialize() - { - base.Initialize(); - Air = new GasMixture(_gasVolume){Temperature = _gasTemperature}; - Air.SetMoles(_gas,_gasAmount); - } - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(ref _dissipationInterval, "dissipationInterval", 1); - serializer.DataField(ref _gas, "gas", Gas.WaterVapor); - serializer.DataField(ref _gasVolume, "gasVolume", 200); - serializer.DataField(ref _gasTemperature, "gasTemperature", Atmospherics.T20C); - serializer.DataField(ref _gasAmount, "gasAmount", 20); - } - - public void StartMove(Vector2 dir, float velocity) - { - _running = true; - _direction = dir; - _velocity = velocity; - - if (Owner.TryGetComponent(out ICollidableComponent collidable)) - { - var controller = collidable.EnsureController(); - controller.Move(_direction, _velocity); - } - } - - public void Update(float frameTime) - { - if (!_running) - return; - - if (Owner.TryGetComponent(out ICollidableComponent collidable)) - { - var worldBounds = collidable.WorldAABB; - var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); - - var tiles = mapGrid.GetTilesIntersecting(worldBounds); - - foreach (var tile in tiles) - { - var pos = tile.GridIndices.ToEntityCoordinates(_mapManager, tile.GridIndex); - var atmos = pos.GetTileAtmosphere(_entityManager); - - if (atmos?.Air == null) - { - return; - } - - if (atmos.Air.React(this) != ReactionResult.NoReaction) - { - Owner.Delete(); - } - } - } - - _disspateTimer += frameTime; - if (_disspateTimer > _dissipationInterval) - { - Air.SetMoles(_gas, Air.TotalMoles/2 ); - } - - if (Air.TotalMoles < 1) - { - Owner.Delete(); - } - } - - void ICollideBehavior.CollideWith(IEntity collidedWith) - { - // Check for collision with a impassable object (e.g. wall) and stop - if (collidedWith.TryGetComponent(out ICollidableComponent collidable) && - (collidable.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && - collidable.Hard && - Owner.TryGetComponent(out ICollidableComponent coll)) - { - var controller = coll.EnsureController(); - controller.Stop(); - Owner.Delete(); - } - } - } -} diff --git a/Content.Server/Chemistry/TileReactions/ExtinguishTileReaction.cs b/Content.Server/Chemistry/TileReactions/ExtinguishTileReaction.cs new file mode 100644 index 0000000000..01c13b5deb --- /dev/null +++ b/Content.Server/Chemistry/TileReactions/ExtinguishTileReaction.cs @@ -0,0 +1,37 @@ +using System; +using Content.Server.Atmos; +using Content.Shared.Atmos; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry.TileReactions +{ + [UsedImplicitly] + public class ExtinguishTileReaction : ITileReaction + { + private float _coolingTemperature = 2f; + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _coolingTemperature, "coolingTemperature", 2f); + } + + public ReagentUnit TileReact(TileRef tile, ReagentPrototype reagent, ReagentUnit reactVolume) + { + if (reactVolume <= ReagentUnit.Zero || tile.Tile.IsEmpty) return ReagentUnit.Zero; + var tileAtmos = tile.GridIndices.GetTileAtmosphere(tile.GridIndex); + if (tileAtmos == null || !tileAtmos.Hotspot.Valid) return ReagentUnit.Zero; + tileAtmos.Air.Temperature = + MathF.Max(MathF.Min(tileAtmos.Air.Temperature - (_coolingTemperature * 1000f), + tileAtmos.Air.Temperature / _coolingTemperature), + Atmospherics.TCMB); + tileAtmos.Air.React(tileAtmos); + tileAtmos.Hotspot = new Hotspot(); + tileAtmos.UpdateVisuals(); + return ReagentUnit.Zero; + } + } +} diff --git a/Content.Server/Chemistry/TileReactions/FlammableTileReaction.cs b/Content.Server/Chemistry/TileReactions/FlammableTileReaction.cs new file mode 100644 index 0000000000..2318247869 --- /dev/null +++ b/Content.Server/Chemistry/TileReactions/FlammableTileReaction.cs @@ -0,0 +1,32 @@ +using System; +using Content.Server.Atmos; +using Content.Shared.Atmos; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry.TileReactions +{ + [UsedImplicitly] + public class FlammableTileReaction : ITileReaction + { + private float _temperatureMultiplier = 1.25f; + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _temperatureMultiplier, "temperatureMultiplier", 1.15f); + } + + public ReagentUnit TileReact(TileRef tile, ReagentPrototype reagent, ReagentUnit reactVolume) + { + if (reactVolume <= ReagentUnit.Zero || tile.Tile.IsEmpty) return ReagentUnit.Zero; + var tileAtmos = tile.GridIndices.GetTileAtmosphere(tile.GridIndex); + if (tileAtmos == null || !tileAtmos.Hotspot.Valid) return ReagentUnit.Zero; + tileAtmos.Air.Temperature *= MathF.Max(_temperatureMultiplier * reactVolume.Float(), 1f); + tileAtmos.Air.React(tileAtmos); + return reactVolume; + } + } +} diff --git a/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs b/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs new file mode 100644 index 0000000000..20debe8e85 --- /dev/null +++ b/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects.Components.Fluids; +using Content.Server.GameObjects.Components.Movement; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry.TileReactions +{ + [UsedImplicitly] + public class SpillIfPuddlePresentTileReaction : ITileReaction + { + public void ExposeData(ObjectSerializer serializer) + { + } + + public ReagentUnit TileReact(TileRef tile, ReagentPrototype reagent, ReagentUnit reactVolume) + { + if (reactVolume < 5 || !tile.TryGetPuddle(null, out _)) return ReagentUnit.Zero; + + return tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", true, false) != null ? reactVolume : ReagentUnit.Zero; + } + } +} diff --git a/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs b/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs new file mode 100644 index 0000000000..4b70de62f1 --- /dev/null +++ b/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs @@ -0,0 +1,49 @@ +using Content.Server.GameObjects.Components.Fluids; +using Content.Server.GameObjects.Components.Movement; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.Chemistry; +using JetBrains.Annotations; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Server.Chemistry.TileReactions +{ + [UsedImplicitly] + public class SpillTileReaction : ITileReaction + { + private float _launchForwardsMultiplier = 1f; + private float _requiredSlipSpeed = 6f; + private float _paralyzeTime = 1f; + private bool _overflow; + + public void ExposeData(ObjectSerializer serializer) + { + // If you want to modify more puddle/slippery values, add them here. + serializer.DataField(ref _paralyzeTime, "paralyzeTime", 1f); + serializer.DataField(ref _launchForwardsMultiplier, "launchForwardsMultiplier", 1f); + serializer.DataField(ref _requiredSlipSpeed, "requiredSlipSpeed", 6f); + serializer.DataField(ref _overflow, "overflow", false); + } + + public ReagentUnit TileReact(TileRef tile, ReagentPrototype reagent, ReagentUnit reactVolume) + { + if (reactVolume < 5) return ReagentUnit.Zero; + + // TODO Make this not puddle smear. + var puddle = tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", _overflow, false); + + if (puddle != null) + { + var slippery = puddle.Owner.GetComponent(); + slippery.LaunchForwardsMultiplier = _launchForwardsMultiplier; + slippery.RequiredSlipSpeed = _requiredSlipSpeed; + slippery.ParalyzeTime = _paralyzeTime; + + return reactVolume; + } + + return ReagentUnit.Zero; + } + } +} diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs index 6626fe3bb8..37e08ad5e0 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Pumps/BasePumpComponent.cs @@ -2,7 +2,6 @@ using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; -using Content.Shared.GameObjects.Atmos; using Content.Shared.GameObjects.Components.Atmos; using Robust.Server.GameObjects; using Robust.Shared.Log; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs index 210c7d12ed..54a7674208 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs @@ -3,7 +3,7 @@ using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.EntitySystems; -using Content.Shared.GameObjects.Atmos; +using Content.Shared.GameObjects.Components.Atmos; using Robust.Server.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs index ee5317b787..6a47760c45 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs @@ -3,7 +3,7 @@ using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.EntitySystems; -using Content.Shared.GameObjects.Atmos; +using Content.Shared.GameObjects.Components.Atmos; using Robust.Server.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs index 68fadf41ad..a9828dade1 100644 --- a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs @@ -1,31 +1,43 @@ using System.Linq; using Content.Server.GameObjects.Components.Fluids; using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components; +using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Physics; +using Microsoft.DiaSymReader; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Chemistry { [RegisterComponent] - class VaporComponent : Component, ICollideBehavior + class VaporComponent : SharedVaporComponent, ICollideBehavior { + public const float ReactTime = 0.125f; + [Dependency] private readonly IMapManager _mapManager = default!; - public override string Name => "Vapor"; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [ViewVariables] private ReagentUnit _transferAmount; + private bool _reached; + private float _reactTimer; + private float _timer; + private EntityCoordinates _target; private bool _running; private Vector2 _direction; private float _velocity; + private float _aliveTime; public override void Initialize() { @@ -38,11 +50,13 @@ namespace Content.Server.GameObjects.Components.Chemistry } } - public void Start(Vector2 dir, float velocity) + public void Start(Vector2 dir, float velocity, EntityCoordinates target, float aliveTime) { _running = true; + _target = target; _direction = dir; _velocity = velocity; + _aliveTime = aliveTime; // Set Move if (Owner.TryGetComponent(out ICollidableComponent collidable)) { @@ -57,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Chemistry serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(0.5)); } - public void Update() + public void Update(float frameTime) { if (!Owner.TryGetComponent(out SolutionContainerComponent contents)) return; @@ -65,22 +79,36 @@ namespace Content.Server.GameObjects.Components.Chemistry if (!_running) return; - // Get all intersecting tiles with the vapor and spray the divided solution on there - if (Owner.TryGetComponent(out ICollidableComponent collidable)) + _timer += frameTime; + _reactTimer += frameTime; + + if (_reactTimer >= ReactTime && Owner.TryGetComponent(out ICollidableComponent collidable)) { - var worldBounds = collidable.WorldAABB; + _reactTimer = 0; var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); - var tiles = mapGrid.GetTilesIntersecting(worldBounds); - var amount = _transferAmount / ReagentUnit.New(tiles.Count()); - foreach (var tile in tiles) + var tile = mapGrid.GetTileRef(Owner.Transform.Coordinates.ToMapIndices(Owner.EntityManager, _mapManager)); + foreach (var reagentQuantity in contents.ReagentList.ToArray()) { - var pos = tile.GridIndices.ToEntityCoordinates(_mapManager, tile.GridIndex); - contents.SplitSolution(amount).SpillAt(pos, "PuddleSmear", false); // TODO: Make non PuddleSmear? + if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; + var reagent = _prototypeManager.Index(reagentQuantity.ReagentId); + contents.TryRemoveReagent(reagentQuantity.ReagentId, reagent.ReactionTile(tile, (reagentQuantity.Quantity / _transferAmount) * 0.25f)); } } - if (contents.CurrentVolume == 0) + // Check if we've reached our target. + if(!_reached && _target.TryDistance(Owner.EntityManager, Owner.Transform.Coordinates, out var distance) && distance <= 0.5f) + { + _reached = true; + + if (Owner.TryGetComponent(out ICollidableComponent coll)) + { + var controller = coll.EnsureController(); + controller.Stop(); + } + } + + if (contents.CurrentVolume == 0 || _timer > _aliveTime) { // Delete this Owner.Delete(); @@ -111,6 +139,16 @@ namespace Content.Server.GameObjects.Components.Chemistry void ICollideBehavior.CollideWith(IEntity collidedWith) { + if (!Owner.TryGetComponent(out SolutionContainerComponent contents)) + return; + + foreach (var reagentQuantity in contents.ReagentList.ToArray()) + { + if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; + var reagent = _prototypeManager.Index(reagentQuantity.ReagentId); + contents.TryRemoveReagent(reagentQuantity.ReagentId, reagent.ReactionEntity(collidedWith, ReactionMethod.Touch, reagentQuantity.Quantity * 0.125f)); + } + // Check for collision with a impassable object (e.g. wall) and stop if (collidedWith.TryGetComponent(out ICollidableComponent collidable)) { @@ -121,6 +159,8 @@ namespace Content.Server.GameObjects.Components.Chemistry var controller = coll.EnsureController(); controller.Stop(); } + + Owner.Delete(); } } } diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index 4ec98b6b43..a2ed029f44 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -164,6 +164,16 @@ namespace Content.Server.GameObjects.Components.Fluids } } + /// + /// 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) { diff --git a/Content.Server/GameObjects/Components/Fluids/SpillExtensions.cs b/Content.Server/GameObjects/Components/Fluids/SpillExtensions.cs index e8b8f9c24f..b3645d821a 100644 --- a/Content.Server/GameObjects/Components/Fluids/SpillExtensions.cs +++ b/Content.Server/GameObjects/Components/Fluids/SpillExtensions.cs @@ -1,6 +1,9 @@ #nullable enable using System.Diagnostics.CodeAnalysis; +using Content.Server.Utility; using Content.Shared.Chemistry; +using Content.Shared.GameObjects; +using Robust.Server.GameObjects.EntitySystems.TileLookup; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; @@ -125,5 +128,78 @@ namespace Content.Server.GameObjects.Components.Fluids puddle = solution.SpillAt(coordinates, prototype, sound); return puddle != null; } + + public static bool TryGetPuddle(this TileRef tileRef, GridTileLookupSystem? gridTileLookupSystem, [NotNullWhen(true)] out PuddleComponent? puddle) + { + foreach (var entity in tileRef.GetEntitiesInTileFast(gridTileLookupSystem)) + { + if (entity.TryGetComponent(out PuddleComponent? p)) + { + puddle = p; + return true; + } + } + + puddle = null; + return false; + } + + public static PuddleComponent? SpillAt(this TileRef tileRef, Solution solution, string prototype, bool overflow = true, bool sound = true) + { + if (solution.TotalVolume <= 0) + { + return null; + } + + var mapManager = IoCManager.Resolve(); + var entityManager = IoCManager.Resolve(); + var serverEntityManager = IoCManager.Resolve(); + + var gridId = tileRef.GridIndex; + + // If space return early, let that spill go out into the void + if (tileRef.Tile.IsEmpty) + { + return null; + } + + PuddleComponent? puddle = null; + + // Get normalized co-ordinate for spill location and spill it in the centre + // TODO: Does SnapGrid or something else already do this? + var spillTileMapGrid = mapManager.GetGrid(gridId); + var spillGridCoords = spillTileMapGrid.GridTileToLocal(tileRef.GridIndices); + + var spilt = false; + + foreach (var spillEntity in entityManager.GetEntitiesAt(spillTileMapGrid.ParentMapId, spillGridCoords.Position)) + { + if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent)) + continue; + + if (!overflow && puddleComponent.WouldOverflow(solution)) + return null; + + if (!puddleComponent.TryAddSolution(solution, sound)) + continue; + + puddle = puddleComponent; + spilt = true; + break; + } + + // Did we add to an existing puddle + if (spilt) + { + return puddle; + } + + var puddleEnt = serverEntityManager.SpawnEntity(prototype, spillGridCoords); + puddle = puddleEnt.GetComponent(); + + puddle.TryAddSolution(solution, sound); + + return puddle; + } } } diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs index 234e6c4848..644149c31e 100644 --- a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -1,29 +1,51 @@ -using Content.Server.GameObjects.Components.Chemistry; +using System; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Shared.Audio; using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components; +using Content.Shared.GameObjects.Components.Fluids; +using Content.Shared.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Fluids { [RegisterComponent] - class SprayComponent : Component, IAfterInteract + class SprayComponent : SharedSprayComponent, IAfterInteract, IUse, IActivate { - [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; + public const float SprayDistance = 3f; - public override string Name => "Spray"; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; private ReagentUnit _transferAmount; private string _spraySound; private float _sprayVelocity; + private float _sprayAliveTime; + private TimeSpan _lastUseTime; + private TimeSpan _cooldownEnd; + private float _cooldownTime; + private string _vaporPrototype; + private int _vaporAmount; + private float _vaporSpread; + private bool _hasSafety; + private bool _safety; /// /// The amount of solution to be sprayer from this solution when using it @@ -56,24 +78,50 @@ namespace Content.Server.GameObjects.Components.Fluids Logger.Warning( $"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}"); } + + if (_hasSafety) + { + SetSafety(Owner, _safety); + } } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); + serializer.DataField(ref _vaporPrototype, "sprayedPrototype", "Vapor"); + serializer.DataField(ref _vaporAmount, "vaporAmount", 1); + serializer.DataField(ref _vaporSpread, "vaporSpread", 90f); + serializer.DataField(ref _cooldownTime, "cooldownTime", 0.5f); serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(10)); - serializer.DataField(ref _sprayVelocity, "sprayVelocity", 5.0f); + serializer.DataField(ref _sprayVelocity, "sprayVelocity", 1.5f); serializer.DataField(ref _spraySound, "spraySound", string.Empty); + serializer.DataField(ref _sprayAliveTime, "sprayAliveTime", 0.75f); + serializer.DataField(ref _hasSafety, "hasSafety", false); + serializer.DataField(ref _safety, "safety", true); } void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { + if (!ActionBlockerSystem.CanInteract(eventArgs.User)) + return; + + if (_hasSafety && _safety) + { + Owner.PopupMessage(eventArgs.User, Loc.GetString("Its safety is on!")); + return; + } + if (CurrentVolume <= 0) { Owner.PopupMessage(eventArgs.User, Loc.GetString("It's empty!")); return; } + var curTime = _gameTiming.CurTime; + + if(curTime < _cooldownEnd) + return; + var playerPos = eventArgs.User.Transform.Coordinates; if (eventArgs.ClickLocation.GetGridId(_serverEntityManager) != playerPos.GetGridId(_serverEntityManager)) return; @@ -82,18 +130,86 @@ namespace Content.Server.GameObjects.Components.Fluids return; var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; - var solution = contents.SplitSolution(_transferAmount); + var threeQuarters = direction * 0.75f; + var quarter = direction * 0.25f; - playerPos = playerPos.Offset(direction); // Move a bit so we don't hit the player - //TODO: check for wall? - var vapor = _serverEntityManager.SpawnEntity("Vapor", playerPos); - // Add the solution to the vapor and actually send the thing - var vaporComponent = vapor.GetComponent(); - vaporComponent.TryAddSolution(solution); - vaporComponent.Start(direction, _sprayVelocity); //TODO: maybe make the velocity depending on the distance to the click + var amount = Math.Max(Math.Min((contents.CurrentVolume / _transferAmount).Int(), _vaporAmount), 1); + + var spread = _vaporSpread / amount; + + for (var i = 0; i < amount; i++) + { + var rotation = new Angle(direction.ToAngle() + Angle.FromDegrees(spread * i) - Angle.FromDegrees(spread * (amount-1)/2)); + + var (_, diffPos) = eventArgs.ClickLocation - playerPos; + var diffNorm = diffPos.Normalized; + var diffLength = diffPos.Length; + + var target = eventArgs.User.Transform.Coordinates.Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter); + + if (target.TryDistance(Owner.EntityManager, playerPos, out var distance) && distance > SprayDistance) + target = eventArgs.User.Transform.Coordinates.Offset(diffNorm * SprayDistance); + + var solution = contents.SplitSolution(_transferAmount); + + if (solution.TotalVolume <= ReagentUnit.Zero) + break; + + var vapor = _serverEntityManager.SpawnEntity(_vaporPrototype, playerPos.Offset(distance < 1 ? quarter : threeQuarters)); + vapor.Transform.LocalRotation = rotation; + + if (vapor.TryGetComponent(out AppearanceComponent appearance)) // Vapor sprite should face down. + { + appearance.SetData(VaporVisuals.Rotation, -Angle.South + rotation); + appearance.SetData(VaporVisuals.Color, contents.SubstanceColor.WithAlpha(1f)); + appearance.SetData(VaporVisuals.State, true); + } + + // Add the solution to the vapor and actually send the thing + var vaporComponent = vapor.GetComponent(); + vaporComponent.TryAddSolution(solution); + + vaporComponent.Start(rotation.ToVec(), _sprayVelocity, target, _sprayAliveTime); + } //Play sound - EntitySystem.Get().PlayFromEntity(_spraySound, Owner); + EntitySystem.Get().PlayFromEntity(_spraySound, Owner, AudioHelpers.WithVariation(0.125f)); + + _lastUseTime = curTime; + _cooldownEnd = _lastUseTime + TimeSpan.FromSeconds(_cooldownTime); + + if (Owner.TryGetComponent(out ItemCooldownComponent cooldown)) + { + cooldown.CooldownStart = _lastUseTime; + cooldown.CooldownEnd = _cooldownEnd; + } + } + + public bool UseEntity(UseEntityEventArgs eventArgs) + { + ToggleSafety(eventArgs.User); + return true; + } + + public void Activate(ActivateEventArgs eventArgs) + { + ToggleSafety(eventArgs.User); + } + + private void ToggleSafety(IEntity user) + { + SetSafety(user, !_safety); + } + + private void SetSafety(IEntity user, bool state) + { + if (!ActionBlockerSystem.CanInteract(user) || !_hasSafety) + return; + + _safety = state; + + if(Owner.TryGetComponent(out AppearanceComponent appearance)) + appearance.SetData(SprayVisuals.Safety, _safety); } } } diff --git a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs index 2e19baa7a1..5b23d16e9c 100644 --- a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs @@ -1,11 +1,15 @@ using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Movement; +using Content.Shared.Interfaces.GameObjects.Components; +using NFluidsynth; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Timers; +using Logger = Robust.Shared.Log.Logger; namespace Content.Server.GameObjects.Components.Mobs { diff --git a/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs b/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs index 8a1247d4d9..54d99cc895 100644 --- a/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/SlipperyComponent.cs @@ -12,12 +12,81 @@ namespace Content.Server.GameObjects.Components.Movement [ComponentReference(typeof(SharedSlipperyComponent))] public class SlipperyComponent : SharedSlipperyComponent { + private float _paralyzeTime = 2f; + private float _intersectPercentage = 0.3f; + private float _requiredSlipSpeed = 0f; + private bool _slippery; + private float _launchForwardsMultiplier = 1f; + /// /// Path to the sound to be played when a mob slips. /// [ViewVariables] private string SlipSound { get; set; } = "/Audio/Effects/slip.ogg"; + /// + /// How many seconds the mob will be paralyzed for. + /// + [ViewVariables(VVAccess.ReadWrite)] + public override float ParalyzeTime + { + get => _paralyzeTime; + set + { + _paralyzeTime = value; + Dirty(); + } + } + + /// + /// Percentage of shape intersection for a slip to occur. + /// + [ViewVariables(VVAccess.ReadWrite)] + public override float IntersectPercentage + { + get => _intersectPercentage; + set + { + _intersectPercentage = value; + Dirty(); + } + } + + /// + /// Entities will only be slipped if their speed exceeds this limit. + /// + [ViewVariables(VVAccess.ReadWrite)] + public override float RequiredSlipSpeed + { + get => _requiredSlipSpeed; + set + { + _requiredSlipSpeed = value; + Dirty(); + } + } + + /// + /// Whether or not this component will try to slip entities. + /// + [ViewVariables(VVAccess.ReadWrite)] + public override bool Slippery + { + get => _slippery; + set + { + _slippery = value; + Dirty(); + } + } + + [ViewVariables(VVAccess.ReadWrite)] + public override float LaunchForwardsMultiplier + { + get => _launchForwardsMultiplier; + set => _launchForwardsMultiplier = value; + } + public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -33,5 +102,10 @@ namespace Content.Server.GameObjects.Components.Movement .PlayFromEntity(SlipSound, Owner, AudioHelpers.WithVariation(0.2f)); } } + + public override ComponentState GetComponentState() + { + return new SlipperyComponentState(_paralyzeTime, _intersectPercentage, _requiredSlipSpeed, _launchForwardsMultiplier, _slippery); + } } } diff --git a/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs b/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs deleted file mode 100644 index a12600f634..0000000000 --- a/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Server.Atmos; -using Robust.Shared.GameObjects.Systems; - -namespace Content.Server.GameObjects.EntitySystems -{ - public class GasVaporSystem : EntitySystem - { - /// - public override void Update(float frameTime) - { - foreach (var GasVapor in ComponentManager.EntityQuery()) - { - if (GasVapor.Initialized) - { - GasVapor.Update(frameTime); - } - } - } - } -} diff --git a/Content.Server/GameObjects/EntitySystems/VaporSystem.cs b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs index c3e3b2ec5b..caaddaaff2 100644 --- a/Content.Server/GameObjects/EntitySystems/VaporSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs @@ -10,7 +10,7 @@ namespace Content.Server.GameObjects.EntitySystems { foreach (var vaporComp in ComponentManager.EntityQuery()) { - vaporComp.Update(); + vaporComp.Update(frameTime); } } } diff --git a/Content.Shared/Chemistry/ReagentPrototype.cs b/Content.Shared/Chemistry/ReagentPrototype.cs index cb8b639242..b68c4975e7 100644 --- a/Content.Shared/Chemistry/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/ReagentPrototype.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using Content.Shared.Interfaces; using Content.Shared.Interfaces.Chemistry; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -13,8 +16,6 @@ namespace Content.Shared.Chemistry [Prototype("reagent")] public class ReagentPrototype : IPrototype, IIndexedPrototype { - private const float CelsiusToKelvin = 273.15f; - [Dependency] private readonly IModuleManager _moduleManager = default!; private string _id; @@ -24,6 +25,7 @@ namespace Content.Shared.Chemistry private Color _substanceColor; private List _metabolism; private string _spritePath; + private List _tileReactions; public string ID => _id; public string Name => _name; @@ -33,6 +35,7 @@ namespace Content.Shared.Chemistry //List of metabolism effects this reagent has, should really only be used server-side. public List Metabolism => _metabolism; + public List TileReactions => _tileReactions; public string SpriteReplacementPath => _spritePath; public ReagentPrototype() @@ -54,10 +57,12 @@ namespace Content.Shared.Chemistry if (_moduleManager.IsServerModule) { serializer.DataField(ref _metabolism, "metabolism", new List { new DefaultMetabolizable() }); + serializer.DataField(ref _tileReactions, "tileReactions", new List { }); } else { _metabolism = new List { new DefaultMetabolizable() }; + _tileReactions = new List(); } } @@ -78,5 +83,55 @@ namespace Content.Shared.Chemistry return SubstanceColor; } + + public ReagentUnit ReactionEntity(IEntity entity, ReactionMethod method, ReagentUnit reactVolume) + { + var removed = ReagentUnit.Zero; + + foreach (var react in entity.GetAllComponents()) + { + switch (method) + { + case ReactionMethod.Touch: + removed += react.ReagentReactTouch(this, reactVolume); + break; + case ReactionMethod.Ingestion: + removed += react.ReagentReactIngestion(this, reactVolume); + break; + case ReactionMethod.Injection: + removed += react.ReagentReactInjection(this, reactVolume); + break; + } + + if (removed > reactVolume) + throw new Exception("Removed more than we have!"); + + if (removed == reactVolume) + break; + } + + return removed; + } + + public ReagentUnit ReactionTile(TileRef tile, ReagentUnit reactVolume) + { + var removed = ReagentUnit.Zero; + + if (tile.Tile.IsEmpty) + return removed; + + foreach (var reaction in _tileReactions) + { + removed += reaction.TileReact(tile, this, reactVolume - removed); + + if (removed > reactVolume) + throw new Exception("Removed more than we have!"); + + if (removed == reactVolume) + break; + } + + return removed; + } } } diff --git a/Content.Shared/Chemistry/ReagentUnit.cs b/Content.Shared/Chemistry/ReagentUnit.cs index ec2d6adb60..bf1d86e437 100644 --- a/Content.Shared/Chemistry/ReagentUnit.cs +++ b/Content.Shared/Chemistry/ReagentUnit.cs @@ -110,14 +110,24 @@ namespace Content.Shared.Chemistry return a.ShiftDown() >= b; } + public static bool operator <(ReagentUnit a, int b) + { + return a.ShiftDown() < b; + } + + public static bool operator >(ReagentUnit a, int b) + { + return a.ShiftDown() > b; + } + public static bool operator ==(ReagentUnit a, int b) { - return a.ShiftDown() == b; + return a.Int() == b; } public static bool operator !=(ReagentUnit a, int b) { - return a.ShiftDown() != b; + return a.Int() != b; } public static bool operator ==(ReagentUnit a, ReagentUnit b) diff --git a/Content.Shared/GameObjects/Atmos/SharedPumpComponent.cs b/Content.Shared/GameObjects/Components/Atmos/SharedPumpComponent.cs similarity index 90% rename from Content.Shared/GameObjects/Atmos/SharedPumpComponent.cs rename to Content.Shared/GameObjects/Components/Atmos/SharedPumpComponent.cs index fe16525766..20e9c2dfb9 100644 --- a/Content.Shared/GameObjects/Atmos/SharedPumpComponent.cs +++ b/Content.Shared/GameObjects/Components/Atmos/SharedPumpComponent.cs @@ -1,8 +1,7 @@ using System; -using Content.Shared.GameObjects.Components.Atmos; using Robust.Shared.Serialization; -namespace Content.Shared.GameObjects.Atmos +namespace Content.Shared.GameObjects.Components.Atmos { [Serializable, NetSerializable] public enum PumpVisuals diff --git a/Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs b/Content.Shared/GameObjects/Components/Atmos/SharedSiphonComponent.cs similarity index 76% rename from Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs rename to Content.Shared/GameObjects/Components/Atmos/SharedSiphonComponent.cs index 39be0e6ca7..169e887c0b 100644 --- a/Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs +++ b/Content.Shared/GameObjects/Components/Atmos/SharedSiphonComponent.cs @@ -1,7 +1,7 @@ -using Robust.Shared.Serialization; -using System; +using System; +using Robust.Shared.Serialization; -namespace Content.Shared.GameObjects.Atmos +namespace Content.Shared.GameObjects.Components.Atmos { [Serializable, NetSerializable] public enum SiphonVisuals diff --git a/Content.Shared/GameObjects/Atmos/SharedVentComponent.cs b/Content.Shared/GameObjects/Components/Atmos/SharedVentComponent.cs similarity index 75% rename from Content.Shared/GameObjects/Atmos/SharedVentComponent.cs rename to Content.Shared/GameObjects/Components/Atmos/SharedVentComponent.cs index 03c7d9d320..fdd4616aa9 100644 --- a/Content.Shared/GameObjects/Atmos/SharedVentComponent.cs +++ b/Content.Shared/GameObjects/Components/Atmos/SharedVentComponent.cs @@ -1,7 +1,7 @@ -using Robust.Shared.Serialization; -using System; +using System; +using Robust.Shared.Serialization; -namespace Content.Shared.GameObjects.Atmos +namespace Content.Shared.GameObjects.Components.Atmos { [Serializable, NetSerializable] public enum VentVisuals diff --git a/Content.Shared/GameObjects/Components/Fluids/SharedSprayComponent.cs b/Content.Shared/GameObjects/Components/Fluids/SharedSprayComponent.cs new file mode 100644 index 0000000000..6ae862912b --- /dev/null +++ b/Content.Shared/GameObjects/Components/Fluids/SharedSprayComponent.cs @@ -0,0 +1,17 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Fluids +{ + public class SharedSprayComponent : Component + { + public override string Name => "Spray"; + } + + [Serializable, NetSerializable] + public enum SprayVisuals + { + Safety, + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs index cfa2268d2b..79ca4dd46c 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedStunnableComponent.cs @@ -29,8 +29,6 @@ namespace Content.Shared.GameObjects.Components.Mobs : _gameTiming.CurTime + (TimeSpan.FromSeconds(Math.Max(StunnedTimer, Math.Max(KnockdownTimer, SlowdownTimer)))); - private const int StunLevels = 8; - private bool _canHelp = true; protected float _stunCap = 20f; protected float _knockdownCap = 20f; diff --git a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs index 36bf78b73a..5f6edd505f 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedSlipperyComponent.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; @@ -29,25 +30,31 @@ namespace Content.Shared.GameObjects.Components.Movement /// How many seconds the mob will be paralyzed for. /// [ViewVariables(VVAccess.ReadWrite)] - private float ParalyzeTime { get; set; } = 3f; + public virtual float ParalyzeTime { get; set; } = 2f; /// /// Percentage of shape intersection for a slip to occur. /// [ViewVariables(VVAccess.ReadWrite)] - private float IntersectPercentage { get; set; } = 0.3f; + public virtual float IntersectPercentage { get; set; } = 0.3f; /// /// Entities will only be slipped if their speed exceeds this limit. /// [ViewVariables(VVAccess.ReadWrite)] - private float RequiredSlipSpeed { get; set; } = 0f; + public virtual float RequiredSlipSpeed { get; set; } = 0f; + + /// + /// The entity's speed will be multiplied by this to slip it forwards. + /// + [ViewVariables(VVAccess.ReadWrite)] + public virtual float LaunchForwardsMultiplier { get; set; } = 1f; /// /// Whether or not this component will try to slip entities. /// [ViewVariables(VVAccess.ReadWrite)] - public bool Slippery { get; set; } + public virtual bool Slippery { get; set; } private bool TrySlip(IEntity entity) { @@ -81,7 +88,7 @@ namespace Content.Shared.GameObjects.Components.Movement if (entity.TryGetComponent(out ICollidableComponent collidable)) { var controller = collidable.EnsureController(); - controller.LinearVelocity = collidable.LinearVelocity; + controller.LinearVelocity = collidable.LinearVelocity * LaunchForwardsMultiplier; } stun.Paralyze(5); @@ -140,10 +147,30 @@ namespace Content.Shared.GameObjects.Components.Movement { base.ExposeData(serializer); - serializer.DataField(this, x => ParalyzeTime, "paralyzeTime", 3f); - serializer.DataField(this, x => IntersectPercentage, "intersectPercentage", 0.3f); - serializer.DataField(this, x => RequiredSlipSpeed, "requiredSlipSpeed", 0f); + serializer.DataField(this, x => x.ParalyzeTime, "paralyzeTime", 3f); + serializer.DataField(this, x => x.IntersectPercentage, "intersectPercentage", 0.3f); + serializer.DataField(this, x => x.RequiredSlipSpeed, "requiredSlipSpeed", 0f); + serializer.DataField(this, x => x.LaunchForwardsMultiplier, "launchForwardsMultiplier", 1f); serializer.DataField(this, x => x.Slippery, "slippery", true); } } + + [Serializable, NetSerializable] + public class SlipperyComponentState : ComponentState + { + public float ParalyzeTime { get; } + public float IntersectPercentage { get; } + public float RequiredSlipSpeed { get; } + public float LaunchForwardsMultiplier { get; } + public bool Slippery { get; } + + public SlipperyComponentState(float paralyzeTime, float intersectPercentage, float requiredSlipSpeed, float launchForwardsMultiplier, bool slippery) : base(ContentNetIDs.SLIP) + { + ParalyzeTime = paralyzeTime; + IntersectPercentage = intersectPercentage; + RequiredSlipSpeed = requiredSlipSpeed; + LaunchForwardsMultiplier = launchForwardsMultiplier; + Slippery = slippery; + } + } } diff --git a/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs b/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs deleted file mode 100644 index a15f92882c..0000000000 --- a/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Shared.GameObjects.Components -{ - public class SharedGasSprayerComponent : Component - { - public sealed override string Name => "GasSprayer"; - } - - [Serializable, NetSerializable] - public enum ExtinguisherVisuals - { - Rotation - } -} diff --git a/Content.Shared/GameObjects/Components/SharedVaporComponent.cs b/Content.Shared/GameObjects/Components/SharedVaporComponent.cs new file mode 100644 index 0000000000..a3826e6894 --- /dev/null +++ b/Content.Shared/GameObjects/Components/SharedVaporComponent.cs @@ -0,0 +1,20 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components +{ + public class SharedVaporComponent : Component + { + public override string Name => "Vapor"; + } + + [Serializable, NetSerializable] + public enum VaporVisuals + { + Rotation, + Color, + State, + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 6210936e41..7ae6ce8c27 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -74,6 +74,7 @@ public const uint SUSPICION_ROLE = 1068; public const uint ROTATION = 1069; public const uint MOB_STATE_MANAGER = 1070; + public const uint SLIP = 1071; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Content.Shared/Interfaces/Chemistry/ITileReaction.cs b/Content.Shared/Interfaces/Chemistry/ITileReaction.cs new file mode 100644 index 0000000000..ae584642b1 --- /dev/null +++ b/Content.Shared/Interfaces/Chemistry/ITileReaction.cs @@ -0,0 +1,11 @@ +using Content.Shared.Chemistry; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Map; + +namespace Content.Shared.Interfaces.Chemistry +{ + public interface ITileReaction : IExposeData + { + ReagentUnit TileReact(TileRef tile, ReagentPrototype reagent, ReagentUnit reactVolume); + } +} diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IReagentReaction.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IReagentReaction.cs new file mode 100644 index 0000000000..5242681767 --- /dev/null +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IReagentReaction.cs @@ -0,0 +1,18 @@ +using Content.Shared.Chemistry; + +namespace Content.Shared.Interfaces.GameObjects.Components +{ + public enum ReactionMethod + { + Touch, + Injection, + Ingestion, + } + + public interface IReagentReaction + { + ReagentUnit ReagentReactTouch(ReagentPrototype reagent, ReagentUnit volume) => ReagentUnit.Zero; + ReagentUnit ReagentReactInjection(ReagentPrototype reagent, ReagentUnit volume) => ReagentUnit.Zero; + ReagentUnit ReagentReactIngestion(ReagentPrototype reagent, ReagentUnit volume) => ReagentUnit.Zero; + } +} diff --git a/Content.Shared/Physics/GasVaporController.cs b/Content.Shared/Physics/GasVaporController.cs deleted file mode 100644 index ca544c47de..0000000000 --- a/Content.Shared/Physics/GasVaporController.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Maths; -using Robust.Shared.Physics; - -namespace Content.Shared.Physics -{ - public class GasVaporController : VirtualController - { - public void Move(Vector2 velocityDirection, float speed) - { - LinearVelocity = velocityDirection * speed; - } - } -} diff --git a/Resources/Audio/Effects/extinguish.ogg b/Resources/Audio/Effects/extinguish.ogg new file mode 100644 index 0000000000..6144023335 Binary files /dev/null and b/Resources/Audio/Effects/extinguish.ogg differ diff --git a/Resources/Audio/Effects/spray2.ogg b/Resources/Audio/Effects/spray2.ogg new file mode 100644 index 0000000000..d184d76bd0 Binary files /dev/null and b/Resources/Audio/Effects/spray2.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Consumable/food.yml b/Resources/Prototypes/Entities/Objects/Consumable/food.yml index 262c7e2770..8f9755ceb2 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/food.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/food.yml @@ -5,6 +5,8 @@ components: - type: Food - type: LoopingSound + - type: SolutionContainer + caps: NoExamine - type: Sprite state: icon netsync: false diff --git a/Resources/Prototypes/Entities/Objects/Misc/extinguisher_spray.yml b/Resources/Prototypes/Entities/Objects/Misc/extinguisher_spray.yml deleted file mode 100644 index da84fe7e02..0000000000 --- a/Resources/Prototypes/Entities/Objects/Misc/extinguisher_spray.yml +++ /dev/null @@ -1,28 +0,0 @@ -- type: entity - name: Extinguisher Spray - id: ExtinguisherSpray - description: Extinguisher Spray - components: - - type: Sprite - sprite: Effects/extinguisherSpray.rsi - layers: - - state: extinguish - - type: Icon - sprite: Effects/extinguisherSpray.rsi - state: extinguish - - type: GasVapor - dissipationInterval: 1 - gas: WaterVapor - gasVolume: 200 - gasTemperature: 293.15 - gasAmount: 20 - - type: Physics - - type: Collidable - shapes: - - !type:PhysShapeAabb - bounds: "-0.25,-0.25,0.25,0.25" - mask: - - Impassable - - type: Appearance - visuals: - - type: ExtinguisherVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index 493e448846..78b20f0e77 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -7,24 +7,63 @@ - type: Sprite sprite: Objects/Misc/fire_extinguisher.rsi layers: - - state: fire_extinguisher_open + - state: fire_extinguisher_closed + map: [ "enum.SprayVisualLayers.Base" ] - type: Icon sprite: Objects/Misc/fire_extinguisher.rsi - state: fire_extinguisher_open + state: fire_extinguisher_closed - type: Item sprite: Objects/Misc/fire_extinguisher.rsi size: 10 - type: SolutionContainer - maxVol: 1000 + maxVol: 100 caps: AddTo, RemoveFrom, NoExamine contents: reagents: - ReagentId: chem.H2O - Quantity: 1000 - - type: GasSprayer - spraySound: /Audio/Effects/spray.ogg - sprayType: ExtinguisherSpray - fuelType: chem.H2O - fuelName: water - fuelCost: 50 + Quantity: 100 + - type: ItemCooldown + - type: Spray + spraySound: /Audio/Effects/extinguish.ogg + sprayedPrototype: ExtinguisherSpray + hasSafety: true + vaporAmount: 3 + vaporSpread: 90 + sprayVelocity: 2.0 + sprayTimeAlive: 1.5 + transferAmount: 5 - type: FireExtinguisher + - type: Appearance + visuals: + - type: SprayVisualizer + safety_on_state: fire_extinguisher_closed + safety_off_state: fire_extinguisher_open + +- type: entity + name: extinguisher spray + id: ExtinguisherSpray + parent: Vapor + abstract: true + components: + - type: Sprite + sprite: Effects/extinguisherSpray.rsi + layers: + - state: extinguish + map: [ "enum.VaporVisualLayers.Base" ] + - type: Icon + sprite: Effects/extinguisherSpray.rsi + state: extinguish + - type: Collidable + hard: false + shapes: + - !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + mask: + - Impassable + - MobImpassable + - SmallImpassable + - type: Appearance + visuals: + - type: VaporVisualizer + animation_delay: 0.8 + animation_state: extinguish diff --git a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml index 0a7dffcd5a..61d57d1045 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml @@ -180,10 +180,44 @@ - type: Slippery paralyzeTime: 7 +- type: entity + id: Vapor + name: "vapor" + abstract: true + components: + - type: SnapGrid + offset: Center + - type: SolutionContainer + maxVol: 50 + - type: Vapor + - type: AnimationPlayer + - type: Sprite + sprite: Effects/chempuff.rsi + layers: + - state: chempuff + map: [ "enum.VaporVisualLayers.Base" ] + - type: Icon + sprite: Effects/chempuff.rsi + state: chempuff + - type: Physics + - type: Collidable + hard: false + shapes: + - !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + mask: + - Impassable + - MobImpassable + - SmallImpassable + - type: Appearance + visuals: + - type: VaporVisualizer + - type: entity name: spray bottle id: SprayBottle parent: BaseItem + suffix: Empty description: A spray bottle with an unscrewable top. components: - type: Sprite @@ -196,7 +230,37 @@ - type: Pourable transferAmount: 5.0 - type: Spillable + - type: ItemCooldown - type: Spray transferAmount: 10 - sprayVelocity: 5 - spraySound: /Audio/Effects/spray.ogg + sprayVelocity: 2 + spraySound: /Audio/Effects/spray2.ogg + +- type: entity + name: spray bottle + id: SprayBottleWater + suffix: Filled + parent: SprayBottle + components: + - type: SolutionContainer + maxVol: 100 + caps: AddTo, RemoveFrom + contents: + reagents: + - ReagentId: chem.H2O + Quantity: 100 + +- type: entity + name: space cleaner + description: BLAM!-brand non-foaming space cleaner! + id: SprayBottleSpaceCleaner + parent: SprayBottle + suffix: "" + components: + - type: SolutionContainer + maxVol: 100 + caps: AddTo, RemoveFrom + contents: + reagents: + - ReagentId: chem.SpaceCleaner + Quantity: 100 diff --git a/Resources/Prototypes/Entities/chemistry.yml b/Resources/Prototypes/Entities/chemistry.yml index a1861d7994..c9589e17f8 100644 --- a/Resources/Prototypes/Entities/chemistry.yml +++ b/Resources/Prototypes/Entities/chemistry.yml @@ -6,21 +6,3 @@ components: - type: SolutionContainer maxVol: 5 - -- type: entity - id: Vapor - name: "vapor" - abstract: true - components: - - type: SnapGrid - offset: Center - - type: SolutionContainer - maxVol: 50 - - type: Vapor - - type: Physics - - type: Collidable - shapes: - - !type:PhysShapeAabb - bounds: "-0.25,-0.25,0.25,0.25" - mask: - - Impassable \ No newline at end of file diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index 468ef7f909..a54a5784cf 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -28,6 +28,9 @@ metabolism: - !type:DefaultDrink rate: 1 + tileReactions: + - !type:ExtinguishTileReaction {} + - !type:SpillIfPuddlePresentTileReaction {} - type: reagent id: chem.Ice @@ -46,6 +49,9 @@ color: "#7e009e" boilingPoint: -127.3 # Random values picked between the actual values for CO2 and O2 meltingPoint: -186.4 + tileReactions: + - !type:FlammableTileReaction + temperatureMultiplier: 1.5 - type: reagent id: chem.Ethanol @@ -55,6 +61,9 @@ color: "#b05b3c" boilingPoint: 78.2 meltingPoint: -114.1 + tileReactions: + - !type:FlammableTileReaction + temperatureMultiplier: 1.35 - type: reagent id: chem.Glucose @@ -118,6 +127,7 @@ color: "#c8ff69" boilingPoint: 147.0 # Made this up, loosely based on bleach meltingPoint: -11.0 + # You should probably add a tile reaction here that tries to clean the tile. - type: reagent id: chem.SpaceLube @@ -127,6 +137,11 @@ color: "#77b58e" boilingPoint: 290.0 # Glycerin meltingPoint: 18.2 + tileReactions: + - !type:SpillTileReaction + paralyzeTime: 3 + launchForwardsMultiplier: 2 + requiredSlipSpeed: 1 - type: reagent id: chem.TableSalt @@ -145,6 +160,9 @@ color: "#757245" boilingPoint: 2977.0 # Aluminum oxide meltingPoint: 2030.0 + tileReactions: + - !type:FlammableTileReaction + temperatureMultiplier: 1.35 - type: reagent id: chem.UnstableMutagen @@ -163,3 +181,5 @@ color: "#a76b1c" boilingPoint: -84.7 # Acetylene. Close enough. meltingPoint: -80.7 + tileReactions: + - !type:FlammableTileReaction diff --git a/Resources/Textures/Effects/chempuff.rsi/chempuff.png b/Resources/Textures/Effects/chempuff.rsi/chempuff.png new file mode 100644 index 0000000000..43e45fdf89 Binary files /dev/null and b/Resources/Textures/Effects/chempuff.rsi/chempuff.png differ diff --git a/Resources/Textures/Effects/chempuff.rsi/meta.json b/Resources/Textures/Effects/chempuff.rsi/meta.json new file mode 100644 index 0000000000..c1dde9153e --- /dev/null +++ b/Resources/Textures/Effects/chempuff.rsi/meta.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation at 87e833a2252741f795df5a05a08fe5f08e4dad2b", + "states": [ + { + "name": "chempuff", + "directions": 1, + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png b/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png index 0eb6d7ebd5..0fe1f2acdb 100644 Binary files a/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png and b/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png differ diff --git a/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json b/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json index 9e3c4a97b6..4a6ba19aca 100644 --- a/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json +++ b/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json @@ -1,4 +1,4 @@ -{ +{ "version": 1, "size": { "x": 32, @@ -7,50 +7,8 @@ "states": [ { "name": "extinguish", - "directions": 8, + "directions": 1, "delays": [ - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], - [ - 0.2, - 0.2, - 0.2, - 0.2 - ], [ 0.2, 0.2, @@ -60,4 +18,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/RobustToolbox b/RobustToolbox index 4cbdde6ec3..f1334ca57d 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 4cbdde6ec3db9074398fbe87573c1860822338ab +Subproject commit f1334ca57d3938503b3498729ea1bf3ed38c3fd2