2021-08-12 13:40:38 +10:00
using System.Linq ;
using Content.Shared.ActionBlocker ;
2025-04-19 16:20:40 +10:00
using Content.Shared.Administration.Logs ;
using Content.Shared.Body.Components ;
using Content.Shared.Climbing.Systems ;
using Content.Shared.Containers ;
2023-01-04 00:45:35 -06:00
using Content.Shared.Database ;
2021-08-12 13:40:38 +10:00
using Content.Shared.Disposal.Components ;
2025-04-19 16:20:40 +10:00
using Content.Shared.Disposal.Unit.Events ;
2023-02-24 19:01:25 -05:00
using Content.Shared.DoAfter ;
2022-01-30 17:53:22 +01:00
using Content.Shared.DragDrop ;
2023-07-06 13:39:34 +10:00
using Content.Shared.Emag.Systems ;
2022-07-31 01:17:30 -03:00
using Content.Shared.Hands.Components ;
2022-03-17 20:13:31 +13:00
using Content.Shared.Hands.EntitySystems ;
2023-10-02 14:35:58 -05:00
using Content.Shared.IdentityManagement ;
2021-08-12 13:40:38 +10:00
using Content.Shared.Interaction ;
2021-12-30 22:56:10 +01:00
using Content.Shared.Item ;
2022-06-24 17:44:30 +10:00
using Content.Shared.Movement.Events ;
2022-07-31 01:17:30 -03:00
using Content.Shared.Popups ;
2024-08-25 22:18:42 +10:00
using Content.Shared.Power ;
2025-04-19 16:20:40 +10:00
using Content.Shared.Power.EntitySystems ;
2025-07-20 23:21:28 -04:00
using Content.Shared.Storage.Components ;
2025-04-19 16:20:40 +10:00
using Content.Shared.Throwing ;
2021-10-20 21:12:23 +02:00
using Content.Shared.Verbs ;
2025-04-19 16:20:40 +10:00
using Content.Shared.Whitelist ;
using Robust.Shared.Audio.Systems ;
2021-08-12 13:40:38 +10:00
using Robust.Shared.Containers ;
2023-04-07 11:21:12 -07:00
using Robust.Shared.Map.Components ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2023-07-06 13:39:34 +10:00
using Robust.Shared.Physics.Events ;
2025-04-19 16:20:40 +10:00
using Robust.Shared.Physics.Systems ;
using Robust.Shared.Serialization ;
using Robust.Shared.Timing ;
2023-02-26 18:48:57 +11:00
using Robust.Shared.Utility ;
2021-05-13 02:05:46 +02:00
2025-04-19 16:20:40 +10:00
namespace Content.Shared.Disposal.Unit ;
2023-07-06 13:39:34 +10:00
2025-04-19 16:20:40 +10:00
[Serializable, NetSerializable]
public sealed partial class DisposalDoAfterEvent : SimpleDoAfterEvent
2021-05-13 02:05:46 +02:00
{
2025-04-19 16:20:40 +10:00
}
public abstract class SharedDisposalUnitSystem : EntitySystem
{
[Dependency] protected readonly ActionBlockerSystem ActionBlockerSystem = default ! ;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default ! ;
[Dependency] protected readonly MetaDataSystem Metadata = default ! ;
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
[Dependency] protected readonly SharedAudioSystem Audio = default ! ;
[Dependency] protected readonly IGameTiming GameTiming = default ! ;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default ! ;
[Dependency] private readonly ClimbSystem _climb = default ! ;
[Dependency] protected readonly SharedContainerSystem Containers = default ! ;
[Dependency] protected readonly SharedJointSystem Joints = default ! ;
[Dependency] private readonly SharedPowerReceiverSystem _power = default ! ;
[Dependency] private readonly SharedDisposalTubeSystem _disposalTubeSystem = default ! ;
[Dependency] private readonly SharedPopupSystem _popupSystem = default ! ;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default ! ;
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default ! ;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default ! ;
[Dependency] private readonly SharedMapSystem _map = default ! ;
protected static TimeSpan ExitAttemptDelay = TimeSpan . FromSeconds ( 0.5 ) ;
// Percentage
public const float PressurePerSecond = 0.05f ;
2023-07-06 13:39:34 +10:00
public override void Initialize ( )
2021-05-13 02:05:46 +02:00
{
2023-07-06 13:39:34 +10:00
base . Initialize ( ) ;
2023-06-21 07:31:19 -07:00
2023-07-06 13:39:34 +10:00
SubscribeLocalEvent < DisposalUnitComponent , PreventCollideEvent > ( OnPreventCollide ) ;
SubscribeLocalEvent < DisposalUnitComponent , CanDropTargetEvent > ( OnCanDragDropOn ) ;
SubscribeLocalEvent < DisposalUnitComponent , GetVerbsEvent < InteractionVerb > > ( AddInsertVerb ) ;
SubscribeLocalEvent < DisposalUnitComponent , GetVerbsEvent < AlternativeVerb > > ( AddDisposalAltVerbs ) ;
SubscribeLocalEvent < DisposalUnitComponent , GetVerbsEvent < Verb > > ( AddClimbInsideVerb ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
SubscribeLocalEvent < DisposalUnitComponent , DisposalDoAfterEvent > ( OnDoAfter ) ;
2021-10-05 14:29:03 +11:00
2025-02-27 18:43:56 +01:00
SubscribeLocalEvent < DisposalUnitComponent , BeforeThrowInsertEvent > ( OnThrowInsert ) ;
2025-04-19 16:20:40 +10:00
SubscribeLocalEvent < DisposalUnitComponent , DisposalUnitComponent . UiButtonPressedMessage > ( OnUiButtonPressed ) ;
2021-10-20 21:12:23 +02:00
2025-04-19 16:20:40 +10:00
SubscribeLocalEvent < DisposalUnitComponent , GotEmaggedEvent > ( OnEmagged ) ;
SubscribeLocalEvent < DisposalUnitComponent , AnchorStateChangedEvent > ( OnAnchorChanged ) ;
SubscribeLocalEvent < DisposalUnitComponent , PowerChangedEvent > ( OnPowerChange ) ;
SubscribeLocalEvent < DisposalUnitComponent , ComponentInit > ( OnDisposalInit ) ;
SubscribeLocalEvent < DisposalUnitComponent , ActivateInWorldEvent > ( OnActivate ) ;
SubscribeLocalEvent < DisposalUnitComponent , AfterInteractUsingEvent > ( OnAfterInteractUsing ) ;
SubscribeLocalEvent < DisposalUnitComponent , DragDropTargetEvent > ( OnDragDropOn ) ;
SubscribeLocalEvent < DisposalUnitComponent , ContainerRelayMovementEntityEvent > ( OnMovement ) ;
2025-07-20 23:21:28 -04:00
SubscribeLocalEvent < DisposalUnitComponent , GetDumpableVerbEvent > ( OnGetDumpableVerb ) ;
SubscribeLocalEvent < DisposalUnitComponent , DumpEvent > ( OnDump ) ;
2023-07-06 13:39:34 +10:00
}
2022-01-30 17:53:22 +01:00
2025-04-19 16:20:40 +10:00
private void AddDisposalAltVerbs ( Entity < DisposalUnitComponent > ent , ref GetVerbsEvent < AlternativeVerb > args )
2023-07-06 13:39:34 +10:00
{
if ( ! args . CanAccess | | ! args . CanInteract )
return ;
2021-10-05 14:29:03 +11:00
2025-04-19 16:20:40 +10:00
var uid = ent . Owner ;
var component = ent . Comp ;
2023-07-06 13:39:34 +10:00
// Behavior for if the disposals bin has items in it
if ( component . Container . ContainedEntities . Count > 0 )
2021-10-05 14:29:03 +11:00
{
2023-07-06 13:39:34 +10:00
// Verbs to flush the unit
AlternativeVerb flushVerb = new ( )
{
Act = ( ) = > ManualEngage ( uid , component ) ,
Text = Loc . GetString ( "disposal-flush-verb-get-data-text" ) ,
Icon = new SpriteSpecifier . Texture ( new ( "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png" ) ) ,
Priority = 1 ,
} ;
args . Verbs . Add ( flushVerb ) ;
2021-10-05 14:29:03 +11:00
2023-07-06 13:39:34 +10:00
// Verb to eject the contents
AlternativeVerb ejectVerb = new ( )
2021-10-20 21:12:23 +02:00
{
2023-07-06 13:39:34 +10:00
Act = ( ) = > TryEjectContents ( uid , component ) ,
Category = VerbCategory . Eject ,
Text = Loc . GetString ( "disposal-eject-verb-get-data-text" )
2021-10-20 21:12:23 +02:00
} ;
2023-07-06 13:39:34 +10:00
args . Verbs . Add ( ejectVerb ) ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
private void AddInsertVerb ( EntityUid uid , DisposalUnitComponent component , GetVerbsEvent < InteractionVerb > args )
2023-07-06 13:39:34 +10:00
{
if ( ! args . CanAccess | | ! args . CanInteract | | args . Hands = = null | | args . Using = = null )
return ;
2022-02-07 14:54:54 +13:00
2025-04-19 16:20:40 +10:00
if ( ! ActionBlockerSystem . CanDrop ( args . User ) )
2023-07-06 13:39:34 +10:00
return ;
2022-02-07 14:54:54 +13:00
2023-07-06 13:39:34 +10:00
if ( ! CanInsert ( uid , component , args . Using . Value ) )
return ;
2022-02-07 14:54:54 +13:00
2023-07-06 13:39:34 +10:00
InteractionVerb insertVerb = new ( )
2021-10-20 21:12:23 +02:00
{
2023-07-06 13:39:34 +10:00
Text = Name ( args . Using . Value ) ,
Category = VerbCategory . Insert ,
Act = ( ) = >
{
2025-06-25 09:13:03 -04:00
_handsSystem . TryDropIntoContainer ( ( args . User , args . Hands ) , args . Using . Value , component . Container , checkActionBlocker : false ) ;
2025-04-19 16:20:40 +10:00
_adminLog . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}" ) ;
2023-07-06 13:39:34 +10:00
AfterInsert ( uid , component , args . Using . Value , args . User ) ;
}
} ;
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
args . Verbs . Add ( insertVerb ) ;
}
2023-02-24 19:01:25 -05:00
2025-04-19 16:20:40 +10:00
private void OnDoAfter ( EntityUid uid , DisposalUnitComponent component , DoAfterEvent args )
2023-07-06 13:39:34 +10:00
{
if ( args . Handled | | args . Cancelled | | args . Args . Target = = null | | args . Args . Used = = null )
return ;
2021-10-20 21:12:23 +02:00
2024-02-15 21:52:52 +01:00
AfterInsert ( uid , component , args . Args . Target . Value , args . Args . User , doInsert : true ) ;
2022-05-03 23:00:22 -04:00
2023-07-06 13:39:34 +10:00
args . Handled = true ;
}
2022-05-03 23:00:22 -04:00
2025-02-27 18:43:56 +01:00
private void OnThrowInsert ( Entity < DisposalUnitComponent > ent , ref BeforeThrowInsertEvent args )
{
if ( ! CanInsert ( ent , ent , args . ThrownEntity ) )
args . Cancelled = true ;
}
2023-07-06 13:39:34 +10:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
var query = EntityQueryEnumerator < DisposalUnitComponent , MetaDataComponent > ( ) ;
2023-07-06 13:39:34 +10:00
while ( query . MoveNext ( out var uid , out var unit , out var metadata ) )
2022-01-30 17:53:22 +01:00
{
2025-04-19 16:20:40 +10:00
Update ( uid , unit , metadata ) ;
2022-01-30 17:53:22 +01:00
}
2023-07-06 13:39:34 +10:00
}
2022-01-30 17:53:22 +01:00
2025-04-19 16:20:40 +10:00
// TODO: This should just use the same thing as entity storage?
private void OnMovement ( EntityUid uid , DisposalUnitComponent component , ref ContainerRelayMovementEntityEvent args )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
var currentTime = GameTiming . CurTime ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
if ( ! ActionBlockerSystem . CanMove ( args . Entity ) )
return ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
if ( ! TryComp ( args . Entity , out HandsComponent ? hands ) | |
hands . Count = = 0 | |
currentTime < component . LastExitAttempt + ExitAttemptDelay )
return ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
Dirty ( uid , component ) ;
component . LastExitAttempt = currentTime ;
Remove ( uid , component , args . Entity ) ;
UpdateUI ( ( uid , component ) ) ;
2023-07-06 13:39:34 +10:00
}
2022-04-24 13:54:25 +10:00
2025-04-19 16:20:40 +10:00
private void OnActivate ( EntityUid uid , DisposalUnitComponent component , ActivateInWorldEvent args )
2023-07-06 13:39:34 +10:00
{
2024-05-31 16:26:19 -04:00
if ( args . Handled | | ! args . Complex )
return ;
2023-07-06 13:39:34 +10:00
args . Handled = true ;
2025-04-19 16:20:40 +10:00
_ui . TryToggleUi ( uid , DisposalUnitComponent . DisposalUnitUiKey . Key , args . User ) ;
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
private void OnAfterInteractUsing ( EntityUid uid , DisposalUnitComponent component , AfterInteractUsingEvent args )
2023-07-06 13:39:34 +10:00
{
if ( args . Handled | | ! args . CanReach )
return ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( ! HasComp < HandsComponent > ( args . User ) )
{
return ;
2021-05-13 02:05:46 +02:00
}
2023-07-06 13:39:34 +10:00
if ( ! CanInsert ( uid , component , args . Used ) | | ! _handsSystem . TryDropIntoContainer ( args . User , args . Used , component . Container ) )
2021-05-13 02:05:46 +02:00
{
2023-07-06 13:39:34 +10:00
return ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
_adminLog . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Used)} into {ToPrettyString(uid)}" ) ;
2023-07-06 13:39:34 +10:00
AfterInsert ( uid , component , args . Used , args . User ) ;
args . Handled = true ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
protected virtual void OnDisposalInit ( Entity < DisposalUnitComponent > ent , ref ComponentInit args )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
ent . Comp . Container = Containers . EnsureContainer < Container > ( ent , DisposalUnitComponent . ContainerId ) ;
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
private void OnPowerChange ( EntityUid uid , DisposalUnitComponent component , ref PowerChangedEvent args )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
if ( ! component . Running )
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2023-07-06 13:39:34 +10:00
UpdateVisualState ( uid , component ) ;
if ( ! args . Powered )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = null ;
2024-03-19 23:27:02 -04:00
Dirty ( uid , component ) ;
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
}
2024-09-20 12:58:26 +01:00
if ( component . Engaged )
2021-08-12 13:40:38 +10:00
{
2024-09-20 12:58:26 +01:00
// Run ManualEngage to recalculate a new flush time
ManualEngage ( uid , component ) ;
2023-07-06 13:39:34 +10:00
}
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
private void OnAnchorChanged ( EntityUid uid , DisposalUnitComponent component , ref AnchorStateChangedEvent args )
2023-07-06 13:39:34 +10:00
{
if ( Terminating ( uid ) )
return ;
2022-01-30 17:53:22 +01:00
2023-07-06 13:39:34 +10:00
UpdateVisualState ( uid , component ) ;
if ( ! args . Anchored )
2023-02-03 22:26:50 +00:00
TryEjectContents ( uid , component ) ;
2023-07-06 13:39:34 +10:00
}
2022-01-30 17:53:22 +01:00
2025-04-19 16:20:40 +10:00
private void OnDragDropOn ( EntityUid uid , DisposalUnitComponent component , ref DragDropTargetEvent args )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
args . Handled = TryInsert ( uid , args . Dragged , args . User ) ;
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
protected virtual void UpdateUI ( Entity < DisposalUnitComponent > entity )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
/// <summary>
/// Returns the estimated time when the disposal unit will be back to full pressure.
/// </summary>
public TimeSpan EstimatedFullPressure ( EntityUid uid , DisposalUnitComponent component )
{
if ( component . NextPressurized < GameTiming . CurTime )
return TimeSpan . Zero ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
return component . NextPressurized ;
}
public bool CanFlush ( EntityUid unit , DisposalUnitComponent component )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
return GetState ( unit , component ) = = DisposalsPressureState . Ready
& & _power . IsPowered ( unit )
& & Comp < TransformComponent > ( unit ) . Anchored ;
}
public void Remove ( EntityUid uid , DisposalUnitComponent component , EntityUid toRemove )
{
if ( GameTiming . ApplyingState )
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
if ( ! Containers . Remove ( toRemove , component . Container ) )
return ;
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
if ( component . Container . ContainedEntities . Count = = 0 )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
// If not manually engaged then reset the flushing entirely.
if ( ! component . Engaged )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = null ;
2025-04-19 16:20:40 +10:00
Dirty ( uid , component ) ;
UpdateUI ( ( uid , component ) ) ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
}
2025-04-19 16:20:40 +10:00
_climb . Climb ( toRemove , toRemove , uid , silent : true ) ;
UpdateVisualState ( uid , component ) ;
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
public void UpdateVisualState ( EntityUid uid , DisposalUnitComponent component , bool flush = false )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
if ( ! TryComp ( uid , out AppearanceComponent ? appearance ) )
{
return ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
if ( ! Transform ( uid ) . Anchored )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . VisualState , DisposalUnitComponent . VisualState . UnAnchored , appearance ) ;
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . Handle , DisposalUnitComponent . HandleState . Normal , appearance ) ;
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . Light , DisposalUnitComponent . LightStates . Off , appearance ) ;
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
}
2025-04-19 16:20:40 +10:00
var state = GetState ( uid , component ) ;
switch ( state )
2021-10-20 21:12:23 +02:00
{
2025-04-19 16:20:40 +10:00
case DisposalsPressureState . Flushed :
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . VisualState , DisposalUnitComponent . VisualState . OverlayFlushing , appearance ) ;
break ;
case DisposalsPressureState . Pressurizing :
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . VisualState , DisposalUnitComponent . VisualState . OverlayCharging , appearance ) ;
break ;
case DisposalsPressureState . Ready :
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . VisualState , DisposalUnitComponent . VisualState . Anchored , appearance ) ;
break ;
2023-07-06 13:39:34 +10:00
}
2022-07-31 01:17:30 -03:00
2025-04-19 16:20:40 +10:00
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . Handle , component . Engaged
? DisposalUnitComponent . HandleState . Engaged
: DisposalUnitComponent . HandleState . Normal , appearance ) ;
2021-10-20 21:12:23 +02:00
2025-04-19 16:20:40 +10:00
if ( ! _power . IsPowered ( uid ) )
{
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . Light , DisposalUnitComponent . LightStates . Off , appearance ) ;
return ;
}
var lightState = DisposalUnitComponent . LightStates . Off ;
2021-10-20 21:12:23 +02:00
2025-04-19 16:20:40 +10:00
if ( component . Container . ContainedEntities . Count > 0 )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
lightState | = DisposalUnitComponent . LightStates . Full ;
2021-10-20 21:12:23 +02:00
}
2025-04-19 16:20:40 +10:00
if ( state is DisposalsPressureState . Pressurizing or DisposalsPressureState . Flushed )
2021-08-12 13:40:38 +10:00
{
2025-04-19 16:20:40 +10:00
lightState | = DisposalUnitComponent . LightStates . Charging ;
}
else
{
lightState | = DisposalUnitComponent . LightStates . Ready ;
}
_appearance . SetData ( uid , DisposalUnitComponent . Visuals . Light , lightState , appearance ) ;
}
/// <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 , DisposalUnitComponent 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 ;
}
public float GetPressure ( EntityUid uid , DisposalUnitComponent component , MetaDataComponent ? metadata = null )
{
if ( ! Resolve ( uid , ref metadata ) )
return 0f ;
var pauseTime = Metadata . GetPauseTime ( uid , metadata ) ;
return MathF . Min ( 1f ,
( float ) ( GameTiming . CurTime - pauseTime - component . NextPressurized ) . TotalSeconds / PressurePerSecond ) ;
}
protected void OnPreventCollide ( EntityUid uid , DisposalUnitComponent component ,
ref PreventCollideEvent args )
{
var otherBody = args . OtherEntity ;
// Items dropped shouldn't collide but items thrown should
if ( HasComp < ItemComponent > ( otherBody ) & & ! HasComp < ThrownItemComponent > ( otherBody ) )
{
args . Cancelled = true ;
2023-07-06 13:39:34 +10:00
}
2025-04-19 16:20:40 +10:00
}
2022-08-14 07:57:25 +02:00
2025-04-19 16:20:40 +10:00
protected void OnCanDragDropOn ( EntityUid uid , DisposalUnitComponent component , ref CanDropTargetEvent args )
{
if ( args . Handled )
return ;
args . CanDrop = CanInsert ( uid , component , args . Dragged ) ;
args . Handled = true ;
}
protected void OnEmagged ( EntityUid uid , DisposalUnitComponent component , ref GotEmaggedEvent args )
{
component . DisablePressure = true ;
args . Handled = true ;
}
public virtual bool CanInsert ( EntityUid uid , DisposalUnitComponent component , EntityUid entity )
{
// TODO: All of the below should be using the EXISTING EVENT
if ( ! Containers . CanInsert ( entity , component . Container ) )
return false ;
if ( ! Transform ( uid ) . Anchored )
return false ;
var storable = HasComp < ItemComponent > ( entity ) ;
if ( ! storable & & ! HasComp < BodyComponent > ( entity ) )
return false ;
if ( _whitelistSystem . IsBlacklistPass ( component . Blacklist , entity ) | |
_whitelistSystem . IsWhitelistFail ( component . Whitelist , entity ) )
return false ;
if ( TryComp < PhysicsComponent > ( entity , out var physics ) & & ( physics . CanCollide ) | | storable )
return true ;
else
return false ;
}
public void DoInsertDisposalUnit ( EntityUid uid ,
EntityUid toInsert ,
EntityUid user ,
DisposalUnitComponent ? disposal = null )
{
if ( ! Resolve ( uid , ref disposal ) )
return ;
if ( ! Containers . Insert ( toInsert , disposal . Container ) )
return ;
_adminLog . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(user):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(uid)}" ) ;
AfterInsert ( uid , disposal , toInsert , user ) ;
}
public virtual void AfterInsert ( EntityUid uid ,
DisposalUnitComponent component ,
EntityUid inserted ,
EntityUid ? user = null ,
bool doInsert = false )
{
Audio . PlayPredicted ( component . InsertSound , uid , user : user ) ;
if ( doInsert & & ! Containers . Insert ( inserted , component . Container ) )
return ;
if ( user ! = inserted & & user ! = null )
_adminLog . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(user.Value):player} inserted {ToPrettyString(inserted)} into {ToPrettyString(uid)}" ) ;
QueueAutomaticEngage ( uid , component ) ;
_ui . CloseUi ( uid , DisposalUnitComponent . DisposalUnitUiKey . Key , inserted ) ;
// Maybe do pullable instead? Eh still fine.
Joints . RecursiveClearJoints ( inserted ) ;
UpdateVisualState ( uid , component ) ;
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
public bool TryInsert ( EntityUid unitId , EntityUid toInsertId , EntityUid ? userId , DisposalUnitComponent ? unit = null )
{
if ( ! Resolve ( unitId , ref unit ) )
return false ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( userId . HasValue & & ! HasComp < HandsComponent > ( userId ) & & toInsertId ! = userId ) // Mobs like mouse can Jump inside even with no hands
{
_popupSystem . PopupEntity ( Loc . GetString ( "disposal-unit-no-hands" ) , userId . Value , userId . Value , PopupType . SmallCaution ) ;
return false ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( ! CanInsert ( unitId , unit , toInsertId ) )
return false ;
2021-08-12 13:40:38 +10:00
2023-10-02 14:35:58 -05:00
bool insertingSelf = userId = = toInsertId ;
var delay = insertingSelf ? unit . EntryDelay : unit . DraggedEntryDelay ;
if ( userId ! = null & & ! insertingSelf )
2024-08-17 14:29:04 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "disposal-unit-being-inserted" , ( "user" , Identity . Entity ( ( EntityUid ) userId , EntityManager ) ) ) , toInsertId , toInsertId , PopupType . Large ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( delay < = 0 | | userId = = null )
{
2024-02-15 21:52:52 +01:00
AfterInsert ( unitId , unit , toInsertId , userId , doInsert : true ) ;
2023-07-06 13:39:34 +10:00
return true ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
// Can't check if our target AND disposals moves currently so we'll just check target.
// if you really want to check if disposals moves then add a predicate.
2023-09-11 09:42:41 +10:00
var doAfterArgs = new DoAfterArgs ( EntityManager , userId . Value , delay , new DisposalDoAfterEvent ( ) , unitId , target : toInsertId , used : unitId )
2023-07-06 13:39:34 +10:00
{
BreakOnDamage = true ,
2024-03-19 12:09:00 +02:00
BreakOnMove = true ,
2024-08-08 04:39:46 -07:00
NeedHand = false ,
2023-07-06 13:39:34 +10:00
} ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
_doAfterSystem . TryStartDoAfter ( doAfterArgs ) ;
return true ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
private void UpdateState ( EntityUid uid , DisposalsPressureState state , DisposalUnitComponent component , MetaDataComponent metadata )
{
if ( component . State = = state )
return ;
component . State = state ;
UpdateVisualState ( uid , component ) ;
Dirty ( uid , component , metadata ) ;
if ( state = = DisposalsPressureState . Ready )
{
component . NextPressurized = TimeSpan . Zero ;
// Manually engaged
if ( component . Engaged )
{
component . NextFlush = GameTiming . CurTime + component . ManualFlushTime ;
}
else if ( component . Container . ContainedEntities . Count > 0 )
{
component . NextFlush = GameTiming . CurTime + component . AutomaticEngageTime ;
}
else
{
component . NextFlush = null ;
}
}
}
/// <summary>
/// Work out if we can stop updating this disposals component i.e. full pressure and nothing colliding.
/// </summary>
private void Update ( EntityUid uid , DisposalUnitComponent component , MetaDataComponent metadata )
{
var state = GetState ( uid , component , metadata ) ;
// Pressurizing, just check if we need a state update.
if ( component . NextPressurized > GameTiming . CurTime )
{
UpdateState ( uid , state , component , metadata ) ;
return ;
}
if ( component . NextFlush ! = null )
{
if ( component . NextFlush . Value < GameTiming . CurTime )
{
TryFlush ( uid , component ) ;
}
}
UpdateState ( uid , state , component , metadata ) ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
public bool TryFlush ( EntityUid uid , DisposalUnitComponent component )
2023-07-06 13:39:34 +10:00
{
if ( ! CanFlush ( uid , component ) )
{
return false ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
if ( component . NextFlush ! = null )
component . NextFlush = component . NextFlush . Value + component . AutomaticEngageTime ;
2022-08-14 07:57:25 +02:00
2023-07-06 13:39:34 +10:00
var beforeFlushArgs = new BeforeDisposalFlushEvent ( ) ;
RaiseLocalEvent ( uid , beforeFlushArgs ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( beforeFlushArgs . Cancelled )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
Disengage ( uid , component ) ;
return false ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
var xform = Transform ( uid ) ;
if ( ! TryComp ( xform . GridUid , out MapGridComponent ? grid ) )
return false ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
var coords = xform . Coordinates ;
2024-08-17 14:29:04 -07:00
var entry = _map . GetLocal ( xform . GridUid . Value , grid , coords )
2025-04-19 16:20:40 +10:00
. FirstOrDefault ( HasComp < Tube . DisposalEntryComponent > ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( entry = = default | | component is not DisposalUnitComponent sDisposals )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . Engaged = false ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2023-09-04 15:11:34 +01:00
Dirty ( uid , component ) ;
2023-07-06 13:39:34 +10:00
return false ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
HandleAir ( uid , sDisposals , xform ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
_disposalTubeSystem . TryInsert ( entry , sDisposals , beforeFlushArgs . Tags ) ;
2021-08-12 13:40:38 +10:00
2023-09-04 15:11:34 +01:00
component . NextPressurized = GameTiming . CurTime ;
if ( ! component . DisablePressure )
component . NextPressurized + = TimeSpan . FromSeconds ( 1f / PressurePerSecond ) ;
2023-07-06 13:39:34 +10:00
component . Engaged = false ;
// stop queuing NOW
component . NextFlush = null ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
UpdateVisualState ( uid , component , true ) ;
2023-09-04 15:11:34 +01:00
Dirty ( uid , component ) ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
return true ;
}
2021-08-12 13:40:38 +10:00
2025-04-19 16:20:40 +10:00
protected virtual void HandleAir ( EntityUid uid , DisposalUnitComponent component , TransformComponent xform )
2023-07-06 13:39:34 +10:00
{
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
}
2025-04-19 16:20:40 +10:00
public void ManualEngage ( EntityUid uid , DisposalUnitComponent component , MetaDataComponent ? metadata = null )
2023-07-06 13:39:34 +10:00
{
component . Engaged = true ;
UpdateVisualState ( uid , component ) ;
2023-09-04 15:11:34 +01:00
Dirty ( uid , component ) ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2023-02-24 19:01:25 -05:00
2023-07-06 13:39:34 +10:00
if ( ! CanFlush ( uid , component ) )
return ;
2023-02-24 19:01:25 -05:00
2023-07-06 13:39:34 +10:00
if ( ! Resolve ( uid , ref metadata ) )
return ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
var pauseTime = Metadata . GetPauseTime ( uid , metadata ) ;
var nextEngage = GameTiming . CurTime - pauseTime + component . ManualFlushTime ;
component . NextFlush = TimeSpan . FromSeconds ( Math . Min ( ( component . NextFlush ? ? TimeSpan . MaxValue ) . TotalSeconds , nextEngage . TotalSeconds ) ) ;
}
2025-04-19 16:20:40 +10:00
public void Disengage ( EntityUid uid , DisposalUnitComponent component )
2023-07-06 13:39:34 +10:00
{
component . Engaged = false ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( component . Container . ContainedEntities . Count = = 0 )
{
component . NextFlush = null ;
2021-07-16 05:22:29 +02:00
}
2023-07-06 13:39:34 +10:00
UpdateVisualState ( uid , component ) ;
2023-09-04 15:11:34 +01:00
Dirty ( uid , component ) ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2022-09-11 16:50:59 +10:00
}
2022-08-14 07:57:25 +02:00
2022-09-11 16:50:59 +10:00
/// <summary>
2023-07-06 13:39:34 +10:00
/// Remove all entities currently in the disposal unit.
2022-09-11 16:50:59 +10:00
/// </summary>
2025-04-19 16:20:40 +10:00
public void TryEjectContents ( EntityUid uid , DisposalUnitComponent component )
2022-09-11 16:50:59 +10:00
{
2023-07-06 13:39:34 +10:00
foreach ( var entity in component . Container . ContainedEntities . ToArray ( ) )
{
Remove ( uid , component , entity ) ;
}
2022-08-14 07:57:25 +02:00
2023-07-06 13:39:34 +10:00
if ( ! component . Engaged )
2022-08-14 07:57:25 +02:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = null ;
2023-08-31 11:08:23 +10:00
Dirty ( uid , component ) ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2022-08-14 07:57:25 +02:00
}
2021-05-13 02:05:46 +02:00
}
2022-09-11 16:50:59 +10:00
/// <summary>
2023-07-06 13:39:34 +10:00
/// If something is inserted (or the likes) then we'll queue up an automatic flush in the future.
2022-09-11 16:50:59 +10:00
/// </summary>
2025-04-19 16:20:40 +10:00
public void QueueAutomaticEngage ( EntityUid uid , DisposalUnitComponent component , MetaDataComponent ? metadata = null )
2022-09-11 16:50:59 +10:00
{
2025-04-19 16:20:40 +10:00
if ( component . Deleted | | ! component . AutomaticEngage | | ! _power . IsPowered ( uid ) & & component . Container . ContainedEntities . Count = = 0 )
2023-07-06 13:39:34 +10:00
{
return ;
}
var pauseTime = Metadata . GetPauseTime ( uid , metadata ) ;
var automaticTime = GameTiming . CurTime + component . AutomaticEngageTime - pauseTime ;
var flushTime = TimeSpan . FromSeconds ( Math . Min ( ( component . NextFlush ? ? TimeSpan . MaxValue ) . TotalSeconds , automaticTime . TotalSeconds ) ) ;
component . NextFlush = flushTime ;
2024-03-19 23:27:02 -04:00
Dirty ( uid , component ) ;
2025-04-19 16:20:40 +10:00
UpdateUI ( ( uid , component ) ) ;
2022-09-11 16:50:59 +10:00
}
2023-07-06 13:39:34 +10:00
2025-04-19 16:20:40 +10:00
private void OnUiButtonPressed ( EntityUid uid , DisposalUnitComponent component , DisposalUnitComponent . UiButtonPressedMessage args )
2023-07-06 13:39:34 +10:00
{
2025-04-19 16:20:40 +10:00
if ( args . Actor is not { Valid : true } player )
{
2023-07-06 13:39:34 +10:00
return ;
2025-04-19 16:20:40 +10:00
}
2023-07-06 13:39:34 +10:00
2025-04-19 16:20:40 +10:00
switch ( args . Button )
{
case DisposalUnitComponent . UiButton . Eject :
TryEjectContents ( uid , component ) ;
_adminLog . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):player} hit eject button on {ToPrettyString(uid)}" ) ;
break ;
case DisposalUnitComponent . UiButton . Engage :
ToggleEngage ( uid , component ) ;
_adminLog . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):player} hit flush button on {ToPrettyString(uid)}, it's now {(component.Engaged ? " on " : " off ")}" ) ;
break ;
case DisposalUnitComponent . UiButton . Power :
_power . TogglePower ( uid , user : args . Actor ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( $"{ToPrettyString(player):player} attempted to hit a nonexistant button on {ToPrettyString(uid)}" ) ;
}
2023-07-06 13:39:34 +10:00
}
2024-06-26 08:25:42 -06:00
2025-04-19 16:20:40 +10:00
public void ToggleEngage ( EntityUid uid , DisposalUnitComponent component )
2024-06-26 08:25:42 -06:00
{
2025-04-19 16:20:40 +10:00
component . Engaged ^ = true ;
if ( component . Engaged )
{
ManualEngage ( uid , component ) ;
}
else
{
Disengage ( uid , component ) ;
}
2024-06-26 08:25:42 -06:00
}
2025-04-19 16:20:40 +10:00
private void AddClimbInsideVerb ( EntityUid uid , DisposalUnitComponent component , GetVerbsEvent < Verb > args )
{
// This is not an interaction, activation, or alternative verb type because unfortunately most users are
// unwilling to accept that this is where they belong and don't want to accidentally climb inside.
if ( ! args . CanAccess | |
! args . CanInteract | |
component . Container . ContainedEntities . Contains ( args . User ) | |
! ActionBlockerSystem . CanMove ( args . User ) )
{
return ;
}
2023-07-06 13:39:34 +10:00
2025-04-19 16:20:40 +10:00
if ( ! CanInsert ( uid , component , args . User ) )
return ;
2023-07-06 13:39:34 +10:00
2025-04-19 16:20:40 +10:00
// Add verb to climb inside of the unit,
Verb verb = new ( )
{
Act = ( ) = > TryInsert ( uid , args . User , args . User ) ,
DoContactInteraction = true ,
Text = Loc . GetString ( "disposal-self-insert-verb-get-data-text" )
} ;
// TODO VERB ICON
// TODO VERB CATEGORY
// create a verb category for "enter"?
// See also, medical scanner. Also maybe add verbs for entering lockers/body bags?
args . Verbs . Add ( verb ) ;
2023-07-06 13:39:34 +10:00
}
2025-07-20 23:21:28 -04:00
private void OnGetDumpableVerb ( Entity < DisposalUnitComponent > ent , ref GetDumpableVerbEvent args )
{
args . Verb = Loc . GetString ( "dump-disposal-verb-name" , ( "unit" , ent ) ) ;
}
private void OnDump ( Entity < DisposalUnitComponent > ent , ref DumpEvent args )
{
if ( args . Handled )
return ;
args . Handled = true ;
args . PlaySound = true ;
foreach ( var entity in args . DumpQueue )
{
DoInsertDisposalUnit ( ent , entity , args . User ) ;
}
}
2023-07-06 13:39:34 +10:00
}