Fire extinguisher/spray refactor (#6314)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
mirrorcult
2022-01-30 07:42:15 -07:00
committed by GitHub
parent c251740e0d
commit fd316c3983
8 changed files with 326 additions and 286 deletions

View File

@@ -1,115 +1,22 @@
using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.Extinguisher;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Extinguisher
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(SharedFireExtinguisherComponent))]
public class FireExtinguisherComponent : SharedFireExtinguisherComponent, IAfterInteract, IActivate, IDropped
{
[Dependency] private readonly IEntityManager _entMan = default!;
namespace Content.Server.Extinguisher;
[RegisterComponent]
[Friend(typeof(FireExtinguisherSystem))]
public class FireExtinguisherComponent : Component
{
public override string Name => "FireExtinguisher";
[DataField("refillSound")]
SoundSpecifier _refillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
[DataField("hasSafety")]
private bool _hasSafety;
[DataField("safety")]
private bool _safety = true;
[DataField("refillSound")] public SoundSpecifier RefillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
[DataField("hasSafety")] public bool HasSafety = true;
[DataField("safety")] public bool Safety = true;
[DataField("safetySound")]
public SoundSpecifier SafetySound { get; } = new SoundPathSpecifier("/Audio/Machines/button.ogg");
// Higher priority than sprays.
int IAfterInteract.Priority => 1;
protected override void Initialize()
{
base.Initialize();
if (_hasSafety)
{
SetSafety(Owner, _safety);
}
}
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
if (eventArgs.Target == null || !eventArgs.CanReach)
{
if (_hasSafety && _safety)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("fire-extinguisher-component-safety-on-message"));
return true;
}
return false;
}
if (eventArgs.Target is not {Valid: true} target ||
!_entMan.HasComponent<ReagentTankComponent>(target) ||
!solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution) ||
!solutionContainerSystem.TryGetDrainableSolution(Owner, out var container))
{
return false;
}
var transfer = FixedPoint2.Min(container.AvailableVolume, targetSolution.DrainAvailable);
if (transfer > 0)
{
var drained = solutionContainerSystem.Drain(target, targetSolution, transfer);
solutionContainerSystem.TryAddSolution(Owner, container, drained);
SoundSystem.Play(Filter.Pvs(Owner), _refillSound.GetSound(), Owner);
eventArgs.Target.Value.PopupMessage(eventArgs.User,
Loc.GetString("fire-extingusiher-component-after-interact-refilled-message", ("owner", Owner)));
}
return true;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
ToggleSafety(eventArgs.User);
}
private void ToggleSafety(EntityUid user)
{
SoundSystem.Play(Filter.Pvs(Owner), SafetySound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f).WithVolume(-4f));
SetSafety(user, !_safety);
}
private void SetSafety(EntityUid user, bool state)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) || !_hasSafety)
return;
_safety = state;
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
appearance.SetData(FireExtinguisherVisuals.Safety, _safety);
}
void IDropped.Dropped(DroppedEventArgs eventArgs)
{
if (_hasSafety && _entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
appearance.SetData(FireExtinguisherVisuals.Safety, _safety);
}
}
}

View File

@@ -0,0 +1,156 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.CharacterAppearance.Systems;
using Content.Shared.Extinguisher;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
namespace Content.Server.Extinguisher;
public class FireExtinguisherSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FireExtinguisherComponent, ComponentInit>(OnFireExtinguisherInit);
SubscribeLocalEvent<FireExtinguisherComponent, DroppedEvent>(OnDropped);
SubscribeLocalEvent<FireExtinguisherComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<FireExtinguisherComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<FireExtinguisherComponent, GetInteractionVerbsEvent>(OnGetInteractionVerbs);
SubscribeLocalEvent<FireExtinguisherComponent, SprayAttemptEvent>(OnSprayAttempt);
}
private void OnFireExtinguisherInit(EntityUid uid, FireExtinguisherComponent component, ComponentInit args)
{
if (component.HasSafety)
{
UpdateAppearance(uid, component);
}
}
private void OnDropped(EntityUid uid, FireExtinguisherComponent component, DroppedEvent args)
{
// idk why this has to be done??????????
UpdateAppearance(uid, component);
}
private void OnUseInHand(EntityUid uid, FireExtinguisherComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
ToggleSafety(uid, args.User, component);
args.Handled = true;
}
private void OnAfterInteract(EntityUid uid, FireExtinguisherComponent component, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach)
{
return;
}
if (args.Handled)
return;
args.Handled = true;
if (component.HasSafety && component.Safety)
{
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), uid,
Filter.Entities(args.User));
return;
}
if (args.Target is not {Valid: true} target ||
!_solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution) ||
!_solutionContainerSystem.TryGetRefillableSolution(uid, out var container))
{
return;
}
var transfer = container.AvailableVolume;
if (TryComp<SolutionTransferComponent>(uid, out var solTrans))
{
transfer = solTrans.TransferAmount;
}
transfer = FixedPoint2.Min(transfer, targetSolution.DrainAvailable);
if (transfer > 0)
{
var drained = _solutionContainerSystem.Drain(target, targetSolution, transfer);
_solutionContainerSystem.TryAddSolution(uid, container, drained);
SoundSystem.Play(Filter.Pvs(uid), component.RefillSound.GetSound(), uid);
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-after-interact-refilled-message", ("owner", uid)),
uid, Filter.Entities(args.Target.Value));
}
}
private void OnGetInteractionVerbs(EntityUid uid, FireExtinguisherComponent component, GetInteractionVerbsEvent args)
{
if (!args.CanInteract)
return;
var verb = new Verb
{
Act = () => ToggleSafety(uid, args.User, component),
Text = Loc.GetString("fire-extinguisher-component-verb-text"),
};
args.Verbs.Add(verb);
}
private void OnSprayAttempt(EntityUid uid, FireExtinguisherComponent component, SprayAttemptEvent args)
{
if (component.HasSafety && component.Safety)
{
_popupSystem.PopupEntity(Loc.GetString("fire-extinguisher-component-safety-on-message"), uid,
Filter.Entities(args.User));
args.Cancel();
}
}
private void UpdateAppearance(EntityUid uid, FireExtinguisherComponent comp,
AppearanceComponent? appearance=null)
{
if (!Resolve(uid, ref appearance, false))
return;
if (comp.HasSafety)
{
appearance.SetData(FireExtinguisherVisuals.Safety, comp.Safety);
}
}
public void ToggleSafety(EntityUid uid, EntityUid user,
FireExtinguisherComponent? extinguisher = null)
{
if (!Resolve(uid, ref extinguisher))
return;
if (!_actionBlockerSystem.CanInteract(user) || !extinguisher.HasSafety)
return;
extinguisher.Safety = !extinguisher.Safety;
SoundSystem.Play(Filter.Pvs(uid), extinguisher.SafetySound.GetSound(), uid,
AudioHelpers.WithVariation(0.125f).WithVolume(-4f));
UpdateAppearance(uid, extinguisher);
}
}

View File

@@ -1,178 +1,39 @@
using System;
using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Cooldown;
using Content.Server.Fluids.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Vapor;
using Robust.Shared.Audio;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Server.Fluids.Components
{
namespace Content.Server.Fluids.Components;
[RegisterComponent]
internal sealed class SprayComponent : SharedSprayComponent, IAfterInteract
[Friend(typeof(SpraySystem))]
public sealed class SprayComponent : Component
{
public const float SprayDistance = 3f;
public const string SolutionName = "spray";
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[DataField("sprayDistance")] public float SprayDistance = 3f;
[DataField("transferAmount")] public FixedPoint2 TransferAmount = FixedPoint2.New(10);
[DataField("sprayVelocity")] public float SprayVelocity = 1.5f;
[DataField("sprayAliveTime")] public float SprayAliveTime = 0.75f;
[DataField("cooldownTime")] public float CooldownTime = 0.5f;
[DataField("transferAmount")]
private FixedPoint2 _transferAmount = FixedPoint2.New(10);
[DataField("sprayVelocity")]
private float _sprayVelocity = 1.5f;
[DataField("sprayAliveTime")]
private float _sprayAliveTime = 0.75f;
private TimeSpan _lastUseTime;
private TimeSpan _cooldownEnd;
[DataField("cooldownTime")]
private float _cooldownTime = 0.5f;
[DataField("sprayedPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
private string _vaporPrototype = "Vapor";
[DataField("vaporAmount")]
private int _vaporAmount = 1;
[DataField("vaporSpread")]
private float _vaporSpread = 90f;
[DataField("impulse")]
private float _impulse = 0f;
public string SprayedPrototype = "Vapor";
/// <summary>
/// The amount of solution to be sprayer from this solution when using it
/// </summary>
[ViewVariables]
public FixedPoint2 TransferAmount
{
get => _transferAmount;
set => _transferAmount = value;
}
[DataField("vaporAmount")] public int VaporAmount = 1;
/// <summary>
/// The speed at which the vapor starts when sprayed
/// </summary>
[ViewVariables]
public float Velocity
{
get => _sprayVelocity;
set => _sprayVelocity = value;
}
[DataField("vaporSpread")] public float VaporSpread = 90f;
[DataField("impulse")] public float Impulse;
[DataField("spraySound", required: true)]
public SoundSpecifier SpraySound { get; } = default!;
public FixedPoint2 CurrentVolume {
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution);
return solution?.CurrentVolume ?? FixedPoint2.Zero;
}
}
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
return false;
if (CurrentVolume <= 0)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("spray-component-is-empty-message"));
return true;
}
var curTime = _gameTiming.CurTime;
if(curTime < _cooldownEnd)
return true;
var playerPos = _entMan.GetComponent<TransformComponent>(eventArgs.User).Coordinates;
var entManager = _entMan;
if (eventArgs.ClickLocation.GetGridId(entManager) != playerPos.GetGridId(entManager))
return true;
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var contents))
return true;
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
var threeQuarters = direction * 0.75f;
var quarter = direction * 0.25f;
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 = _entMan.GetComponent<TransformComponent>(eventArgs.User).Coordinates.Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter);
if (target.TryDistance(_entMan, playerPos, out var distance) && distance > SprayDistance)
target = _entMan.GetComponent<TransformComponent>(eventArgs.User).Coordinates.Offset(diffNorm * SprayDistance);
var solution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner, contents, _transferAmount);
if (solution.TotalVolume <= FixedPoint2.Zero)
break;
var vapor = entManager.SpawnEntity(_vaporPrototype, playerPos.Offset(distance < 1 ? quarter : threeQuarters));
_entMan.GetComponent<TransformComponent>(vapor).LocalRotation = rotation;
if (_entMan.TryGetComponent(vapor, out AppearanceComponent? appearance))
{
appearance.SetData(VaporVisuals.Color, contents.Color.WithAlpha(1f));
appearance.SetData(VaporVisuals.State, true);
}
// Add the solution to the vapor and actually send the thing
var vaporComponent = _entMan.GetComponent<VaporComponent>(vapor);
var vaporSystem = EntitySystem.Get<VaporSystem>();
vaporSystem.TryAddSolution(vaporComponent, solution);
// impulse direction is defined in world-coordinates, not local coordinates
var impulseDirection = _entMan.GetComponent<TransformComponent>(vapor).WorldRotation.ToVec();
vaporSystem.Start(vaporComponent, impulseDirection, _sprayVelocity, target, _sprayAliveTime);
if (_impulse > 0f && _entMan.TryGetComponent(eventArgs.User, out IPhysBody? body))
{
body.ApplyLinearImpulse(-impulseDirection * _impulse);
}
}
SoundSystem.Play(Filter.Pvs(Owner), SpraySound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f));
_lastUseTime = curTime;
_cooldownEnd = _lastUseTime + TimeSpan.FromSeconds(_cooldownTime);
if (_entMan.TryGetComponent(Owner, out ItemCooldownComponent? cooldown))
{
cooldown.CooldownStart = _lastUseTime;
cooldown.CooldownEnd = _cooldownEnd;
}
return true;
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Cooldown;
using Content.Server.Extinguisher;
using Content.Server.Fluids.Components;
using Content.Server.Popups;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.Cooldown;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Vapor;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.Fluids.EntitySystems;
public sealed class SpraySystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly VaporSystem _vaporSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SprayComponent, AfterInteractEvent>(OnAfterInteract, after: new []{ typeof(FireExtinguisherSystem) });
}
private void OnAfterInteract(EntityUid uid, SprayComponent component, AfterInteractEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (!_solutionContainerSystem.TryGetSolution(uid, SprayComponent.SolutionName, out var solution))
return;
var ev = new SprayAttemptEvent(args.User);
RaiseLocalEvent(uid, ev, false);
if (ev.Cancelled)
return;
var curTime = _gameTiming.CurTime;
if (TryComp<ItemCooldownComponent>(uid, out var cooldown)
&& curTime < cooldown.CooldownEnd)
return;
if (solution.CurrentVolume <= 0)
{
_popupSystem.PopupEntity( Loc.GetString("spray-component-is-empty-message"),uid,
Filter.Entities(args.User));
return;
}
var playerPos = Transform(args.User).Coordinates;
if (args.ClickLocation.GetGridId(EntityManager) != playerPos.GetGridId(EntityManager))
return;
var direction = (args.ClickLocation.Position - playerPos.Position).Normalized;
var threeQuarters = direction * 0.75f;
var quarter = direction * 0.25f;
var amount = Math.Max(Math.Min((solution.CurrentVolume / component.TransferAmount).Int(), component.VaporAmount), 1);
var spread = component.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) = args.ClickLocation - playerPos;
var diffNorm = diffPos.Normalized;
var diffLength = diffPos.Length;
var target = Transform(args.User).Coordinates
.Offset((diffNorm + rotation.ToVec()).Normalized * diffLength + quarter);
if (target.TryDistance(EntityManager, playerPos, out var distance) && distance > component.SprayDistance)
target = Transform(args.User).Coordinates
.Offset(diffNorm * component.SprayDistance);
var newSolution = _solutionContainerSystem.SplitSolution(uid, solution, component.TransferAmount);
if (newSolution.TotalVolume <= FixedPoint2.Zero)
break;
var vapor = Spawn(component.SprayedPrototype,
playerPos.Offset(distance < 1 ? quarter : threeQuarters));
Transform(vapor).LocalRotation = rotation;
if (TryComp(vapor, out AppearanceComponent? appearance))
{
appearance.SetData(VaporVisuals.Color, solution.Color.WithAlpha(1f));
appearance.SetData(VaporVisuals.State, true);
}
// Add the solution to the vapor and actually send the thing
var vaporComponent = Comp<VaporComponent>(vapor);
_vaporSystem.TryAddSolution(vaporComponent, newSolution);
// impulse direction is defined in world-coordinates, not local coordinates
var impulseDirection = Transform(vapor).WorldRotation.ToVec();
_vaporSystem.Start(vaporComponent, impulseDirection, component.SprayVelocity, target, component.SprayAliveTime);
if (component.Impulse > 0f && TryComp(args.User, out PhysicsComponent? body))
body.ApplyLinearImpulse(-impulseDirection * component.Impulse);
}
SoundSystem.Play(Filter.Pvs(uid), component.SpraySound.GetSound(), uid, AudioHelpers.WithVariation(0.125f));
RaiseLocalEvent(uid,
new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(component.CooldownTime)));
}
}
public class SprayAttemptEvent : CancellableEntityEventArgs
{
public EntityUid User;
public SprayAttemptEvent(EntityUid user)
{
User = user;
}
}

View File

@@ -1,14 +1,8 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.Extinguisher
{
public abstract class SharedFireExtinguisherComponent : Component
{
public override string Name => "FireExtinguisher";
}
[Serializable, NetSerializable]
public enum FireExtinguisherVisuals : byte
{

View File

@@ -4,11 +4,6 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Fluids
{
public class SharedSprayComponent : Component
{
public override string Name => "Spray";
}
[Serializable, NetSerializable]
public enum SprayVisuals : byte
{

View File

@@ -1,2 +1,3 @@
fire-extinguisher-component-after-interact-refilled-message = {$owner} is now refilled
fire-extinguisher-component-safety-on-message = Its safety is on!
fire-extinguisher-component-verb-text = Toggle safety

View File

@@ -62,16 +62,6 @@
map: [ "enum.VaporVisualLayers.Base" ]
- type: Physics
bodyType: Dynamic
- type: Fixtures
fixtures:
- shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.25,0.25,0.25"
hard: false
mask:
- Impassable
- MobImpassable
- SmallImpassable
- type: Appearance
visuals:
- type: VaporVisualizer