mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-10-01 16:47:26 +00:00
+24









Debug
deltanedas
metalgearsloth
Nemanja
PJBot
Psychpsyo
deltanedas
metalgearsloth
ravage
Pieter-Jan Briers
LEVELcat
Repo
nikthechampiongr
Doru991
DrSmugleaf
Skarletto
NULL882
lunarcomets
Miro Kavaliou
Kara
Kacper Urbańczyk
TsjipTsjip
daerSeebaer
Fluffiest Floofers
Flareguy
Leon Friedrich
Kevin Zheng
drteaspoon420
Ubaser
Nim
Kacper Urbańczyk
Tox Cruize
Ed
Michael Cu
fd2cc8570d
* Automatic changelog update * Make NPC names proper nouns & fix some genders (#20534) * Proper names & genders * Uppercase proper names * Make Smile female * fix bingus wrinkly head (#20531) * rouny meat and steak (#20526) * lost friendship flavor * add rouny steak * rouny special meat * rouny meat textures --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Wearable Wet Floor Sign and 'Janitorial Suicide Vest' (#20311) * Explosive wet floor sign & janitorial suicide vest * fix attributions * Remove name & desc from explosive wet floor sign * Make wet floor sign chameleonable * Automatic changelog update * Minor slippery stuff (#20535) * Update submodule to 162.2.0 (#20570) * Predicted armor (#20560) * clean up some lines in smile the slime prototype (#20552) * Revert "Use full file path for temp replays (#19002)" (#20545) * Add EyesGlasses into ClothesMate (#20523) * Automatic changelog update * Fix Punpun crew monitor sensor (#20484) * Automatic changelog update * EasyPry airlocks for arrivals. Now also prying refactor I guess (#19394) Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> * Automatic changelog update * Make arcades hackable again (#20555) * Automatic changelog update * Health alert tweaks (#20557) * Automatic changelog update * Add active and ended game rule components, generic TryRoundStartAttempt and minPlayers field (#20564) * Improve active game rule querying, add generic try round start attempt method, move minPlayers to GameRuleComponent * Nukeops todo and cleanup * Remove Active field * Add EndedGameRuleComponent * bartender suit (#20521) * give me a drink bartender * guh * aARG * Necropolis and mine walls (#20578) * Automatic changelog update * archaic accent tweaks (#20567) * Automatic changelog update * Add confirmation to kick and respawn in the admin player actions panel (#20542) * Remove windows-latest CI runs, remove unused build-test-release.yml (#20540) * Change .editorconfig to keep existing attribute arrangement (#20538) * Automatic changelog update * Catch replay start and end errors on round restarts (#20565) * Update trivial components to use auto comp states (#20539) * Fix role unbans not applying in real time (#20547) * Disable AHelp buttons when no player is selected, update button styling (#20568) * Automatic changelog update * Added a toggle fullscreen button (default F11) (#20272) * Added a toggle fullscreen button (default F11) * Removed un-needed comments * Review Requested Changes * Fixed Acidental Spacing Change * bwoink, removed extraneous code * nothing, litterally * Automatic changelog update * Fix not networking markings (#20588) * Automatic changelog update * Update RobustToolbox to v162.2.1 (#20590) * Automatic changelog update * Glorfcode (force say on damage/stun/crit) (#20562) * Automatic changelog update * Fix AHelp progressively showing more AHelp panels (#20591) * Automatic changelog update * caninsert entitystorage tweaks (#20589) * Automatic changelog update * Fix mapping actions crashing on load (#20592) * Fix loading a map with a ThirstComponent crashing the game (#20594) * Fix electrocution displaying real name of disguised players (#20603) * Organ fixes (#20488) * Automatic changelog update * Kettle medical rework (#20435) * Initial try at medical rework for kettle * Re-add decal decorations to Kettle's medical * Add more lockers to kettle's med * Actually upload kettle's map, not its proto * Automatic changelog update * Added blocked visuals to volumetric pump (#20610) * Automatic changelog update * Move TimedDespawn to engine (#20515) * Update submodule to 163.0.0 (#20616) * Remove v0.1 version number from local main menu screen (#20617) * Remove cloneData parameter from AutoNetworkedField (#20596) * Update submodule to 164.0.0 (#20618) * fix cognizine ghost role (#20632) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Move ID layer one pixel to correct alignment (#20630) * Update nukie hardsuit descriptions (#20529) * Make holofans destructable (#20445) * Automatic changelog update * bowl is open (#20453) Co-authored-by: deltanedas <@deltanedas:kde.org> * Add TestPair.WaitCommand() (#20615) * Rename ThreatPrototype and mark fields as required (#20611) * Adjust hard bomb shape (#20608) * dragon refactor, objectives and use GenericAntag (#20201) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Added generic empty liquids tank (#20563) * Move view variables verb to the top of the list with no category and localize it (#20546) * saltern update (#20325) Co-authored-by: deltanedas <@deltanedas:kde.org> * Fix followers leaking (#20643) * Add Winter Boots (#20622) * add * fix prototype * Add QM mantle (#20621) * add * fix prototype * Automatic changelog update * Slime mobs breathe nitrogen and resprite their organs (#20577) * Slimes breathe nitrogen and resprite their organs * ups * mmm * Automatic changelog update * Fix the new lizard horn's consistency + issue (#20620) * fix * add * Revert "add" This reverts commit a054a3204a8f185a94ceb80b1bd3bc9f30423711. * Add RandomHumanoidAppearance component to for space ninjas (#20605) * Automatic changelog update * Space cat breathes space (#20550) * Space cat breathes space Made Space Cat lungs ROBUST * Made Space Cat's lungs ROBUST 2.0 * Automatic changelog update * Wide anomaly locator (#20581) * Add files via upload * Add files via upload * Add files via upload * Add files via upload * add textures * fix encoding * fix 2 * Automatic changelog update * Fixed Telescopic Shield Lighting (#20650) * Fixed Telescopic Shield Lighting Bug fix #20199 * no need for these at all --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> * Automatic changelog update * Add a special hardsuit for nukie medic (#20471) * Automatic changelog update * Carry over other mutations when doing species mutation (#20551) * Automatic changelog update * Add Spanish accent to poncho and sombrero (#20377) --------- Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: ravage <142820619+ravage123321@users.noreply.github.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: LEVELcat <68501903+LEVELcat@users.noreply.github.com> Co-authored-by: Repo <47093363+Titian3@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Doru991 <75124791+Doru991@users.noreply.github.com> Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com> Co-authored-by: Skarletto <122584947+Skarletto@users.noreply.github.com> Co-authored-by: NULL882 <104377798+NULL882@users.noreply.github.com> Co-authored-by: lunarcomets <140772713+lunarcomets@users.noreply.github.com> Co-authored-by: Miro Kavaliou <miraslauk@gmail.com> Co-authored-by: Kara <lunarautomaton6@gmail.com> Co-authored-by: Kacper Urbańczyk <kacperjaroslawurbanczyk@gmail.com> Co-authored-by: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Co-authored-by: daerSeebaer <61566539+daerSeebaer@users.noreply.github.com> Co-authored-by: Fluffiest Floofers <thebluewulf@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Kevin Zheng <kevinz5000@gmail.com> Co-authored-by: drteaspoon420 <87363733+drteaspoon420@users.noreply.github.com> Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com> Co-authored-by: Nim <128169402+Nimfar11@users.noreply.github.com> Co-authored-by: Kacper Urbańczyk <mikrel071204@gmail.com> Co-authored-by: Tox Cruize <141375638+TexCruize@users.noreply.github.com> Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> Co-authored-by: Michael Cu <43478115+michaelcu@users.noreply.github.com>
671 lines
24 KiB
C#
671 lines
24 KiB
C#
using System.Linq;
|
|
using Content.Shared.Access.Components;
|
|
using Content.Shared.Access.Systems;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Doors.Components;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Physics;
|
|
using Content.Shared.Stunnable;
|
|
using Content.Shared.Tag;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Events;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Timing;
|
|
using Content.Shared.Prying.Components;
|
|
|
|
namespace Content.Shared.Doors.Systems;
|
|
|
|
public abstract partial class SharedDoorSystem : EntitySystem
|
|
{
|
|
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
|
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;
|
|
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
|
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
|
[Dependency] protected readonly TagSystem Tags = default!;
|
|
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
|
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
|
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
|
[Dependency] private readonly OccluderSystem _occluder = default!;
|
|
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
|
|
|
/// <summary>
|
|
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
|
|
/// door. Used for safety close-blocking and crushing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The intersection percentage relies on WORLD AABBs. So if this is too small, and the grid is rotated 45
|
|
/// degrees, then an entity outside of the airlock may be crushed.
|
|
/// </remarks>
|
|
public const float IntersectPercentage = 0.2f;
|
|
|
|
/// <summary>
|
|
/// A set of doors that are currently opening, closing, or just queued to open/close after some delay.
|
|
/// </summary>
|
|
private readonly HashSet<DoorComponent> _activeDoors = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<DoorComponent, ComponentInit>(OnComponentInit);
|
|
SubscribeLocalEvent<DoorComponent, ComponentRemove>(OnRemove);
|
|
|
|
SubscribeLocalEvent<DoorComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
|
|
|
SubscribeLocalEvent<DoorComponent, ActivateInWorldEvent>(OnActivate);
|
|
|
|
SubscribeLocalEvent<DoorComponent, StartCollideEvent>(HandleCollide);
|
|
SubscribeLocalEvent<DoorComponent, PreventCollideEvent>(PreventCollision);
|
|
SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier);
|
|
|
|
}
|
|
|
|
protected virtual void OnComponentInit(EntityUid uid, DoorComponent door, ComponentInit args)
|
|
{
|
|
if (door.NextStateChange != null)
|
|
_activeDoors.Add(door);
|
|
else
|
|
{
|
|
// Make sure doors are not perpetually stuck opening or closing.
|
|
if (door.State == DoorState.Opening)
|
|
{
|
|
// force to open.
|
|
door.State = DoorState.Open;
|
|
door.Partial = false;
|
|
}
|
|
if (door.State == DoorState.Closing)
|
|
{
|
|
// force to closed.
|
|
door.State = DoorState.Closed;
|
|
door.Partial = false;
|
|
}
|
|
}
|
|
|
|
// should this door have collision and the like enabled?
|
|
var collidable = door.State == DoorState.Closed
|
|
|| door.State == DoorState.Closing && door.Partial
|
|
|| door.State == DoorState.Opening && !door.Partial;
|
|
|
|
SetCollidable(uid, collidable, door);
|
|
AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
|
|
}
|
|
|
|
private void OnRemove(EntityUid uid, DoorComponent door, ComponentRemove args)
|
|
{
|
|
_activeDoors.Remove(door);
|
|
}
|
|
|
|
#region StateManagement
|
|
private void OnHandleState(EntityUid uid, DoorComponent door, ref AfterAutoHandleStateEvent args)
|
|
{
|
|
if (door.NextStateChange == null)
|
|
_activeDoors.Remove(door);
|
|
else
|
|
_activeDoors.Add(door);
|
|
|
|
RaiseLocalEvent(uid, new DoorStateChangedEvent(door.State));
|
|
AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
|
|
}
|
|
|
|
protected void SetState(EntityUid uid, DoorState state, DoorComponent? door = null)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
// If no change, return to avoid firing a new DoorStateChangedEvent.
|
|
if (state == door.State)
|
|
return;
|
|
|
|
switch (state)
|
|
{
|
|
case DoorState.Opening:
|
|
_activeDoors.Add(door);
|
|
door.NextStateChange = GameTiming.CurTime + door.OpenTimeOne;
|
|
break;
|
|
|
|
case DoorState.Closing:
|
|
_activeDoors.Add(door);
|
|
door.NextStateChange = GameTiming.CurTime + door.CloseTimeOne;
|
|
break;
|
|
|
|
case DoorState.Denying:
|
|
_activeDoors.Add(door);
|
|
door.NextStateChange = GameTiming.CurTime + door.DenyDuration;
|
|
break;
|
|
|
|
case DoorState.Emagging:
|
|
_activeDoors.Add(door);
|
|
door.NextStateChange = GameTiming.CurTime + door.EmagDuration;
|
|
break;
|
|
|
|
case DoorState.Open:
|
|
door.Partial = false;
|
|
if (door.NextStateChange == null)
|
|
_activeDoors.Remove(door);
|
|
break;
|
|
case DoorState.Closed:
|
|
// May want to keep the door around to re-check for opening if we got a contact during closing.
|
|
door.Partial = false;
|
|
break;
|
|
}
|
|
|
|
door.State = state;
|
|
Dirty(door);
|
|
RaiseLocalEvent(uid, new DoorStateChangedEvent(state), false);
|
|
AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Interactions
|
|
protected virtual void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args)
|
|
{
|
|
// avoid client-mispredicts, as the server will definitely handle this event
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnPryTimeModifier(EntityUid uid, DoorComponent door, ref GetPryTimeModifierEvent args)
|
|
{
|
|
args.BaseTime = door.PryTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the door state/visuals and play an access denied sound when a user without access interacts with the
|
|
/// door.
|
|
/// </summary>
|
|
public void Deny(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
if (door.State != DoorState.Closed)
|
|
return;
|
|
|
|
// might not be able to deny without power or some other blocker.
|
|
var ev = new BeforeDoorDeniedEvent();
|
|
RaiseLocalEvent(uid, ev, false);
|
|
if (ev.Cancelled)
|
|
return;
|
|
|
|
SetState(uid, DoorState.Denying, door);
|
|
|
|
if (door.DenySound != null)
|
|
PlaySound(uid, door.DenySound, AudioParams.Default.WithVolume(-3), user, predicted);
|
|
}
|
|
|
|
|
|
public bool TryToggleDoor(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return false;
|
|
|
|
if (door.State == DoorState.Closed)
|
|
{
|
|
return TryOpen(uid, door, user, predicted);
|
|
}
|
|
else if (door.State == DoorState.Open)
|
|
{
|
|
return TryClose(uid, door, user, predicted);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region Opening
|
|
public bool TryOpen(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false, bool quiet = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return false;
|
|
|
|
if (!CanOpen(uid, door, user, quiet))
|
|
return false;
|
|
|
|
StartOpening(uid, door, user, predicted);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool CanOpen(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool quiet = true)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return false;
|
|
|
|
if (door.State == DoorState.Welded)
|
|
return false;
|
|
|
|
var ev = new BeforeDoorOpenedEvent() { User = user };
|
|
RaiseLocalEvent(uid, ev, false);
|
|
if (ev.Cancelled)
|
|
return false;
|
|
|
|
if (!HasAccess(uid, user, door))
|
|
{
|
|
if (!quiet)
|
|
Deny(uid, door);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immediately start opening a door
|
|
/// </summary>
|
|
/// <param name="uid"> The uid of the door</param>
|
|
/// <param name="door"> The doorcomponent of the door</param>
|
|
/// <param name="user"> The user (if any) opening the door</param>
|
|
/// <param name="predicted">Whether the interaction would have been
|
|
/// predicted. See comments in the PlaySound method on the Server system for details</param>
|
|
public virtual void StartOpening(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
SetState(uid, DoorState.Opening, door);
|
|
|
|
if (door.OpenSound != null)
|
|
PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted);
|
|
|
|
// I'm not sure what the intent here is/was? It plays a sound if the user is opening a door with a hands
|
|
// component, but no actual hands!? What!? Is this the sound of them head-butting the door to get it to open??
|
|
// I'm 99% sure something is wrong here, but I kind of want to keep it this way.
|
|
|
|
if (user != null && TryComp(user.Value, out HandsComponent? hands) && hands.Hands.Count == 0)
|
|
PlaySound(uid, door.TryOpenDoorSound, AudioParams.Default.WithVolume(-2), user, predicted);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the door is partially opened. The door becomes transparent and stops colliding with entities.
|
|
/// </summary>
|
|
public void OnPartialOpen(EntityUid uid, DoorComponent? door = null)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
SetCollidable(uid, false, door);
|
|
door.Partial = true;
|
|
door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo;
|
|
_activeDoors.Add(door);
|
|
Dirty(door);
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Closing
|
|
public bool TryClose(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return false;
|
|
|
|
if (!CanClose(uid, door, user, false))
|
|
return false;
|
|
|
|
StartClosing(uid, door, user, predicted);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immediately start closing a door
|
|
/// </summary>
|
|
/// <param name="uid"> The uid of the door</param>
|
|
/// <param name="door"> The doorcomponent of the door</param>
|
|
/// <param name="user"> The user (if any) opening the door</param>
|
|
/// <param name="predicted">Whether the interaction would have been
|
|
/// predicted. See comments in the PlaySound method on the Server system for details</param>
|
|
public bool CanClose(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool quiet = true)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return false;
|
|
|
|
// since both closing/closed and welded are door states, we need to prevent 'closing'
|
|
// a welded door or else there will be weird state bugs
|
|
if (door.State is DoorState.Welded or DoorState.Closed)
|
|
return false;
|
|
|
|
var ev = new BeforeDoorClosedEvent(door.PerformCollisionCheck);
|
|
RaiseLocalEvent(uid, ev, false);
|
|
if (ev.Cancelled)
|
|
return false;
|
|
|
|
if (!HasAccess(uid, user, door))
|
|
return false;
|
|
|
|
return !ev.PerformCollisionCheck || !GetColliding(uid).Any();
|
|
}
|
|
|
|
public virtual void StartClosing(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
SetState(uid, DoorState.Closing, door);
|
|
|
|
if (door.CloseSound != null)
|
|
PlaySound(uid, door.CloseSound, AudioParams.Default.WithVolume(-5), user, predicted);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the door is partially closed. This is when the door becomes "solid". If this process fails (e.g., a
|
|
/// mob entered the door as it was closing), then this returns false. Otherwise, returns true;
|
|
/// </summary>
|
|
public bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
|
{
|
|
if (!Resolve(uid, ref door, ref physics))
|
|
return false;
|
|
|
|
door.Partial = true;
|
|
Dirty(door);
|
|
|
|
// Make sure no entity waled into the airlock when it started closing.
|
|
if (!CanClose(uid, door))
|
|
{
|
|
door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo;
|
|
door.State = DoorState.Opening;
|
|
AppearanceSystem.SetData(uid, DoorVisuals.State, DoorState.Opening);
|
|
return false;
|
|
}
|
|
|
|
SetCollidable(uid, true, door, physics);
|
|
door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo;
|
|
_activeDoors.Add(door);
|
|
|
|
// Crush any entities. Note that we don't check airlock safety here. This should have been checked before
|
|
// the door closed.
|
|
Crush(uid, door, physics);
|
|
return true;
|
|
}
|
|
#endregion
|
|
|
|
#region Collisions
|
|
|
|
protected virtual void SetCollidable(
|
|
EntityUid uid,
|
|
bool collidable,
|
|
DoorComponent? door = null,
|
|
PhysicsComponent? physics = null,
|
|
OccluderComponent? occluder = null)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
if (Resolve(uid, ref physics, false))
|
|
PhysicsSystem.SetCanCollide(uid, collidable, body: physics);
|
|
|
|
if (!collidable)
|
|
door.CurrentlyCrushing.Clear();
|
|
|
|
if (door.Occludes)
|
|
_occluder.SetEnabled(uid, collidable, occluder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Crushes everyone colliding with us by more than <see cref="IntersectPercentage"/>%.
|
|
/// </summary>
|
|
public void Crush(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
|
{
|
|
if (!Resolve(uid, ref door))
|
|
return;
|
|
|
|
if (!door.CanCrush)
|
|
return;
|
|
|
|
// Find entities and apply curshing effects
|
|
var stunTime = door.DoorStunTime + door.OpenTimeOne;
|
|
foreach (var entity in GetColliding(uid, physics))
|
|
{
|
|
door.CurrentlyCrushing.Add(entity);
|
|
if (door.CrushDamage != null)
|
|
_damageableSystem.TryChangeDamage(entity, door.CrushDamage, origin: uid);
|
|
|
|
_stunSystem.TryParalyze(entity, stunTime, true);
|
|
}
|
|
|
|
if (door.CurrentlyCrushing.Count == 0)
|
|
return;
|
|
|
|
// queue the door to open so that the player is no longer stunned once it has FINISHED opening.
|
|
door.NextStateChange = GameTiming.CurTime + door.DoorStunTime;
|
|
door.Partial = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all entities that collide with this door by more than <see cref="IntersectPercentage"/> percent.\
|
|
/// </summary>
|
|
public IEnumerable<EntityUid> GetColliding(EntityUid uid, PhysicsComponent? physics = null)
|
|
{
|
|
if (!Resolve(uid, ref physics))
|
|
yield break;
|
|
|
|
// TODO SLOTH fix electro's code.
|
|
// ReSharper disable once InconsistentNaming
|
|
var doorAABB = _entityLookup.GetWorldAABB(uid);
|
|
|
|
foreach (var otherPhysics in PhysicsSystem.GetCollidingEntities(Transform(uid).MapID, doorAABB))
|
|
{
|
|
if (otherPhysics == physics)
|
|
continue;
|
|
|
|
//TODO: Make only shutters ignore these objects upon colliding instead of all airlocks
|
|
// Excludes Glasslayer for windows, GlassAirlockLayer for windoors, TableLayer for tables
|
|
if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.TableLayer)
|
|
continue;
|
|
|
|
//If the colliding entity is a slippable item ignore it by the airlock
|
|
if (otherPhysics.CollisionLayer == (int)CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int)CollisionGroup.ItemMask)
|
|
continue;
|
|
|
|
if ((physics.CollisionMask & otherPhysics.CollisionLayer) == 0 && (otherPhysics.CollisionMask & physics.CollisionLayer) == 0)
|
|
continue;
|
|
|
|
if (_entityLookup.GetWorldAABB(otherPhysics.Owner).IntersectPercentage(doorAABB) < IntersectPercentage)
|
|
continue;
|
|
|
|
yield return otherPhysics.Owner;
|
|
}
|
|
}
|
|
|
|
private void PreventCollision(EntityUid uid, DoorComponent component, ref PreventCollideEvent args)
|
|
{
|
|
if (component.CurrentlyCrushing.Contains(args.OtherEntity))
|
|
{
|
|
args.Cancelled = true;
|
|
}
|
|
}
|
|
|
|
protected virtual void HandleCollide(EntityUid uid, DoorComponent door, ref StartCollideEvent args)
|
|
{
|
|
// TODO ACCESS READER move access reader to shared and predict door opening/closing
|
|
// Then this can be moved to the shared system without mispredicting.
|
|
}
|
|
#endregion
|
|
|
|
#region Access
|
|
|
|
/// <summary>
|
|
/// Does the user have the permissions required to open this door?
|
|
/// </summary>
|
|
public bool HasAccess(EntityUid uid, EntityUid? user = null, DoorComponent? door = null, AccessReaderComponent? access = null)
|
|
{
|
|
// TODO network AccessComponent for predicting doors
|
|
|
|
// if there is no "user" we skip the access checks. Access is also ignored in some game-modes.
|
|
if (user == null || AccessType == AccessTypes.AllowAll)
|
|
return true;
|
|
|
|
// If the door is on emergency access we skip the checks.
|
|
if (TryComp<AirlockComponent>(uid, out var airlock) && airlock.EmergencyAccess)
|
|
return true;
|
|
|
|
// Anyone can click to open firelocks
|
|
if (Resolve(uid, ref door) && door.State == DoorState.Closed &&
|
|
TryComp<FirelockComponent>(uid, out var firelock))
|
|
return true;
|
|
|
|
if (!Resolve(uid, ref access, false))
|
|
return true;
|
|
|
|
var isExternal = access.AccessLists.Any(list => list.Contains("External"));
|
|
|
|
return AccessType switch
|
|
{
|
|
// Some game modes modify access rules.
|
|
AccessTypes.AllowAllIdExternal => !isExternal || _accessReaderSystem.IsAllowed(user.Value, uid, access),
|
|
AccessTypes.AllowAllNoExternal => !isExternal,
|
|
_ => _accessReaderSystem.IsAllowed(user.Value, uid, access)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the base access behavior of all doors on the station.
|
|
/// </summary>
|
|
public AccessTypes AccessType = AccessTypes.Id;
|
|
|
|
/// <summary>
|
|
/// How door access should be handled.
|
|
/// </summary>
|
|
public enum AccessTypes
|
|
{
|
|
/// <summary> ID based door access. </summary>
|
|
Id,
|
|
/// <summary>
|
|
/// Allows everyone to open doors, except external which airlocks are still handled with ID's
|
|
/// </summary>
|
|
AllowAllIdExternal,
|
|
/// <summary>
|
|
/// Allows everyone to open doors, except external airlocks which are never allowed, even if the user has
|
|
/// ID access.
|
|
/// </summary>
|
|
AllowAllNoExternal,
|
|
/// <summary> Allows everyone to open all doors. </summary>
|
|
AllowAll
|
|
}
|
|
#endregion
|
|
|
|
#region Updating
|
|
/// <summary>
|
|
/// Schedule an open or closed door to progress to the next state after some time.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the requested delay is null or non-positive, this will make the door stay open or closed indefinitely.
|
|
/// </remarks>
|
|
public void SetNextStateChange(EntityUid uid, TimeSpan? delay, DoorComponent? door = null)
|
|
{
|
|
if (!Resolve(uid, ref door, false))
|
|
return;
|
|
|
|
// If the door is not currently just open or closed, it is busy doing something else (or welded shut). So in
|
|
// that case we do nothing.
|
|
if (door.State != DoorState.Open && door.State != DoorState.Closed)
|
|
return;
|
|
|
|
// Is this trying to prevent an update? (e.g., cancel an auto-close)
|
|
if (delay == null || delay.Value <= TimeSpan.Zero)
|
|
{
|
|
door.NextStateChange = null;
|
|
_activeDoors.Remove(door);
|
|
return;
|
|
}
|
|
|
|
door.NextStateChange = GameTiming.CurTime + delay.Value;
|
|
_activeDoors.Add(door);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterate over active doors and progress them to the next state if they need to be updated.
|
|
/// </summary>
|
|
public override void Update(float frameTime)
|
|
{
|
|
var time = GameTiming.CurTime;
|
|
|
|
foreach (var door in _activeDoors.ToList())
|
|
{
|
|
if (door.Deleted || door.NextStateChange == null)
|
|
{
|
|
_activeDoors.Remove(door);
|
|
continue;
|
|
}
|
|
|
|
if (Paused(door.Owner))
|
|
continue;
|
|
|
|
if (door.NextStateChange.Value < time)
|
|
NextState(door, time);
|
|
|
|
if (door.State == DoorState.Closed &&
|
|
TryComp<PhysicsComponent>(door.Owner, out var doorBody))
|
|
{
|
|
// If something bumped into us during closing then start to re-open, otherwise, remove it from active.
|
|
_activeDoors.Remove(door);
|
|
CheckDoorBump(door, doorBody);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void CheckDoorBump(DoorComponent component, PhysicsComponent body) { }
|
|
|
|
/// <summary>
|
|
/// Makes a door proceed to the next state (if applicable).
|
|
/// </summary>
|
|
private void NextState(DoorComponent door, TimeSpan time)
|
|
{
|
|
door.NextStateChange = null;
|
|
|
|
if (door.CurrentlyCrushing.Count > 0)
|
|
// This is a closed door that is crushing people and needs to auto-open. Note that we don't check "can open"
|
|
// here. The door never actually finished closing and we don't want people to get stuck inside of doors.
|
|
StartOpening(door.Owner, door, predicted: true);
|
|
|
|
switch (door.State)
|
|
{
|
|
case DoorState.Opening:
|
|
// Either fully or partially open this door.
|
|
if (door.Partial)
|
|
SetState(door.Owner, DoorState.Open, door);
|
|
else
|
|
OnPartialOpen(door.Owner, door);
|
|
|
|
break;
|
|
|
|
case DoorState.Closing:
|
|
// Either fully or partially close this door.
|
|
if (door.Partial)
|
|
SetState(door.Owner, DoorState.Closed, door);
|
|
else
|
|
OnPartialClose(door.Owner, door);
|
|
|
|
break;
|
|
|
|
case DoorState.Denying:
|
|
// Finish denying entry and return to the closed state.
|
|
SetState(door.Owner, DoorState.Closed, door);
|
|
break;
|
|
|
|
case DoorState.Emagging:
|
|
StartOpening(door.Owner, door);
|
|
break;
|
|
|
|
case DoorState.Open:
|
|
// This door is open, and queued for an auto-close.
|
|
if (!TryClose(door.Owner, door, predicted: true))
|
|
{
|
|
// The door failed to close (blocked?). Try again in one second.
|
|
door.NextStateChange = time + TimeSpan.FromSeconds(1);
|
|
}
|
|
break;
|
|
|
|
case DoorState.Welded:
|
|
// A welded door? This should never have been active in the first place.
|
|
Log.Error($"Welded door was in the list of active doors. Door: {ToPrettyString(door.Owner)}");
|
|
break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
|
|
}
|