Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,461 @@
#nullable enable
using System;
using System.Threading;
using Content.Server.Power.Components;
using Content.Server.VendingMachines;
using Content.Server.Wires.Components;
using Content.Shared.Doors;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
using static Content.Shared.Wires.SharedWiresComponent;
using static Content.Shared.Wires.SharedWiresComponent.WiresAction;
namespace Content.Server.Doors.Components
{
/// <summary>
/// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IDoorCheck))]
public class AirlockComponent : Component, IWires, IDoorCheck
{
public override string Name => "Airlock";
[ComponentDependency]
private readonly ServerDoorComponent? _doorComponent = null;
[ComponentDependency]
private readonly SharedAppearanceComponent? _appearanceComponent = null;
[ComponentDependency]
private readonly PowerReceiverComponent? _receiverComponent = null;
[ComponentDependency]
private readonly WiresComponent? _wiresComponent = null;
/// <summary>
/// Duration for which power will be disabled after pulsing either power wire.
/// </summary>
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
private CancellationTokenSource _powerWiresPulsedTimerCancel = new();
private bool _powerWiresPulsed;
/// <summary>
/// True if either power wire was pulsed in the last <see cref="PowerWiresTimeout"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private bool PowerWiresPulsed
{
get => _powerWiresPulsed;
set
{
_powerWiresPulsed = value;
UpdateWiresStatus();
UpdatePowerCutStatus();
}
}
private bool _boltsDown;
[ViewVariables(VVAccess.ReadWrite)]
public bool BoltsDown
{
get => _boltsDown;
set
{
_boltsDown = value;
UpdateBoltLightStatus();
}
}
private bool _boltLightsWirePulsed = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool BoltLightsVisible
{
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
&& _doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Closed;
set
{
_boltLightsWirePulsed = value;
UpdateBoltLightStatus();
}
}
private static readonly TimeSpan AutoCloseDelayFast = TimeSpan.FromSeconds(1);
[ViewVariables(VVAccess.ReadWrite)]
private bool _autoClose = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool _normalCloseSpeed = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool _safety = true;
public override void Initialize()
{
base.Initialize();
if (_receiverComponent != null && _appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.Powered, _receiverComponent.Powered);
}
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
PowerDeviceOnOnPowerStateChanged(powerChanged);
break;
}
}
void IDoorCheck.OnStateChange(SharedDoorComponent.DoorState doorState)
{
// Only show the maintenance panel if the airlock is closed
if (_wiresComponent != null)
{
_wiresComponent.IsPanelVisible = doorState != SharedDoorComponent.DoorState.Open;
}
// If the door is closed, we should look if the bolt was locked while closing
UpdateBoltLightStatus();
}
bool IDoorCheck.OpenCheck() => CanChangeState();
bool IDoorCheck.CloseCheck() => CanChangeState();
bool IDoorCheck.DenyCheck() => CanChangeState();
bool IDoorCheck.SafetyCheck() => _safety;
bool IDoorCheck.AutoCloseCheck() => _autoClose;
TimeSpan? IDoorCheck.GetCloseSpeed()
{
if (_normalCloseSpeed)
{
return null;
}
return AutoCloseDelayFast;
}
bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs)
{
if (_wiresComponent != null && _wiresComponent.IsPanelOpen &&
eventArgs.User.TryGetComponent(out ActorComponent? actor))
{
_wiresComponent.OpenInterface(actor.PlayerSession);
return true;
}
return false;
}
bool IDoorCheck.CanPryCheck(InteractUsingEventArgs eventArgs)
{
if (IsBolted())
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("The airlock's bolts prevent it from being forced!"));
return false;
}
if (IsPowered())
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("The powered motors block your efforts!"));
return false;
}
return true;
}
private bool CanChangeState()
{
return IsPowered() && !IsBolted();
}
private bool IsBolted()
{
return _boltsDown;
}
private bool IsPowered()
{
return _receiverComponent == null || _receiverComponent.Powered;
}
private void UpdateBoltLightStatus()
{
if (_appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
}
}
private void UpdateWiresStatus()
{
if (_doorComponent == null)
{
return;
}
var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR");
if (PowerWiresPulsed)
{
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
}
else if (_wiresComponent != null &&
_wiresComponent.IsWireCut(Wires.MainPower) &&
_wiresComponent.IsWireCut(Wires.BackupPower))
{
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
}
var boltStatus =
new StatusLightData(Color.Red, BoltsDown ? StatusLightState.On : StatusLightState.Off, "BOLT");
var boltLightsStatus = new StatusLightData(Color.Lime,
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL");
var timingStatus =
new StatusLightData(Color.Orange, !_autoClose ? StatusLightState.Off :
!_normalCloseSpeed ? StatusLightState.BlinkingSlow :
StatusLightState.On,
"TIME");
var safetyStatus =
new StatusLightData(Color.Red, _safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
if (_wiresComponent == null)
{
return;
}
_wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
_wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
_wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
_wiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
_wiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
_wiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
/*
_wires.SetStatus(6, powerLight);
_wires.SetStatus(7, powerLight);
_wires.SetStatus(8, powerLight);
_wires.SetStatus(9, powerLight);
_wires.SetStatus(10, powerLight);
_wires.SetStatus(11, powerLight);*/
}
private void UpdatePowerCutStatus()
{
if (_receiverComponent == null)
{
return;
}
if (PowerWiresPulsed)
{
_receiverComponent.PowerDisabled = true;
return;
}
if (_wiresComponent == null)
{
return;
}
_receiverComponent.PowerDisabled =
_wiresComponent.IsWireCut(Wires.MainPower) ||
_wiresComponent.IsWireCut(Wires.BackupPower);
}
private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e)
{
if (_appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.Powered, e.Powered);
}
// BoltLights also got out
UpdateBoltLightStatus();
}
private enum Wires
{
/// <summary>
/// Pulsing turns off power for <see cref="AirlockComponent.PowerWiresTimeout"/>.
/// Cutting turns off power permanently if <see cref="BackupPower"/> is also cut.
/// Mending restores power.
/// </summary>
MainPower,
/// <see cref="MainPower"/>
BackupPower,
/// <summary>
/// Pulsing causes for bolts to toggle (but only raise if power is on)
/// Cutting causes Bolts to drop
/// Mending does nothing
/// </summary>
Bolts,
/// <summary>
/// Pulsing causes light to toggle
/// Cutting causes light to go out
/// Mending causes them to go on again
/// </summary>
BoltLight,
// Placeholder for when AI is implemented
AIControl,
/// <summary>
/// Pulsing causes door to close faster
/// Cutting disables door timer, causing door to stop closing automatically
/// Mending restores door timer
/// </summary>
Timing,
/// <summary>
/// Pulsing toggles safety
/// Cutting disables safety
/// Mending enables safety
/// </summary>
Safety,
}
public void RegisterWires(WiresComponent.WiresBuilder builder)
{
builder.CreateWire(Wires.MainPower);
builder.CreateWire(Wires.BackupPower);
builder.CreateWire(Wires.Bolts);
builder.CreateWire(Wires.BoltLight);
builder.CreateWire(Wires.Timing);
builder.CreateWire(Wires.Safety);
/*
builder.CreateWire(6);
builder.CreateWire(7);
builder.CreateWire(8);
builder.CreateWire(9);
builder.CreateWire(10);
builder.CreateWire(11);*/
UpdateWiresStatus();
}
public void WiresUpdate(WiresUpdateEventArgs args)
{
if(_doorComponent == null)
{
return;
}
if (args.Action == Pulse)
{
switch (args.Identifier)
{
case Wires.MainPower:
case Wires.BackupPower:
PowerWiresPulsed = true;
_powerWiresPulsedTimerCancel.Cancel();
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
Owner.SpawnTimer(PowerWiresTimeout,
() => PowerWiresPulsed = false,
_powerWiresPulsedTimerCancel.Token);
break;
case Wires.Bolts:
if (!BoltsDown)
{
SetBoltsWithAudio(true);
}
else
{
if (IsPowered()) // only raise again if powered
{
SetBoltsWithAudio(false);
}
}
break;
case Wires.BoltLight:
// we need to change the property here to set the appearance again
BoltLightsVisible = !_boltLightsWirePulsed;
break;
case Wires.Timing:
_normalCloseSpeed = !_normalCloseSpeed;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = !_safety;
break;
}
}
else if (args.Action == Mend)
{
switch (args.Identifier)
{
case Wires.MainPower:
case Wires.BackupPower:
// mending power wires instantly restores power
_powerWiresPulsedTimerCancel?.Cancel();
PowerWiresPulsed = false;
break;
case Wires.BoltLight:
BoltLightsVisible = true;
break;
case Wires.Timing:
_autoClose = true;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = true;
break;
}
}
else if (args.Action == Cut)
{
switch (args.Identifier)
{
case Wires.Bolts:
SetBoltsWithAudio(true);
break;
case Wires.BoltLight:
BoltLightsVisible = false;
break;
case Wires.Timing:
_autoClose = false;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = false;
break;
}
}
UpdateWiresStatus();
UpdatePowerCutStatus();
}
public void SetBoltsWithAudio(bool newBolts)
{
if (newBolts == BoltsDown)
{
return;
}
BoltsDown = newBolts;
SoundSystem.Play(Filter.Broadcast(), newBolts ? "/Audio/Machines/boltsdown.ogg" : "/Audio/Machines/boltsup.ogg", Owner);
}
}
}

View File

@@ -0,0 +1,669 @@
#nullable enable
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Access;
using Content.Server.Access.Components;
using Content.Server.Construction.Components;
using Content.Server.GameObjects.Components.Atmos;
using Content.Server.Hands.Components;
using Content.Server.Stunnable.Components;
using Content.Server.Tools.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Doors;
using Content.Shared.Interaction;
using Content.Shared.Tool;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Doors.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(SharedDoorComponent))]
public class ServerDoorComponent : SharedDoorComponent, IActivate, IStartCollide, IInteractUsing, IMapInit
{
[ComponentDependency]
private readonly IDoorCheck? _doorCheck = null;
[ViewVariables]
[DataField("board")]
private string? _boardPrototype;
public override DoorState State
{
get => base.State;
protected set
{
if (State == value)
{
return;
}
base.State = value;
StateChangeStartTime = State switch
{
DoorState.Open or DoorState.Closed => null,
DoorState.Opening or DoorState.Closing => GameTiming.CurTime,
_ => throw new ArgumentOutOfRangeException(),
};
if (_doorCheck != null)
{
_doorCheck.OnStateChange(State);
RefreshAutoClose();
}
Dirty();
}
}
private static readonly TimeSpan AutoCloseDelay = TimeSpan.FromSeconds(5);
private CancellationTokenSource? _stateChangeCancelTokenSource;
private CancellationTokenSource? _autoCloseCancelTokenSource;
private const int DoorCrushDamage = 15;
private const float DoorStunTime = 5f;
/// <summary>
/// Whether the door will ever crush.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("inhibitCrush")]
private bool _inhibitCrush;
/// <summary>
/// Whether the door blocks light.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("occludes")]
private bool _occludes = true;
public bool Occludes => _occludes;
/// <summary>
/// Whether the door will open when it is bumped into.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("bumpOpen")]
private bool _bumpOpen = true;
/// <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>
[ViewVariables(VVAccess.ReadWrite)] [DataField("startOpen")]
private bool _startOpen;
/// <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>
[DataField("welded")]
private bool _isWeldedShut;
/// <summary>
/// Whether the airlock is welded shut.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool IsWeldedShut
{
get => _isWeldedShut;
set
{
if (_isWeldedShut == value)
{
return;
}
_isWeldedShut = value;
SetAppearance(_isWeldedShut ? DoorVisualState.Welded : DoorVisualState.Closed);
}
}
/// <summary>
/// Whether the door can ever be welded shut.
/// </summary>
[DataField("weldable")]
private bool _weldable = true;
/// <summary>
/// Whether the door can currently be welded.
/// </summary>
private bool CanWeldShut => _weldable && State == DoorState.Closed;
/// <summary>
/// Whether something is currently using a welder on this so DoAfter isn't spammed.
/// </summary>
private bool _beingWelded;
//[ViewVariables(VVAccess.ReadWrite)]
//[DataField("canCrush")]
//private bool _canCrush = true; // TODO implement door crushing
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);
}
CreateDoorElectronicsBoard();
}
public override void OnRemove()
{
_stateChangeCancelTokenSource?.Cancel();
_autoCloseCancelTokenSource?.Cancel();
base.OnRemove();
}
void IMapInit.MapInit()
{
if (_startOpen)
{
if (IsWeldedShut)
{
Logger.Warning("{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true.", Owner.Name);
return;
}
QuickOpen();
}
CreateDoorElectronicsBoard();
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (_doorCheck != null && _doorCheck.BlockActivate(eventArgs))
{
return;
}
if (State == DoorState.Open)
{
TryClose(eventArgs.User);
}
else if (State == DoorState.Closed)
{
TryOpen(eventArgs.User);
}
}
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
{
if (State != DoorState.Closed)
{
return;
}
if (!_bumpOpen)
{
return;
}
// Disabled because it makes it suck hard to walk through double doors.
TryOpen(otherFixture.Body.Owner);
}
#region Opening
public void TryOpen(IEntity user)
{
if (CanOpenByEntity(user))
{
Open();
if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0)
{
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Effects/bang.ogg", Owner,
AudioParams.Default.WithVolume(-2));
}
}
else
{
Deny();
}
}
public bool CanOpenByEntity(IEntity user)
{
if(!CanOpenGeneric())
{
return false;
}
if (!Owner.TryGetComponent(out AccessReader? access))
{
return true;
}
var doorSystem = EntitySystem.Get<DoorSystem>();
var isAirlockExternal = HasAccessType("External");
return doorSystem.AccessType switch
{
DoorSystem.AccessTypes.AllowAll => true,
DoorSystem.AccessTypes.AllowAllIdExternal => isAirlockExternal || access.IsAllowed(user),
DoorSystem.AccessTypes.AllowAllNoExternal => !isAirlockExternal,
_ => access.IsAllowed(user)
};
}
/// <summary>
/// Returns whether a door has a certain access type. For example, maintenance doors will have access type
/// "Maintenance" in their AccessReader.
/// </summary>
private bool HasAccessType(string accessType)
{
if (Owner.TryGetComponent(out AccessReader? access))
{
return access.AccessLists.Any(list => list.Contains(accessType));
}
return true;
}
/// <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()
{
// note the welded check -- CanCloseGeneric does not have this
if (IsWeldedShut)
{
return false;
}
if(_doorCheck != null)
{
return _doorCheck.OpenCheck();
}
return true;
}
/// <summary>
/// Opens the door. Does not check if this is possible.
/// </summary>
public void Open()
{
State = DoorState.Opening;
if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = false;
}
_stateChangeCancelTokenSource?.Cancel();
_stateChangeCancelTokenSource = new();
Owner.SpawnTimer(OpenTimeOne, async () =>
{
OnPartialOpen();
await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token);
State = DoorState.Open;
}, _stateChangeCancelTokenSource.Token);
}
protected override void OnPartialOpen()
{
if (Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = false;
}
base.OnPartialOpen();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false));
}
private void QuickOpen()
{
if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = false;
}
OnPartialOpen();
State = DoorState.Open;
}
#endregion
#region Closing
public void TryClose(IEntity user)
{
if (!CanCloseByEntity(user))
{
Deny();
return;
}
Close();
}
public bool CanCloseByEntity(IEntity user)
{
if (!CanCloseGeneric())
{
return false;
}
if (!Owner.TryGetComponent(out AccessReader? access))
{
return true;
}
return access.IsAllowed(user);
}
/// <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()
{
if (_doorCheck != null && !_doorCheck.CloseCheck())
{
return false;
}
return !IsSafetyColliding();
}
private bool SafetyCheck()
{
return (_doorCheck != null && _doorCheck.SafetyCheck()) || _inhibitCrush;
}
/// <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()
{
var safety = SafetyCheck();
if (safety && Owner.TryGetComponent(out PhysicsComponent? physicsComponent))
{
var broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
// Use this version so we can ignore the CanCollide being false
foreach(var e in broadPhaseSystem.GetCollidingEntities(physicsComponent.Owner.Transform.MapID, physicsComponent.GetWorldAABB()))
{
if ((physicsComponent.CollisionMask & e.CollisionLayer) != 0 && broadPhaseSystem.IntersectionPercent(physicsComponent, e) > 0.01f) return true;
}
}
return false;
}
/// <summary>
/// Closes the door. Does not check if this is possible.
/// </summary>
public void Close()
{
State = DoorState.Closing;
// no more autoclose; we ARE closed
_autoCloseCancelTokenSource?.Cancel();
_stateChangeCancelTokenSource?.Cancel();
_stateChangeCancelTokenSource = new();
Owner.SpawnTimer(CloseTimeOne, async () =>
{
// 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);
if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = true;
}
State = DoorState.Closed;
}, _stateChangeCancelTokenSource.Token);
}
protected override void OnPartialClose()
{
base.OnPartialClose();
// if safety is off, crushes people inside of the door, temporarily turning off collisions with them while doing so.
var becomeairtight = SafetyCheck() || !TryCrush();
if (becomeairtight && Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = true;
}
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, true));
}
/// <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()
{
if (PhysicsComponent == null)
{
return false;
}
var collidingentities = PhysicsComponent.GetCollidingEntities(Vector2.Zero, false);
if (!collidingentities.Any())
{
return false;
}
var doorAABB = PhysicsComponent.GetWorldAABB();
var hitsomebody = false;
// Crush
foreach (var e in collidingentities)
{
if (!e.Owner.TryGetComponent(out StunnableComponent? stun)
|| !e.Owner.TryGetComponent(out IDamageableComponent? damage))
{
continue;
}
var percentage = e.GetWorldAABB().IntersectPercentage(doorAABB);
if (percentage < 0.1f)
continue;
hitsomebody = true;
CurrentlyCrushing.Add(e.Owner.Uid);
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;
}
#endregion
public void Deny()
{
if (_doorCheck != null && !_doorCheck.DenyCheck())
{
return;
}
if (State == DoorState.Open || IsWeldedShut)
return;
_stateChangeCancelTokenSource?.Cancel();
_stateChangeCancelTokenSource = new();
SetAppearance(DoorVisualState.Deny);
Owner.SpawnTimer(DenyTime, () =>
{
SetAppearance(DoorVisualState.Closed);
}, _stateChangeCancelTokenSource.Token);
}
/// <summary>
/// Stops the current auto-close timer if there is one. Starts a new one if this is appropriate (i.e. entity has an IDoorCheck component that allows auto-closing).
/// </summary>
public void RefreshAutoClose()
{
_autoCloseCancelTokenSource?.Cancel();
if (State != DoorState.Open || _doorCheck == null || !_doorCheck.AutoCloseCheck())
{
return;
}
_autoCloseCancelTokenSource = new();
var realCloseTime = _doorCheck.GetCloseSpeed() ?? AutoCloseDelay;
Owner.SpawnRepeatingTimer(realCloseTime, async () =>
{
if (CanCloseGeneric())
{
// Close() cancels _autoCloseCancellationTokenSource, so we're fine.
Close();
}
}, _autoCloseCancelTokenSource.Token);
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if(!eventArgs.Using.TryGetComponent(out ToolComponent? tool))
{
return false;
}
// for prying doors
if (tool.HasQuality(ToolQuality.Prying) && !IsWeldedShut)
{
var successfulPry = false;
if (_doorCheck != null)
{
_doorCheck.OnStartPry(eventArgs);
successfulPry = await tool.UseTool(eventArgs.User, Owner,
_doorCheck.GetPryTime() ?? 0.5f, ToolQuality.Prying, () => _doorCheck.CanPryCheck(eventArgs));
}
else
{
successfulPry = await tool.UseTool(eventArgs.User, Owner, 0.5f, ToolQuality.Prying);
}
if (successfulPry && !IsWeldedShut)
{
if (State == DoorState.Closed)
{
Open();
}
else if (State == DoorState.Open)
{
Close();
}
return true;
}
}
// 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))
{
// just in case
if (!CanWeldShut)
{
return false;
}
_beingWelded = false;
IsWeldedShut = !IsWeldedShut;
return true;
}
_beingWelded = false;
}
}
else
{
_beingWelded = false;
}
return false;
}
/// <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}!");
*/
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new DoorComponentState(State, StateChangeStartTime, CurrentlyCrushing, GameTiming.CurTime);
}
}
}

View File

@@ -0,0 +1,43 @@
#nullable enable
using Content.Shared.Doors;
namespace Content.Server.Doors
{
/// <summary>
/// Used on the server side to manage global access level overrides.
/// </summary>
internal sealed class DoorSystem : SharedDoorSystem
{
/// <summary>
/// Determines the base access behavior of all doors on the station.
/// </summary>
public AccessTypes AccessType { get; set; }
/// <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
}
public override void Initialize()
{
base.Initialize();
AccessType = AccessTypes.Id;
}
}
}

View File

@@ -0,0 +1,77 @@
#nullable enable
using System;
using Content.Shared.Doors;
using Content.Shared.Interaction;
namespace Content.Server.Doors
{
public interface IDoorCheck
{
/// <summary>
/// Called when the door's State variable is changed to a new variable that it was not equal to before.
/// </summary>
void OnStateChange(SharedDoorComponent.DoorState doorState) { }
/// <summary>
/// Called when the door is determining whether it is able to open.
/// </summary>
/// <returns>True if the door should open, false if it should not.</returns>
bool OpenCheck() => true;
/// <summary>
/// Called when the door is determining whether it is able to close.
/// </summary>
/// <returns>True if the door should close, false if it should not.</returns>
bool CloseCheck() => true;
/// <summary>
/// Called when the door is determining whether it is able to deny.
/// </summary>
/// <returns>True if the door should deny, false if it should not.</returns>
bool DenyCheck() => true;
/// <summary>
/// Whether the door's safety is on.
/// </summary>
/// <returns>True if safety is on, false if it is not.</returns>
bool SafetyCheck() => false;
/// <summary>
/// Whether the door should close automatically.
/// </summary>
/// <returns>True if the door should close automatically, false if it should not.</returns>
bool AutoCloseCheck() => false;
/// <summary>
/// Gets an override for the amount of time to pry open the door, or null if there is no override.
/// </summary>
/// <returns>Float if there is an override, null otherwise.</returns>
float? GetPryTime() => null;
/// <summary>
/// Gets an override for the amount of time before the door automatically closes, or null if there is no override.
/// </summary>
/// <returns>TimeSpan if there is an override, null otherwise.</returns>
TimeSpan? GetCloseSpeed() => null;
/// <summary>
/// A check to determine whether or not a click on the door should interact with it with the intent to open/close.
/// </summary>
/// <returns>True if the door's IActivate should not run, false otherwise.</returns>
bool BlockActivate(ActivateEventArgs eventArgs) => false;
/// <summary>
/// Called when somebody begins to pry open the door.
/// </summary>
/// <param name="eventArgs">The eventArgs of the InteractUsing method that called this function.</param>
void OnStartPry(InteractUsingEventArgs eventArgs) { }
/// <summary>
/// Check representing whether or not the door can be pried open.
/// </summary>
/// <param name="eventArgs">The eventArgs of the InteractUsing method that called this function.</param>
/// <returns>True if the door can be pried open, false if it cannot.</returns>
bool CanPryCheck(InteractUsingEventArgs eventArgs) => true;
}
}