Disposals refactor (#17803)

This commit is contained in:
metalgearsloth
2023-07-06 13:39:34 +10:00
committed by GitHub
parent 0790f31f21
commit 3eb93988e5
21 changed files with 1321 additions and 1261 deletions

View File

@@ -1,26 +0,0 @@
using Content.Shared.Disposal.Components;
using Robust.Client.Animations;
using Robust.Shared.Audio;
namespace Content.Client.Disposal.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedDisposalUnitComponent))]
public sealed class DisposalUnitComponent : SharedDisposalUnitComponent
{
[DataField("flushSound")]
public readonly SoundSpecifier? FlushSound;
public Animation FlushAnimation = default!;
public DisposalUnitBoundUserInterfaceState? UiState;
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not DisposalUnitComponentState state) return;
RecentlyEjected = state.RecentlyEjected;
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.Disposal.Components;
namespace Content.Client.Disposal;
[RegisterComponent]
public sealed class DisposalUnitComponent : SharedDisposalUnitComponent
{
}

View File

@@ -1,8 +1,12 @@
using Content.Client.Disposal.Components;
using Content.Client.Disposal.UI;
using Content.Shared.Disposal; using Content.Shared.Disposal;
using Content.Shared.Disposal.Components;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.Graphics;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Events;
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent; using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
namespace Content.Client.Disposal.Systems; namespace Content.Client.Disposal.Systems;
@@ -12,131 +16,65 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!; [Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!; [Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
private const string AnimationKey = "disposal_unit_animation";
private readonly List<EntityUid> _pressuringDisposals = new(); private const string AnimationKey = "disposal_unit_animation";
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<DisposalUnitComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<DisposalUnitComponent, PreventCollideEvent>(OnPreventCollide);
SubscribeLocalEvent<DisposalUnitComponent, CanDropTargetEvent>(OnCanDragDropOn);
SubscribeLocalEvent<DisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<DisposalUnitComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<DisposalUnitComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange); SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange);
} }
public void UpdateActive(EntityUid disposalEntity, bool active) private void OnHandleState(EntityUid uid, DisposalUnitComponent component, ref ComponentHandleState args)
{ {
if (active) if (args.Current is not DisposalUnitComponentState state)
{
if (!_pressuringDisposals.Contains(disposalEntity))
_pressuringDisposals.Add(disposalEntity);
}
else
{
_pressuringDisposals.Remove(disposalEntity);
}
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
for (var i = _pressuringDisposals.Count - 1; i >= 0; i--)
{
var disposal = _pressuringDisposals[i];
if (!UpdateInterface(disposal))
continue;
_pressuringDisposals.RemoveAt(i);
}
}
private bool UpdateInterface(EntityUid disposalUnit)
{
if (!TryComp(disposalUnit, out DisposalUnitComponent? component) || component.Deleted)
return true;
if (component.Deleted)
return true;
if (!TryComp(disposalUnit, out ClientUserInterfaceComponent? userInterface))
return true;
var state = component.UiState;
if (state == null)
return true;
foreach (var inter in userInterface.Interfaces)
{
if (inter is DisposalUnitBoundUserInterface boundInterface)
{
return boundInterface.UpdateWindowState(state) != false;
}
}
return true;
}
private void OnComponentInit(EntityUid uid, DisposalUnitComponent disposalUnit, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return; return;
if (!sprite.LayerMapTryGet(DisposalUnitVisualLayers.Base, out var baseLayerIdx)) component.FlushSound = state.FlushSound;
return; // Couldn't find the "normal" layer to return to after flush animation component.State = state.State;
component.NextPressurized = state.NextPressurized;
if (!sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayerIdx)) component.AutomaticEngageTime = state.AutomaticEngageTime;
return; // Couldn't find the flush animation layer component.NextFlush = state.NextFlush;
component.Powered = state.Powered;
var originalBaseState = sprite.LayerGetState(baseLayerIdx); component.Engaged = state.Engaged;
var flushState = sprite.LayerGetState(flushLayerIdx); component.RecentlyEjected.Clear();
component.RecentlyEjected.AddRange(state.RecentlyEjected);
// Setup the flush animation to play
disposalUnit.FlushAnimation = new Animation
{
Length = TimeSpan.FromSeconds(disposalUnit.FlushTime),
AnimationTracks = {
new AnimationTrackSpriteFlick {
LayerKey = DisposalUnitVisualLayers.BaseFlush,
KeyFrames = {
// Play the flush animation
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
// Return to base state (though, depending on how the unit is
// configured we might get an appearence change event telling
// us to go to charging state)
new AnimationTrackSpriteFlick.KeyFrame(originalBaseState, disposalUnit.FlushTime)
}
},
}
};
if (disposalUnit.FlushSound != null)
{
disposalUnit.FlushAnimation.AnimationTracks.Add(
new AnimationTrackPlaySound
{
KeyFrames = {
new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(disposalUnit.FlushSound), 0)
}
});
}
EnsureComp<AnimationPlayerComponent>(uid);
UpdateState(uid, disposalUnit, sprite);
} }
private void OnAppearanceChange(EntityUid uid, DisposalUnitComponent unit, ref AppearanceChangeEvent args) public override void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, SharedDisposalUnitComponent? disposal = null)
{
return;
}
private void OnComponentInit(EntityUid uid, SharedDisposalUnitComponent sharedDisposalUnit, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite) || !TryComp<AppearanceComponent>(uid, out var appearance))
return;
UpdateState(uid, sharedDisposalUnit, sprite, appearance);
}
private void OnAppearanceChange(EntityUid uid, SharedDisposalUnitComponent unit, ref AppearanceChangeEvent args)
{ {
if (args.Sprite == null) if (args.Sprite == null)
{
return; return;
}
UpdateState(uid, unit, args.Sprite); UpdateState(uid, unit, args.Sprite, args.Component);
} }
// Update visuals and tick animation /// <summary>
private void UpdateState(EntityUid uid, DisposalUnitComponent unit, SpriteComponent sprite) /// Update visuals and tick animation
/// </summary>
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
{ {
if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state)) if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state, appearance))
{ {
return; return;
} }
@@ -144,24 +82,69 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored); sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored); sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseCharging, state == VisualState.Charging); sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseCharging, state == VisualState.Charging);
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state == VisualState.Flushing); sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
// This is a transient state so not too worried about replaying in range.
if (state == VisualState.Flushing) if (state == VisualState.Flushing)
{ {
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey)) if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
{ {
_animationSystem.Play(uid, unit.FlushAnimation, AnimationKey); var flushState = new RSI.StateId("disposal-flush");
// Setup the flush animation to play
var anim = new Animation
{
Length = unit.FlushDelay,
AnimationTracks =
{
new AnimationTrackSpriteFlick
{
LayerKey = DisposalUnitVisualLayers.BaseFlush,
KeyFrames =
{
// Play the flush animation
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
// Return to base state (though, depending on how the unit is
// configured we might get an appearance change event telling
// us to go to charging state)
new AnimationTrackSpriteFlick.KeyFrame("disposal-charging", (float) unit.FlushDelay.TotalSeconds)
}
},
}
};
if (unit.FlushSound != null)
{
anim.AnimationTracks.Add(
new AnimationTrackPlaySound
{
KeyFrames =
{
new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(unit.FlushSound), 0)
}
});
}
_animationSystem.Play(uid, anim, AnimationKey);
} }
} }
else if (state == VisualState.Charging)
{
sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, new RSI.StateId("disposal-charging"));
}
else
{
_animationSystem.Stop(uid, AnimationKey);
}
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState)) if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
{ {
handleState = HandleState.Normal; handleState = HandleState.Normal;
} }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal); sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState)) if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
{ {
lightState = LightStates.Off; lightState = LightStates.Off;
} }

View File

@@ -1,6 +1,6 @@
using Content.Client.Disposal.Components;
using Content.Client.Disposal.Systems; using Content.Client.Disposal.Systems;
using Content.Shared.Disposal; using Content.Shared.Disposal;
using Content.Shared.Disposal.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -16,7 +16,9 @@ namespace Content.Client.Disposal.UI
{ {
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
// What are you doing here
public MailingUnitWindow? MailingUnitWindow; public MailingUnitWindow? MailingUnitWindow;
public DisposalUnitWindow? DisposalUnitWindow; public DisposalUnitWindow? DisposalUnitWindow;
public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
@@ -76,41 +78,27 @@ namespace Content.Client.Disposal.UI
return; return;
} }
var entityId = Owner.Owner;
if (!_entityManager.TryGetComponent(entityId, out DisposalUnitComponent? component))
return;
switch (state) switch (state)
{ {
case MailingUnitBoundUserInterfaceState mailingUnitState: case MailingUnitBoundUserInterfaceState mailingUnitState:
MailingUnitWindow?.UpdateState(mailingUnitState); MailingUnitWindow?.UpdateState(mailingUnitState);
component.UiState = mailingUnitState.DisposalState;
break; break;
case DisposalUnitBoundUserInterfaceState disposalUnitState: case DisposalUnitBoundUserInterfaceState disposalUnitState:
DisposalUnitWindow?.UpdateState(disposalUnitState); DisposalUnitWindow?.UpdateState(disposalUnitState);
component.UiState = disposalUnitState;
break; break;
} }
_entityManager.System<DisposalUnitSystem>().UpdateActive(entityId, true);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);
if (!disposing) return; if (!disposing)
return;
MailingUnitWindow?.Dispose(); MailingUnitWindow?.Dispose();
DisposalUnitWindow?.Dispose(); DisposalUnitWindow?.Dispose();
} }
public bool? UpdateWindowState(DisposalUnitBoundUserInterfaceState state)
{
return UiKey is DisposalUnitUiKey
? DisposalUnitWindow?.UpdateState(state)
: MailingUnitWindow?.UpdatePressure(state.FullPressureTime);
}
} }
} }

View File

@@ -2,6 +2,7 @@ using Content.Shared.Disposal.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent; using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
namespace Content.Client.Disposal.UI namespace Content.Client.Disposal.UI
@@ -12,6 +13,8 @@ namespace Content.Client.Disposal.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class DisposalUnitWindow : DefaultWindow public sealed partial class DisposalUnitWindow : DefaultWindow
{ {
public TimeSpan FullPressure;
public DisposalUnitWindow() public DisposalUnitWindow()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -22,14 +25,19 @@ namespace Content.Client.Disposal.UI
/// Update the interface state for the disposals window. /// Update the interface state for the disposals window.
/// </summary> /// </summary>
/// <returns>true if we should stop updating every frame.</returns> /// <returns>true if we should stop updating every frame.</returns>
public bool UpdateState(DisposalUnitBoundUserInterfaceState state) public void UpdateState(DisposalUnitBoundUserInterfaceState state)
{ {
Title = state.UnitName; Title = state.UnitName;
UnitState.Text = state.UnitState; UnitState.Text = state.UnitState;
Power.Pressed = state.Powered; Power.Pressed = state.Powered;
Engage.Pressed = state.Engaged; Engage.Pressed = state.Engaged;
FullPressure = state.FullPressureTime;
}
return !state.Powered || PressureBar.UpdatePressure(state.FullPressureTime); protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
PressureBar.UpdatePressure(FullPressure);
} }
} }
} }

View File

@@ -6,6 +6,7 @@ using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems; using Content.Server.Disposal.Unit.EntitySystems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Disposal; using Content.Shared.Disposal;
using Content.Shared.Disposal.Components;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Reflection; using Robust.Shared.Reflection;

View File

@@ -1,7 +0,0 @@
namespace Content.Server.Disposal.Unit.Components;
[RegisterComponent]
public sealed class ActiveDisposalUnitComponent : Component
{
}

View File

@@ -1,77 +1,13 @@
using System.Threading;
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Disposal.Components; using Content.Shared.Disposal.Components;
using Robust.Shared.Containers;
namespace Content.Server.Disposal.Unit.Components namespace Content.Server.Disposal.Unit.Components;
// GasMixture life.
[RegisterComponent]
public sealed class DisposalUnitComponent : SharedDisposalUnitComponent
{ {
[RegisterComponent] [DataField("air")]
[ComponentReference(typeof(SharedDisposalUnitComponent))] public GasMixture Air = new(Atmospherics.CellVolume);
public sealed class DisposalUnitComponent : SharedDisposalUnitComponent, IGasMixtureHolder
{
/// <summary>
/// Last time that an entity tried to exit this disposal unit.
/// </summary>
[ViewVariables]
public TimeSpan LastExitAttempt;
/// <summary>
/// The current pressure of this disposal unit.
/// Prevents it from flushing if it is not equal to or bigger than 1.
/// </summary>
[DataField("pressure")]
public float Pressure = 1f;
[DataField("autoEngageEnabled")]
public bool AutomaticEngage = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("autoEngageTime")]
public readonly TimeSpan AutomaticEngageTime = TimeSpan.FromSeconds(30);
[ViewVariables(VVAccess.ReadWrite)]
[DataField("flushDelay")]
public readonly TimeSpan FlushDelay = TimeSpan.FromSeconds(3);
/// <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>
/// Token used to cancel the automatic engage of a disposal unit
/// after an entity enters it.
/// </summary>
public CancellationTokenSource? AutomaticEngageToken;
/// <summary>
/// Container of entities inside this disposal unit.
/// </summary>
[ViewVariables] public Container Container = default!;
[ViewVariables] public bool Powered = false;
[ViewVariables] public PressureState State = PressureState.Ready;
[ViewVariables(VVAccess.ReadWrite)]
public bool Engaged { get; set; }
[DataField("air")]
public GasMixture Air { get; set; } = new(Atmospherics.CellVolume);
[ViewVariables]
public TimeSpan NextFlush = TimeSpan.MaxValue;
[ViewVariables]
public bool AutoFlushing = false;
}
} }

View File

@@ -4,6 +4,7 @@ using Content.Server.Disposal.Tube;
using Content.Server.Disposal.Tube.Components; using Content.Server.Disposal.Tube.Components;
using Content.Server.Disposal.Unit.Components; using Content.Server.Disposal.Unit.Components;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Disposal.Components;
using Content.Shared.Item; using Content.Shared.Item;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -13,8 +14,7 @@ using Robust.Shared.Physics.Systems;
namespace Content.Server.Disposal.Unit.EntitySystems namespace Content.Server.Disposal.Unit.EntitySystems
{ {
[UsedImplicitly] public sealed class DisposableSystem : EntitySystem
internal sealed class DisposableSystem : EntitySystem
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!; [Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;

View File

@@ -1,157 +0,0 @@
using Content.Shared.Interaction;
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems;
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
using Content.Shared.DoAfter;
using Content.Shared.Placeable;
using Content.Shared.Storage;
using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Storage.EntitySystems
{
public sealed class DumpableSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(StorageSystem) });
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<DisposalUnitComponent>(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<ServerStorageComponent>(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<ServerStorageComponent>(uid, out var storage) || storage.StoredEntities == null || storage.StoredEntities.Count == 0)
return;
if (HasComp<DisposalUnitComponent>(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);
transform.LocalPosition += _random.NextVector2Box() / 2;
transform.LocalRotation = _random.NextAngle();
}
if (args.Args.Target == null)
return;
if (HasComp<DisposalUnitComponent>(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))
{
foreach (var entity in dumpQueue)
{
Transform(entity).LocalPosition = Transform(args.Args.Target.Value).LocalPosition + _random.NextVector2Box() / 4;
}
}
}
}
}

View File

@@ -30,6 +30,7 @@ using Robust.Server.Player;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -52,10 +53,10 @@ namespace Content.Server.Storage.EntitySystems
[Dependency] private readonly SharedInteractionSystem _sharedInteractionSystem = default!; [Dependency] private readonly SharedInteractionSystem _sharedInteractionSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!; [Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!;
@@ -226,7 +227,9 @@ namespace Content.Server.Storage.EntitySystems
|| !itemQuery.HasComponent(entity) || !itemQuery.HasComponent(entity)
|| !CanInsert(uid, entity, out _, storageComp) || !CanInsert(uid, entity, out _, storageComp)
|| !_interactionSystem.InRangeUnobstructed(args.User, entity)) || !_interactionSystem.InRangeUnobstructed(args.User, entity))
{
continue; continue;
}
validStorables.Add(entity); validStorables.Add(entity);
} }

View File

@@ -1,145 +1,215 @@
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; 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 const string ContainerId = "disposals";
public abstract class SharedDisposalUnitComponent : Component
/// <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? [Serializable, NetSerializable]
// Need to suss it out. public enum VisualState : byte
/// <summary> {
/// We'll track whatever just left disposals so we know what collision we need to ignore until they stop intersecting our BB. UnAnchored,
/// </summary> Anchored,
public List<EntityUid> RecentlyEjected = new(); Flushing,
Charging
}
[DataField("flushTime", required: true)] [Serializable, NetSerializable]
public readonly float FlushTime; public enum HandleState : byte
{
Normal,
Engaged
}
[DataField("mobsCanEnter")] [Serializable, NetSerializable]
public bool MobsCanEnter = true; [Flags]
public enum LightStates : byte
{
Off = 0,
Charging = 1 << 0,
Full = 1 << 1,
Ready = 1 << 2
}
/// <summary> [Serializable, NetSerializable]
/// Removes the pressure requirement for flushing. public enum UiButton : byte
/// </summary> {
[DataField("disablePressure"), ViewVariables(VVAccess.ReadWrite)] Eject,
public bool DisablePressure = false; Engage,
Power
}
[Serializable, NetSerializable] [Serializable, NetSerializable]
public enum Visuals : byte 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, UnitName = unitName;
Handle, UnitState = unitState;
Light FullPressureTime = fullPressureTime;
Powered = powered;
Engaged = engaged;
} }
[Serializable, NetSerializable] public bool Equals(DisposalUnitBoundUserInterfaceState? other)
public enum VisualState : byte
{ {
UnAnchored, if (ReferenceEquals(null, other)) return false;
Anchored, if (ReferenceEquals(this, other)) return true;
Flushing, return UnitName == other.UnitName &&
Charging UnitState == other.UnitState &&
} Powered == other.Powered &&
Engaged == other.Engaged &&
[Serializable, NetSerializable] FullPressureTime.Equals(other.FullPressureTime);
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
} }
} }
/// <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
} }

View File

@@ -8,94 +8,154 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; 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 float GetPressure(EntityUid uid, SharedDisposalUnitComponent component, MetaDataComponent? metadata = null)
public abstract class SharedDisposalUnitSystem : EntitySystem
{ {
[Dependency] protected readonly IGameTiming GameTiming = default!; if (!Resolve(uid, ref metadata))
[Dependency] private readonly MobStateSystem _mobState = default!; 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 protected void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component,
public const float PressurePerSecond = 0.05f; 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(); args.Cancelled = true;
SubscribeLocalEvent<SharedDisposalUnitComponent, PreventCollideEvent>(OnPreventCollide); return;
SubscribeLocalEvent<SharedDisposalUnitComponent, CanDropTargetEvent>(OnCanDragDropOn);
SubscribeLocalEvent<SharedDisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
} }
private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component, if (component.RecentlyEjected.Contains(otherBody))
ref PreventCollideEvent args)
{ {
var otherBody = args.OtherEntity; args.Cancelled = true;
}
}
// Items dropped shouldn't collide but items thrown should protected void OnCanDragDropOn(EntityUid uid, SharedDisposalUnitComponent component, ref CanDropTargetEvent args)
if (EntityManager.HasComponent<ItemComponent>(otherBody) && {
!EntityManager.HasComponent<ThrownItemComponent>(otherBody)) if (args.Handled)
{ return;
args.Cancelled = true;
return;
}
if (component.RecentlyEjected.Contains(otherBody)) args.CanDrop = CanInsert(uid, component, args.Dragged);
{ args.Handled = true;
args.Cancelled = 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) //Check if the entity is a mob and if mobs can be inserted
{ if (TryComp<MobStateComponent>(entity, out var damageState) && !component.MobsCanEnter)
if (args.Handled) return false;
return;
args.CanDrop = CanInsert(uid, component, args.Dragged); if (EntityManager.TryGetComponent(entity, out PhysicsComponent? physics) &&
args.Handled = true; (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; FlushSound = flushSound;
args.Handled = true; State = state;
} NextPressurized = nextPressurized;
AutomaticEngageTime = automaticEngageTime;
public virtual bool CanInsert(EntityUid uid, SharedDisposalUnitComponent component, EntityUid entity) NextFlush = nextFlush;
{ Powered = powered;
if (!EntityManager.GetComponent<TransformComponent>(uid).Anchored) Engaged = engaged;
return false; RecentlyEjected = recentlyEjected;
// 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));
} }
} }
} }

View File

@@ -1,29 +1,30 @@
using System.Threading;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization; 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> /// <summary>
/// Lets you dump this container on the ground using a verb, /// The multiplier modifier
/// or when interacting with it on a disposal unit or placeable surface.
/// </summary> /// </summary>
[RegisterComponent] [DataField("multiplier"), AutoNetworkedField]
public sealed class DumpableComponent : Component public float Multiplier = 1.0f;
{
/// <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;
}
} }

View 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);
}
}
}
}

View File

@@ -34,6 +34,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedJointSystem _joints = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
@@ -261,6 +262,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
return true; return true;
} }
_joints.RecursiveClearJoints(toInsert);
var inside = EnsureComp<InsideEntityStorageComponent>(toInsert); var inside = EnsureComp<InsideEntityStorageComponent>(toInsert);
inside.Storage = container; inside.Storage = container;
return component.Contents.Insert(toInsert, EntityManager); return component.Contents.Insert(toInsert, EntityManager);

View File

@@ -37,6 +37,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<GrapplingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide); SubscribeLocalEvent<GrapplingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide);
SubscribeLocalEvent<GrapplingProjectileComponent, JointRemovedEvent>(OnGrappleJointRemoved);
SubscribeLocalEvent<CanWeightlessMoveEvent>(OnWeightlessMove); SubscribeLocalEvent<CanWeightlessMoveEvent>(OnWeightlessMove);
SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel); SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel);
@@ -45,6 +46,11 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
SubscribeLocalEvent<GrapplingGunComponent, HandDeselectedEvent>(OnGrapplingDeselected); 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) private void OnGrapplingShot(EntityUid uid, GrapplingGunComponent component, ref GunShotEvent args)
{ {
foreach (var (shotUid, _) in args.Ammo) foreach (var (shotUid, _) in args.Ammo)

View File

@@ -23,4 +23,6 @@ disposal-unit-thrown-missed = Missed!
# state # state
disposal-unit-state-Ready = Ready disposal-unit-state-Ready = Ready
# Yes I want it to always say Pressurizing
disposal-unit-state-Flushed = Pressurizing
disposal-unit-state-Pressurizing = Pressurizing disposal-unit-state-Pressurizing = Pressurizing

View File

@@ -178,7 +178,7 @@ entities:
parent: 37 parent: 37
type: Transform type: Transform
- containers: - containers:
DisposalUnit: !type:Container disposals: !type:Container
showEnts: False showEnts: False
occludes: True occludes: True
ents: ents:

View File

@@ -16,8 +16,6 @@
map: [ "enum.DisposalUnitVisualLayers.Unanchored" ] map: [ "enum.DisposalUnitVisualLayers.Unanchored" ]
- state: disposal - state: disposal
map: [ "enum.DisposalUnitVisualLayers.Base" ] map: [ "enum.DisposalUnitVisualLayers.Base" ]
- state: disposal-charging
map: [ "enum.DisposalUnitVisualLayers.BaseCharging" ]
- state: disposal-flush - state: disposal-flush
map: [ "enum.DisposalUnitVisualLayers.BaseFlush" ] map: [ "enum.DisposalUnitVisualLayers.BaseFlush" ]
- state: dispover-charge - state: dispover-charge
@@ -70,7 +68,7 @@
type: DisposalUnitBoundUserInterface type: DisposalUnitBoundUserInterface
- type: ContainerContainer - type: ContainerContainer
containers: containers:
DisposalUnit: !type:Container disposals: !type:Container
- type: StaticPrice - type: StaticPrice
price: 62 price: 62
- type: PowerSwitch - type: PowerSwitch
@@ -84,9 +82,6 @@
graph: DisposalMachine graph: DisposalMachine
node: disposal_unit node: disposal_unit
- type: DisposalUnit - type: DisposalUnit
flushSound:
path: /Audio/Machines/disposalflush.ogg
flushTime: 2
- type: UserInterface - type: UserInterface
interfaces: interfaces:
- key: enum.DisposalUnitUiKey.Key - key: enum.DisposalUnitUiKey.Key
@@ -123,9 +118,6 @@
- type: DisposalUnit - type: DisposalUnit
autoEngageEnabled: false autoEngageEnabled: false
mobsCanEnter: false mobsCanEnter: false
flushSound:
path: /Audio/Machines/disposalflush.ogg
flushTime: 2
- type: MailingUnit - type: MailingUnit
- type: DeviceNetwork - type: DeviceNetwork
deviceNetId: Wired deviceNetId: Wired