diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index a765115910..10784bf2e2 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -151,6 +151,8 @@ "Flippable", "Airtight", "MovedByPressure", + "Spray", + "Vapor", "DamageOnHighSpeedImpact", }; } diff --git a/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs new file mode 100644 index 0000000000..dd5fea942b --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/VaporComponent.cs @@ -0,0 +1,129 @@ +using Content.Server.GameObjects.Components.Fluids; +using Content.Shared.Chemistry; +using Content.Shared.Physics; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.EntityFrameworkCore.Update.Internal; +using Robust.Server.GameObjects; +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.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Serialization; +using Robust.Shared.Timers; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Server.GameObjects.Components.Chemistry +{ + [RegisterComponent] + class VaporComponent : Component, ICollideBehavior + { +#pragma warning disable 649 + [Dependency] private readonly IMapManager _mapManager = default!; +#pragma warning enable 649 + public override string Name => "Vapor"; + + [ViewVariables] + private SolutionComponent _contents; + [ViewVariables] + private ReagentUnit _transferAmount; + + private bool _running; + private Vector2 _direction; + private float _velocity; + + + public override void Initialize() + { + base.Initialize(); + _contents = Owner.GetComponent(); + } + + public void Start(Vector2 dir, float velocity) + { + _running = true; + _direction = dir; + _velocity = velocity; + // Set Move + if (Owner.TryGetComponent(out ICollidableComponent collidable)) + { + var controller = collidable.EnsureController(); + controller.Move(_direction, _velocity); + } + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(0.5)); + } + + public void Update() + { + if (!_running) + return; + + // Get all intersecting tiles with the vapor and spray the divided solution on there + if (Owner.TryGetComponent(out ICollidableComponent collidable)) + { + var worldBounds = collidable.WorldAABB; + 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 pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex); + SpillHelper.SpillAt(pos, _contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear? + } + } + + if (_contents.CurrentVolume == 0) + { + // Delete this + Owner.Delete(); + } + } + + internal bool TryAddSolution(Solution solution) + { + if (solution.TotalVolume == 0) + { + return false; + } + var result = _contents.TryAddSolution(solution); + if (!result) + { + return false; + } + + return true; + } + + void ICollideBehavior.CollideWith(IEntity collidedWith) + { + // Check for collision with a impassable object (e.g. wall) and stop + if (collidedWith.TryGetComponent(out ICollidableComponent collidable)) + { + if ((collidable.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && collidable.Hard) + { + if (Owner.TryGetComponent(out ICollidableComponent coll)) + { + var controller = coll.EnsureController(); + controller.Stop(); + } + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs b/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs index d7898c0ef9..493da11ffa 100644 --- a/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs +++ b/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs @@ -18,10 +18,11 @@ namespace Content.Server.GameObjects.Components.Fluids /// Entity location to spill at /// Initial solution for the prototype /// Prototype to use - internal static void SpillAt(IEntity entity, Solution solution, string prototype) + /// Play the spill sound + internal static void SpillAt(IEntity entity, Solution solution, string prototype, bool sound = true) { var entityLocation = entity.Transform.GridPosition; - SpillAt(entityLocation, solution, prototype); + SpillAt(entityLocation, solution, prototype, sound); } // Other functions will be calling this one @@ -32,7 +33,8 @@ namespace Content.Server.GameObjects.Components.Fluids /// /// Initial solution for the prototype /// Prototype to use - internal static PuddleComponent? SpillAt(GridCoordinates gridCoordinates, Solution solution, string prototype) + /// Play the spill sound + internal static PuddleComponent? SpillAt(GridCoordinates gridCoordinates, Solution solution, string prototype, bool sound = true) { if (solution.TotalVolume == 0) { @@ -67,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Fluids continue; } - if (!puddleComponent.TryAddSolution(solution)) + if (!puddleComponent.TryAddSolution(solution, sound)) { continue; } @@ -84,7 +86,7 @@ namespace Content.Server.GameObjects.Components.Fluids var puddle = serverEntityManager.SpawnEntity(prototype, spillGridCoords); var newPuddleComponent = puddle.GetComponent(); - newPuddleComponent.TryAddSolution(solution); + newPuddleComponent.TryAddSolution(solution, sound); return newPuddleComponent; } diff --git a/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs new file mode 100644 index 0000000000..11c450f391 --- /dev/null +++ b/Content.Server/GameObjects/Components/Fluids/SprayComponent.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.Interfaces; +using Content.Shared.Chemistry; +using Content.Shared.Interfaces.GameObjects.Components; +using Microsoft.EntityFrameworkCore.Update.Internal; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +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 + { +#pragma warning disable 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; +#pragma warning restore 649 + public override string Name => "Spray"; + + private ReagentUnit _transferAmount; + private string _spraySound; + private float _sprayVelocity; + + /// + /// The amount of solution to be sprayer from this solution when using it + /// + [ViewVariables] + public ReagentUnit TransferAmount + { + get => _transferAmount; + set => _transferAmount = value; + } + + /// + /// The speed at which the vapor starts when sprayed + /// + [ViewVariables] + public float Velocity + { + get => _sprayVelocity; + set => _sprayVelocity = value; + } + + private SolutionComponent _contents; + public ReagentUnit CurrentVolume => _contents.CurrentVolume; + + public override void Initialize() + { + base.Initialize(); + _contents = Owner.GetComponent(); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(10)); + serializer.DataField(ref _sprayVelocity, "sprayVelocity", 5.0f); + serializer.DataField(ref _spraySound, "spraySound", string.Empty); + } + + void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (CurrentVolume <= 0) + { + _notifyManager.PopupMessage(Owner, eventArgs.User, Loc.GetString("It's empty!")); + return; + } + + var playerPos = eventArgs.User.Transform.GridPosition; + if (eventArgs.ClickLocation.GridID != playerPos.GridID) + return; + + var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; + var solution = _contents.SplitSolution(_transferAmount); + + 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 + + //Play sound + EntitySystem.Get().PlayFromEntity(_spraySound, Owner); + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/VaporSystem.cs b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs new file mode 100644 index 0000000000..65823a8bb0 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/VaporSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.GameObjects.Components.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class VaporSystem : EntitySystem + { + /// + public override void Update(float frameTime) + { + foreach (var vaporComp in ComponentManager.EntityQuery()) + { + vaporComp.Update(); + } + } + } +} diff --git a/Content.Shared/Physics/VaporController.cs b/Content.Shared/Physics/VaporController.cs new file mode 100644 index 0000000000..190dd62b54 --- /dev/null +++ b/Content.Shared/Physics/VaporController.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.Physics +{ + public class VaporController : VirtualController + { + public void Move(Vector2 velocityDirection, float speed) + { + LinearVelocity = velocityDirection * speed; + } + } +} diff --git a/Resources/Audio/Effects/spray.ogg b/Resources/Audio/Effects/spray.ogg new file mode 100644 index 0000000000..d7796264c6 Binary files /dev/null and b/Resources/Audio/Effects/spray.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml index a7d5b15d51..7386b835b5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/janitor.yml @@ -174,3 +174,24 @@ state: soapomega - type: Slippery paralyzeTime: 7 + +- type: entity + name: spray bottle + id: SprayBottle + parent: BaseItem + description: A spray bottle with an unscrewable top. + components: + - type: Sprite + texture: Objects/Specific/Janitorial/cleaner.png + - type: Icon + texture: Objects/Specific/Janitorial/cleaner.png + - type: Solution + maxVol: 100 + caps: 1 + - type: Pourable + transferAmount: 5.0 + - type: CanSpill + - type: Spray + transferAmount: 10 + sprayVelocity: 5 + spraySound: /Audio/Effects/spray.ogg diff --git a/Resources/Prototypes/Entities/chemistry.yml b/Resources/Prototypes/Entities/chemistry.yml index f10a3525e6..4dc90f70a0 100644 --- a/Resources/Prototypes/Entities/chemistry.yml +++ b/Resources/Prototypes/Entities/chemistry.yml @@ -6,3 +6,21 @@ components: - type: Solution maxVol: 5 + +- type: entity + id: Vapor + name: "vapor" + abstract: true + components: + - type: SnapGrid + offset: Center + - type: Solution + 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/Textures/Objects/Specific/Janitorial/cleaner.png b/Resources/Textures/Objects/Specific/Janitorial/cleaner.png new file mode 100644 index 0000000000..f25c12a24e Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/cleaner.png differ