Disposals refactor (#17803)
This commit is contained in:
@@ -1,145 +1,215 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Disposal.Components
|
||||
namespace Content.Shared.Disposal.Components;
|
||||
|
||||
[NetworkedComponent]
|
||||
public abstract class SharedDisposalUnitComponent : Component
|
||||
{
|
||||
[NetworkedComponent]
|
||||
public abstract class SharedDisposalUnitComponent : Component
|
||||
public const string ContainerId = "disposals";
|
||||
|
||||
/// <summary>
|
||||
/// Sounds played upon the unit flushing.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("soundFlush")]
|
||||
public SoundSpecifier? FlushSound = new SoundPathSpecifier("/Audio/Machines/disposalflush.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// State for this disposals unit.
|
||||
/// </summary>
|
||||
[DataField("state")]
|
||||
public DisposalsPressureState State;
|
||||
|
||||
// TODO: Just make this use vaulting.
|
||||
/// <summary>
|
||||
/// We'll track whatever just left disposals so we know what collision we need to ignore until they stop intersecting our BB.
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("recentlyEjected")]
|
||||
public readonly List<EntityUid> RecentlyEjected = new();
|
||||
|
||||
/// <summary>
|
||||
/// Next time the disposal unit will be pressurized.
|
||||
/// </summary>
|
||||
[DataField("nextPressurized", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextPressurized = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to flush a disposals unit manually.
|
||||
/// </summary>
|
||||
[DataField("flushTime")]
|
||||
public TimeSpan ManualFlushTime = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes from the start of a flush animation to return the sprite to normal.
|
||||
/// </summary>
|
||||
[DataField("flushDelay")]
|
||||
public TimeSpan FlushDelay = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField("mobsCanEnter")]
|
||||
public bool MobsCanEnter = true;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the pressure requirement for flushing.
|
||||
/// </summary>
|
||||
[DataField("disablePressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePressure = false;
|
||||
|
||||
/// <summary>
|
||||
/// Last time that an entity tried to exit this disposal unit.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan LastExitAttempt;
|
||||
|
||||
[DataField("autoEngageEnabled")]
|
||||
public bool AutomaticEngage = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("autoEngageTime")]
|
||||
public TimeSpan AutomaticEngageTime = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Delay from trying to enter disposals ourselves.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("entryDelay")]
|
||||
public float EntryDelay = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Delay from trying to shove someone else into disposals.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float DraggedEntryDelay = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Container of entities inside this disposal unit.
|
||||
/// </summary>
|
||||
[ViewVariables] public Container Container = default!;
|
||||
|
||||
// TODO: Network power shit instead fam.
|
||||
[ViewVariables, DataField("powered")]
|
||||
public bool Powered;
|
||||
|
||||
/// <summary>
|
||||
/// Was the disposals unit engaged for a manual flush.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("engaged")]
|
||||
public bool Engaged;
|
||||
|
||||
/// <summary>
|
||||
/// Next time this unit will flush. Is the lesser of <see cref="FlushDelay"/> and <see cref="AutomaticEngageTime"/>
|
||||
/// </summary>
|
||||
[ViewVariables, DataField("nextFlush", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? NextFlush;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum Visuals : byte
|
||||
{
|
||||
public const string ContainerId = "DisposalUnit";
|
||||
VisualState,
|
||||
Handle,
|
||||
Light
|
||||
}
|
||||
|
||||
// TODO: Could maybe turn the contact off instead far more cheaply as farseer (though not box2d) had support for it?
|
||||
// Need to suss it out.
|
||||
/// <summary>
|
||||
/// We'll track whatever just left disposals so we know what collision we need to ignore until they stop intersecting our BB.
|
||||
/// </summary>
|
||||
public List<EntityUid> RecentlyEjected = new();
|
||||
[Serializable, NetSerializable]
|
||||
public enum VisualState : byte
|
||||
{
|
||||
UnAnchored,
|
||||
Anchored,
|
||||
Flushing,
|
||||
Charging
|
||||
}
|
||||
|
||||
[DataField("flushTime", required: true)]
|
||||
public readonly float FlushTime;
|
||||
[Serializable, NetSerializable]
|
||||
public enum HandleState : byte
|
||||
{
|
||||
Normal,
|
||||
Engaged
|
||||
}
|
||||
|
||||
[DataField("mobsCanEnter")]
|
||||
public bool MobsCanEnter = true;
|
||||
[Serializable, NetSerializable]
|
||||
[Flags]
|
||||
public enum LightStates : byte
|
||||
{
|
||||
Off = 0,
|
||||
Charging = 1 << 0,
|
||||
Full = 1 << 1,
|
||||
Ready = 1 << 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the pressure requirement for flushing.
|
||||
/// </summary>
|
||||
[DataField("disablePressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePressure = false;
|
||||
[Serializable, NetSerializable]
|
||||
public enum UiButton : byte
|
||||
{
|
||||
Eject,
|
||||
Engage,
|
||||
Power
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum Visuals : byte
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable<DisposalUnitBoundUserInterfaceState>
|
||||
{
|
||||
public readonly string UnitName;
|
||||
public readonly string UnitState;
|
||||
public readonly TimeSpan FullPressureTime;
|
||||
public readonly bool Powered;
|
||||
public readonly bool Engaged;
|
||||
|
||||
public DisposalUnitBoundUserInterfaceState(string unitName, string unitState, TimeSpan fullPressureTime, bool powered,
|
||||
bool engaged)
|
||||
{
|
||||
VisualState,
|
||||
Handle,
|
||||
Light
|
||||
UnitName = unitName;
|
||||
UnitState = unitState;
|
||||
FullPressureTime = fullPressureTime;
|
||||
Powered = powered;
|
||||
Engaged = engaged;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VisualState : byte
|
||||
public bool Equals(DisposalUnitBoundUserInterfaceState? other)
|
||||
{
|
||||
UnAnchored,
|
||||
Anchored,
|
||||
Flushing,
|
||||
Charging
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum HandleState : byte
|
||||
{
|
||||
Normal,
|
||||
Engaged
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum LightStates : byte
|
||||
{
|
||||
Off = 0,
|
||||
Charging = 1 << 0,
|
||||
Full = 1 << 1,
|
||||
Ready = 1 << 2
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum UiButton : byte
|
||||
{
|
||||
Eject,
|
||||
Engage,
|
||||
Power
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum PressureState : byte
|
||||
{
|
||||
Ready,
|
||||
Pressurizing
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new DisposalUnitComponentState(RecentlyEjected);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class DisposalUnitComponentState : ComponentState
|
||||
{
|
||||
public List<EntityUid> RecentlyEjected;
|
||||
|
||||
public DisposalUnitComponentState(List<EntityUid> uids)
|
||||
{
|
||||
RecentlyEjected = uids;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DisposalUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable<DisposalUnitBoundUserInterfaceState>
|
||||
{
|
||||
public readonly string UnitName;
|
||||
public readonly string UnitState;
|
||||
public readonly TimeSpan FullPressureTime;
|
||||
public readonly bool Powered;
|
||||
public readonly bool Engaged;
|
||||
|
||||
public DisposalUnitBoundUserInterfaceState(string unitName, string unitState, TimeSpan fullPressureTime, bool powered,
|
||||
bool engaged)
|
||||
{
|
||||
UnitName = unitName;
|
||||
UnitState = unitState;
|
||||
FullPressureTime = fullPressureTime;
|
||||
Powered = powered;
|
||||
Engaged = engaged;
|
||||
}
|
||||
|
||||
public bool Equals(DisposalUnitBoundUserInterfaceState? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return UnitName == other.UnitName &&
|
||||
UnitState == other.UnitState &&
|
||||
Powered == other.Powered &&
|
||||
Engaged == other.Engaged &&
|
||||
FullPressureTime.Equals(other.FullPressureTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message data sent from client to server when a disposal unit ui button is pressed.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly UiButton Button;
|
||||
|
||||
public UiButtonPressedMessage(UiButton button)
|
||||
{
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DisposalUnitUiKey : byte
|
||||
{
|
||||
Key
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return UnitName == other.UnitName &&
|
||||
UnitState == other.UnitState &&
|
||||
Powered == other.Powered &&
|
||||
Engaged == other.Engaged &&
|
||||
FullPressureTime.Equals(other.FullPressureTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message data sent from client to server when a disposal unit ui button is pressed.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly UiButton Button;
|
||||
|
||||
public UiButtonPressedMessage(UiButton button)
|
||||
{
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DisposalUnitUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DisposalsPressureState : byte
|
||||
{
|
||||
Ready,
|
||||
|
||||
/// <summary>
|
||||
/// Has been flushed recently within FlushDelay.
|
||||
/// </summary>
|
||||
Flushed,
|
||||
|
||||
/// <summary>
|
||||
/// FlushDelay has elapsed and now we're transitioning back to Ready.
|
||||
/// </summary>
|
||||
Pressurizing
|
||||
}
|
||||
|
||||
@@ -8,94 +8,154 @@ using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Throwing;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Disposal
|
||||
namespace Content.Shared.Disposal;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DisposalDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DisposalDoAfterEvent : SimpleDoAfterEvent
|
||||
}
|
||||
|
||||
public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] protected readonly MetaDataSystem Metadata = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] protected readonly SharedJointSystem Joints = default!;
|
||||
|
||||
protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5);
|
||||
|
||||
// Percentage
|
||||
public const float PressurePerSecond = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current pressure state of a disposals unit.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public DisposalsPressureState GetState(EntityUid uid, SharedDisposalUnitComponent component, MetaDataComponent? metadata = null)
|
||||
{
|
||||
var nextPressure = Metadata.GetPauseTime(uid, metadata) + component.NextPressurized - GameTiming.CurTime;
|
||||
var pressurizeTime = 1f / PressurePerSecond;
|
||||
var pressurizeDuration = pressurizeTime - component.FlushDelay.TotalSeconds;
|
||||
|
||||
if (nextPressure.TotalSeconds > pressurizeDuration)
|
||||
{
|
||||
return DisposalsPressureState.Flushed;
|
||||
}
|
||||
|
||||
if (nextPressure > TimeSpan.Zero)
|
||||
{
|
||||
return DisposalsPressureState.Pressurizing;
|
||||
}
|
||||
|
||||
return DisposalsPressureState.Ready;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
public float GetPressure(EntityUid uid, SharedDisposalUnitComponent component, MetaDataComponent? metadata = null)
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
if (!Resolve(uid, ref metadata))
|
||||
return 0f;
|
||||
|
||||
protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5);
|
||||
var pauseTime = Metadata.GetPauseTime(uid, metadata);
|
||||
return MathF.Min(1f,
|
||||
(float) (GameTiming.CurTime - pauseTime - component.NextPressurized).TotalSeconds / PressurePerSecond);
|
||||
}
|
||||
|
||||
// Percentage
|
||||
public const float PressurePerSecond = 0.05f;
|
||||
protected void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component,
|
||||
ref PreventCollideEvent args)
|
||||
{
|
||||
var otherBody = args.OtherEntity;
|
||||
|
||||
public override void Initialize()
|
||||
// Items dropped shouldn't collide but items thrown should
|
||||
if (EntityManager.HasComponent<ItemComponent>(otherBody) &&
|
||||
!EntityManager.HasComponent<ThrownItemComponent>(otherBody))
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedDisposalUnitComponent, PreventCollideEvent>(OnPreventCollide);
|
||||
SubscribeLocalEvent<SharedDisposalUnitComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
SubscribeLocalEvent<SharedDisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component,
|
||||
ref PreventCollideEvent args)
|
||||
if (component.RecentlyEjected.Contains(otherBody))
|
||||
{
|
||||
var otherBody = args.OtherEntity;
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Items dropped shouldn't collide but items thrown should
|
||||
if (EntityManager.HasComponent<ItemComponent>(otherBody) &&
|
||||
!EntityManager.HasComponent<ThrownItemComponent>(otherBody))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
protected void OnCanDragDropOn(EntityUid uid, SharedDisposalUnitComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (component.RecentlyEjected.Contains(otherBody))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
args.CanDrop = CanInsert(uid, component, args.Dragged);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected void OnEmagged(EntityUid uid, SharedDisposalUnitComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
component.DisablePressure = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public virtual bool CanInsert(EntityUid uid, SharedDisposalUnitComponent component, EntityUid entity)
|
||||
{
|
||||
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored)
|
||||
return false;
|
||||
|
||||
// TODO: Probably just need a disposable tag.
|
||||
if (!EntityManager.TryGetComponent(entity, out ItemComponent? storable) &&
|
||||
!EntityManager.HasComponent<BodyComponent>(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnCanDragDropOn(EntityUid uid, SharedDisposalUnitComponent component, ref CanDropTargetEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
//Check if the entity is a mob and if mobs can be inserted
|
||||
if (TryComp<MobStateComponent>(entity, out var damageState) && !component.MobsCanEnter)
|
||||
return false;
|
||||
|
||||
args.CanDrop = CanInsert(uid, component, args.Dragged);
|
||||
args.Handled = true;
|
||||
if (EntityManager.TryGetComponent(entity, out PhysicsComponent? physics) &&
|
||||
(physics.CanCollide || storable != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnEmagged(EntityUid uid, SharedDisposalUnitComponent component, ref GotEmaggedEvent args)
|
||||
return damageState != null && (!component.MobsCanEnter || _mobState.IsDead(entity, damageState));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Proper prediction
|
||||
/// </summary>
|
||||
public abstract void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, SharedDisposalUnitComponent? disposal = null);
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class DisposalUnitComponentState : ComponentState
|
||||
{
|
||||
public SoundSpecifier? FlushSound;
|
||||
public DisposalsPressureState State;
|
||||
public TimeSpan NextPressurized;
|
||||
public TimeSpan AutomaticEngageTime;
|
||||
public TimeSpan? NextFlush;
|
||||
public bool Powered;
|
||||
public bool Engaged;
|
||||
public List<EntityUid> RecentlyEjected;
|
||||
|
||||
public DisposalUnitComponentState(SoundSpecifier? flushSound, DisposalsPressureState state, TimeSpan nextPressurized, TimeSpan automaticEngageTime, TimeSpan? nextFlush, bool powered, bool engaged, List<EntityUid> recentlyEjected)
|
||||
{
|
||||
component.DisablePressure = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public virtual bool CanInsert(EntityUid uid, SharedDisposalUnitComponent component, EntityUid entity)
|
||||
{
|
||||
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored)
|
||||
return false;
|
||||
|
||||
// TODO: Probably just need a disposable tag.
|
||||
if (!EntityManager.TryGetComponent(entity, out ItemComponent? storable) &&
|
||||
!EntityManager.HasComponent<BodyComponent>(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check if the entity is a mob and if mobs can be inserted
|
||||
if (TryComp<MobStateComponent>(entity, out var damageState) && !component.MobsCanEnter)
|
||||
return false;
|
||||
|
||||
if (EntityManager.TryGetComponent(entity, out PhysicsComponent? physics) &&
|
||||
(physics.CanCollide || storable != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return damageState != null && (!component.MobsCanEnter || _mobState.IsDead(entity, damageState));
|
||||
FlushSound = flushSound;
|
||||
State = state;
|
||||
NextPressurized = nextPressurized;
|
||||
AutomaticEngageTime = automaticEngageTime;
|
||||
NextFlush = nextFlush;
|
||||
Powered = powered;
|
||||
Engaged = engaged;
|
||||
RecentlyEjected = recentlyEjected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
using System.Threading;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Storage.Components
|
||||
namespace Content.Shared.Storage.Components;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DumpableDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DumpableDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lets you dump this container on the ground using a verb,
|
||||
/// or when interacting with it on a disposal unit or placeable surface.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class DumpableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long each item adds to the doafter.
|
||||
/// </summary>
|
||||
[DataField("delayPerItem"), AutoNetworkedField]
|
||||
public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
/// <summary>
|
||||
/// Lets you dump this container on the ground using a verb,
|
||||
/// or when interacting with it on a disposal unit or placeable surface.
|
||||
/// The multiplier modifier
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class DumpableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long each item adds to the doafter.
|
||||
/// </summary>
|
||||
[DataField("delayPerItem")] public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
/// <summary>
|
||||
/// The multiplier modifier
|
||||
/// </summary>
|
||||
[DataField("multiplier")] public float Multiplier = 1.0f;
|
||||
}
|
||||
[DataField("multiplier"), AutoNetworkedField]
|
||||
public float Multiplier = 1.0f;
|
||||
}
|
||||
|
||||
158
Content.Shared/Storage/EntitySystems/DumpableSystem.cs
Normal file
158
Content.Shared/Storage/EntitySystems/DumpableSystem.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Placeable;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Storage.EntitySystems;
|
||||
|
||||
public sealed class DumpableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedDisposalUnitSystem _disposalUnitSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(SharedEntityStorageSystem) });
|
||||
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
|
||||
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
|
||||
SubscribeLocalEvent<DumpableComponent, DumpableDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Handled)
|
||||
return;
|
||||
|
||||
if (!HasComp<SharedDisposalUnitComponent>(args.Target) && !HasComp<PlaceableSurfaceComponent>(args.Target))
|
||||
return;
|
||||
|
||||
StartDoAfter(uid, args.Target.Value, args.User, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (!TryComp<SharedStorageComponent>(uid, out var storage) || storage.StoredEntities == null || storage.StoredEntities.Count == 0)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
StartDoAfter(uid, args.Target, args.User, dumpable);//Had multiplier of 0.6f
|
||||
},
|
||||
Text = Loc.GetString("dump-verb-name"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/drop.svg.192dpi.png")),
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void AddUtilityVerbs(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (!TryComp<SharedStorageComponent>(uid, out var storage) || storage.StoredEntities == null || storage.StoredEntities.Count == 0)
|
||||
return;
|
||||
|
||||
if (HasComp<SharedDisposalUnitComponent>(args.Target))
|
||||
{
|
||||
UtilityVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
StartDoAfter(uid, args.Target, args.User, dumpable);
|
||||
},
|
||||
Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)),
|
||||
IconEntity = uid
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
if (HasComp<PlaceableSurfaceComponent>(args.Target))
|
||||
{
|
||||
UtilityVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
StartDoAfter(uid, args.Target, args.User, dumpable);
|
||||
},
|
||||
Text = Loc.GetString("dump-placeable-verb-name", ("surface", args.Target)),
|
||||
IconEntity = uid
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable)
|
||||
{
|
||||
if (!TryComp<SharedStorageComponent>(storageUid, out var storage) || storage.StoredEntities == null)
|
||||
return;
|
||||
|
||||
float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier;
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(userUid, delay, new DumpableDoAfterEvent(), storageUid, target: targetUid, used: storageUid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
NeedHand = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DumpableComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || !TryComp<SharedStorageComponent>(uid, out var storage) || storage.StoredEntities == null)
|
||||
return;
|
||||
|
||||
Queue<EntityUid> dumpQueue = new();
|
||||
foreach (var entity in storage.StoredEntities)
|
||||
{
|
||||
dumpQueue.Enqueue(entity);
|
||||
}
|
||||
|
||||
foreach (var entity in dumpQueue)
|
||||
{
|
||||
var transform = Transform(entity);
|
||||
_container.AttachParentToContainerOrGrid(transform);
|
||||
_transformSystem.SetLocalPositionRotation(transform, transform.LocalPosition + _random.NextVector2Box() / 2, _random.NextAngle());
|
||||
}
|
||||
|
||||
if (args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (HasComp<SharedDisposalUnitComponent>(args.Args.Target.Value))
|
||||
{
|
||||
foreach (var entity in dumpQueue)
|
||||
{
|
||||
_disposalUnitSystem.DoInsertDisposalUnit(args.Args.Target.Value, entity, args.Args.User);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<PlaceableSurfaceComponent>(args.Args.Target.Value))
|
||||
{
|
||||
var targetPos = _xformQuery.GetComponent(args.Args.Target.Value).LocalPosition;
|
||||
|
||||
foreach (var entity in dumpQueue)
|
||||
{
|
||||
_transformSystem.SetLocalPosition(entity, targetPos + _random.NextVector2Box() / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
@@ -261,6 +262,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
_joints.RecursiveClearJoints(toInsert);
|
||||
var inside = EnsureComp<InsideEntityStorageComponent>(toInsert);
|
||||
inside.Storage = container;
|
||||
return component.Contents.Insert(toInsert, EntityManager);
|
||||
|
||||
@@ -37,6 +37,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GrapplingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide);
|
||||
SubscribeLocalEvent<GrapplingProjectileComponent, JointRemovedEvent>(OnGrappleJointRemoved);
|
||||
SubscribeLocalEvent<CanWeightlessMoveEvent>(OnWeightlessMove);
|
||||
SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel);
|
||||
|
||||
@@ -45,6 +46,11 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
SubscribeLocalEvent<GrapplingGunComponent, HandDeselectedEvent>(OnGrapplingDeselected);
|
||||
}
|
||||
|
||||
private void OnGrappleJointRemoved(EntityUid uid, GrapplingProjectileComponent component, JointRemovedEvent args)
|
||||
{
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
private void OnGrapplingShot(EntityUid uid, GrapplingGunComponent component, ref GunShotEvent args)
|
||||
{
|
||||
foreach (var (shotUid, _) in args.Ammo)
|
||||
|
||||
Reference in New Issue
Block a user