2023-09-02 07:02:35 +03:00
using System.Diagnostics.CodeAnalysis ;
2021-08-12 13:40:38 +10:00
using System.Linq ;
2023-01-04 00:45:35 -06:00
using Content.Server.Administration.Logs ;
2021-08-12 13:40:38 +10:00
using Content.Server.Atmos.EntitySystems ;
2023-06-21 07:31:19 -07:00
using Content.Server.Disposal.Tube ;
2021-08-12 13:40:38 +10:00
using Content.Server.Disposal.Tube.Components ;
2021-10-20 21:12:23 +02:00
using Content.Server.Disposal.Unit.Components ;
2022-07-31 01:17:30 -03:00
using Content.Server.Popups ;
2021-08-12 13:40:38 +10:00
using Content.Server.Power.Components ;
2023-01-20 11:05:54 -05:00
using Content.Server.Power.EntitySystems ;
2021-08-12 13:40:38 +10:00
using Content.Shared.ActionBlocker ;
using Content.Shared.Atmos ;
2023-01-04 00:45:35 -06:00
using Content.Shared.Database ;
2022-05-03 01:43:25 +03:00
using Content.Shared.Destructible ;
2021-08-12 13:40:38 +10:00
using Content.Shared.Disposal ;
using Content.Shared.Disposal.Components ;
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 ;
2021-08-12 13:40:38 +10:00
using Content.Shared.Throwing ;
2021-10-20 21:12:23 +02:00
using Content.Shared.Verbs ;
2023-12-02 11:19:32 -06:00
using Robust.Server.Audio ;
2021-08-12 13:40:38 +10:00
using Robust.Server.GameObjects ;
using Robust.Shared.Containers ;
2023-07-06 13:39:34 +10:00
using Robust.Shared.GameStates ;
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 ;
2023-10-29 04:21:02 +11:00
using Robust.Shared.Player ;
2021-08-12 13:40:38 +10:00
using Robust.Shared.Random ;
2023-02-26 18:48:57 +11:00
using Robust.Shared.Utility ;
2021-05-13 02:05:46 +02:00
2023-07-06 13:39:34 +10:00
namespace Content.Server.Disposal.Unit.EntitySystems ;
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
2021-05-13 02:05:46 +02:00
{
2023-07-06 13:39:34 +10:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
[Dependency] private readonly IRobustRandom _robustRandom = default ! ;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default ! ;
[Dependency] private readonly AppearanceSystem _appearance = default ! ;
[Dependency] private readonly AtmosphereSystem _atmosSystem = default ! ;
2023-12-02 11:19:32 -06:00
[Dependency] private readonly AudioSystem _audioSystem = default ! ;
2023-07-06 13:39:34 +10:00
[Dependency] private readonly DisposalTubeSystem _disposalTubeSystem = default ! ;
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
[Dependency] private readonly PopupSystem _popupSystem = default ! ;
[Dependency] private readonly PowerReceiverSystem _power = default ! ;
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default ! ;
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
[Dependency] private readonly TransformSystem _transformSystem = default ! ;
[Dependency] private readonly UserInterfaceSystem _ui = default ! ;
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 , ComponentGetState > ( OnGetState ) ;
SubscribeLocalEvent < DisposalUnitComponent , PreventCollideEvent > ( OnPreventCollide ) ;
SubscribeLocalEvent < DisposalUnitComponent , CanDropTargetEvent > ( OnCanDragDropOn ) ;
SubscribeLocalEvent < DisposalUnitComponent , GotEmaggedEvent > ( OnEmagged ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
// Shouldn't need re-anchoring.
SubscribeLocalEvent < DisposalUnitComponent , AnchorStateChangedEvent > ( OnAnchorChanged ) ;
SubscribeLocalEvent < DisposalUnitComponent , EntityUnpausedEvent > ( OnUnpaused ) ;
// TODO: Predict me when hands predicted
SubscribeLocalEvent < DisposalUnitComponent , ContainerRelayMovementEntityEvent > ( OnMovement ) ;
SubscribeLocalEvent < DisposalUnitComponent , PowerChangedEvent > ( OnPowerChange ) ;
SubscribeLocalEvent < DisposalUnitComponent , ComponentInit > ( OnDisposalInit ) ;
2021-05-13 02:05:46 +02:00
2023-07-06 13:39:34 +10:00
SubscribeLocalEvent < DisposalUnitComponent , ThrowHitByEvent > ( OnThrowCollide ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
SubscribeLocalEvent < DisposalUnitComponent , ActivateInWorldEvent > ( OnActivate ) ;
SubscribeLocalEvent < DisposalUnitComponent , AfterInteractUsingEvent > ( OnAfterInteractUsing ) ;
SubscribeLocalEvent < DisposalUnitComponent , DragDropTargetEvent > ( OnDragDropOn ) ;
SubscribeLocalEvent < DisposalUnitComponent , DestructionEventArgs > ( OnDestruction ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
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
2023-07-06 13:39:34 +10:00
SubscribeLocalEvent < DisposalUnitComponent , SharedDisposalUnitComponent . UiButtonPressedMessage > ( OnUiButtonPressed ) ;
}
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
private void OnGetState ( EntityUid uid , DisposalUnitComponent component , ref ComponentGetState args )
{
args . State = new DisposalUnitComponentState (
component . FlushSound ,
component . State ,
component . NextPressurized ,
component . AutomaticEngageTime ,
component . NextFlush ,
component . Powered ,
component . Engaged ,
2023-09-11 09:42:41 +10:00
GetNetEntityList ( component . RecentlyEjected ) ) ;
2023-07-06 13:39:34 +10:00
}
2022-01-30 17:53:22 +01:00
2023-07-06 13:39:34 +10:00
private void OnUnpaused ( EntityUid uid , SharedDisposalUnitComponent component , ref EntityUnpausedEvent args )
{
if ( component . NextFlush ! = null )
component . NextFlush = component . NextFlush . Value + args . PausedTime ;
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
component . NextPressurized + = args . PausedTime ;
}
2021-10-05 14:29:03 +11:00
2023-07-06 13:39:34 +10:00
private void AddDisposalAltVerbs ( EntityUid uid , SharedDisposalUnitComponent component , GetVerbsEvent < AlternativeVerb > args )
{
if ( ! args . CanAccess | | ! args . CanInteract )
return ;
2021-10-05 14:29:03 +11:00
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
2023-07-06 13:39:34 +10:00
private void AddClimbInsideVerb ( EntityUid uid , SharedDisposalUnitComponent 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 ( ! component . MobsCanEnter | |
! args . CanAccess | |
! args . CanInteract | |
component . Container . ContainedEntities . Contains ( args . User ) | |
! _actionBlockerSystem . CanMove ( args . User ) )
{
return ;
}
// 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 ) ;
}
2022-02-07 14:54:54 +13:00
2023-07-06 13:39:34 +10:00
private void AddInsertVerb ( EntityUid uid , SharedDisposalUnitComponent component , GetVerbsEvent < InteractionVerb > args )
{
if ( ! args . CanAccess | | ! args . CanInteract | | args . Hands = = null | | args . Using = = null )
return ;
2022-02-07 14:54:54 +13:00
2023-07-06 13:39:34 +10:00
if ( ! _actionBlockerSystem . CanDrop ( args . User ) )
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 = ( ) = >
{
_handsSystem . TryDropIntoContainer ( args . User , args . Using . Value , component . Container , checkActionBlocker : false , args . Hands ) ;
_adminLogger . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}" ) ;
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
2023-07-06 13:39:34 +10:00
private void OnDoAfter ( EntityUid uid , SharedDisposalUnitComponent component , DoAfterEvent args )
{
if ( args . Handled | | args . Cancelled | | args . Args . Target = = null | | args . Args . Used = = null )
return ;
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
AfterInsert ( uid , component , args . Args . Target . Value , args . Args . User ) ;
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
2023-07-06 13:39:34 +10:00
public override void DoInsertDisposalUnit ( EntityUid uid , EntityUid toInsert , EntityUid user , SharedDisposalUnitComponent ? disposal = null )
{
2023-09-02 07:02:35 +03:00
if ( ! ResolveDisposals ( uid , ref disposal ) )
2023-07-06 13:39:34 +10:00
return ;
2022-05-03 23:00:22 -04:00
2023-12-27 21:30:03 -08:00
if ( ! _containerSystem . Insert ( toInsert , disposal . Container ) )
2023-07-06 13:39:34 +10:00
return ;
2023-06-21 07:31:19 -07:00
2023-07-06 13:39:34 +10:00
_adminLogger . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(user):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(uid)}" ) ;
AfterInsert ( uid , disposal , toInsert , user ) ;
}
2022-09-11 16:50:59 +10:00
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
2023-07-06 13:39:34 +10:00
var query = EntityQueryEnumerator < DisposalUnitComponent , MetaDataComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var unit , out var metadata ) )
2022-01-30 17:53:22 +01:00
{
2023-07-06 13:39:34 +10:00
Update ( uid , unit , metadata , frameTime ) ;
2022-01-30 17:53:22 +01:00
}
2023-07-06 13:39:34 +10:00
}
2022-01-30 17:53:22 +01:00
2023-07-06 13:39:34 +10:00
#region UI Handlers
private void OnUiButtonPressed ( EntityUid uid , SharedDisposalUnitComponent component , SharedDisposalUnitComponent . UiButtonPressedMessage args )
{
if ( args . Session . AttachedEntity is not { Valid : true } player )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
switch ( args . Button )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
case SharedDisposalUnitComponent . UiButton . Eject :
TryEjectContents ( uid , component ) ;
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):player} hit eject button on {ToPrettyString(uid)}" ) ;
break ;
case SharedDisposalUnitComponent . UiButton . Engage :
ToggleEngage ( uid , component ) ;
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):player} hit flush button on {ToPrettyString(uid)}, it's now {(component.Engaged ? " on " : " off ")}" ) ;
break ;
case SharedDisposalUnitComponent . UiButton . Power :
_power . TogglePower ( uid , user : args . Session . AttachedEntity ) ;
break ;
default :
throw new ArgumentOutOfRangeException ( $"{ToPrettyString(player):player} attempted to hit a nonexistant button on {ToPrettyString(uid)}" ) ;
2021-08-12 13:40:38 +10:00
}
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 void ToggleEngage ( EntityUid uid , SharedDisposalUnitComponent component )
{
component . Engaged ^ = true ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( component . Engaged )
{
ManualEngage ( uid , component ) ;
}
else
{
Disengage ( uid , component ) ;
}
}
2022-04-24 13:54:25 +10:00
2023-07-06 13:39:34 +10:00
#endregion
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
#region Eventbus Handlers
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
private void OnActivate ( EntityUid uid , SharedDisposalUnitComponent component , ActivateInWorldEvent args )
{
if ( ! TryComp ( args . User , out ActorComponent ? actor ) )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
args . Handled = true ;
_ui . TryOpen ( uid , SharedDisposalUnitComponent . DisposalUnitUiKey . Key , actor . PlayerSession ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
private void OnAfterInteractUsing ( EntityUid uid , SharedDisposalUnitComponent component , AfterInteractUsingEvent args )
{
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
2023-07-06 13:39:34 +10:00
_adminLogger . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Used)} into {ToPrettyString(uid)}" ) ;
AfterInsert ( uid , component , args . Used , args . User ) ;
args . Handled = true ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
/// <summary>
/// Thrown items have a chance of bouncing off the unit and not going in.
/// </summary>
private void OnThrowCollide ( EntityUid uid , SharedDisposalUnitComponent component , ThrowHitByEvent args )
{
2023-12-02 11:19:32 -06:00
var canInsert = CanInsert ( uid , component , args . Thrown ) ;
var randDouble = _robustRandom . NextDouble ( ) ;
if ( ! canInsert | | randDouble > 0.75 )
2023-07-06 13:39:34 +10:00
{
2023-12-02 11:19:32 -06:00
_audioSystem . PlayPvs ( component . MissSound , uid ) ;
2023-07-06 13:39:34 +10:00
_popupSystem . PopupEntity ( Loc . GetString ( "disposal-unit-thrown-missed" ) , uid ) ;
return ;
2021-05-13 02:05:46 +02:00
}
2021-07-16 05:22:29 +02:00
2023-12-02 11:19:32 -06:00
var inserted = _containerSystem . Insert ( args . Thrown , component . Container ) ;
if ( ! inserted )
{
throw new InvalidOperationException ( "Container insertion failed but CanInsert returned true" ) ;
}
2023-08-02 12:30:04 +03:00
if ( args . Component . Thrower ! = null )
_adminLogger . Add ( LogType . Landed , LogImpact . Low , $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(uid)}" ) ;
2021-12-03 10:25:07 +01:00
2023-07-06 13:39:34 +10:00
AfterInsert ( uid , component , args . Thrown ) ;
}
2022-01-30 17:53:22 +01:00
2023-07-06 13:39:34 +10:00
private void OnDisposalInit ( EntityUid uid , SharedDisposalUnitComponent component , ComponentInit args )
{
component . Container = _containerSystem . EnsureContainer < Container > ( uid , SharedDisposalUnitComponent . ContainerId ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
UpdateInterface ( uid , component , component . Powered ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
private void OnPowerChange ( EntityUid uid , SharedDisposalUnitComponent component , ref PowerChangedEvent args )
{
if ( ! component . Running | | args . Powered = = component . Powered )
return ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
component . Powered = args . Powered ;
UpdateVisualState ( uid , component ) ;
UpdateInterface ( uid , component , args . Powered ) ;
if ( ! args . Powered )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = null ;
Dirty ( component ) ;
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
if ( component . Engaged & & ! TryFlush ( uid , component ) )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
QueueAutomaticEngage ( uid , component ) ;
}
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
// TODO: This should just use the same thing as entity storage?
private void OnMovement ( EntityUid uid , SharedDisposalUnitComponent component , ref ContainerRelayMovementEntityEvent args )
{
var currentTime = GameTiming . CurTime ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +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
}
2023-07-06 13:39:34 +10:00
component . LastExitAttempt = currentTime ;
Remove ( uid , component , args . Entity ) ;
}
2022-10-29 10:59:01 +13:00
2023-07-06 13:39:34 +10:00
private void OnAnchorChanged ( EntityUid uid , SharedDisposalUnitComponent component , ref AnchorStateChangedEvent args )
{
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
2023-07-06 13:39:34 +10:00
private void OnDestruction ( EntityUid uid , SharedDisposalUnitComponent component , DestructionEventArgs args )
{
TryEjectContents ( uid , component ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
private void OnDragDropOn ( EntityUid uid , SharedDisposalUnitComponent component , ref DragDropTargetEvent args )
{
args . Handled = TryInsert ( uid , args . Dragged , args . User ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
#endregion
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
private void UpdateState ( EntityUid uid , DisposalsPressureState state , SharedDisposalUnitComponent component , MetaDataComponent metadata )
{
if ( component . State = = state )
return ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
component . State = state ;
UpdateVisualState ( uid , component ) ;
UpdateInterface ( uid , component , component . Powered ) ;
Dirty ( component , metadata ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( state = = DisposalsPressureState . Ready )
{
component . NextPressurized = TimeSpan . Zero ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
// Manually engaged
if ( component . Engaged )
2023-06-21 07:31:19 -07:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = GameTiming . CurTime + component . ManualFlushTime ;
2023-06-21 07:31:19 -07:00
}
2023-07-06 13:39:34 +10:00
else if ( component . Container . ContainedEntities . Count > 0 )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = GameTiming . CurTime + component . AutomaticEngageTime ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
else
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
component . NextFlush = null ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
}
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
/// <summary>
/// Work out if we can stop updating this disposals component i.e. full pressure and nothing colliding.
/// </summary>
private void Update ( EntityUid uid , SharedDisposalUnitComponent component , MetaDataComponent metadata , float frameTime )
{
var state = GetState ( uid , component , metadata ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
// Pressurizing, just check if we need a state update.
if ( component . NextPressurized > GameTiming . CurTime )
{
UpdateState ( uid , state , component , metadata ) ;
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
if ( component . NextFlush ! = null )
2021-10-20 21:12:23 +02:00
{
2023-07-06 13:39:34 +10:00
if ( component . NextFlush . Value < GameTiming . CurTime )
2022-07-31 01:17:30 -03:00
{
2023-07-06 13:39:34 +10:00
TryFlush ( uid , component ) ;
2022-07-31 01:17:30 -03:00
}
2023-07-06 13:39:34 +10:00
}
2022-07-31 01:17:30 -03:00
2023-07-06 13:39:34 +10:00
UpdateState ( uid , state , component , metadata ) ;
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
Box2 ? disposalsBounds = null ;
var count = component . RecentlyEjected . Count ;
2021-10-20 21:12:23 +02:00
2023-07-06 13:39:34 +10:00
if ( count > 0 )
{
if ( ! HasComp < PhysicsComponent > ( uid ) )
2021-10-20 21:12:23 +02:00
{
2023-07-06 13:39:34 +10:00
component . RecentlyEjected . Clear ( ) ;
2021-10-20 21:12:23 +02:00
}
2023-07-06 13:39:34 +10:00
else
2021-10-20 21:12:23 +02:00
{
2023-07-06 13:39:34 +10:00
disposalsBounds = _lookup . GetWorldAABB ( uid ) ;
}
2021-10-20 21:12:23 +02:00
}
2023-07-06 13:39:34 +10:00
for ( var i = 0 ; i < component . RecentlyEjected . Count ; i + + )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
var ejectedId = component . RecentlyEjected [ i ] ;
if ( HasComp < PhysicsComponent > ( ejectedId ) )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
// TODO: We need to use a specific collision method (which sloth hasn't coded yet) for actual bounds overlaps.
// TODO: Come do this sloth :^)
// Check for itemcomp as we won't just block the disposal unit "sleeping" for something it can't collide with anyway.
if ( ! HasComp < ItemComponent > ( ejectedId )
& & _lookup . GetWorldAABB ( ejectedId ) . Intersects ( disposalsBounds ! . Value ) )
{
continue ;
}
2022-08-14 07:57:25 +02:00
2023-07-06 13:39:34 +10:00
component . RecentlyEjected . RemoveAt ( i ) ;
i - - ;
2022-08-14 07:57:25 +02:00
}
2023-07-06 13:39:34 +10:00
}
2022-08-14 07:57:25 +02:00
2023-07-06 13:39:34 +10:00
if ( count ! = component . RecentlyEjected . Count )
Dirty ( component , metadata ) ;
}
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 )
_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 )
{
AfterInsert ( unitId , unit , toInsertId , userId ) ;
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 ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
NeedHand = false
} ;
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
2023-07-06 13:39:34 +10:00
public bool TryFlush ( EntityUid uid , SharedDisposalUnitComponent component )
{
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 ;
var entry = grid . GetLocal ( coords )
. FirstOrDefault ( HasComp < 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 ;
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 ) ;
UpdateInterface ( uid , component , component . Powered ) ;
2021-08-12 13:40:38 +10:00
2023-09-04 15:11:34 +01:00
Dirty ( 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
2023-07-06 13:39:34 +10:00
private void HandleAir ( EntityUid uid , DisposalUnitComponent component , TransformComponent xform )
{
var air = component . Air ;
2023-11-28 18:03:44 -05:00
var indices = _transformSystem . GetGridTilePositionOrDefault ( ( uid , xform ) ) ;
2023-02-03 22:26:50 +00:00
2023-07-06 13:39:34 +10:00
if ( _atmosSystem . GetTileMixture ( xform . GridUid , xform . MapUid , indices , true ) is { Temperature : > 0f } environment )
{
var transferMoles = 0.1f * ( 0.25f * Atmospherics . OneAtmosphere * 1.01f - air . Pressure ) * air . Volume / ( environment . Temperature * Atmospherics . R ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
component . Air = environment . Remove ( transferMoles ) ;
2021-08-12 13:40:38 +10:00
}
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 void UpdateInterface ( EntityUid uid , SharedDisposalUnitComponent component , bool powered )
{
var compState = GetState ( uid , component ) ;
var stateString = Loc . GetString ( $"disposal-unit-state-{compState}" ) ;
var state = new SharedDisposalUnitComponent . DisposalUnitBoundUserInterfaceState ( Name ( uid ) , stateString , EstimatedFullPressure ( uid , component ) , powered , component . Engaged ) ;
_ui . TrySetUiState ( uid , SharedDisposalUnitComponent . DisposalUnitUiKey . Key , state ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
var stateUpdatedEvent = new DisposalUnitUIStateUpdatedEvent ( state ) ;
RaiseLocalEvent ( uid , stateUpdatedEvent ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
/// <summary>
/// Returns the estimated time when the disposal unit will be back to full pressure.
/// </summary>
private TimeSpan EstimatedFullPressure ( EntityUid uid , SharedDisposalUnitComponent component )
{
if ( component . NextPressurized < GameTiming . CurTime )
return TimeSpan . Zero ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
return component . NextPressurized ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
public void UpdateVisualState ( EntityUid uid , SharedDisposalUnitComponent component , bool flush = false )
{
if ( ! TryComp ( uid , out AppearanceComponent ? appearance ) )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
if ( ! Transform ( uid ) . Anchored )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . VisualState , SharedDisposalUnitComponent . VisualState . UnAnchored , appearance ) ;
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . Handle , SharedDisposalUnitComponent . HandleState . Normal , appearance ) ;
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . Light , SharedDisposalUnitComponent . LightStates . Off , appearance ) ;
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
var state = GetState ( uid , component ) ;
switch ( state )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
case DisposalsPressureState . Flushed :
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . VisualState , SharedDisposalUnitComponent . VisualState . Flushing , appearance ) ;
break ;
case DisposalsPressureState . Pressurizing :
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . VisualState , SharedDisposalUnitComponent . VisualState . Charging , appearance ) ;
break ;
case DisposalsPressureState . Ready :
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . VisualState , SharedDisposalUnitComponent . VisualState . Anchored , appearance ) ;
break ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . Handle , component . Engaged
? SharedDisposalUnitComponent . HandleState . Engaged
: SharedDisposalUnitComponent . HandleState . Normal , appearance ) ;
if ( ! component . Powered )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . Light , SharedDisposalUnitComponent . LightStates . Off , appearance ) ;
return ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
var lightState = SharedDisposalUnitComponent . LightStates . Off ;
if ( component . Container . ContainedEntities . Count > 0 )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +10:00
lightState | = SharedDisposalUnitComponent . LightStates . Full ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( state is DisposalsPressureState . Pressurizing or DisposalsPressureState . Flushed )
{
lightState | = SharedDisposalUnitComponent . LightStates . Charging ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
else
{
lightState | = SharedDisposalUnitComponent . LightStates . Ready ;
}
_appearance . SetData ( uid , SharedDisposalUnitComponent . Visuals . Light , lightState , appearance ) ;
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
public void Remove ( EntityUid uid , SharedDisposalUnitComponent component , EntityUid toRemove )
{
2023-12-27 21:30:03 -08:00
_containerSystem . Remove ( toRemove , component . Container ) ;
2023-07-06 13:39:34 +10:00
if ( component . Container . ContainedEntities . Count = = 0 )
2021-08-12 13:40:38 +10:00
{
2023-07-06 13:39:34 +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 ;
2021-08-12 13:40:38 +10:00
}
2023-07-06 13:39:34 +10:00
}
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
if ( ! component . RecentlyEjected . Contains ( toRemove ) )
component . RecentlyEjected . Add ( toRemove ) ;
2021-08-12 13:40:38 +10:00
2023-07-06 13:39:34 +10:00
UpdateVisualState ( uid , component ) ;
2023-09-04 15:11:34 +01:00
Dirty ( 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 CanFlush ( EntityUid unit , SharedDisposalUnitComponent component )
{
return GetState ( unit , component ) = = DisposalsPressureState . Ready
& & component . Powered
& & Comp < TransformComponent > ( unit ) . Anchored ;
}
public void ManualEngage ( EntityUid uid , SharedDisposalUnitComponent component , MetaDataComponent ? metadata = null )
{
component . Engaged = true ;
UpdateVisualState ( uid , component ) ;
UpdateInterface ( uid , component , component . Powered ) ;
2023-09-04 15:11:34 +01:00
Dirty ( 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 ) ) ;
}
public void Disengage ( EntityUid uid , SharedDisposalUnitComponent component )
{
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 ) ;
UpdateInterface ( uid , component , component . Powered ) ;
2023-09-04 15:11:34 +01:00
Dirty ( 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>
2023-07-06 13:39:34 +10:00
public void TryEjectContents ( EntityUid uid , SharedDisposalUnitComponent 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 ) ;
2022-08-14 07:57:25 +02:00
}
2021-05-13 02:05:46 +02:00
}
2022-09-11 16:50:59 +10:00
2023-08-31 11:08:23 +10:00
public override bool HasDisposals ( EntityUid ? uid )
{
return HasComp < DisposalUnitComponent > ( uid ) ;
}
2023-09-02 07:02:35 +03:00
public override bool ResolveDisposals ( EntityUid uid , [ NotNullWhen ( true ) ] ref SharedDisposalUnitComponent ? component )
{
if ( component ! = null )
return true ;
TryComp < DisposalUnitComponent > ( uid , out var storage ) ;
component = storage ;
return component ! = null ;
}
2023-07-06 13:39:34 +10:00
public override bool CanInsert ( EntityUid uid , SharedDisposalUnitComponent component , EntityUid entity )
{
2023-09-11 09:42:41 +10:00
if ( ! base . CanInsert ( uid , component , entity ) )
2023-07-06 13:39:34 +10:00
return false ;
2023-09-11 09:42:41 +10:00
return _containerSystem . CanInsert ( entity , component . Container ) ;
2023-07-06 13:39:34 +10: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>
2023-07-06 13:39:34 +10:00
public void QueueAutomaticEngage ( EntityUid uid , SharedDisposalUnitComponent component , MetaDataComponent ? metadata = null )
2022-09-11 16:50:59 +10:00
{
2023-07-06 13:39:34 +10:00
if ( component . Deleted | | ! component . AutomaticEngage | | ! component . Powered & & component . Container . ContainedEntities . Count = = 0 )
{
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 ;
Dirty ( component ) ;
2022-09-11 16:50:59 +10:00
}
2023-07-06 13:39:34 +10:00
public void AfterInsert ( EntityUid uid , SharedDisposalUnitComponent component , EntityUid inserted , EntityUid ? user = null )
{
2023-12-02 11:19:32 -06:00
_audioSystem . PlayPvs ( component . InsertSound , uid ) ;
2023-12-27 21:30:03 -08:00
if ( ! _containerSystem . Insert ( inserted , component . Container ) )
2023-07-06 13:39:34 +10:00
return ;
if ( user ! = inserted & & user ! = null )
_adminLogger . Add ( LogType . Action , LogImpact . Medium , $"{ToPrettyString(user.Value):player} inserted {ToPrettyString(inserted)} into {ToPrettyString(uid)}" ) ;
QueueAutomaticEngage ( uid , component ) ;
if ( TryComp ( inserted , out ActorComponent ? actor ) )
{
_ui . TryClose ( uid , SharedDisposalUnitComponent . DisposalUnitUiKey . Key , actor . PlayerSession ) ;
}
// Maybe do pullable instead? Eh still fine.
Joints . RecursiveClearJoints ( inserted ) ;
UpdateVisualState ( uid , component ) ;
}
}
/// <summary>
/// Sent before the disposal unit flushes it's contents.
/// Allows adding tags for sorting and preventing the disposal unit from flushing.
/// </summary>
public sealed class DisposalUnitUIStateUpdatedEvent : EntityEventArgs
{
public SharedDisposalUnitComponent . DisposalUnitBoundUserInterfaceState State ;
public DisposalUnitUIStateUpdatedEvent ( SharedDisposalUnitComponent . DisposalUnitBoundUserInterfaceState state )
{
State = state ;
}
}
/// <summary>
/// Sent before the disposal unit flushes it's contents.
/// Allows adding tags for sorting and preventing the disposal unit from flushing.
/// </summary>
public sealed class BeforeDisposalFlushEvent : CancellableEntityEventArgs
{
public readonly List < string > Tags = new ( ) ;
2021-05-13 02:05:46 +02:00
}