diff --git a/Content.Client/Chemistry/Visualizers/VaporVisualizerSystem.cs b/Content.Client/Chemistry/Visualizers/VaporVisualizerSystem.cs index 42e8f40f9a..6c086198db 100644 --- a/Content.Client/Chemistry/Visualizers/VaporVisualizerSystem.cs +++ b/Content.Client/Chemistry/Visualizers/VaporVisualizerSystem.cs @@ -35,6 +35,14 @@ public sealed class VaporVisualizerSystem : VisualizerSystem(uid, VaporVisuals.State, out var state) && + state && + TryComp(uid, out var animPlayer) && + !AnimationSystem.HasRunningAnimation(uid, animPlayer, VaporVisualsComponent.AnimationKey)) + { + AnimationSystem.Play(uid, animPlayer, comp.VaporFlick, VaporVisualsComponent.AnimationKey); + } } /// @@ -46,13 +54,6 @@ public sealed class VaporVisualizerSystem : VisualizerSystem(uid, VaporVisuals.State, out var state, args.Component) && state) && - TryComp(uid, out var animPlayer) && - !AnimationSystem.HasRunningAnimation(uid, animPlayer, VaporVisualsComponent.AnimationKey)) - { - AnimationSystem.Play(uid, animPlayer, comp.VaporFlick, VaporVisualsComponent.AnimationKey); - } } } diff --git a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs index 106867ccbc..38e22ad14e 100644 --- a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs @@ -63,7 +63,7 @@ namespace Content.Server.Chemistry.EntitySystems _physics.SetLinearDamping(physics, 0f); _physics.SetAngularDamping(physics, 0f); - _throwing.TryThrow(vapor.Owner, dir * speed, speed, user: user, pushbackRatio: 50f); + _throwing.TryThrow(vapor.Owner, dir, speed, user: user, pushbackRatio: 50f); var distance = (target.Position - vaporXform.WorldPosition).Length; var time = (distance / physics.LinearVelocity.Length); diff --git a/Content.Server/Chemistry/TileReactions/CleanDecalsReaction.cs b/Content.Server/Chemistry/TileReactions/CleanDecalsReaction.cs new file mode 100644 index 0000000000..0ae41d31a4 --- /dev/null +++ b/Content.Server/Chemistry/TileReactions/CleanDecalsReaction.cs @@ -0,0 +1,55 @@ +using Content.Server.Decals; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Decals; +using Content.Shared.FixedPoint; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.Chemistry.TileReactions; + +/// +/// Purges all cleanable decals on a tile. +/// +[DataDefinition] +public sealed class CleanDecalsReaction : ITileReaction +{ + /// + /// For every cleaned decal we lose this much reagent. + /// + [DataField("cleanCost")] + public FixedPoint2 CleanCost { get; private set; } = FixedPoint2.New(0.25f); + + public FixedPoint2 TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume) + { + var entMan = IoCManager.Resolve(); + + if (reactVolume <= CleanCost || + !entMan.TryGetComponent(tile.GridUid, out var grid) || + !entMan.TryGetComponent(tile.GridUid, out var decalGrid)) + { + return FixedPoint2.Zero; + } + + var lookupSystem = entMan.System(); + var decalSystem = entMan.System(); + // Very generous hitbox. + var decals = decalSystem + .GetDecalsIntersecting(tile.GridUid, lookupSystem.GetLocalBounds(tile, grid.TileSize).Enlarged(0.5f).Translated(new Vector2(-0.5f, -0.5f))); + var amount = FixedPoint2.Zero; + + foreach (var decal in decals) + { + if (!decal.Decal.Cleanable) + continue; + + decalSystem.RemoveDecal(tile.GridUid, decal.Index, decalGrid); + amount += CleanCost; + + if (amount > reactVolume) + break; + } + + return amount; + } +} diff --git a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs index bf3ab310ad..a2bd5decbd 100644 --- a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs @@ -1,50 +1,64 @@ using System.Linq; -using Content.Server.Cleanable; -using Content.Server.Decals; +using Content.Server.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; +using Content.Shared.Fluids.Components; using Robust.Shared.Map; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Chemistry.TileReactions +namespace Content.Server.Chemistry.TileReactions; + +/// +/// Turns all of the reagents on a puddle into water. +/// +[DataDefinition] +public sealed class CleanTileReaction : ITileReaction { - [DataDefinition] - public sealed class CleanTileReaction : ITileReaction + /// + /// How much it costs to clean 1 unit of reagent. + /// + /// + /// In terms of space cleaner can clean 1 average puddle per 5 units. + /// + [DataField("cleanCost")] + public float CleanAmountMultiplier { get; private set; } = 0.25f; + + /// + /// What reagent to replace the tile conents with. + /// + [DataField("reagent", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string ReplacementReagent = "Water"; + + FixedPoint2 ITileReaction.TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume) { - /// - /// Multiplier used in CleanTileReaction. - /// 1 (default) means normal consumption rate of the cleaning reagent. - /// 0 means no consumption of the cleaning reagent, i.e. the reagent is inexhaustible. - /// - [DataField("cleanAmountMultiplier")] - public float CleanAmountMultiplier { get; private set; } = 1.0f; + var entMan = IoCManager.Resolve(); + var entities = entMan.System().GetEntitiesIntersecting(tile).ToArray(); + var puddleQuery = entMan.GetEntityQuery(); + var solutionContainerSystem = entMan.System(); + // Multiply as the amount we can actually purge is higher than the react amount. + var purgeAmount = reactVolume / CleanAmountMultiplier; - FixedPoint2 ITileReaction.TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume) + foreach (var entity in entities) { - var entities = EntitySystem.Get().GetEntitiesIntersecting(tile).ToArray(); - var amount = FixedPoint2.Zero; - var entMan = IoCManager.Resolve(); - foreach (var entity in entities) + if (!puddleQuery.TryGetComponent(entity, out var puddle) || + !solutionContainerSystem.TryGetSolution(entity, puddle.SolutionName, out var puddleSolution)) { - if (entMan.TryGetComponent(entity, out CleanableComponent? cleanable)) - { - var next = amount + (cleanable.CleanAmount * CleanAmountMultiplier); - // Nothing left? - if (reactVolume < next) - break; - - amount = next; - entMan.QueueDeleteEntity(entity); - } + continue; } - var decalSystem = EntitySystem.Get(); - foreach (var (uid, _) in decalSystem.GetDecalsInRange(tile.GridUid, tile.GridIndices+new Vector2(0.5f, 0.5f), validDelegate: x => x.Cleanable)) - { - decalSystem.RemoveDecal(tile.GridUid, uid); - } + var purgeable = + solutionContainerSystem.SplitSolutionWithout(entity, puddleSolution, purgeAmount, ReplacementReagent, reagent.ID); - return amount; + purgeAmount -= purgeable.Volume; + + solutionContainerSystem.TryAddSolution(entity, puddleSolution, new Solution(ReplacementReagent, purgeable.Volume)); + + if (purgeable.Volume <= FixedPoint2.Zero) + break; } + + return (reactVolume / CleanAmountMultiplier - purgeAmount) * CleanAmountMultiplier; } } diff --git a/Content.Server/Cleanable/CleanableComponent.cs b/Content.Server/Cleanable/CleanableComponent.cs deleted file mode 100644 index 210bc15c7c..0000000000 --- a/Content.Server/Cleanable/CleanableComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Shared.FixedPoint; - -namespace Content.Server.Cleanable -{ - [RegisterComponent] - public sealed class CleanableComponent : Component - { - [DataField("cleanAmount")] - private FixedPoint2 _cleanAmount = FixedPoint2.Zero; - [ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 CleanAmount => _cleanAmount; - } -} diff --git a/Content.Server/Fluids/Components/SprayComponent.cs b/Content.Server/Fluids/Components/SprayComponent.cs index 8ba5f82d8c..0c2848de06 100644 --- a/Content.Server/Fluids/Components/SprayComponent.cs +++ b/Content.Server/Fluids/Components/SprayComponent.cs @@ -12,26 +12,22 @@ public sealed class SprayComponent : Component { public const string SolutionName = "spray"; - [DataField("sprayDistance")] public float SprayDistance = 3f; + [ViewVariables(VVAccess.ReadWrite), DataField("sprayDistance")] + public float SprayDistance = 3.5f; - [DataField("transferAmount")] public FixedPoint2 TransferAmount = FixedPoint2.New(10); + [ViewVariables(VVAccess.ReadWrite), DataField("sprayVelocity")] + public float SprayVelocity = 3.5f; - [DataField("sprayVelocity")] public float SprayVelocity = 1.5f; - - [DataField("sprayAliveTime")] public float SprayAliveTime = 0.75f; - - [DataField("cooldownTime")] public float CooldownTime = 0.5f; - - [DataField("sprayedPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] + [ViewVariables(VVAccess.ReadWrite), DataField("sprayedPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string SprayedPrototype = "Vapor"; - [DataField("vaporAmount")] public int VaporAmount = 1; + [ViewVariables(VVAccess.ReadWrite), DataField("vaporAmount")] + public int VaporAmount = 1; - [DataField("vaporSpread")] public float VaporSpread = 90f; + [ViewVariables(VVAccess.ReadWrite), DataField("vaporSpread")] + public float VaporSpread = 90f; - [DataField("impulse")] public float Impulse; - - [DataField("spraySound", required: true)] + [ViewVariables(VVAccess.ReadWrite), DataField("spraySound", required: true)] [Access(typeof(SpraySystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends public SoundSpecifier SpraySound { get; } = default!; } diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs index 5ba11b5dd3..4e7892d897 100644 --- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs @@ -4,6 +4,7 @@ using Content.Server.Cooldown; using Content.Server.Extinguisher; using Content.Server.Fluids.Components; using Content.Server.Popups; +using Content.Shared.Chemistry.Components; using Content.Shared.Cooldown; using Content.Shared.FixedPoint; using Content.Shared.Interaction; @@ -50,7 +51,9 @@ public sealed class SpraySystem : EntitySystem var curTime = _gameTiming.CurTime; if (TryComp(uid, out var cooldown) && curTime < cooldown.CooldownEnd) + { return; + } if (solution.Volume <= 0) { @@ -59,26 +62,37 @@ public sealed class SpraySystem : EntitySystem return; } + if (!TryComp(uid, out var transfer)) + return; + var xformQuery = GetEntityQuery(); var userXform = xformQuery.GetComponent(args.User); var userMapPos = userXform.MapPosition; - var clickMapPos = args.ClickLocation.ToMap(EntityManager); + var clickMapPos = args.ClickLocation.ToMap(EntityManager, _transform); var diffPos = clickMapPos.Position - userMapPos.Position; if (diffPos == Vector2.Zero || diffPos == Vector2.NaN) return; - var diffLength = diffPos.Length; var diffNorm = diffPos.Normalized; + var diffLength = diffPos.Length; + + if (diffLength > component.SprayDistance) + { + diffLength = component.SprayDistance; + } + var diffAngle = diffNorm.ToAngle(); // Vectors to determine the spawn offset of the vapor clouds. var threeQuarters = diffNorm * 0.75f; var quarter = diffNorm * 0.25f; - var amount = Math.Max(Math.Min((solution.Volume / component.TransferAmount).Int(), component.VaporAmount), 1); + var amount = Math.Max(Math.Min((solution.Volume / transfer.TransferAmount).Int(), component.VaporAmount), 1); var spread = component.VaporSpread / amount; + // TODO: Just use usedelay homie. + var cooldownTime = 0f; for (var i = 0; i < amount; i++) { @@ -89,11 +103,11 @@ public sealed class SpraySystem : EntitySystem var target = userMapPos .Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter); - var distance = target.Position.Length; + var distance = (target.Position - userMapPos.Position).Length; if (distance > component.SprayDistance) target = userMapPos.Offset(diffNorm * component.SprayDistance); - var newSolution = _solutionContainer.SplitSolution(uid, solution, component.TransferAmount); + var newSolution = _solutionContainer.SplitSolution(uid, solution, transfer.TransferAmount); if (newSolution.Volume <= FixedPoint2.Zero) break; @@ -117,13 +131,16 @@ public sealed class SpraySystem : EntitySystem // impulse direction is defined in world-coordinates, not local coordinates var impulseDirection = rotation.ToVec(); - _vapor.Start(vaporComponent, vaporXform, impulseDirection, component.SprayVelocity, target, component.SprayAliveTime, args.User); + var time = diffLength / component.SprayVelocity; + cooldownTime = MathF.Max(time, cooldownTime); + + _vapor.Start(vaporComponent, vaporXform, impulseDirection * diffLength, component.SprayVelocity, target, time, args.User); } _audio.PlayPvs(component.SpraySound, uid, component.SpraySound.Params.WithVariation(0.125f)); RaiseLocalEvent(uid, - new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(component.CooldownTime)), true); + new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(cooldownTime)), true); } } diff --git a/Content.Shared/Vapor/SharedVaporComponent.cs b/Content.Shared/Vapor/SharedVaporComponent.cs index 83ad35757e..43c65f4959 100644 --- a/Content.Shared/Vapor/SharedVaporComponent.cs +++ b/Content.Shared/Vapor/SharedVaporComponent.cs @@ -1,12 +1,10 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Vapor +namespace Content.Shared.Vapor; + +[Serializable, NetSerializable] +public enum VaporVisuals { - [Serializable, NetSerializable] - public enum VaporVisuals - { - Rotation, - Color, - State, - } + Color, + State, } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml index 8106e170a7..6850a4585a 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml @@ -27,7 +27,6 @@ canChangeTransferAmount: true - type: ItemCooldown - type: Spray - transferAmount: 10 sprayVelocity: 2 spraySound: path: /Audio/Effects/spray2.ogg @@ -51,9 +50,8 @@ maxVol: 250 - type: Spray sprayedPrototype: BigVapor - transferAmount: 10 sprayVelocity: 5 - sprayAliveTime: 1.5 + sprayAliveTime: 5 spraySound: path: /Audio/Effects/spray2.ogg diff --git a/Resources/Prototypes/Reagents/cleaning.yml b/Resources/Prototypes/Reagents/cleaning.yml index 7c297f4b50..a254af4282 100644 --- a/Resources/Prototypes/Reagents/cleaning.yml +++ b/Resources/Prototypes/Reagents/cleaning.yml @@ -31,6 +31,7 @@ meltingPoint: -11.0 tileReactions: - !type:CleanTileReaction {} + - !type:CleanDecalsReaction {} - type: reagent id: SpaceLube diff --git a/Resources/Prototypes/Reagents/fun.yml b/Resources/Prototypes/Reagents/fun.yml index 758ed5df93..044f9206b5 100644 --- a/Resources/Prototypes/Reagents/fun.yml +++ b/Resources/Prototypes/Reagents/fun.yml @@ -52,7 +52,7 @@ maxOnTileWhitelist: tags: [ Bee ] - !type:CleanTileReaction # Bees are extremely obsessive about cleanliness within what they consider their hive. - cleanAmountMultiplier: 0 # Consume absolutely zero bees. Buzz buzz. + cleanCost: 0 # Consume absolutely zero bees. Buzz buzz. metabolisms: Poison: effects: