2020-08-22 22:29:20 +02:00
using System ;
2020-07-17 10:51:43 +02:00
using System.Linq ;
2020-08-13 14:40:27 +02:00
using System.Threading ;
2020-08-30 19:16:29 +10:00
using System.Threading.Tasks ;
2021-06-09 22:19:39 +02:00
using Content.Server.Access ;
using Content.Server.Access.Components ;
2021-06-19 13:25:05 +02:00
using Content.Server.Atmos.Components ;
2021-07-25 09:04:58 +02:00
using Content.Server.Atmos.EntitySystems ;
2021-06-09 22:19:39 +02:00
using Content.Server.Construction.Components ;
using Content.Server.Hands.Components ;
using Content.Server.Stunnable.Components ;
using Content.Server.Tools.Components ;
Bodysystem and damagesystem rework (#1544)
* Things and stuff with grids, unfinished w/ code debug changes.
* Updated submodule and also lost some progress cause I fucked it up xd
* First unfinished draft of the BodySystem. Doesn't compile.
* More changes to make it compile, but still just a framework. Doesn't do anything at the moment.
* Many cleanup changes.
* Revert "Merge branch 'master' of https://github.com/GlassEclipse/space-station-14 into body_system"
This reverts commit ddd4aebbc76cf2a0b7b102f72b93d55a0816c88c, reversing
changes made to 12d0dd752706bdda8879393bd8191a1199a0c978.
* Commit human.yml
* Updated a lot of things to be more classy, more progress overall, etc. etc.
* Latest update with many changes
* Minor changes
* Fixed Travis build bug
* Adds first draft of Body Scanner console, apparently I also forgot to tie Mechanisms into body parts so now a heart just sits in the Torso like a good boy :)
* Commit rest of stuff
* Latest changes
* Latest changes again
* 14 naked cowboys
* Yay!
* Latest changes (probably doesnt compile)
* Surgery!!!!!!!!!~1116y
* Cleaned some stuff up
* More cleanup
* Refactoring of code. Basic surgery path now done.
* Removed readme, has been added to HackMD
* Fixes typo (and thus test errors)
* WIP changes, committing so I can pull latest master changes
* Still working on that god awful merge
* Latest changes
* Latest changes!!
* Beginning of refactor to BoundUserInterface
* Surgery!
* Latest changes - fixes pr change requests and random fixes
* oops
* Fixes bodypart recursion
* Beginning of work on revamping the damage system.
* More latest changes
* Latest changes
* Finished merge
* Commit before removing old healthcode
* Almost done with removing speciescomponent...
* It compiles!!!
* yahoo more work
* Fixes to make it work
* Merge conflict fixes
* Deleting species visualizer was a mistake
* IDE warnings are VERBOTEN
* makes the server not kill itself on startup, some cleanup (#1)
* Namespaces, comments and exception fixes
* Fix conveyor and conveyor switch serialization
SS14 in reactive when
* Move damage, acts and body to shared
Damage cleanup
Comment cleanup
* Rename SpeciesComponent to RotationComponent and cleanup
Damage cleanup
Comment cleanup
* Fix nullable warnings
* Address old reviews
Fix off welder suicide damage type, deathmatch and suspicion
* Fix new test fail with units being able to accept items when unpowered
* Remove RotationComponent, change references to IBodyManagerComponent
* Add a bloodstream to humans
* More cleanups
* Add body conduits, connections, connectors substances and valves
* Revert "Add body conduits, connections, connectors substances and valves"
This reverts commit 9ab0b50e6b15fe98852d7b0836c0cdbf4bd76d20.
* Implement the heart mechanism behavior with the circulatory network
* Added network property to mechanism behaviors
* Changed human organ sprites and added missing ones
* Fix tests
* Add individual body part sprite rendering
* Fix error where dropped mechanisms are not initialized
* Implement client/server body damage
* Make DamageContainer take care of raising events
* Reimplement medical scanner with the new body system
* Improve the medical scanner ui
* Merge conflict fixes
* Fix crash when colliding with something
* Fix microwave suicides and eyes sprite rendering
* Fix nullable reference error
* Fix up surgery client side
* Fix missing using from merge conflict
* Add breathing
*inhale
* Merge conflict fixes
* Fix accumulatedframetime being reset to 0 instead of decreased by the threshold
https://github.com/space-wizards/space-station-14/pull/1617
* Use and add to the new AtmosHelpers
* Fix feet
* Add proper coloring to dropped body parts
* Fix Urist's lungs being too strong
* Merge conflict fixes
* Merge conflict fixes
* Merge conflict fixes
Co-authored-by: GlassEclipse <tsymall5@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
2020-08-17 01:42:42 +02:00
using Content.Shared.Damage ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Damage.Components ;
using Content.Shared.Doors ;
using Content.Shared.Interaction ;
2021-07-10 17:35:33 +02:00
using Content.Shared.Sound ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Tool ;
2020-07-20 16:03:05 +02:00
using Robust.Shared.Audio ;
2021-06-09 22:19:39 +02:00
using Robust.Shared.Containers ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.GameObjects ;
2021-02-12 07:02:14 -08:00
using Robust.Shared.Log ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Maths ;
2021-07-21 21:15:12 +10:00
using Robust.Shared.Physics ;
2021-03-08 04:09:59 +11:00
using Robust.Shared.Physics.Broadphase ;
using Robust.Shared.Physics.Collision ;
2021-06-09 22:19:39 +02:00
using Robust.Shared.Physics.Dynamics ;
2021-03-21 09:12:03 -07:00
using Robust.Shared.Player ;
2021-06-09 22:19:39 +02:00
using Robust.Shared.Players ;
using Robust.Shared.Serialization.Manager.Attributes ;
2020-02-23 17:37:57 +01:00
using Robust.Shared.ViewVariables ;
2021-02-18 20:45:45 -08:00
using Timer = Robust . Shared . Timing . Timer ;
2017-10-22 23:48:01 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Doors.Components
2017-10-22 23:48:01 +02:00
{
2019-07-31 15:02:36 +02:00
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
2021-02-12 07:02:14 -08:00
[ComponentReference(typeof(SharedDoorComponent))]
2021-07-21 20:28:37 +10:00
public class ServerDoorComponent : SharedDoorComponent , IActivate , IInteractUsing , IMapInit
2017-10-22 23:48:01 +02:00
{
2021-03-17 05:07:49 -07:00
[ViewVariables]
[DataField("board")]
private string? _boardPrototype ;
2021-07-10 17:35:33 +02:00
[DataField("tryOpenDoorSound")]
private SoundSpecifier _tryOpenDoorSound = new SoundPathSpecifier ( "/Audio/Effects/bang.ogg" ) ;
2021-02-12 07:02:14 -08:00
public override DoorState State
2019-09-18 22:12:36 +02:00
{
2021-02-12 07:02:14 -08:00
get = > base . State ;
2020-10-10 22:33:56 +11:00
protected set
{
2021-02-12 07:02:14 -08:00
if ( State = = value )
{
2020-10-10 22:33:56 +11:00
return ;
2021-02-12 07:02:14 -08:00
}
base . State = value ;
StateChangeStartTime = State switch
{
DoorState . Open or DoorState . Closed = > null ,
DoorState . Opening or DoorState . Closing = > GameTiming . CurTime ,
_ = > throw new ArgumentOutOfRangeException ( ) ,
} ;
2020-10-10 22:33:56 +11:00
2021-08-02 04:57:06 -07:00
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , new DoorStateChangedEvent ( State ) , false ) ;
_autoCloseCancelTokenSource ? . Cancel ( ) ;
2020-10-12 23:38:35 +11:00
2021-02-12 07:02:14 -08:00
Dirty ( ) ;
2020-10-10 22:33:56 +11:00
}
2019-09-18 22:12:36 +02:00
}
2017-10-22 23:48:01 +02:00
2021-02-12 07:02:14 -08:00
private static readonly TimeSpan AutoCloseDelay = TimeSpan . FromSeconds ( 5 ) ;
2017-10-22 23:48:01 +02:00
2021-02-12 07:02:14 -08:00
private CancellationTokenSource ? _stateChangeCancelTokenSource ;
private CancellationTokenSource ? _autoCloseCancelTokenSource ;
2019-03-17 13:24:26 +01:00
2020-07-17 10:51:43 +02:00
private const int DoorCrushDamage = 15 ;
private const float DoorStunTime = 5f ;
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door will ever crush.
/// </summary>
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "inhibitCrush" ) ]
2021-02-12 07:02:14 -08:00
private bool _inhibitCrush ;
2020-02-23 17:37:57 +01:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door blocks light.
/// </summary>
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "occludes" ) ]
private bool _occludes = true ;
2020-08-19 12:23:42 +02:00
public bool Occludes = > _occludes ;
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door will open when it is bumped into.
/// </summary>
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "bumpOpen" ) ]
2021-07-21 20:28:37 +10:00
public bool BumpOpen = true ;
2020-11-22 06:47:56 +00:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door starts open when it's first loaded from prototype. A door won't start open if its prototype is also welded shut.
/// Handled in Startup().
/// </summary>
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "startOpen" ) ]
2021-08-02 04:57:06 -07:00
private bool _startOpen = false ;
2020-11-22 06:47:56 +00:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the airlock is welded shut. Can be set by the prototype, although this will fail if the door isn't weldable.
/// When set by prototype, handled in Startup().
/// </summary>
2021-03-05 01:08:38 +01:00
[DataField("welded")]
2021-02-12 07:02:14 -08:00
private bool _isWeldedShut ;
2021-03-05 01:08:38 +01:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the airlock is welded shut.
/// </summary>
2020-09-08 13:30:22 +02:00
[ViewVariables(VVAccess.ReadWrite)]
2020-08-30 19:16:29 +10:00
public bool IsWeldedShut
{
get = > _isWeldedShut ;
set
{
if ( _isWeldedShut = = value )
{
return ;
}
_isWeldedShut = value ;
SetAppearance ( _isWeldedShut ? DoorVisualState . Welded : DoorVisualState . Closed ) ;
}
}
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door can ever be welded shut.
/// </summary>
2021-03-09 19:12:27 +11:00
[DataField("weldable")]
private bool _weldable = true ;
2021-03-05 01:08:38 +01:00
2021-08-02 04:57:06 -07:00
/// <summary>
/// Sound to play when the door opens.
/// </summary>
[DataField("openSound")]
public SoundSpecifier ? OpenSound ;
/// <summary>
/// Sound to play when the door closes.
/// </summary>
[DataField("closeSound")]
public SoundSpecifier ? CloseSound ;
/// <summary>
/// Sound to play if the door is denied.
/// </summary>
[DataField("denySound")]
public SoundSpecifier ? DenySound ;
/// <summary>
/// Default time that the door should take to pry open.
/// </summary>
[DataField("pryTime")]
public float PryTime = 0.5f ;
/// <summary>
/// Minimum interval allowed between deny sounds in milliseconds.
/// </summary>
[DataField("denySoundMinimumInterval")]
public float DenySoundMinimumInterval = 250.0f ;
/// <summary>
/// Used to stop people from spamming the deny sound.
/// </summary>
private TimeSpan LastDenySoundTime = TimeSpan . Zero ;
2021-02-12 07:02:14 -08:00
/// <summary>
/// Whether the door can currently be welded.
/// </summary>
private bool CanWeldShut = > _weldable & & State = = DoorState . Closed ;
2020-10-09 22:10:21 +11:00
/// <summary>
/// Whether something is currently using a welder on this so DoAfter isn't spammed.
/// </summary>
2021-03-05 01:08:38 +01:00
private bool _beingWelded ;
2020-08-30 19:16:29 +10:00
2021-08-02 04:57:06 -07:00
2021-05-31 15:01:13 -07:00
//[ViewVariables(VVAccess.ReadWrite)]
//[DataField("canCrush")]
//private bool _canCrush = true; // TODO implement door crushing
2021-02-12 07:02:14 -08:00
protected override void Startup ( )
{
base . Startup ( ) ;
if ( IsWeldedShut )
{
if ( ! CanWeldShut )
{
Logger . Warning ( "{0} prototype loaded with incompatible flags: 'welded' is true, but door cannot be welded." , Owner . Name ) ;
return ;
}
SetAppearance ( DoorVisualState . Welded ) ;
}
2021-03-17 05:07:49 -07:00
CreateDoorElectronicsBoard ( ) ;
2020-02-23 17:37:57 +01:00
}
2021-06-19 19:41:26 -07:00
protected override void OnRemove ( )
2017-10-22 23:48:01 +02:00
{
2021-02-12 07:02:14 -08:00
_stateChangeCancelTokenSource ? . Cancel ( ) ;
_autoCloseCancelTokenSource ? . Cancel ( ) ;
2018-07-26 23:55:34 +02:00
base . OnRemove ( ) ;
2017-10-22 23:48:01 +02:00
}
2021-02-12 07:02:14 -08:00
void IMapInit . MapInit ( )
{
if ( _startOpen )
{
if ( IsWeldedShut )
{
Logger . Warning ( "{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true." , Owner . Name ) ;
return ;
}
2021-08-02 04:57:06 -07:00
QuickOpen ( false ) ;
2021-02-12 07:02:14 -08:00
}
2021-03-17 05:07:49 -07:00
CreateDoorElectronicsBoard ( ) ;
2021-02-12 07:02:14 -08:00
}
void IActivate . Activate ( ActivateEventArgs eventArgs )
2017-10-22 23:48:01 +02:00
{
2021-08-02 04:57:06 -07:00
DoorClickShouldActivateEvent ev = new DoorClickShouldActivateEvent ( eventArgs ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
if ( ev . Handled )
2021-02-12 07:02:14 -08:00
return ;
2019-09-18 22:12:36 +02:00
if ( State = = DoorState . Open )
2017-10-22 23:48:01 +02:00
{
2019-10-13 17:13:16 +02:00
TryClose ( eventArgs . User ) ;
2017-10-22 23:48:01 +02:00
}
2019-09-18 22:12:36 +02:00
else if ( State = = DoorState . Closed )
2017-10-22 23:48:01 +02:00
{
2019-09-01 22:57:22 +02:00
TryOpen ( eventArgs . User ) ;
2017-10-22 23:48:01 +02:00
}
}
2021-02-12 07:02:14 -08:00
#region Opening
public void TryOpen ( IEntity user )
2019-11-29 07:53:26 -08:00
{
2021-02-12 07:02:14 -08:00
if ( CanOpenByEntity ( user ) )
2020-08-22 22:29:20 +02:00
{
2021-02-12 07:02:14 -08:00
Open ( ) ;
2019-11-29 07:53:26 -08:00
2021-07-31 19:52:33 +02:00
if ( user . TryGetComponent ( out HandsComponent ? hands ) & & hands . Count = = 0 )
2021-02-12 07:02:14 -08:00
{
2021-08-11 20:52:06 -07:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , _tryOpenDoorSound . GetSound ( ) , Owner ,
AudioParams . Default . WithVolume ( - 2 ) ) ;
2021-02-12 07:02:14 -08:00
}
}
else
{
Deny ( ) ;
}
2019-09-06 10:05:02 +02:00
}
2021-02-12 07:02:14 -08:00
public bool CanOpenByEntity ( IEntity user )
2019-09-06 10:05:02 +02:00
{
2021-02-12 07:02:14 -08:00
if ( ! CanOpenGeneric ( ) )
{
return false ;
}
2020-08-25 13:37:21 +02:00
2021-02-12 07:02:14 -08:00
if ( ! Owner . TryGetComponent ( out AccessReader ? access ) )
2019-09-06 10:05:02 +02:00
{
return true ;
}
2020-08-16 14:20:16 +02:00
2021-05-30 23:30:44 +10:00
var doorSystem = EntitySystem . Get < DoorSystem > ( ) ;
2020-08-25 13:37:21 +02:00
var isAirlockExternal = HasAccessType ( "External" ) ;
return doorSystem . AccessType switch
{
2021-05-30 23:30:44 +10:00
DoorSystem . AccessTypes . AllowAll = > true ,
DoorSystem . AccessTypes . AllowAllIdExternal = > isAirlockExternal | | access . IsAllowed ( user ) ,
DoorSystem . AccessTypes . AllowAllNoExternal = > ! isAirlockExternal ,
2021-02-12 07:02:14 -08:00
_ = > access . IsAllowed ( user )
2020-08-25 13:37:21 +02:00
} ;
2019-09-06 10:05:02 +02:00
}
2020-08-25 13:37:21 +02:00
/// <summary>
/// Returns whether a door has a certain access type. For example, maintenance doors will have access type
/// "Maintenance" in their AccessReader.
/// </summary>
2021-02-12 07:02:14 -08:00
private bool HasAccessType ( string accessType )
2019-09-01 22:57:22 +02:00
{
2021-02-12 07:02:14 -08:00
if ( Owner . TryGetComponent ( out AccessReader ? access ) )
2019-09-01 22:57:22 +02:00
{
2021-02-12 07:02:14 -08:00
return access . AccessLists . Any ( list = > list . Contains ( accessType ) ) ;
2019-09-01 22:57:22 +02:00
}
2020-07-20 16:03:05 +02:00
2020-08-25 13:37:21 +02:00
return true ;
}
2020-07-20 16:03:05 +02:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Checks if we can open at all, for anyone or anything. Will return false if inhibited by an IDoorCheck component.
/// </summary>
/// <returns>Boolean describing whether this door can open.</returns>
public bool CanOpenGeneric ( )
2020-08-25 13:37:21 +02:00
{
2021-02-12 07:02:14 -08:00
// note the welded check -- CanCloseGeneric does not have this
if ( IsWeldedShut )
2020-07-20 16:03:05 +02:00
{
2021-02-12 07:02:14 -08:00
return false ;
2020-08-25 13:37:21 +02:00
}
2021-03-05 01:08:38 +01:00
2021-08-02 04:57:06 -07:00
var ev = new BeforeDoorOpenedEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
return ! ev . Cancelled ;
2019-09-01 22:57:22 +02:00
}
2021-02-12 07:02:14 -08:00
/// <summary>
/// Opens the door. Does not check if this is possible.
/// </summary>
2017-10-22 23:48:01 +02:00
public void Open ( )
{
2019-09-18 22:12:36 +02:00
State = DoorState . Opening ;
2021-02-12 07:02:14 -08:00
if ( Occludes & & Owner . TryGetComponent ( out OccluderComponent ? occluder ) )
2020-02-23 17:30:45 +01:00
{
occluder . Enabled = false ;
}
2019-03-17 13:24:26 +01:00
2021-02-12 07:02:14 -08:00
_stateChangeCancelTokenSource ? . Cancel ( ) ;
_stateChangeCancelTokenSource = new ( ) ;
2020-12-02 04:26:39 +11:00
2021-08-02 04:57:06 -07:00
if ( OpenSound ! = null )
{
2021-08-11 20:52:06 -07:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , OpenSound . GetSound ( ) , Owner ,
2021-08-02 04:57:06 -07:00
AudioParams . Default . WithVolume ( - 5 ) ) ;
}
2020-10-30 05:02:49 +01:00
Owner . SpawnTimer ( OpenTimeOne , async ( ) = >
2019-03-17 13:24:26 +01:00
{
2021-02-12 07:02:14 -08:00
OnPartialOpen ( ) ;
await Timer . Delay ( OpenTimeTwo , _stateChangeCancelTokenSource . Token ) ;
2019-03-17 13:24:26 +01:00
2019-09-18 22:12:36 +02:00
State = DoorState . Open ;
2021-08-02 04:57:06 -07:00
RefreshAutoClose ( ) ;
2021-02-12 07:02:14 -08:00
} , _stateChangeCancelTokenSource . Token ) ;
2017-10-22 23:48:01 +02:00
}
2021-02-12 07:02:14 -08:00
protected override void OnPartialOpen ( )
2019-10-13 17:13:16 +02:00
{
2021-02-12 07:02:14 -08:00
if ( Owner . TryGetComponent ( out AirtightComponent ? airtight ) )
{
2021-07-25 09:04:58 +02:00
EntitySystem . Get < AirtightSystem > ( ) . SetAirblocked ( airtight , false ) ;
2021-02-12 07:02:14 -08:00
}
base . OnPartialOpen ( ) ;
Owner . EntityManager . EventBus . RaiseEvent ( EventSource . Local , new AccessReaderChangeMessage ( Owner , false ) ) ;
2019-10-13 17:13:16 +02:00
}
2021-08-02 04:57:06 -07:00
private void QuickOpen ( bool refresh )
2019-10-13 17:13:16 +02:00
{
2021-02-12 07:02:14 -08:00
if ( Occludes & & Owner . TryGetComponent ( out OccluderComponent ? occluder ) )
2019-10-13 17:13:16 +02:00
{
2021-02-12 07:02:14 -08:00
occluder . Enabled = false ;
2019-10-13 17:13:16 +02:00
}
2021-02-12 07:02:14 -08:00
OnPartialOpen ( ) ;
State = DoorState . Open ;
2021-08-02 04:57:06 -07:00
if ( refresh )
RefreshAutoClose ( ) ;
2019-10-13 17:13:16 +02:00
}
2021-02-12 07:02:14 -08:00
#endregion
#region Closing
2019-10-13 17:13:16 +02:00
public void TryClose ( IEntity user )
{
2021-02-12 07:02:14 -08:00
if ( ! CanCloseByEntity ( user ) )
2019-10-13 17:13:16 +02:00
{
Deny ( ) ;
return ;
}
2020-08-16 14:20:16 +02:00
2019-10-13 17:13:16 +02:00
Close ( ) ;
}
2021-02-12 07:02:14 -08:00
public bool CanCloseByEntity ( IEntity user )
2020-07-17 10:51:43 +02:00
{
2021-02-12 07:02:14 -08:00
if ( ! CanCloseGeneric ( ) )
{
return false ;
}
2020-08-22 22:29:20 +02:00
2021-02-12 07:02:14 -08:00
if ( ! Owner . TryGetComponent ( out AccessReader ? access ) )
2020-07-17 10:51:43 +02:00
{
2021-02-12 07:02:14 -08:00
return true ;
}
2020-07-17 10:51:43 +02:00
2021-02-12 07:02:14 -08:00
return access . IsAllowed ( user ) ;
}
2020-07-17 10:51:43 +02:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Checks if we can close at all, for anyone or anything. Will return false if inhibited by an IDoorCheck component or if we are colliding with somebody while our Safety is on.
/// </summary>
/// <returns>Boolean describing whether this door can close.</returns>
public bool CanCloseGeneric ( )
{
2021-08-02 04:57:06 -07:00
var ev = new BeforeDoorClosedEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
if ( ev . Cancelled )
2021-02-12 07:02:14 -08:00
return false ;
2020-08-16 14:20:16 +02:00
2021-02-12 07:02:14 -08:00
return ! IsSafetyColliding ( ) ;
}
2020-10-10 15:25:13 +02:00
2021-02-12 07:02:14 -08:00
private bool SafetyCheck ( )
{
2021-08-02 04:57:06 -07:00
var ev = new DoorSafetyEnabledEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
return ev . Safety | | _inhibitCrush ;
2020-07-17 10:51:43 +02:00
}
2021-02-12 07:02:14 -08:00
/// <summary>
/// Checks if we care about safety, and if so, if something is colliding with it; ignores the CanCollide of the door's PhysicsComponent.
/// </summary>
/// <returns>True if something is colliding with us and we shouldn't crush things, false otherwise.</returns>
private bool IsSafetyColliding ( )
2020-08-21 18:29:43 +02:00
{
2021-02-12 07:02:14 -08:00
var safety = SafetyCheck ( ) ;
2020-08-21 18:29:43 +02:00
2021-03-08 04:09:59 +11:00
if ( safety & & Owner . TryGetComponent ( out PhysicsComponent ? physicsComponent ) )
2021-02-12 07:02:14 -08:00
{
2021-07-21 21:15:12 +10:00
var broadPhaseSystem = EntitySystem . Get < SharedBroadphaseSystem > ( ) ;
2021-02-12 07:02:14 -08:00
2021-03-08 04:09:59 +11:00
// Use this version so we can ignore the CanCollide being false
2021-04-13 20:57:29 +10:00
foreach ( var e in broadPhaseSystem . GetCollidingEntities ( physicsComponent . Owner . Transform . MapID , physicsComponent . GetWorldAABB ( ) ) )
2021-02-12 07:02:14 -08:00
{
2021-03-08 04:09:59 +11:00
if ( ( physicsComponent . CollisionMask & e . CollisionLayer ) ! = 0 & & broadPhaseSystem . IntersectionPercent ( physicsComponent , e ) > 0.01f ) return true ;
2021-02-12 07:02:14 -08:00
}
}
return false ;
}
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
/// <summary>
/// Closes the door. Does not check if this is possible.
/// </summary>
public void Close ( )
{
State = DoorState . Closing ;
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
// no more autoclose; we ARE closed
_autoCloseCancelTokenSource ? . Cancel ( ) ;
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
_stateChangeCancelTokenSource ? . Cancel ( ) ;
_stateChangeCancelTokenSource = new ( ) ;
2021-08-02 04:57:06 -07:00
if ( CloseSound ! = null )
{
2021-08-11 20:52:06 -07:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , CloseSound . GetSound ( ) , Owner ,
2021-08-02 04:57:06 -07:00
AudioParams . Default . WithVolume ( - 10 ) ) ;
}
2021-02-12 07:02:14 -08:00
Owner . SpawnTimer ( CloseTimeOne , async ( ) = >
2020-08-21 18:29:43 +02:00
{
2021-02-12 07:02:14 -08:00
// if somebody walked into the door as it was closing, and we don't crush things
if ( IsSafetyColliding ( ) )
{
Open ( ) ;
return ;
}
OnPartialClose ( ) ;
await Timer . Delay ( CloseTimeTwo , _stateChangeCancelTokenSource . Token ) ;
2021-03-05 01:08:38 +01:00
2021-02-12 07:02:14 -08:00
if ( Occludes & & Owner . TryGetComponent ( out OccluderComponent ? occluder ) )
{
occluder . Enabled = true ;
}
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
State = DoorState . Closed ;
} , _stateChangeCancelTokenSource . Token ) ;
2020-08-21 18:29:43 +02:00
}
2021-02-12 07:02:14 -08:00
protected override void OnPartialClose ( )
2020-08-21 18:29:43 +02:00
{
2021-02-12 07:02:14 -08:00
base . OnPartialClose ( ) ;
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
// if safety is off, crushes people inside of the door, temporarily turning off collisions with them while doing so.
var becomeairtight = SafetyCheck ( ) | | ! TryCrush ( ) ;
2020-08-21 18:29:43 +02:00
2021-02-12 07:02:14 -08:00
if ( becomeairtight & & Owner . TryGetComponent ( out AirtightComponent ? airtight ) )
2020-08-21 18:29:43 +02:00
{
2021-07-25 09:04:58 +02:00
EntitySystem . Get < AirtightSystem > ( ) . SetAirblocked ( airtight , true ) ;
2020-08-21 18:29:43 +02:00
}
2021-02-12 07:02:14 -08:00
Owner . EntityManager . EventBus . RaiseEvent ( EventSource . Local , new AccessReaderChangeMessage ( Owner , true ) ) ;
2020-08-21 18:29:43 +02:00
}
2021-02-12 07:02:14 -08:00
/// <summary>
/// Crushes everyone colliding with us by more than 10%.
/// </summary>
/// <returns>True if we crushed somebody, false if we did not.</returns>
private bool TryCrush ( )
2017-10-22 23:48:01 +02:00
{
2021-02-12 07:02:14 -08:00
if ( PhysicsComponent = = null )
2017-10-22 23:48:01 +02:00
{
2021-02-12 07:02:14 -08:00
return false ;
2017-10-22 23:48:01 +02:00
}
2019-03-17 13:24:26 +01:00
2021-02-12 07:02:14 -08:00
var collidingentities = PhysicsComponent . GetCollidingEntities ( Vector2 . Zero , false ) ;
if ( ! collidingentities . Any ( ) )
2020-07-17 10:51:43 +02:00
{
2021-02-12 07:02:14 -08:00
return false ;
2020-07-17 10:51:43 +02:00
}
2019-03-17 13:24:26 +01:00
2021-03-08 04:09:59 +11:00
var doorAABB = PhysicsComponent . GetWorldAABB ( ) ;
2021-02-12 07:02:14 -08:00
var hitsomebody = false ;
2020-12-02 04:26:39 +11:00
2021-02-12 07:02:14 -08:00
// Crush
foreach ( var e in collidingentities )
2019-03-17 13:24:26 +01:00
{
2021-04-13 20:57:29 +10:00
if ( ! e . Owner . TryGetComponent ( out StunnableComponent ? stun )
| | ! e . Owner . TryGetComponent ( out IDamageableComponent ? damage ) )
2020-02-23 17:30:45 +01:00
{
2021-02-12 07:02:14 -08:00
continue ;
2020-02-23 17:30:45 +01:00
}
2020-07-17 10:51:43 +02:00
2021-03-08 04:09:59 +11:00
var percentage = e . GetWorldAABB ( ) . IntersectPercentage ( doorAABB ) ;
2020-08-22 22:29:20 +02:00
2021-02-12 07:02:14 -08:00
if ( percentage < 0.1f )
continue ;
2020-07-17 10:51:43 +02:00
2021-02-12 07:02:14 -08:00
hitsomebody = true ;
2021-04-13 20:57:29 +10:00
CurrentlyCrushing . Add ( e . Owner . Uid ) ;
2020-07-17 10:51:43 +02:00
2021-02-12 07:02:14 -08:00
damage . ChangeDamage ( DamageType . Blunt , DoorCrushDamage , false , Owner ) ;
stun . Paralyze ( DoorStunTime ) ;
}
// If we hit someone, open up after stun (opens right when stun ends)
if ( hitsomebody )
{
Owner . SpawnTimer ( TimeSpan . FromSeconds ( DoorStunTime ) - OpenTimeOne - OpenTimeTwo , Open ) ;
return true ;
}
return false ;
2017-10-22 23:48:01 +02:00
}
2021-02-12 07:02:14 -08:00
#endregion
public void Deny ( )
2019-09-01 22:57:22 +02:00
{
2021-08-02 04:57:06 -07:00
var ev = new BeforeDoorDeniedEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
if ( ev . Cancelled )
2021-02-12 07:02:14 -08:00
return ;
if ( State = = DoorState . Open | | IsWeldedShut )
2020-07-10 09:12:34 -04:00
return ;
2021-02-12 07:02:14 -08:00
_stateChangeCancelTokenSource ? . Cancel ( ) ;
_stateChangeCancelTokenSource = new ( ) ;
2019-11-29 07:53:26 -08:00
SetAppearance ( DoorVisualState . Deny ) ;
2021-08-02 04:57:06 -07:00
if ( DenySound ! = null )
{
if ( LastDenySoundTime = = TimeSpan . Zero )
{
LastDenySoundTime = _gameTiming . CurTime ;
}
else
{
var difference = _gameTiming . CurTime - LastDenySoundTime ;
if ( difference < TimeSpan . FromMilliseconds ( DenySoundMinimumInterval ) )
return ;
}
LastDenySoundTime = _gameTiming . CurTime ;
2021-08-11 20:52:06 -07:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , DenySound . GetSound ( ) , Owner ,
2021-08-02 04:57:06 -07:00
AudioParams . Default . WithVolume ( - 3 ) ) ;
}
2020-10-30 05:02:49 +01:00
Owner . SpawnTimer ( DenyTime , ( ) = >
2019-09-01 22:57:22 +02:00
{
2019-11-29 07:53:26 -08:00
SetAppearance ( DoorVisualState . Closed ) ;
2021-02-12 07:02:14 -08:00
} , _stateChangeCancelTokenSource . Token ) ;
2019-09-01 22:57:22 +02:00
}
2021-02-12 07:02:14 -08:00
/// <summary>
2021-08-02 04:57:06 -07:00
/// Starts a new auto close timer if this is appropriate
/// (i.e. event raised is not cancelled).
2021-02-12 07:02:14 -08:00
/// </summary>
public void RefreshAutoClose ( )
2017-10-22 23:48:01 +02:00
{
2021-08-02 04:57:06 -07:00
if ( State ! = DoorState . Open )
return ;
2021-02-12 07:02:14 -08:00
2021-08-02 04:57:06 -07:00
var autoev = new BeforeDoorAutoCloseEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , autoev , false ) ;
if ( autoev . Cancelled )
2017-10-22 23:48:01 +02:00
return ;
2021-08-02 04:57:06 -07:00
2021-02-12 07:02:14 -08:00
_autoCloseCancelTokenSource = new ( ) ;
2017-10-22 23:48:01 +02:00
2021-08-02 04:57:06 -07:00
var ev = new DoorGetCloseTimeModifierEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
var realCloseTime = AutoCloseDelay * ev . CloseTimeModifier ;
2020-07-20 16:03:05 +02:00
2021-02-15 04:01:26 -08:00
Owner . SpawnRepeatingTimer ( realCloseTime , async ( ) = >
2017-10-22 23:48:01 +02:00
{
2021-02-12 07:02:14 -08:00
if ( CanCloseGeneric ( ) )
2017-10-22 23:48:01 +02:00
{
2021-02-12 07:02:14 -08:00
// Close() cancels _autoCloseCancellationTokenSource, so we're fine.
Close ( ) ;
2017-10-22 23:48:01 +02:00
}
2021-02-12 07:02:14 -08:00
} , _autoCloseCancelTokenSource . Token ) ;
2019-03-17 13:24:26 +01:00
}
2020-08-30 19:16:29 +10:00
2021-02-12 07:02:14 -08:00
async Task < bool > IInteractUsing . InteractUsing ( InteractUsingEventArgs eventArgs )
2020-08-30 19:16:29 +10:00
{
2021-02-12 07:02:14 -08:00
if ( ! eventArgs . Using . TryGetComponent ( out ToolComponent ? tool ) )
2020-10-09 22:10:21 +11:00
{
2020-08-30 19:16:29 +10:00
return false ;
2020-10-09 22:10:21 +11:00
}
2020-08-30 19:16:29 +10:00
2021-02-12 07:02:14 -08:00
// for prying doors
if ( tool . HasQuality ( ToolQuality . Prying ) & & ! IsWeldedShut )
2020-10-09 22:10:21 +11:00
{
2021-08-02 04:57:06 -07:00
var ev = new DoorGetPryTimeModifierEvent ( ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , ev , false ) ;
2020-08-30 19:16:29 +10:00
2021-08-02 04:57:06 -07:00
var canEv = new BeforeDoorPryEvent ( eventArgs ) ;
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , canEv , false ) ;
var successfulPry = await tool . UseTool ( eventArgs . User , Owner ,
ev . PryTimeModifier * PryTime , ToolQuality . Prying , ( ) = > ! canEv . Cancelled ) ;
2020-10-10 15:25:13 +02:00
2021-02-12 07:02:14 -08:00
if ( successfulPry & & ! IsWeldedShut )
{
2021-08-02 04:57:06 -07:00
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , new OnDoorPryEvent ( eventArgs ) , false ) ;
2021-02-12 07:02:14 -08:00
if ( State = = DoorState . Closed )
{
Open ( ) ;
}
else if ( State = = DoorState . Open )
{
Close ( ) ;
}
return true ;
}
}
2020-08-30 19:16:29 +10:00
2021-02-12 07:02:14 -08:00
// for welding doors
if ( CanWeldShut & & tool . Owner . TryGetComponent ( out WelderComponent ? welder ) & & welder . WelderLit )
{
if ( ! _beingWelded )
{
_beingWelded = true ;
if ( await welder . UseTool ( eventArgs . User , Owner , 3f , ToolQuality . Welding , 3f , ( ) = > CanWeldShut ) )
{
2021-02-15 04:01:26 -08:00
// just in case
if ( ! CanWeldShut )
{
return false ;
}
2021-02-12 07:02:14 -08:00
_beingWelded = false ;
IsWeldedShut = ! IsWeldedShut ;
return true ;
}
_beingWelded = false ;
}
}
else
2020-10-09 22:10:21 +11:00
{
_beingWelded = false ;
}
2021-02-12 07:02:14 -08:00
return false ;
2020-08-30 19:16:29 +10:00
}
2020-10-10 22:33:56 +11:00
2021-03-17 05:07:49 -07:00
/// <summary>
/// Creates the corresponding door electronics board on the door.
/// This exists so when you deconstruct doors that were serialized with the map,
/// you can retrieve the door electronics board.
/// </summary>
private void CreateDoorElectronicsBoard ( )
{
// Ensure that the construction component is aware of the board container.
if ( Owner . TryGetComponent ( out ConstructionComponent ? construction ) )
construction . AddContainer ( "board" ) ;
// We don't do anything if this is null or empty.
if ( string . IsNullOrEmpty ( _boardPrototype ) )
return ;
var container = Owner . EnsureContainer < Container > ( "board" , out var existed ) ;
return ;
/ * // TODO ShadowCommander: Re-enable when access is added to boards. Requires map update.
if ( existed )
{
// We already contain a board. Note: We don't check if it's the right one!
if ( container . ContainedEntities . Count ! = 0 )
return ;
}
var board = Owner . EntityManager . SpawnEntity ( _boardPrototype , Owner . Transform . Coordinates ) ;
if ( ! container . Insert ( board ) )
Logger . Warning ( $"Couldn't insert board {board} into door {Owner}!" ) ;
* /
}
2021-02-18 09:09:07 +01:00
public override ComponentState GetComponentState ( ICommonSession player )
2020-10-10 22:33:56 +11:00
{
2021-02-12 07:02:14 -08:00
return new DoorComponentState ( State , StateChangeStartTime , CurrentlyCrushing , GameTiming . CurTime ) ;
2020-10-10 22:33:56 +11:00
}
}
2017-10-22 23:48:01 +02:00
}