ECS Doors (#5887)
This commit is contained in:
8
Content.Client/Doors/AirlockComponent.cs
Normal file
8
Content.Client/Doors/AirlockComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Doors;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedAirlockComponent))]
|
||||||
|
public sealed class AirlockComponent : SharedAirlockComponent { }
|
||||||
5
Content.Client/Doors/AirlockSystem.cs
Normal file
5
Content.Client/Doors/AirlockSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using Content.Shared.Doors.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Doors;
|
||||||
|
|
||||||
|
public sealed class AirlockSystem : SharedAirlockSystem { }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Client.Wires.Visualizers;
|
using Content.Client.Wires.Visualizers;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Animations;
|
using Robust.Client.Animations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
|||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.Doors
|
namespace Content.Client.Doors
|
||||||
{
|
{
|
||||||
@@ -15,6 +16,7 @@ namespace Content.Client.Doors
|
|||||||
public class AirlockVisualizer : AppearanceVisualizer, ISerializationHooks
|
public class AirlockVisualizer : AppearanceVisualizer, ISerializationHooks
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
private const string AnimationKey = "airlock_animation";
|
private const string AnimationKey = "airlock_animation";
|
||||||
|
|
||||||
@@ -122,15 +124,20 @@ namespace Content.Client.Doors
|
|||||||
|
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
{
|
{
|
||||||
|
// only start playing animations once.
|
||||||
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
base.OnChangeData(component);
|
base.OnChangeData(component);
|
||||||
|
|
||||||
var sprite = _entMan.GetComponent<ISpriteComponent>(component.Owner);
|
var sprite = _entMan.GetComponent<ISpriteComponent>(component.Owner);
|
||||||
var animPlayer = _entMan.GetComponent<AnimationPlayerComponent>(component.Owner);
|
var animPlayer = _entMan.GetComponent<AnimationPlayerComponent>(component.Owner);
|
||||||
if (!component.TryGetData(DoorVisuals.VisualState, out DoorVisualState state))
|
if (!component.TryGetData(DoorVisuals.State, out DoorState state))
|
||||||
{
|
{
|
||||||
state = DoorVisualState.Closed;
|
state = DoorState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var door = _entMan.GetComponent<DoorComponent>(component.Owner);
|
||||||
var unlitVisible = true;
|
var unlitVisible = true;
|
||||||
var boltedVisible = false;
|
var boltedVisible = false;
|
||||||
var weldedVisible = false;
|
var weldedVisible = false;
|
||||||
@@ -141,7 +148,7 @@ namespace Content.Client.Doors
|
|||||||
}
|
}
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case DoorVisualState.Open:
|
case DoorState.Open:
|
||||||
sprite.LayerSetState(DoorVisualLayers.Base, "open");
|
sprite.LayerSetState(DoorVisualLayers.Base, "open");
|
||||||
unlitVisible = _openUnlitVisible;
|
unlitVisible = _openUnlitVisible;
|
||||||
if (_openUnlitVisible && !_simpleVisuals)
|
if (_openUnlitVisible && !_simpleVisuals)
|
||||||
@@ -149,7 +156,7 @@ namespace Content.Client.Doors
|
|||||||
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit");
|
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Closed:
|
case DoorState.Closed:
|
||||||
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
||||||
if (!_simpleVisuals)
|
if (!_simpleVisuals)
|
||||||
{
|
{
|
||||||
@@ -157,17 +164,19 @@ namespace Content.Client.Doors
|
|||||||
sprite.LayerSetState(DoorVisualLayers.BaseBolted, "bolted_unlit");
|
sprite.LayerSetState(DoorVisualLayers.BaseBolted, "bolted_unlit");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Opening:
|
case DoorState.Opening:
|
||||||
animPlayer.Play(OpenAnimation, AnimationKey);
|
animPlayer.Play(OpenAnimation, AnimationKey);
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Closing:
|
case DoorState.Closing:
|
||||||
animPlayer.Play(CloseAnimation, AnimationKey);
|
if (door.CurrentlyCrushing.Count == 0)
|
||||||
|
animPlayer.Play(CloseAnimation, AnimationKey);
|
||||||
|
else
|
||||||
|
sprite.LayerSetState(DoorVisualLayers.Base, "closed");
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Deny:
|
case DoorState.Denying:
|
||||||
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
animPlayer.Play(DenyAnimation, AnimationKey);
|
||||||
animPlayer.Play(DenyAnimation, AnimationKey);
|
|
||||||
break;
|
break;
|
||||||
case DoorVisualState.Welded:
|
case DoorState.Welded:
|
||||||
weldedVisible = true;
|
weldedVisible = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -185,7 +194,7 @@ namespace Content.Client.Doors
|
|||||||
|
|
||||||
if (!_simpleVisuals)
|
if (!_simpleVisuals)
|
||||||
{
|
{
|
||||||
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && state != DoorVisualState.Closed);
|
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && state != DoorState.Closed && state != DoorState.Welded);
|
||||||
sprite.LayerSetVisible(DoorVisualLayers.BaseWelded, weldedVisible);
|
sprite.LayerSetVisible(DoorVisualLayers.BaseWelded, weldedVisible);
|
||||||
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
|
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.Doors;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
|
||||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
|
||||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Client.Doors
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Bare-bones client-side door component; used to stop door-based mispredicts.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(SharedDoorComponent))]
|
|
||||||
public class ClientDoorComponent : SharedDoorComponent
|
|
||||||
{
|
|
||||||
[DataField("openDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
|
||||||
public int OpenDrawDepth = (int) DrawDepth.Doors;
|
|
||||||
|
|
||||||
[DataField("closedDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
|
||||||
public int ClosedDrawDepth = (int) DrawDepth.Doors;
|
|
||||||
|
|
||||||
private bool _stateChangeHasProgressed = false;
|
|
||||||
private TimeSpan _timeOffset;
|
|
||||||
|
|
||||||
public override DoorState State
|
|
||||||
{
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
if (State == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.State = value;
|
|
||||||
|
|
||||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, new DoorStateChangedEvent(State), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
|
||||||
{
|
|
||||||
base.HandleComponentState(curState, nextState);
|
|
||||||
|
|
||||||
if (curState is not DoorComponentState doorCompState)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentlyCrushing = doorCompState.CurrentlyCrushing;
|
|
||||||
StateChangeStartTime = doorCompState.StartTime;
|
|
||||||
State = doorCompState.DoorState;
|
|
||||||
|
|
||||||
if (StateChangeStartTime == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_timeOffset = State switch
|
|
||||||
{
|
|
||||||
DoorState.Opening => OpenTimeOne,
|
|
||||||
DoorState.Closing => CloseTimeOne,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (doorCompState.CurTime >= StateChangeStartTime + _timeOffset)
|
|
||||||
{
|
|
||||||
_stateChangeHasProgressed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateChangeHasProgressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnUpdate()
|
|
||||||
{
|
|
||||||
if (!_stateChangeHasProgressed)
|
|
||||||
{
|
|
||||||
if (GameTiming.CurTime < StateChangeStartTime + _timeOffset) return;
|
|
||||||
|
|
||||||
if (State == DoorState.Opening)
|
|
||||||
{
|
|
||||||
OnPartialOpen();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OnPartialClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateChangeHasProgressed = true;
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +1,35 @@
|
|||||||
using System;
|
using Content.Shared.Doors.Components;
|
||||||
using System.Collections.Generic;
|
using Content.Shared.Doors.Systems;
|
||||||
using Content.Shared.Doors;
|
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using static Content.Shared.Doors.SharedDoorComponent;
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Client.Doors
|
namespace Content.Client.Doors;
|
||||||
|
|
||||||
|
public sealed class DoorSystem : SharedDoorSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
// Gotta love it when both the client-side and server-side sprite components both have a draw depth, but for
|
||||||
/// Used by the client to "predict" when doors will change how collideable they are as part of their opening / closing.
|
// whatever bloody reason the shared component doesn't.
|
||||||
/// </summary>
|
protected override void UpdateAppearance(EntityUid uid, DoorComponent? door = null)
|
||||||
internal sealed class DoorSystem : SharedDoorSystem
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (!Resolve(uid, ref door))
|
||||||
/// List of doors that need to be periodically checked.
|
return;
|
||||||
/// </summary>
|
|
||||||
private readonly List<ClientDoorComponent> _activeDoors = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
base.UpdateAppearance(uid, door);
|
||||||
|
|
||||||
|
if (TryComp(uid, out SpriteComponent? sprite))
|
||||||
{
|
{
|
||||||
base.Initialize();
|
sprite.DrawDepth = (door.State == DoorState.Open)
|
||||||
|
|
||||||
UpdatesOutsidePrediction = true;
|
|
||||||
|
|
||||||
SubscribeLocalEvent<ClientDoorComponent, DoorStateChangedEvent>(OnDoorStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoorStateChanged(EntityUid uid, ClientDoorComponent door, DoorStateChangedEvent args)
|
|
||||||
{
|
|
||||||
switch (args.State)
|
|
||||||
{
|
|
||||||
case DoorState.Closed:
|
|
||||||
case DoorState.Open:
|
|
||||||
_activeDoors.Remove(door);
|
|
||||||
break;
|
|
||||||
case DoorState.Closing:
|
|
||||||
case DoorState.Opening:
|
|
||||||
_activeDoors.Add(door);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out SpriteComponent sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Update sprite draw depth. If the door is opening or closing, we will use the closed-draw depth.
|
|
||||||
sprite.DrawDepth = (args.State == DoorState.Open)
|
|
||||||
? door.OpenDrawDepth
|
? door.OpenDrawDepth
|
||||||
: door.ClosedDrawDepth;
|
: door.ClosedDrawDepth;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
// TODO AUDIO PREDICT see comments in server-side PlaySound()
|
||||||
public override void Update(float frameTime)
|
protected override void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||||
{
|
{
|
||||||
for (var i = _activeDoors.Count - 1; i >= 0; i--)
|
if (GameTiming.InPrediction && GameTiming.IsFirstTimePredicted)
|
||||||
{
|
SoundSystem.Play(Filter.Local(), sound, uid, audioParams);
|
||||||
var comp = _activeDoors[i];
|
|
||||||
if (comp.Deleted)
|
|
||||||
{
|
|
||||||
_activeDoors.RemoveAt(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
comp.OnUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ namespace Content.Client.Entry
|
|||||||
"ResearchPointSource",
|
"ResearchPointSource",
|
||||||
"ResearchClient",
|
"ResearchClient",
|
||||||
"IdCardConsole",
|
"IdCardConsole",
|
||||||
"Airlock",
|
|
||||||
"ThermalRegulator",
|
"ThermalRegulator",
|
||||||
"AtmosFixMarker",
|
"AtmosFixMarker",
|
||||||
"CablePlacer",
|
"CablePlacer",
|
||||||
@@ -75,7 +74,6 @@ namespace Content.Client.Entry
|
|||||||
"Brain",
|
"Brain",
|
||||||
"CommunicationsConsole",
|
"CommunicationsConsole",
|
||||||
"BarSign",
|
"BarSign",
|
||||||
"DoorBumpOpener",
|
|
||||||
"SolarPanel",
|
"SolarPanel",
|
||||||
"BodyScanner",
|
"BodyScanner",
|
||||||
"Stunbaton",
|
"Stunbaton",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Shared.Doors;
|
using Content.Server.Doors.Systems;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ namespace Content.IntegrationTests.Tests.Doors
|
|||||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
|
|
||||||
EntityUid airlock = default;
|
EntityUid airlock = default;
|
||||||
ServerDoorComponent doorComponent = null;
|
DoorComponent doorComponent = null;
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
@@ -66,32 +66,32 @@ namespace Content.IntegrationTests.Tests.Doors
|
|||||||
airlock = entityManager.SpawnEntity("AirlockDummy", MapCoordinates.Nullspace);
|
airlock = entityManager.SpawnEntity("AirlockDummy", MapCoordinates.Nullspace);
|
||||||
|
|
||||||
Assert.True(entityManager.TryGetComponent(airlock, out doorComponent));
|
Assert.True(entityManager.TryGetComponent(airlock, out doorComponent));
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Closed));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closed));
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
doorComponent.Open();
|
EntitySystem.Get<DoorSystem>().StartOpening(airlock);
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Opening));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Opening));
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|
||||||
await WaitUntil(server, () => doorComponent.State == SharedDoorComponent.DoorState.Open);
|
await WaitUntil(server, () => doorComponent.State == DoorState.Open);
|
||||||
|
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Open));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Open));
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
doorComponent.Close();
|
EntitySystem.Get<DoorSystem>().TryClose((EntityUid) airlock);
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Closing));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closing));
|
||||||
});
|
});
|
||||||
|
|
||||||
await WaitUntil(server, () => doorComponent.State == SharedDoorComponent.DoorState.Closed);
|
await WaitUntil(server, () => doorComponent.State == DoorState.Closed);
|
||||||
|
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Closed));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closed));
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
@@ -123,7 +123,7 @@ namespace Content.IntegrationTests.Tests.Doors
|
|||||||
IPhysBody physBody = null;
|
IPhysBody physBody = null;
|
||||||
EntityUid physicsDummy = default;
|
EntityUid physicsDummy = default;
|
||||||
EntityUid airlock = default;
|
EntityUid airlock = default;
|
||||||
ServerDoorComponent doorComponent = null;
|
DoorComponent doorComponent = null;
|
||||||
|
|
||||||
var physicsDummyStartingX = -1;
|
var physicsDummyStartingX = -1;
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ namespace Content.IntegrationTests.Tests.Doors
|
|||||||
Assert.True(entityManager.TryGetComponent(physicsDummy, out physBody));
|
Assert.True(entityManager.TryGetComponent(physicsDummy, out physBody));
|
||||||
|
|
||||||
Assert.True(entityManager.TryGetComponent(airlock, out doorComponent));
|
Assert.True(entityManager.TryGetComponent(airlock, out doorComponent));
|
||||||
Assert.That(doorComponent.State, Is.EqualTo(SharedDoorComponent.DoorState.Closed));
|
Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closed));
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
@@ -262,7 +263,7 @@ namespace Content.Server.AI.Pathfinding
|
|||||||
{
|
{
|
||||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
// If we're a door
|
// If we're a door
|
||||||
if (entMan.HasComponent<AirlockComponent>(entity) || entMan.HasComponent<ServerDoorComponent>(entity))
|
if (entMan.HasComponent<AirlockComponent>(entity) || entMan.HasComponent<DoorComponent>(entity))
|
||||||
{
|
{
|
||||||
// If we need access to traverse this then add to readers, otherwise no point adding it (except for maybe tile costs in future)
|
// If we need access to traverse this then add to readers, otherwise no point adding it (except for maybe tile costs in future)
|
||||||
// TODO: Check for powered I think (also need an event for when it's depowered
|
// TODO: Check for powered I think (also need an event for when it's depowered
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.Doors.Components;
|
|
||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -19,10 +19,10 @@ namespace Content.Server.Construction.Conditions
|
|||||||
|
|
||||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||||
{
|
{
|
||||||
if (!entityManager.TryGetComponent(uid, out ServerDoorComponent? doorComponent))
|
if (!entityManager.TryGetComponent(uid, out DoorComponent? doorComponent))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return doorComponent.IsWeldedShut == Welded;
|
return doorComponent.State == DoorState.Welded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoExamine(ExaminedEvent args)
|
public bool DoExamine(ExaminedEvent args)
|
||||||
@@ -31,9 +31,10 @@ namespace Content.Server.Construction.Conditions
|
|||||||
|
|
||||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
if (!entMan.TryGetComponent(entity, out ServerDoorComponent? door)) return false;
|
if (!entMan.TryGetComponent(entity, out DoorComponent? door)) return false;
|
||||||
|
|
||||||
if (door.IsWeldedShut != Welded)
|
var isWelded = door.State == DoorState.Welded;
|
||||||
|
if (isWelded != Welded)
|
||||||
{
|
{
|
||||||
if (Welded == true)
|
if (Welded == true)
|
||||||
args.PushMarkup(Loc.GetString("construction-examine-condition-door-weld", ("entityName", entMan.GetComponent<MetaDataComponent>(entity).EntityName)) + "\n");
|
args.PushMarkup(Loc.GetString("construction-examine-condition-door-weld", ("entityName", entMan.GetComponent<MetaDataComponent>(entity).EntityName)) + "\n");
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Content.Server.Doors.Systems;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.VendingMachines;
|
using Content.Server.VendingMachines;
|
||||||
using Content.Server.WireHacking;
|
using Content.Server.WireHacking;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -18,10 +20,11 @@ using static Content.Shared.Wires.SharedWiresComponent.WiresAction;
|
|||||||
namespace Content.Server.Doors.Components
|
namespace Content.Server.Doors.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
|
/// Companion component to DoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class AirlockComponent : Component, IWires
|
[ComponentReference(typeof(SharedAirlockComponent))]
|
||||||
|
public sealed class AirlockComponent : SharedAirlockComponent, IWires
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
@@ -88,7 +91,7 @@ namespace Content.Server.Doors.Components
|
|||||||
private bool BoltLightsVisible
|
private bool BoltLightsVisible
|
||||||
{
|
{
|
||||||
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
|
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
|
||||||
&& _entityManager.TryGetComponent<ServerDoorComponent>(Owner, out var doorComponent) && doorComponent.State == SharedDoorComponent.DoorState.Closed;
|
&& _entityManager.TryGetComponent<DoorComponent>(Owner, out var doorComponent) && doorComponent.State == DoorState.Closed;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_boltLightsWirePulsed = value;
|
_boltLightsWirePulsed = value;
|
||||||
@@ -96,18 +99,19 @@ namespace Content.Server.Doors.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
/// <summary>
|
||||||
[DataField("autoClose")]
|
/// Delay until an open door automatically closes.
|
||||||
public bool AutoClose = true;
|
/// </summary>
|
||||||
|
[DataField("autoCloseDelay")]
|
||||||
|
public TimeSpan AutoCloseDelay = TimeSpan.FromSeconds(5f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplicative modifier for the auto-close delay. Can be modified by hacking the airlock wires. Setting to
|
||||||
|
/// zero will disable auto-closing.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("autoCloseDelayModifier")]
|
|
||||||
public float AutoCloseDelayModifier = 1.0f;
|
public float AutoCloseDelayModifier = 1.0f;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("safety")]
|
|
||||||
public bool Safety = true;
|
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -168,19 +172,15 @@ namespace Content.Server.Doors.Components
|
|||||||
var boltLightsStatus = new StatusLightData(Color.Lime,
|
var boltLightsStatus = new StatusLightData(Color.Lime,
|
||||||
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BOLT LED");
|
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BOLT LED");
|
||||||
|
|
||||||
var ev = new DoorGetCloseTimeModifierEvent();
|
|
||||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
|
|
||||||
var timingStatus =
|
var timingStatus =
|
||||||
new StatusLightData(Color.Orange, !AutoClose ? StatusLightState.Off :
|
new StatusLightData(Color.Orange, (AutoCloseDelayModifier <= 0) ? StatusLightState.Off :
|
||||||
!MathHelper.CloseToPercent(ev.CloseTimeModifier, 1.0f) ? StatusLightState.BlinkingSlow :
|
!MathHelper.CloseToPercent(AutoCloseDelayModifier, 1.0f) ? StatusLightState.BlinkingSlow :
|
||||||
StatusLightState.On,
|
StatusLightState.On,
|
||||||
"TIME");
|
"TIME");
|
||||||
|
|
||||||
var safetyStatus =
|
var safetyStatus =
|
||||||
new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFETY");
|
new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFETY");
|
||||||
|
|
||||||
|
|
||||||
wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
|
wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
|
||||||
wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
|
wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
|
||||||
wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
|
wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
|
||||||
@@ -212,17 +212,6 @@ namespace Content.Server.Doors.Components
|
|||||||
wiresComponent.IsWireCut(Wires.BackupPower);
|
wiresComponent.IsWireCut(Wires.BackupPower);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e)
|
|
||||||
{
|
|
||||||
if (_entityManager.TryGetComponent<AppearanceComponent>(Owner, out var appearanceComponent))
|
|
||||||
{
|
|
||||||
appearanceComponent.SetData(DoorVisuals.Powered, e.Powered);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoltLights also got out
|
|
||||||
UpdateBoltLightStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Wires
|
private enum Wires
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,6 +239,7 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLight,
|
BoltLight,
|
||||||
|
|
||||||
// Placeholder for when AI is implemented
|
// Placeholder for when AI is implemented
|
||||||
|
// aaaaany day now.
|
||||||
AIControl,
|
AIControl,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -281,7 +271,7 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
public void WiresUpdate(WiresUpdateEventArgs args)
|
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||||
{
|
{
|
||||||
if (!_entityManager.TryGetComponent<ServerDoorComponent>(Owner, out var doorComponent))
|
if (!_entityManager.TryGetComponent<DoorComponent>(Owner, out var doorComponent))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -318,11 +308,13 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = !_boltLightsWirePulsed;
|
BoltLightsVisible = !_boltLightsWirePulsed;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
|
// This is permanent, until the wire gets cut & mended.
|
||||||
AutoCloseDelayModifier = 0.5f;
|
AutoCloseDelayModifier = 0.5f;
|
||||||
doorComponent.RefreshAutoClose();
|
EntitySystem.Get<AirlockSystem>().UpdateAutoClose(Owner, this);
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
Safety = !Safety;
|
Safety = !Safety;
|
||||||
|
Dirty();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,11 +333,12 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = true;
|
BoltLightsVisible = true;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
AutoClose = true;
|
AutoCloseDelayModifier = 1;
|
||||||
doorComponent.RefreshAutoClose();
|
EntitySystem.Get<AirlockSystem>().UpdateAutoClose(Owner, this);
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
Safety = true;
|
Safety = true;
|
||||||
|
Dirty();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,11 +354,12 @@ namespace Content.Server.Doors.Components
|
|||||||
BoltLightsVisible = false;
|
BoltLightsVisible = false;
|
||||||
break;
|
break;
|
||||||
case Wires.Timing:
|
case Wires.Timing:
|
||||||
AutoClose = false;
|
AutoCloseDelayModifier = 0; // disable auto close
|
||||||
doorComponent.RefreshAutoClose();
|
EntitySystem.Get<AirlockSystem>().UpdateAutoClose(Owner, this);
|
||||||
break;
|
break;
|
||||||
case Wires.Safety:
|
case Wires.Safety:
|
||||||
Safety = false;
|
Safety = false;
|
||||||
|
Dirty();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.Doors.Components
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class DoorBumpOpenerComponent : Component
|
|
||||||
{
|
|
||||||
public override string Name => "DoorBumpOpener";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Shared.Doors;
|
using Content.Server.Doors.Systems;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
@@ -27,9 +28,14 @@ namespace Content.Server.Doors.Components
|
|||||||
|
|
||||||
public bool EmergencyPressureStop()
|
public bool EmergencyPressureStop()
|
||||||
{
|
{
|
||||||
if (_entMan.TryGetComponent<ServerDoorComponent>(Owner, out var doorComp) && doorComp.State == SharedDoorComponent.DoorState.Open && doorComp.CanCloseGeneric())
|
var doorSys = EntitySystem.Get<DoorSystem>();
|
||||||
|
if (_entMan.TryGetComponent<DoorComponent>(Owner, out var door) &&
|
||||||
|
door.State == DoorState.Open &&
|
||||||
|
doorSys.CanClose(Owner, door))
|
||||||
{
|
{
|
||||||
doorComp.Close();
|
doorSys.StartClosing(Owner, door);
|
||||||
|
|
||||||
|
// Door system also sets airtight, but only after a delay. We want it to be immediate.
|
||||||
if (_entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
|
if (_entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
|
||||||
{
|
{
|
||||||
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
|
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
|
||||||
|
|||||||
@@ -1,774 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Server.Access;
|
|
||||||
using Content.Server.Atmos.Components;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.Construction;
|
|
||||||
using Content.Server.Construction.Components;
|
|
||||||
using Content.Server.Hands.Components;
|
|
||||||
using Content.Server.Stunnable;
|
|
||||||
using Content.Server.Tools;
|
|
||||||
using Content.Server.Tools.Components;
|
|
||||||
using Content.Shared.Access.Components;
|
|
||||||
using Content.Shared.Access.Systems;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.Doors;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Sound;
|
|
||||||
using Content.Shared.Tools;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Containers;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
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, IInteractUsing, IMapInit
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
[DataField("board", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
|
||||||
private string? _boardPrototype;
|
|
||||||
|
|
||||||
[DataField("weldingQuality", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
|
||||||
private string _weldingQuality = "Welding";
|
|
||||||
|
|
||||||
[DataField("pryingQuality", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
|
||||||
private string _pryingQuality = "Prying";
|
|
||||||
|
|
||||||
[DataField("tryOpenDoorSound")]
|
|
||||||
private SoundSpecifier _tryOpenDoorSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg");
|
|
||||||
|
|
||||||
[DataField("crushDamage", required: true)]
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public DamageSpecifier CrushDamage = default!;
|
|
||||||
|
|
||||||
[DataField("changeAirtight")]
|
|
||||||
public bool ChangeAirtight = true;
|
|
||||||
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, new DoorStateChangedEvent(State), false);
|
|
||||||
_autoCloseCancelTokenSource?.Cancel();
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly TimeSpan AutoCloseDelay = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
private CancellationTokenSource? _stateChangeCancelTokenSource;
|
|
||||||
private CancellationTokenSource? _autoCloseCancelTokenSource;
|
|
||||||
|
|
||||||
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")]
|
|
||||||
public bool BumpOpen = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the door will open when it is activated or clicked.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("clickOpen")]
|
|
||||||
public bool ClickOpen = 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 = false;
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// 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>
|
|
||||||
/// Should this door automatically close if its been open for too long?
|
|
||||||
/// </summary>
|
|
||||||
[DataField("autoClose")]
|
|
||||||
public bool AutoClose = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default time that the door should take to pry open.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("pryTime")]
|
|
||||||
public float PryTime = 1.5f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum interval allowed between deny sounds in milliseconds.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("denySoundMinimumInterval")]
|
|
||||||
public float DenySoundMinimumInterval = 450.0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to stop people from spamming the deny sound.
|
|
||||||
/// </summary>
|
|
||||||
private TimeSpan LastDenySoundTime = TimeSpan.Zero;
|
|
||||||
|
|
||||||
/// <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.", _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SetAppearance(DoorVisualState.Welded);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateDoorElectronicsBoard();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected 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.", _entMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QuickOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateDoorElectronicsBoard();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (!ClickOpen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var ev = new DoorClickShouldActivateEvent(eventArgs);
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
if (ev.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (State == DoorState.Open)
|
|
||||||
{
|
|
||||||
TryClose(eventArgs.User);
|
|
||||||
}
|
|
||||||
else if (State == DoorState.Closed)
|
|
||||||
{
|
|
||||||
TryOpen(eventArgs.User);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Opening
|
|
||||||
|
|
||||||
public void TryOpen(EntityUid? user = null)
|
|
||||||
{
|
|
||||||
var msg = new DoorOpenAttemptEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, msg);
|
|
||||||
|
|
||||||
if (msg.Cancelled) return;
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
// a machine opened it or something, idk
|
|
||||||
Open();
|
|
||||||
}
|
|
||||||
else if (CanOpenByEntity(user.Value))
|
|
||||||
{
|
|
||||||
Open();
|
|
||||||
|
|
||||||
if (_entMan.TryGetComponent(user, out HandsComponent? hands) && hands.Count == 0)
|
|
||||||
{
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), _tryOpenDoorSound.GetSound(), Owner,
|
|
||||||
AudioParams.Default.WithVolume(-2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Deny();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanOpenByEntity(EntityUid user)
|
|
||||||
{
|
|
||||||
if (!CanOpenGeneric())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entMan.TryGetComponent(Owner, out AccessReaderComponent? access))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doorSystem = EntitySystem.Get<DoorSystem>();
|
|
||||||
var isAirlockExternal = HasAccessType("External");
|
|
||||||
|
|
||||||
var accessSystem = EntitySystem.Get<AccessReaderSystem>();
|
|
||||||
return doorSystem.AccessType switch
|
|
||||||
{
|
|
||||||
DoorSystem.AccessTypes.AllowAll => true,
|
|
||||||
DoorSystem.AccessTypes.AllowAllIdExternal => isAirlockExternal || accessSystem.IsAllowed(access, user),
|
|
||||||
DoorSystem.AccessTypes.AllowAllNoExternal => !isAirlockExternal,
|
|
||||||
_ => accessSystem.IsAllowed(access, 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 (_entMan.TryGetComponent(Owner, out AccessReaderComponent? 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ev = new BeforeDoorOpenedEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
return !ev.Cancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opens the door. Does not check if this is possible.
|
|
||||||
/// </summary>
|
|
||||||
public void Open()
|
|
||||||
{
|
|
||||||
State = DoorState.Opening;
|
|
||||||
if (Occludes && _entMan.TryGetComponent(Owner, out OccluderComponent? occluder))
|
|
||||||
{
|
|
||||||
occluder.Enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ChangeAirtight && _entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateChangeCancelTokenSource?.Cancel();
|
|
||||||
_stateChangeCancelTokenSource = new();
|
|
||||||
|
|
||||||
if (OpenSound != null)
|
|
||||||
{
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), OpenSound.GetSound(), Owner,
|
|
||||||
AudioParams.Default.WithVolume(-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
Owner.SpawnTimer(OpenTimeOne, async () =>
|
|
||||||
{
|
|
||||||
OnPartialOpen();
|
|
||||||
await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token);
|
|
||||||
|
|
||||||
State = DoorState.Open;
|
|
||||||
RefreshAutoClose();
|
|
||||||
}, _stateChangeCancelTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnPartialOpen()
|
|
||||||
{
|
|
||||||
base.OnPartialOpen();
|
|
||||||
|
|
||||||
if (ChangeAirtight && _entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
_entMan.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void QuickOpen(bool refresh)
|
|
||||||
{
|
|
||||||
if (Occludes && _entMan.TryGetComponent(Owner, out OccluderComponent? occluder))
|
|
||||||
{
|
|
||||||
occluder.Enabled = false;
|
|
||||||
}
|
|
||||||
OnPartialOpen();
|
|
||||||
State = DoorState.Open;
|
|
||||||
if(refresh)
|
|
||||||
RefreshAutoClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Closing
|
|
||||||
|
|
||||||
public void TryClose(EntityUid? user = null)
|
|
||||||
{
|
|
||||||
var msg = new DoorCloseAttemptEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, msg);
|
|
||||||
|
|
||||||
if (msg.Cancelled) return;
|
|
||||||
|
|
||||||
if (user != null && !CanCloseByEntity(user.Value))
|
|
||||||
{
|
|
||||||
Deny();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanCloseByEntity(EntityUid user)
|
|
||||||
{
|
|
||||||
if (!CanCloseGeneric())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entMan.TryGetComponent(Owner, out AccessReaderComponent? access))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessSystem = EntitySystem.Get<AccessReaderSystem>();
|
|
||||||
return accessSystem.IsAllowed(access, 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()
|
|
||||||
{
|
|
||||||
var ev = new BeforeDoorClosedEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
if (ev.Cancelled)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return !IsSafetyColliding();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool SafetyCheck()
|
|
||||||
{
|
|
||||||
var ev = new DoorSafetyEnabledEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
return ev.Safety || _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 && _entMan.TryGetComponent(Owner, out PhysicsComponent? physicsComponent))
|
|
||||||
{
|
|
||||||
var broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
|
||||||
|
|
||||||
// Use this version so we can ignore the CanCollide being false
|
|
||||||
foreach(var _ in broadPhaseSystem.GetCollidingEntities(physicsComponent, -0.015f))
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (CloseSound != null)
|
|
||||||
{
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), CloseSound.GetSound(), Owner,
|
|
||||||
AudioParams.Default.WithVolume(-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 && _entMan.TryGetComponent(Owner, 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 = ChangeAirtight && (SafetyCheck() || !TryCrush());
|
|
||||||
|
|
||||||
if (becomeairtight && _entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_entMan.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 (!_entMan.TryGetComponent(Owner, out PhysicsComponent physicsComponent) || physicsComponent is not IPhysBody body)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collidingentities = body.GetCollidingEntities(Vector2.Zero, false);
|
|
||||||
|
|
||||||
if (!collidingentities.Any())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doorAABB = body.GetWorldAABB();
|
|
||||||
var hitsomebody = false;
|
|
||||||
|
|
||||||
// Crush
|
|
||||||
foreach (var e in collidingentities)
|
|
||||||
{
|
|
||||||
var percentage = e.GetWorldAABB().IntersectPercentage(doorAABB);
|
|
||||||
|
|
||||||
if (percentage < 0.1f)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
hitsomebody = true;
|
|
||||||
CurrentlyCrushing.Add(e.Owner);
|
|
||||||
|
|
||||||
if (_entMan.HasComponent<DamageableComponent>(e.Owner))
|
|
||||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner, CrushDamage);
|
|
||||||
|
|
||||||
EntitySystem.Get<StunSystem>().TryParalyze(e.Owner, TimeSpan.FromSeconds(DoorStunTime), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
var ev = new BeforeDoorDeniedEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
if (ev.Cancelled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (State == DoorState.Open || IsWeldedShut)
|
|
||||||
return;
|
|
||||||
|
|
||||||
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;
|
|
||||||
SoundSystem.Play(Filter.Pvs(Owner), DenySound.GetSound(), Owner,
|
|
||||||
AudioParams.Default.WithVolume(-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateChangeCancelTokenSource?.Cancel();
|
|
||||||
_stateChangeCancelTokenSource = new();
|
|
||||||
SetAppearance(DoorVisualState.Deny);
|
|
||||||
Owner.SpawnTimer(DenyTime, () =>
|
|
||||||
{
|
|
||||||
SetAppearance(DoorVisualState.Closed);
|
|
||||||
}, _stateChangeCancelTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts a new auto close timer if this is appropriate
|
|
||||||
/// (i.e. event raised is not cancelled).
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshAutoClose()
|
|
||||||
{
|
|
||||||
if (State != DoorState.Open)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!AutoClose)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var autoev = new BeforeDoorAutoCloseEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, autoev, false);
|
|
||||||
if (autoev.Cancelled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_autoCloseCancelTokenSource = new();
|
|
||||||
|
|
||||||
var ev = new DoorGetCloseTimeModifierEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
var realCloseTime = AutoCloseDelay * ev.CloseTimeModifier;
|
|
||||||
|
|
||||||
Owner.SpawnRepeatingTimer(realCloseTime, async () =>
|
|
||||||
{
|
|
||||||
if (CanCloseGeneric())
|
|
||||||
{
|
|
||||||
// Close() cancels _autoCloseCancellationTokenSource, so we're fine.
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}, _autoCloseCancelTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if(!_entMan.TryGetComponent(eventArgs.Using, out ToolComponent? tool))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var toolSystem = EntitySystem.Get<ToolSystem>();
|
|
||||||
|
|
||||||
// for prying doors
|
|
||||||
if (tool.Qualities.Contains(_pryingQuality) && !IsWeldedShut)
|
|
||||||
{
|
|
||||||
var ev = new DoorGetPryTimeModifierEvent();
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, ev, false);
|
|
||||||
|
|
||||||
var canEv = new BeforeDoorPryEvent(eventArgs);
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, canEv, false);
|
|
||||||
|
|
||||||
if (canEv.Cancelled) return false;
|
|
||||||
|
|
||||||
var successfulPry = await toolSystem.UseTool(eventArgs.Using, eventArgs.User, Owner,
|
|
||||||
0f, ev.PryTimeModifier * PryTime, _pryingQuality);
|
|
||||||
|
|
||||||
if (successfulPry && !IsWeldedShut)
|
|
||||||
{
|
|
||||||
_entMan.EventBus.RaiseLocalEvent(Owner, new OnDoorPryEvent(eventArgs), false);
|
|
||||||
if (State == DoorState.Closed)
|
|
||||||
{
|
|
||||||
Open();
|
|
||||||
}
|
|
||||||
else if (State == DoorState.Open)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// regardless of whether this action actually succeeded, it generated a do-after bar. So prevent other
|
|
||||||
// interactions from happening afterwards by returning true.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for welding doors
|
|
||||||
if (_beingWelded || !CanWeldShut || !_entMan.TryGetComponent(tool.Owner, out WelderComponent? welder) || !welder.Lit)
|
|
||||||
{
|
|
||||||
// no interaction occurred
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_beingWelded = true;
|
|
||||||
|
|
||||||
// perform a do-after delay
|
|
||||||
var result = await toolSystem.UseTool(eventArgs.Using, eventArgs.User, Owner, 3f, 3f, _weldingQuality, () => CanWeldShut);
|
|
||||||
|
|
||||||
// if successful, toggle the weld-status (while also ensuring that it can still be welded shut after the delay)
|
|
||||||
if (result)
|
|
||||||
IsWeldedShut = CanWeldShut && !IsWeldedShut;
|
|
||||||
|
|
||||||
_beingWelded = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 (_entMan.TryGetComponent(Owner, out ConstructionComponent? construction))
|
|
||||||
EntitySystem.Get<ConstructionSystem>().AddContainer(Owner, "board", construction);
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
return new DoorComponentState(State, StateChangeStartTime, CurrentlyCrushing, GameTiming.CurTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.WireHacking;
|
using Content.Server.WireHacking;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Content.Server.Doors.Systems
|
namespace Content.Server.Doors.Systems
|
||||||
{
|
{
|
||||||
public class AirlockSystem : EntitySystem
|
public sealed class AirlockSystem : SharedAirlockSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -19,12 +22,8 @@ namespace Content.Server.Doors.Systems
|
|||||||
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
|
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
|
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorSafetyEnabledEvent>(OnDoorSafetyCheck);
|
SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new [] {typeof(DoorSystem)});
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorAutoCloseEvent>(OnDoorAutoCloseCheck);
|
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorGetCloseTimeModifierEvent>(OnDoorCloseTimeModifier);
|
|
||||||
SubscribeLocalEvent<AirlockComponent, DoorClickShouldActivateEvent>(OnDoorClickShouldActivate);
|
|
||||||
SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
|
SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,21 +34,59 @@ namespace Content.Server.Doors.Systems
|
|||||||
appearanceComponent.SetData(DoorVisuals.Powered, args.Powered);
|
appearanceComponent.SetData(DoorVisuals.Powered, args.Powered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!args.Powered)
|
||||||
|
{
|
||||||
|
// stop any scheduled auto-closing
|
||||||
|
DoorSystem.SetNextStateChange(uid, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// door received power. Lets "wake" the door up, in case it is currently open and needs to auto-close.
|
||||||
|
DoorSystem.SetNextStateChange(uid, TimeSpan.FromSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
// BoltLights also got out
|
// BoltLights also got out
|
||||||
component.UpdateBoltLightStatus();
|
component.UpdateBoltLightStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args)
|
private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args)
|
||||||
{
|
{
|
||||||
|
// TODO move to shared? having this be server-side, but having client-side door opening/closing & prediction
|
||||||
|
// means that sometimes the panels & bolt lights may be visible despite a door being completely open.
|
||||||
|
|
||||||
// Only show the maintenance panel if the airlock is closed
|
// Only show the maintenance panel if the airlock is closed
|
||||||
if (TryComp<WiresComponent>(uid, out var wiresComponent))
|
if (TryComp<WiresComponent>(uid, out var wiresComponent))
|
||||||
{
|
{
|
||||||
wiresComponent.IsPanelVisible =
|
wiresComponent.IsPanelVisible =
|
||||||
component.OpenPanelVisible
|
component.OpenPanelVisible
|
||||||
|| args.State != SharedDoorComponent.DoorState.Open;
|
|| args.State != DoorState.Open;
|
||||||
}
|
}
|
||||||
// If the door is closed, we should look if the bolt was locked while closing
|
// If the door is closed, we should look if the bolt was locked while closing
|
||||||
component.UpdateBoltLightStatus();
|
component.UpdateBoltLightStatus();
|
||||||
|
|
||||||
|
UpdateAutoClose(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the auto close timer.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateAutoClose(EntityUid uid, AirlockComponent? airlock = null, DoorComponent? door = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref airlock, ref door))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (door.State != DoorState.Open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!airlock.CanChangeState())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var autoev = new BeforeDoorAutoCloseEvent();
|
||||||
|
RaiseLocalEvent(uid, autoev, false);
|
||||||
|
if (autoev.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DoorSystem.SetNextStateChange(uid, airlock.AutoCloseDelay * airlock.AutoCloseDelayModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args)
|
private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args)
|
||||||
@@ -58,10 +95,23 @@ namespace Content.Server.Doors.Systems
|
|||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeforeDoorClosed(EntityUid uid, AirlockComponent component, BeforeDoorClosedEvent args)
|
protected override void OnBeforeDoorClosed(EntityUid uid, SharedAirlockComponent component, BeforeDoorClosedEvent args)
|
||||||
{
|
{
|
||||||
if (!component.CanChangeState())
|
base.OnBeforeDoorClosed(uid, component, args);
|
||||||
|
|
||||||
|
if (args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// only block based on bolts / power status when initially closing the door, not when its already
|
||||||
|
// mid-transition. Particularly relevant for when the door was pried-closed with a crowbar, which bypasses
|
||||||
|
// the initial power-check.
|
||||||
|
|
||||||
|
if (TryComp(uid, out DoorComponent? door)
|
||||||
|
&& !door.Partial
|
||||||
|
&& !Comp<AirlockComponent>(uid).CanChangeState())
|
||||||
|
{
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args)
|
private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args)
|
||||||
@@ -70,26 +120,10 @@ namespace Content.Server.Doors.Systems
|
|||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoorSafetyCheck(EntityUid uid, AirlockComponent component, DoorSafetyEnabledEvent args)
|
private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWorldEvent args)
|
||||||
{
|
|
||||||
args.Safety = component.Safety;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoorAutoCloseCheck(EntityUid uid, AirlockComponent component, BeforeDoorAutoCloseEvent args)
|
|
||||||
{
|
|
||||||
if (!component.AutoClose)
|
|
||||||
args.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoorCloseTimeModifier(EntityUid uid, AirlockComponent component, DoorGetCloseTimeModifierEvent args)
|
|
||||||
{
|
|
||||||
args.CloseTimeModifier *= component.AutoCloseDelayModifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoorClickShouldActivate(EntityUid uid, AirlockComponent component, DoorClickShouldActivateEvent args)
|
|
||||||
{
|
{
|
||||||
if (TryComp<WiresComponent>(uid, out var wiresComponent) && wiresComponent.IsPanelOpen &&
|
if (TryComp<WiresComponent>(uid, out var wiresComponent) && wiresComponent.IsPanelOpen &&
|
||||||
EntityManager.TryGetComponent(args.Args.User, out ActorComponent? actor))
|
EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
||||||
{
|
{
|
||||||
wiresComponent.OpenInterface(actor.PlayerSession);
|
wiresComponent.OpenInterface(actor.PlayerSession);
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
@@ -100,12 +134,12 @@ namespace Content.Server.Doors.Systems
|
|||||||
{
|
{
|
||||||
if (component.IsBolted())
|
if (component.IsBolted())
|
||||||
{
|
{
|
||||||
component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message"));
|
component.Owner.PopupMessage(args.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message"));
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
if (component.IsPowered())
|
if (component.IsPowered())
|
||||||
{
|
{
|
||||||
component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message"));
|
component.Owner.PopupMessage(args.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message"));
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,293 @@
|
|||||||
using Content.Server.Doors.Components;
|
using Content.Server.Access;
|
||||||
|
using Content.Server.Atmos.Components;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Construction;
|
||||||
|
using Content.Server.Construction.Components;
|
||||||
|
using Content.Server.Tools;
|
||||||
|
using Content.Server.Tools.Components;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Physics.Dynamics;
|
using Robust.Shared.Physics.Dynamics;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Content.Server.Doors
|
namespace Content.Server.Doors.Systems;
|
||||||
|
|
||||||
|
public sealed class DoorSystem : SharedDoorSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
[Dependency] private readonly ConstructionSystem _constructionSystem = default!;
|
||||||
/// Used on the server side to manage global access level overrides.
|
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||||
/// </summary>
|
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
|
||||||
internal sealed class DoorSystem : SharedDoorSystem
|
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
/// <summary>
|
base.Initialize();
|
||||||
/// Determines the base access behavior of all doors on the station.
|
|
||||||
/// </summary>
|
|
||||||
public AccessTypes AccessType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
SubscribeLocalEvent<DoorComponent, MapInitEvent>(OnMapInit);
|
||||||
/// How door access should be handled.
|
SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
/// </summary>
|
|
||||||
public enum AccessTypes
|
SubscribeLocalEvent<DoorComponent, PryFinishedEvent>(OnPryFinished);
|
||||||
|
SubscribeLocalEvent<DoorComponent, PryCancelledEvent>(OnPryCancelled);
|
||||||
|
SubscribeLocalEvent<DoorComponent, WeldFinishedEvent>(OnWeldFinished);
|
||||||
|
SubscribeLocalEvent<DoorComponent, WeldCancelledEvent>(OnWeldCancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO AUDIO PREDICT Figure out a better way to handle sound and prediction. For now, this works well enough?
|
||||||
|
//
|
||||||
|
// Currently a client will predict when a door is going to close automatically. So any client in PVS range can just
|
||||||
|
// play their audio locally. Playing it server-side causes an odd delay, while in shared it causes double-audio.
|
||||||
|
//
|
||||||
|
// But if we just do that, then if a door is closed prematurely as the result of an interaction (i.e., using "E" on
|
||||||
|
// an open door), then the audio would only be played for the client performing the interaction.
|
||||||
|
//
|
||||||
|
// So we do this:
|
||||||
|
// - Play audio client-side IF the closing is being predicted (auto-close or predicted interaction)
|
||||||
|
// - Server assumes automated closing is predicted by clients and does not play audio unless otherwise specified.
|
||||||
|
// - Major exception is player interactions, which other players cannot predict
|
||||||
|
// - In that case, send audio to all players, except possibly the interacting player if it was a predicted
|
||||||
|
// interaction.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selectively send sound to clients, taking care to not send the double-audio.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">The audio source</param>
|
||||||
|
/// <param name="sound">The sound</param>
|
||||||
|
/// <param name="predictingPlayer">The user (if any) that instigated an interaction</param>
|
||||||
|
/// <param name="predicted">Whether this interaction would have been predicted. If the predicting player is null,
|
||||||
|
/// this assumes it would have been predicted by all players in PVS range.</param>
|
||||||
|
protected override void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||||
|
{
|
||||||
|
// If this sound would have been predicted by all clients, do not play any audio.
|
||||||
|
if (predicted && predictingPlayer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var filter = Filter.Pvs(uid);
|
||||||
|
|
||||||
|
if (predicted)
|
||||||
{
|
{
|
||||||
/// <summary> ID based door access. </summary>
|
// This interaction is predicted, but only by the instigating user, who will have played their own sounds.
|
||||||
Id,
|
filter.RemoveWhereAttachedEntity(e => e == predictingPlayer);
|
||||||
/// <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()
|
// send the sound to players.
|
||||||
{
|
SoundSystem.Play(filter, sound, uid, AudioParams.Default.WithVolume(-5));
|
||||||
base.Initialize();
|
}
|
||||||
|
|
||||||
AccessType = AccessTypes.Id;
|
#region DoAfters
|
||||||
SubscribeLocalEvent<ServerDoorComponent, StartCollideEvent>(HandleCollide);
|
/// <summary>
|
||||||
|
/// Weld or pry open a door.
|
||||||
|
/// </summary>
|
||||||
|
private void OnInteractUsing(EntityUid uid, DoorComponent door, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp(args.Used, out ToolComponent? tool))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tool.Qualities.Contains(door.PryingQuality))
|
||||||
|
{
|
||||||
|
TryPryDoor(uid, args.Used, args.User, door);
|
||||||
|
args.Handled = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollide(EntityUid uid, ServerDoorComponent component, StartCollideEvent args)
|
if (door.Weldable && tool.Qualities.Contains(door.WeldingQuality))
|
||||||
{
|
{
|
||||||
if (!EntityManager.HasComponent<DoorBumpOpenerComponent>(args.OtherFixture.Body.Owner))
|
TryWeldDoor(uid, args.Used, args.User, door);
|
||||||
{
|
args.Handled = true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.State != SharedDoorComponent.DoorState.Closed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.BumpOpen)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disabled because it makes it suck hard to walk through double doors.
|
|
||||||
|
|
||||||
component.TryOpen(args.OtherFixture.Body.Owner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to weld a door shut, or unweld it if it is already welded. This does not actually check if the user
|
||||||
|
/// is holding the correct tool.
|
||||||
|
/// </summary>
|
||||||
|
private async void TryWeldDoor(EntityUid target, EntityUid used, EntityUid user, DoorComponent door)
|
||||||
|
{
|
||||||
|
if (!door.Weldable || door.BeingWelded || door.CurrentlyCrushing.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// is the door in a weld-able state?
|
||||||
|
if (door.State != DoorState.Closed && door.State != DoorState.Welded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// perform a do-after delay
|
||||||
|
door.BeingWelded = true;
|
||||||
|
_toolSystem.UseTool(used, user, target, 3f, 3f, door.WeldingQuality,
|
||||||
|
new WeldFinishedEvent(), new WeldCancelledEvent(), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pry open a door. This does not check if the user is holding the required tool.
|
||||||
|
/// </summary>
|
||||||
|
private async void TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door)
|
||||||
|
{
|
||||||
|
if (door.State == DoorState.Welded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var canEv = new BeforeDoorPryEvent(user);
|
||||||
|
RaiseLocalEvent(target, canEv, false);
|
||||||
|
|
||||||
|
if (canEv.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var modEv = new DoorGetPryTimeModifierEvent();
|
||||||
|
RaiseLocalEvent(target, modEv, false);
|
||||||
|
|
||||||
|
_toolSystem.UseTool(tool, user, target, 0f, modEv.PryTimeModifier * door.PryTime, door.PryingQuality,
|
||||||
|
new PryFinishedEvent(), new PryCancelledEvent(), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeldCancelled(EntityUid uid, DoorComponent door, WeldCancelledEvent args)
|
||||||
|
{
|
||||||
|
door.BeingWelded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeldFinished(EntityUid uid, DoorComponent door, WeldFinishedEvent args)
|
||||||
|
{
|
||||||
|
door.BeingWelded = false;
|
||||||
|
|
||||||
|
if (!door.Weldable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (door.State == DoorState.Closed)
|
||||||
|
SetState(uid, DoorState.Welded, door);
|
||||||
|
else if (door.State == DoorState.Welded)
|
||||||
|
SetState(uid, DoorState.Closed, door);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPryCancelled(EntityUid uid, DoorComponent door, PryCancelledEvent args)
|
||||||
|
{
|
||||||
|
door.BeingPried = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPryFinished(EntityUid uid, DoorComponent door, PryFinishedEvent args)
|
||||||
|
{
|
||||||
|
door.BeingPried = false;
|
||||||
|
|
||||||
|
if (door.State == DoorState.Closed)
|
||||||
|
StartOpening(uid, door);
|
||||||
|
else if (door.State == DoorState.Open)
|
||||||
|
StartClosing(uid, door);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the user have the permissions required to open this door?
|
||||||
|
/// </summary>
|
||||||
|
public override bool HasAccess(EntityUid uid, EntityUid? user = 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 (!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(access, user.Value),
|
||||||
|
AccessTypes.AllowAllNoExternal => !isExternal,
|
||||||
|
_ => _accessReaderSystem.IsAllowed(access, user.Value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open a door if a player or door-bumper (PDA, ID-card) collide with the door. Sadly, bullets no longer
|
||||||
|
/// generate "access denied" sounds as you fire at a door.
|
||||||
|
/// </summary>
|
||||||
|
protected override void HandleCollide(EntityUid uid, DoorComponent door, 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.
|
||||||
|
if (!door.BumpOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (door.State != DoorState.Closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryComp(args.OtherFixture.Body.Owner, out TagComponent? tags) && tags.HasTag("DoorBumpOpener"))
|
||||||
|
TryOpen(uid, door, args.OtherFixture.Body.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPartialOpen(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door, ref physics))
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.OnPartialOpen(uid, door, physics);
|
||||||
|
|
||||||
|
if (door.ChangeAirtight && TryComp(uid, out AirtightComponent? airtight))
|
||||||
|
{
|
||||||
|
_airtightSystem.SetAirblocked(airtight, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path-finding. Has nothing directly to do with access readers.
|
||||||
|
RaiseLocalEvent(new AccessReaderChangeMessage(uid, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door, ref physics))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!base.OnPartialClose(uid, door, physics))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// update airtight, if we did not crush something.
|
||||||
|
if (door.ChangeAirtight && door.CurrentlyCrushing.Count == 0 && TryComp(uid, out AirtightComponent? airtight))
|
||||||
|
_airtightSystem.SetAirblocked(airtight, true);
|
||||||
|
|
||||||
|
// Path-finding. Has nothing directly to do with access readers.
|
||||||
|
RaiseLocalEvent(new AccessReaderChangeMessage(uid, true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, DoorComponent door, MapInitEvent args)
|
||||||
|
{
|
||||||
|
// Ensure that the construction component is aware of the board container.
|
||||||
|
if (TryComp(uid, out ConstructionComponent? construction))
|
||||||
|
_constructionSystem.AddContainer(uid, "board", construction);
|
||||||
|
|
||||||
|
// We don't do anything if this is null or empty.
|
||||||
|
if (string.IsNullOrEmpty(door.BoardPrototype))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var container = uid.EnsureContainer<Container>("board", out var existed);
|
||||||
|
|
||||||
|
/* // 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 class PryFinishedEvent : EntityEventArgs { }
|
||||||
|
public class PryCancelledEvent : EntityEventArgs { }
|
||||||
|
public class WeldFinishedEvent : EntityEventArgs { }
|
||||||
|
public class WeldCancelledEvent : EntityEventArgs { }
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
using Content.Server.Atmos.Monitor.Components;
|
using Content.Server.Atmos.Monitor.Components;
|
||||||
using Content.Server.Atmos.Monitor.Systems;
|
using Content.Server.Atmos.Monitor.Systems;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
using Content.Shared.Atmos.Monitor;
|
using Content.Shared.Atmos.Monitor;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Log;
|
|
||||||
|
|
||||||
namespace Content.Server.Doors.Systems
|
namespace Content.Server.Doors.Systems
|
||||||
{
|
{
|
||||||
public class FirelockSystem : EntitySystem
|
public class FirelockSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -19,8 +23,8 @@ namespace Content.Server.Doors.Systems
|
|||||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
|
||||||
SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||||
SubscribeLocalEvent<FirelockComponent, DoorClickShouldActivateEvent>(OnDoorClickShouldActivate);
|
|
||||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorPryEvent>(OnBeforeDoorPry);
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorPryEvent>(OnBeforeDoorPry);
|
||||||
|
|
||||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
|
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
|
||||||
SubscribeLocalEvent<FirelockComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
SubscribeLocalEvent<FirelockComponent, AtmosMonitorAlarmEvent>(OnAtmosAlarm);
|
||||||
}
|
}
|
||||||
@@ -42,26 +46,20 @@ namespace Content.Server.Doors.Systems
|
|||||||
args.PryTimeModifier *= component.LockedPryTimeModifier;
|
args.PryTimeModifier *= component.LockedPryTimeModifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoorClickShouldActivate(EntityUid uid, FirelockComponent component, DoorClickShouldActivateEvent args)
|
|
||||||
{
|
|
||||||
// We're a firelock, you can't click to open it
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args)
|
private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args)
|
||||||
{
|
{
|
||||||
if (!TryComp<ServerDoorComponent>(uid, out var doorComponent) || doorComponent.State != SharedDoorComponent.DoorState.Closed)
|
if (!TryComp<DoorComponent>(uid, out var door) || door.State != DoorState.Closed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.IsHoldingPressure())
|
if (component.IsHoldingPressure())
|
||||||
{
|
{
|
||||||
component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-pressure-message"));
|
component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-pressure-message"));
|
||||||
}
|
}
|
||||||
else if (component.IsHoldingFire())
|
else if (component.IsHoldingFire())
|
||||||
{
|
{
|
||||||
component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-fire-message"));
|
component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-fire-message"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +79,12 @@ namespace Content.Server.Doors.Systems
|
|||||||
|
|
||||||
private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosMonitorAlarmEvent args)
|
private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosMonitorAlarmEvent args)
|
||||||
{
|
{
|
||||||
if (!TryComp<ServerDoorComponent>(uid, out var doorComponent)) return;
|
if (!TryComp<DoorComponent>(uid, out var doorComponent)) return;
|
||||||
|
|
||||||
if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
if (args.HighestNetworkType == AtmosMonitorAlarmType.Normal)
|
||||||
{
|
{
|
||||||
if (doorComponent.State == SharedDoorComponent.DoorState.Closed)
|
if (doorComponent.State == DoorState.Closed)
|
||||||
doorComponent.Open();
|
_doorSystem.TryOpen(uid);
|
||||||
}
|
}
|
||||||
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
else if (args.HighestNetworkType == AtmosMonitorAlarmType.Danger)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Server.Doors;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
|
using Content.Server.Doors.Systems;
|
||||||
using Content.Server.Explosion.Components;
|
using Content.Server.Explosion.Components;
|
||||||
using Content.Server.Flash;
|
using Content.Server.Flash;
|
||||||
using Content.Server.Flash.Components;
|
using Content.Server.Flash.Components;
|
||||||
@@ -39,6 +41,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly ExplosionSystem _explosions = default!;
|
[Dependency] private readonly ExplosionSystem _explosions = default!;
|
||||||
[Dependency] private readonly FlashSystem _flashSystem = default!;
|
[Dependency] private readonly FlashSystem _flashSystem = default!;
|
||||||
|
[Dependency] private readonly DoorSystem _sharedDoorSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -106,21 +109,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||||||
|
|
||||||
private void HandleDoorTrigger(EntityUid uid, ToggleDoorOnTriggerComponent component, TriggerEvent args)
|
private void HandleDoorTrigger(EntityUid uid, ToggleDoorOnTriggerComponent component, TriggerEvent args)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<ServerDoorComponent>(uid, out var door))
|
_sharedDoorSystem.TryToggleDoor(uid);
|
||||||
{
|
|
||||||
switch (door.State)
|
|
||||||
{
|
|
||||||
case SharedDoorComponent.DoorState.Open:
|
|
||||||
door.Close();
|
|
||||||
break;
|
|
||||||
case SharedDoorComponent.DoorState.Closed:
|
|
||||||
door.Open();
|
|
||||||
break;
|
|
||||||
case SharedDoorComponent.DoorState.Closing:
|
|
||||||
case SharedDoorComponent.DoorState.Opening:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollide(EntityUid uid, TriggerOnCollideComponent component, StartCollideEvent args)
|
private void HandleCollide(EntityUid uid, TriggerOnCollideComponent component, StartCollideEvent args)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Doors;
|
|
||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
using Content.Server.Suspicion;
|
using Content.Server.Suspicion;
|
||||||
@@ -11,6 +10,7 @@ using Content.Server.Suspicion.Roles;
|
|||||||
using Content.Server.Traitor.Uplink;
|
using Content.Server.Traitor.Uplink;
|
||||||
using Content.Server.Traitor.Uplink.Account;
|
using Content.Server.Traitor.Uplink.Account;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.MobState.Components;
|
using Content.Shared.MobState.Components;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -49,7 +49,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
[Dependency] private readonly IEntityManager _entities = default!;
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly DoorSystem _doorSystem = default!;
|
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||||
|
|
||||||
public override string Prototype => "Suspicion";
|
public override string Prototype => "Suspicion";
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
SoundSystem.Play(filter, _addedSound.GetSound(), AudioParams.Default);
|
SoundSystem.Play(filter, _addedSound.GetSound(), AudioParams.Default);
|
||||||
|
|
||||||
_doorSystem.AccessType = DoorSystem.AccessTypes.AllowAllNoExternal;
|
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.AllowAllNoExternal;
|
||||||
|
|
||||||
_checkTimerCancel = new CancellationTokenSource();
|
_checkTimerCancel = new CancellationTokenSource();
|
||||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
||||||
@@ -225,7 +225,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
public override void Removed()
|
public override void Removed()
|
||||||
{
|
{
|
||||||
_doorSystem.AccessType = DoorSystem.AccessTypes.Id;
|
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
|
||||||
EndTime = null;
|
EndTime = null;
|
||||||
_traitors.Clear();
|
_traitors.Clear();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.Doors.Components;
|
using Content.Server.Doors.Components;
|
||||||
|
using Content.Server.Doors.Systems;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Shuttles.Components;
|
using Content.Server.Shuttles.Components;
|
||||||
using Content.Shared.Doors;
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -23,6 +25,7 @@ namespace Content.Server.Shuttles.EntitySystems
|
|||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
||||||
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
|
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
|
||||||
|
[Dependency] private readonly DoorSystem _doorSystem = default!;
|
||||||
|
|
||||||
private const string DockingFixture = "docking";
|
private const string DockingFixture = "docking";
|
||||||
private const string DockingJoint = "docking";
|
private const string DockingJoint = "docking";
|
||||||
@@ -365,16 +368,16 @@ namespace Content.Server.Shuttles.EntitySystems
|
|||||||
dockA.DockJoint = joint;
|
dockA.DockJoint = joint;
|
||||||
dockB.DockJoint = joint;
|
dockB.DockJoint = joint;
|
||||||
|
|
||||||
if (TryComp(dockA.Owner, out ServerDoorComponent? doorA))
|
if (TryComp(dockA.Owner, out DoorComponent? doorA))
|
||||||
{
|
{
|
||||||
doorA.ChangeAirtight = false;
|
doorA.ChangeAirtight = false;
|
||||||
doorA.Open();
|
_doorSystem.StartOpening(doorA.Owner, doorA);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(dockB.Owner, out ServerDoorComponent? doorB))
|
if (TryComp(dockB.Owner, out DoorComponent? doorB))
|
||||||
{
|
{
|
||||||
doorB.ChangeAirtight = false;
|
doorB.ChangeAirtight = false;
|
||||||
doorB.Open();
|
_doorSystem.StartOpening(doorB.Owner, doorB);
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = new DockEvent
|
var msg = new DockEvent
|
||||||
@@ -448,16 +451,16 @@ namespace Content.Server.Shuttles.EntitySystems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(dock.Owner, out ServerDoorComponent? doorA))
|
if (TryComp(dock.Owner, out DoorComponent? doorA))
|
||||||
{
|
{
|
||||||
doorA.ChangeAirtight = true;
|
doorA.ChangeAirtight = true;
|
||||||
doorA.Close();
|
_doorSystem.TryClose(doorA.Owner, doorA);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryComp(dock.DockedWith, out ServerDoorComponent? doorB))
|
if (TryComp(dock.DockedWith, out DoorComponent? doorB))
|
||||||
{
|
{
|
||||||
doorB.ChangeAirtight = true;
|
doorB.ChangeAirtight = true;
|
||||||
doorB.Close();
|
_doorSystem.TryClose(doorB.Owner, doorB);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could maybe give the shuttle a light push away, or at least if there's no other docks left?
|
// Could maybe give the shuttle a light push away, or at least if there's no other docks left?
|
||||||
|
|||||||
229
Content.Shared/Doors/Components/DoorComponent.cs
Normal file
229
Content.Shared/Doors/Components/DoorComponent.cs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Sound;
|
||||||
|
using Content.Shared.Tools;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||||
|
|
||||||
|
namespace Content.Shared.Doors.Components;
|
||||||
|
|
||||||
|
[NetworkedComponent]
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class DoorComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Door";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state of the door -- whether it is open, closed, opening, or closing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should never be set directly.
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public DoorState State = DoorState.Closed;
|
||||||
|
|
||||||
|
[DataField("startOpen")]
|
||||||
|
public readonly bool StartOpen = false;
|
||||||
|
|
||||||
|
#region Timing
|
||||||
|
// if you want do dynamically adjust these times, you need to add networking for them. So for now, they are all
|
||||||
|
// read-only.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closing time until impassable. Total time is this plus <see cref="CloseTimeTwo"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("closeTimeOne")]
|
||||||
|
public readonly TimeSpan CloseTimeOne = TimeSpan.FromSeconds(0.4f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closing time until fully closed. Total time is this plus <see cref="CloseTimeOne"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("closeTimeTwo")]
|
||||||
|
public readonly TimeSpan CloseTimeTwo = TimeSpan.FromSeconds(0.2f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opening time until passable. Total time is this plus <see cref="OpenTimeTwo"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("openTimeOne")]
|
||||||
|
public readonly TimeSpan OpenTimeOne = TimeSpan.FromSeconds(0.4f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opening time until fully open. Total time is this plus <see cref="OpenTimeOne"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("openTimeTwo")]
|
||||||
|
public readonly TimeSpan OpenTimeTwo = TimeSpan.FromSeconds(0.2f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interval between deny sounds & visuals;
|
||||||
|
/// </summary>
|
||||||
|
[DataField("denyDuration")]
|
||||||
|
public readonly TimeSpan DenyDuration = TimeSpan.FromSeconds(0.45f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the door is active, this is the time when the state will next update.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan? NextStateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the door is currently partially closed or open. I.e., when the door is "closing" and is already opaque,
|
||||||
|
/// but not yet actually closed.
|
||||||
|
/// </summary>
|
||||||
|
public bool Partial;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Welding
|
||||||
|
// TODO WELDING. Consider creating a WeldableComponent for use with doors, crates and lockers? Currently they all
|
||||||
|
// have their own welding logic.
|
||||||
|
[DataField("weldingQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
||||||
|
public string WeldingQuality = "Welding";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the door can ever be welded shut.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("weldable")]
|
||||||
|
public bool Weldable = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether something is currently using a welder on this so DoAfter isn't spammed.
|
||||||
|
/// </summary>
|
||||||
|
public bool BeingWelded;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public bool BeingPried;
|
||||||
|
|
||||||
|
#region Sounds
|
||||||
|
/// <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>
|
||||||
|
/// Sound to play when a disarmed (hands comp with 0 hands) entity opens the door. What?
|
||||||
|
/// </summary>
|
||||||
|
[DataField("tryOpenDoorSound")]
|
||||||
|
public SoundSpecifier TryOpenDoorSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg");
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Crushing
|
||||||
|
/// <summary>
|
||||||
|
/// This is how long a door-crush will stun you. This also determines how long it takes the door to open up
|
||||||
|
/// again. Total stun time is actually given by this plus <see cref="OpenTimeOne"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("doorStunTime")]
|
||||||
|
public readonly TimeSpan DoorStunTime = TimeSpan.FromSeconds(2f);
|
||||||
|
|
||||||
|
[DataField("crushDamage")]
|
||||||
|
public DamageSpecifier? CrushDamage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If false, this door is incapable of crushing entities. Note that this differs from the airlock's "safety"
|
||||||
|
/// feature that checks for colliding entities.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("canCrush")]
|
||||||
|
public readonly bool CanCrush = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of EntityUids of entities we're currently crushing. Cleared in OnPartialOpen().
|
||||||
|
/// </summary>
|
||||||
|
public List<EntityUid> CurrentlyCrushing = new();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string? BoardPrototype;
|
||||||
|
|
||||||
|
[DataField("pryingQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
||||||
|
public string PryingQuality = "Prying";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default time that the door should take to pry open.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("pryTime")]
|
||||||
|
public float PryTime = 1.5f;
|
||||||
|
|
||||||
|
[DataField("changeAirtight")]
|
||||||
|
public bool ChangeAirtight = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the door blocks light.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("occludes")]
|
||||||
|
public bool Occludes = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the door will open when it is bumped into.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("bumpOpen")]
|
||||||
|
public bool BumpOpen = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the door will open when it is activated or clicked.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("clickOpen")]
|
||||||
|
public bool ClickOpen = true;
|
||||||
|
|
||||||
|
[DataField("openDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||||
|
public int OpenDrawDepth = (int) DrawDepth.DrawDepth.Doors;
|
||||||
|
|
||||||
|
[DataField("closedDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||||
|
public int ClosedDrawDepth = (int) DrawDepth.DrawDepth.Doors;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum DoorState
|
||||||
|
{
|
||||||
|
Closed,
|
||||||
|
Closing,
|
||||||
|
Open,
|
||||||
|
Opening,
|
||||||
|
Welded,
|
||||||
|
Denying,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum DoorVisuals
|
||||||
|
{
|
||||||
|
State,
|
||||||
|
Powered,
|
||||||
|
BoltLights
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class DoorComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public readonly DoorState DoorState;
|
||||||
|
public readonly List<EntityUid> CurrentlyCrushing;
|
||||||
|
public readonly TimeSpan? NextStateChange;
|
||||||
|
public readonly bool Partial;
|
||||||
|
|
||||||
|
public DoorComponentState(DoorComponent door)
|
||||||
|
{
|
||||||
|
DoorState = door.State;
|
||||||
|
CurrentlyCrushing = door.CurrentlyCrushing;
|
||||||
|
NextStateChange = door.NextStateChange;
|
||||||
|
Partial = door.Partial;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Content.Shared/Doors/Components/SharedAirlockComponent.cs
Normal file
29
Content.Shared/Doors/Components/SharedAirlockComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Shared.Doors.Components;
|
||||||
|
|
||||||
|
[NetworkedComponent]
|
||||||
|
public abstract class SharedAirlockComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Airlock";
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("safety")]
|
||||||
|
public bool Safety = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class AirlockComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public readonly bool Safety;
|
||||||
|
|
||||||
|
public AirlockComponentState(bool safety)
|
||||||
|
{
|
||||||
|
Safety = safety;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Doors.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Shared.Doors
|
namespace Content.Shared.Doors
|
||||||
@@ -8,9 +8,9 @@ namespace Content.Shared.Doors
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DoorStateChangedEvent : EntityEventArgs
|
public class DoorStateChangedEvent : EntityEventArgs
|
||||||
{
|
{
|
||||||
public SharedDoorComponent.DoorState State;
|
public readonly DoorState State;
|
||||||
|
|
||||||
public DoorStateChangedEvent(SharedDoorComponent.DoorState state)
|
public DoorStateChangedEvent(DoorState state)
|
||||||
{
|
{
|
||||||
State = state;
|
State = state;
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,11 @@ namespace Content.Shared.Doors
|
|||||||
/// Raised when the door is determining whether it is able to close.
|
/// Raised when the door is determining whether it is able to close.
|
||||||
/// Cancel to stop the door from being closed.
|
/// Cancel to stop the door from being closed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This event is raised both when the door is initially closed, and when it is just about to become "partially"
|
||||||
|
/// closed (opaque & collidable). If canceled while partially closing, it will start opening again. Useful for
|
||||||
|
/// things like airlock anti-crush safety features.
|
||||||
|
/// </remarks>
|
||||||
public class BeforeDoorClosedEvent : CancellableEntityEventArgs
|
public class BeforeDoorClosedEvent : CancellableEntityEventArgs
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -40,19 +45,13 @@ namespace Content.Shared.Doors
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised to determine whether the door's safety is on.
|
|
||||||
/// Modify Safety to set the door's safety.
|
|
||||||
/// </summary>
|
|
||||||
public class DoorSafetyEnabledEvent : HandledEntityEventArgs
|
|
||||||
{
|
|
||||||
public bool Safety = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised to determine whether the door should automatically close.
|
/// Raised to determine whether the door should automatically close.
|
||||||
/// Cancel to stop it from automatically closing.
|
/// Cancel to stop it from automatically closing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is called when a door decides whether it SHOULD auto close, not when it actually closes.
|
||||||
|
/// </remarks>
|
||||||
public class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs
|
public class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -66,52 +65,17 @@ namespace Content.Shared.Doors
|
|||||||
public float PryTimeModifier = 1.0f;
|
public float PryTimeModifier = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised to determine how long the door's close time should be modified by.
|
|
||||||
/// Multiply CloseTimeModifier by the desired amount.
|
|
||||||
/// </summary>
|
|
||||||
public class DoorGetCloseTimeModifierEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public float CloseTimeModifier = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised to determine whether clicking the door should open/close it.
|
|
||||||
/// </summary>
|
|
||||||
public class DoorClickShouldActivateEvent : HandledEntityEventArgs
|
|
||||||
{
|
|
||||||
public ActivateEventArgs Args;
|
|
||||||
|
|
||||||
public DoorClickShouldActivateEvent(ActivateEventArgs args)
|
|
||||||
{
|
|
||||||
Args = args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised when an attempt to pry open the door is made.
|
/// Raised when an attempt to pry open the door is made.
|
||||||
/// Cancel to stop the door from being pried open.
|
/// Cancel to stop the door from being pried open.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BeforeDoorPryEvent : CancellableEntityEventArgs
|
public class BeforeDoorPryEvent : CancellableEntityEventArgs
|
||||||
{
|
{
|
||||||
public InteractUsingEventArgs Args;
|
public readonly EntityUid User;
|
||||||
|
|
||||||
public BeforeDoorPryEvent(InteractUsingEventArgs args)
|
public BeforeDoorPryEvent(EntityUid user)
|
||||||
{
|
{
|
||||||
Args = args;
|
User = user;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised when a door is successfully pried open.
|
|
||||||
/// </summary>
|
|
||||||
public class OnDoorPryEvent : EntityEventArgs
|
|
||||||
{
|
|
||||||
public InteractUsingEventArgs Args;
|
|
||||||
|
|
||||||
public OnDoorPryEvent(InteractUsingEventArgs args)
|
|
||||||
{
|
|
||||||
Args = args;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Shared.Doors
|
|
||||||
{
|
|
||||||
[NetworkedComponent]
|
|
||||||
public abstract class SharedDoorComponent : Component
|
|
||||||
{
|
|
||||||
public override string Name => "Door";
|
|
||||||
|
|
||||||
[Dependency]
|
|
||||||
protected readonly IGameTiming _gameTiming = default!;
|
|
||||||
|
|
||||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private DoorState _state = DoorState.Closed;
|
|
||||||
/// <summary>
|
|
||||||
/// The current state of the door -- whether it is open, closed, opening, or closing.
|
|
||||||
/// </summary>
|
|
||||||
public virtual DoorState State
|
|
||||||
{
|
|
||||||
get => _state;
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
if (_state == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_state = value;
|
|
||||||
|
|
||||||
SetAppearance(State switch
|
|
||||||
{
|
|
||||||
DoorState.Open => DoorVisualState.Open,
|
|
||||||
DoorState.Closed => DoorVisualState.Closed,
|
|
||||||
DoorState.Opening => DoorVisualState.Opening,
|
|
||||||
DoorState.Closing => DoorVisualState.Closing,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closing time until impassable.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("closeTimeOne")]
|
|
||||||
protected TimeSpan CloseTimeOne = TimeSpan.FromSeconds(0.4f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closing time until fully closed.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("closeTimeTwo")]
|
|
||||||
protected TimeSpan CloseTimeTwo = TimeSpan.FromSeconds(0.2f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opening time until passable.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("openTimeOne")]
|
|
||||||
protected TimeSpan OpenTimeOne = TimeSpan.FromSeconds(0.4f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opening time until fully open.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("openTimeTwo")]
|
|
||||||
protected TimeSpan OpenTimeTwo = TimeSpan.FromSeconds(0.2f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time to finish denying.
|
|
||||||
/// </summary>
|
|
||||||
protected static TimeSpan DenyTime => TimeSpan.FromSeconds(0.45f);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used by ServerDoorComponent to get the CurTime for the client to use to know when to open, and by ClientDoorComponent to know the CurTime to correctly open.
|
|
||||||
/// </summary>
|
|
||||||
[Dependency] protected IGameTiming GameTiming = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time the door began to open or close, if the door is opening or closing, or null if it is neither.
|
|
||||||
/// </summary>
|
|
||||||
protected TimeSpan? StateChangeStartTime = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of EntityUids of entities we're currently crushing. Cleared in OnPartialOpen().
|
|
||||||
/// </summary>
|
|
||||||
protected List<EntityUid> CurrentlyCrushing = new();
|
|
||||||
|
|
||||||
public bool IsCrushing(EntityUid entity)
|
|
||||||
{
|
|
||||||
return CurrentlyCrushing.Contains(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void SetAppearance(DoorVisualState state)
|
|
||||||
{
|
|
||||||
if (_entMan.TryGetComponent<AppearanceComponent>(Owner, out var appearanceComponent))
|
|
||||||
{
|
|
||||||
appearanceComponent.SetData(DoorVisuals.VisualState, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door is partially opened.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnPartialOpen()
|
|
||||||
{
|
|
||||||
if (_entMan.TryGetComponent<PhysicsComponent>(Owner, out var physicsComponent))
|
|
||||||
{
|
|
||||||
physicsComponent.CanCollide = false;
|
|
||||||
}
|
|
||||||
// we can't be crushing anyone anymore, since we're opening
|
|
||||||
CurrentlyCrushing.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the door is partially closed.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnPartialClose()
|
|
||||||
{
|
|
||||||
if (_entMan.TryGetComponent<PhysicsComponent>(Owner, out var physicsComponent))
|
|
||||||
{
|
|
||||||
physicsComponent.CanCollide = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum DoorState
|
|
||||||
{
|
|
||||||
Open,
|
|
||||||
Closed,
|
|
||||||
Opening,
|
|
||||||
Closing,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum DoorVisualState
|
|
||||||
{
|
|
||||||
Open,
|
|
||||||
Closed,
|
|
||||||
Opening,
|
|
||||||
Closing,
|
|
||||||
Deny,
|
|
||||||
Welded
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum DoorVisuals
|
|
||||||
{
|
|
||||||
VisualState,
|
|
||||||
Powered,
|
|
||||||
BoltLights
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class DoorComponentState : ComponentState
|
|
||||||
{
|
|
||||||
public readonly SharedDoorComponent.DoorState DoorState;
|
|
||||||
public readonly TimeSpan? StartTime;
|
|
||||||
public readonly List<EntityUid> CurrentlyCrushing;
|
|
||||||
public readonly TimeSpan CurTime;
|
|
||||||
|
|
||||||
public DoorComponentState(SharedDoorComponent.DoorState doorState, TimeSpan? startTime, List<EntityUid> currentlyCrushing, TimeSpan curTime)
|
|
||||||
{
|
|
||||||
DoorState = doorState;
|
|
||||||
StartTime = startTime;
|
|
||||||
CurrentlyCrushing = currentlyCrushing;
|
|
||||||
CurTime = curTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DoorOpenAttemptEvent : CancellableEntityEventArgs
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DoorCloseAttemptEvent : CancellableEntityEventArgs
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Physics.Dynamics;
|
|
||||||
|
|
||||||
namespace Content.Shared.Doors
|
|
||||||
{
|
|
||||||
public abstract class SharedDoorSystem : EntitySystem
|
|
||||||
{
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<SharedDoorComponent, PreventCollideEvent>(PreventCollision);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreventCollision(EntityUid uid, SharedDoorComponent component, PreventCollideEvent args)
|
|
||||||
{
|
|
||||||
if (component.IsCrushing(args.BodyB.Owner))
|
|
||||||
{
|
|
||||||
args.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
Content.Shared/Doors/Systems/SharedAirlockSystem.cs
Normal file
41
Content.Shared/Doors/Systems/SharedAirlockSystem.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Content.Shared.Doors.Systems;
|
||||||
|
|
||||||
|
public abstract class SharedAirlockSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] protected readonly SharedDoorSystem DoorSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SharedAirlockComponent, ComponentGetState>(OnGetState);
|
||||||
|
SubscribeLocalEvent<SharedAirlockComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
SubscribeLocalEvent<SharedAirlockComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetState(EntityUid uid, SharedAirlockComponent airlock, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
// Need to network airlock safety state to avoid mis-predicts when a door auto-closes as the client walks through the door.
|
||||||
|
args.State = new AirlockComponentState(airlock.Safety);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHandleState(EntityUid uid, SharedAirlockComponent airlock, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not AirlockComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
airlock.Safety = state.Safety;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnBeforeDoorClosed(EntityUid uid, SharedAirlockComponent airlock, BeforeDoorClosedEvent args)
|
||||||
|
{
|
||||||
|
if (airlock.Safety && DoorSystem.GetColliding(uid).Any())
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
579
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Normal file
579
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Stunnable;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Physics.Dynamics;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Content.Shared.Doors.Systems;
|
||||||
|
|
||||||
|
public abstract class SharedDoorSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||||
|
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||||
|
[Dependency] protected readonly IGameTiming GameTiming = 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, ComponentStartup>(OnStartup);
|
||||||
|
SubscribeLocalEvent<DoorComponent, ComponentShutdown>(OnShutdown);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<DoorComponent, ComponentGetState>(OnGetState);
|
||||||
|
SubscribeLocalEvent<DoorComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<DoorComponent, ActivateInWorldEvent>(OnActivate);
|
||||||
|
SubscribeLocalEvent<DoorComponent, ExaminedEvent>(OnExamine);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<DoorComponent, StartCollideEvent>(HandleCollide);
|
||||||
|
SubscribeLocalEvent<DoorComponent, PreventCollideEvent>(PreventCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartup(EntityUid uid, DoorComponent door, ComponentStartup args)
|
||||||
|
{
|
||||||
|
// if the door state is not standard (i.e., door starts open), make sure collision & occlusion are properly set.
|
||||||
|
if (door.StartOpen)
|
||||||
|
{
|
||||||
|
// disable occluder & physics
|
||||||
|
OnPartialOpen(uid, door);
|
||||||
|
|
||||||
|
// THEN set the correct state, inc disabling partial = true
|
||||||
|
SetState(uid, DoorState.Open, door);
|
||||||
|
|
||||||
|
// The airlock component may schedule an auto-close for this door during the SetState.
|
||||||
|
// Give the door is supposed to start open, let's prevent any auto-closing that might occur.
|
||||||
|
door.NextStateChange = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance(uid, door);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdown(EntityUid uid, DoorComponent door, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
_activeDoors.Remove(door);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region StateManagement
|
||||||
|
private void OnGetState(EntityUid uid, DoorComponent door, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new DoorComponentState(door);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHandleState(EntityUid uid, DoorComponent door, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not DoorComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
door.CurrentlyCrushing = state.CurrentlyCrushing;
|
||||||
|
door.State = state.DoorState;
|
||||||
|
door.NextStateChange = state.NextStateChange;
|
||||||
|
door.Partial = state.Partial;
|
||||||
|
|
||||||
|
if (state.NextStateChange == null)
|
||||||
|
_activeDoors.Remove(door);
|
||||||
|
else
|
||||||
|
_activeDoors.Add(door);
|
||||||
|
|
||||||
|
RaiseLocalEvent(uid, new DoorStateChangedEvent(door.State), false);
|
||||||
|
UpdateAppearance(uid, door);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetState(EntityUid uid, DoorState state, DoorComponent? door = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door))
|
||||||
|
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.Open:
|
||||||
|
case DoorState.Closed:
|
||||||
|
door.Partial = false;
|
||||||
|
if (door.NextStateChange == null)
|
||||||
|
_activeDoors.Remove(door);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
door.State = state;
|
||||||
|
door.Dirty();
|
||||||
|
RaiseLocalEvent(uid, new DoorStateChangedEvent(state), false);
|
||||||
|
UpdateAppearance(uid, door);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateAppearance(EntityUid uid, DoorComponent? door = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||||
|
return;
|
||||||
|
|
||||||
|
appearance.SetData(DoorVisuals.State, door.State);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Interactions
|
||||||
|
private void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || !door.ClickOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryToggleDoor(uid, door, args.User);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(EntityUid uid, DoorComponent door, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (door.State == DoorState.Welded)
|
||||||
|
args.PushText(Loc.GetString("door-component-examine-is-welded"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.GetSound(), 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)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CanOpen(uid, door, user, false))
|
||||||
|
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();
|
||||||
|
RaiseLocalEvent(uid, ev, false);
|
||||||
|
if (ev.Cancelled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!HasAccess(uid, user))
|
||||||
|
{
|
||||||
|
if (!quiet)
|
||||||
|
Deny(uid, door);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.GetSound(), 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 SharedHandsComponent? hands) && hands.Hands.Count == 0)
|
||||||
|
PlaySound(uid, door.TryOpenDoorSound.GetSound(), 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 virtual void OnPartialOpen(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door, ref physics))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// we can't be crushing anyone anymore, since we're opening
|
||||||
|
door.CurrentlyCrushing.Clear();
|
||||||
|
physics.CanCollide = false;
|
||||||
|
door.Partial = true;
|
||||||
|
door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo;
|
||||||
|
_activeDoors.Add(door);
|
||||||
|
door.Dirty();
|
||||||
|
|
||||||
|
if (door.Occludes && TryComp(uid, out OccluderComponent? occluder))
|
||||||
|
occluder.Enabled = false;
|
||||||
|
}
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanClose(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool quiet = true)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var ev = new BeforeDoorClosedEvent();
|
||||||
|
RaiseLocalEvent(uid, ev, false);
|
||||||
|
if (ev.Cancelled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return HasAccess(uid, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.GetSound(), 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 virtual bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref door, ref physics))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
door.Partial = true;
|
||||||
|
door.Dirty();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
UpdateAppearance(uid, door);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
physics.CanCollide = true;
|
||||||
|
door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo;
|
||||||
|
_activeDoors.Add(door);
|
||||||
|
|
||||||
|
if (door.Occludes && TryComp(uid, out OccluderComponent? occluder))
|
||||||
|
occluder.Enabled = true;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
// is this door capable of crushing? NOT the same as an airlock safety check. The door will still close.
|
||||||
|
if (!door.CanCrush)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Crush
|
||||||
|
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);
|
||||||
|
|
||||||
|
_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.
|
||||||
|
var doorAABB = physics.GetWorldAABB();
|
||||||
|
|
||||||
|
foreach (var body in _physicsSystem.GetCollidingEntities(Transform(uid).MapID, doorAABB))
|
||||||
|
{
|
||||||
|
// static bodies (e.g., furniture) shouldn't stop airlocks/windoors from closing.
|
||||||
|
if (body.BodyType == BodyType.Static)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (body.GetWorldAABB().IntersectPercentage(doorAABB) < IntersectPercentage)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return body.Owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreventCollision(EntityUid uid, DoorComponent component, PreventCollideEvent args)
|
||||||
|
{
|
||||||
|
if (component.CurrentlyCrushing.Contains(args.BodyB.Owner))
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HandleCollide(EntityUid uid, DoorComponent door, 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
|
||||||
|
public virtual bool HasAccess(EntityUid uid, EntityUid? user = null, AccessReaderComponent? access = null)
|
||||||
|
{
|
||||||
|
// TODO network AccessComponent for predicting doors
|
||||||
|
|
||||||
|
// Currently all door open/close & door-bumper collision stuff is done server side.
|
||||||
|
// so this return value means nothing.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.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.
|
||||||
|
Logger.Error($"Welded door was in the list of active doors. Door: {ToPrettyString(door.Owner)}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected abstract void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
using Content.Shared.Hands;
|
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
using Content.Shared.Rotation;
|
using Content.Shared.Rotation;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Standing
|
namespace Content.Shared.Standing
|
||||||
{
|
{
|
||||||
public sealed class StandingStateSystem : EntitySystem
|
public sealed class StandingStateSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null)
|
public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null)
|
||||||
{
|
{
|
||||||
@@ -56,10 +56,14 @@ namespace Content.Shared.Standing
|
|||||||
standingState.Dirty();
|
standingState.Dirty();
|
||||||
RaiseLocalEvent(uid, new DownedEvent(), false);
|
RaiseLocalEvent(uid, new DownedEvent(), false);
|
||||||
|
|
||||||
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
return true;
|
||||||
|
|
||||||
// Seemed like the best place to put it
|
// Seemed like the best place to put it
|
||||||
appearance?.SetData(RotationVisuals.RotationState, RotationState.Horizontal);
|
appearance?.SetData(RotationVisuals.RotationState, RotationState.Horizontal);
|
||||||
|
|
||||||
// Currently shit is only downed by server but when it's predicted we can probably only play this on server / client
|
// Currently shit is only downed by server but when it's predicted we can probably only play this on server / client
|
||||||
|
// > no longer true with door crushing. There just needs to be a better way to handle audio prediction.
|
||||||
if (playSound)
|
if (playSound)
|
||||||
{
|
{
|
||||||
SoundSystem.Play(Filter.Pvs(uid), standingState.DownSoundCollection.GetSound(), uid, AudioHelpers.WithVariation(0.25f));
|
SoundSystem.Play(Filter.Pvs(uid), standingState.DownSoundCollection.GetSound(), uid, AudioHelpers.WithVariation(0.25f));
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
door-component-examine-is-welded = It has been welded shut.
|
||||||
@@ -96,7 +96,6 @@
|
|||||||
- type: Body
|
- type: Body
|
||||||
template: HumanoidTemplate
|
template: HumanoidTemplate
|
||||||
preset: HumanPreset
|
preset: HumanPreset
|
||||||
- type: DoorBumpOpener
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
save: false
|
save: false
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
tags:
|
tags:
|
||||||
- CanPilot
|
- CanPilot
|
||||||
- FootstepSound
|
- FootstepSound
|
||||||
|
- DoorBumpOpener
|
||||||
- type: Reactive
|
- type: Reactive
|
||||||
groups:
|
groups:
|
||||||
Flammable: [ Touch ]
|
Flammable: [ Touch ]
|
||||||
@@ -293,7 +294,6 @@
|
|||||||
attributes:
|
attributes:
|
||||||
proper: true
|
proper: true
|
||||||
- type: StandingState
|
- type: StandingState
|
||||||
- type: DoorBumpOpener
|
|
||||||
- type: entity
|
- type: entity
|
||||||
save: false
|
save: false
|
||||||
name: Urist McHands
|
name: Urist McHands
|
||||||
|
|||||||
@@ -93,7 +93,6 @@
|
|||||||
- type: Body
|
- type: Body
|
||||||
template: HumanoidTemplate
|
template: HumanoidTemplate
|
||||||
preset: SlimePreset
|
preset: SlimePreset
|
||||||
- type: DoorBumpOpener
|
|
||||||
- type: HumanoidAppearance
|
- type: HumanoidAppearance
|
||||||
hairMatchesSkin: true
|
hairMatchesSkin: true
|
||||||
hairAlpha: 0.5
|
hairAlpha: 0.5
|
||||||
|
|||||||
@@ -113,6 +113,5 @@
|
|||||||
categoriesFacialHair: VoxFacialHair
|
categoriesFacialHair: VoxFacialHair
|
||||||
- type: Inventory
|
- type: Inventory
|
||||||
speciesId: vox
|
speciesId: vox
|
||||||
- type: DoorBumpOpener
|
|
||||||
- type: Butcherable
|
- type: Butcherable
|
||||||
meat: FoodMeatChicken
|
meat: FoodMeatChicken
|
||||||
|
|||||||
@@ -47,7 +47,9 @@
|
|||||||
whitelist:
|
whitelist:
|
||||||
components:
|
components:
|
||||||
- IdCard
|
- IdCard
|
||||||
- type: DoorBumpOpener
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- DoorBumpOpener
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BasePDA
|
parent: BasePDA
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
HeldPrefix: default
|
HeldPrefix: default
|
||||||
- type: Access
|
- type: Access
|
||||||
- type: IdCard
|
- type: IdCard
|
||||||
- type: DoorBumpOpener
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- DoorBumpOpener
|
||||||
|
|
||||||
#IDs with layers
|
#IDs with layers
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,6 @@
|
|||||||
map: ["enum.DoorVisualLayers.BaseBolted"]
|
map: ["enum.DoorVisualLayers.BaseBolted"]
|
||||||
- state: panel_open
|
- state: panel_open
|
||||||
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
||||||
- type: Physics
|
|
||||||
canCollide: false
|
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
- shape:
|
- shape:
|
||||||
@@ -65,7 +63,7 @@
|
|||||||
openTimeTwo: 0.6
|
openTimeTwo: 0.6
|
||||||
startOpen: true
|
startOpen: true
|
||||||
bumpOpen: false
|
bumpOpen: false
|
||||||
inhibitCrush: false
|
clickOpen: false
|
||||||
crushDamage:
|
crushDamage:
|
||||||
types:
|
types:
|
||||||
Blunt: 15
|
Blunt: 15
|
||||||
@@ -90,8 +88,6 @@
|
|||||||
type: WiresBoundUserInterface
|
type: WiresBoundUserInterface
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
fixVacuum: true
|
fixVacuum: true
|
||||||
- type: Occluder
|
|
||||||
enabled: false
|
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: Firelock
|
graph: Firelock
|
||||||
node: Firelock
|
node: Firelock
|
||||||
@@ -101,14 +97,6 @@
|
|||||||
parent: Firelock
|
parent: Firelock
|
||||||
name: glass firelock
|
name: glass firelock
|
||||||
components:
|
components:
|
||||||
- type: Door
|
|
||||||
occludes: false
|
|
||||||
inhibitCrush: false
|
|
||||||
crushDamage:
|
|
||||||
types:
|
|
||||||
Blunt: 15
|
|
||||||
- type: Occluder
|
|
||||||
enabled: false
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Doors/Airlocks/Glass/firelock.rsi
|
sprite: Structures/Doors/Airlocks/Glass/firelock.rsi
|
||||||
|
|
||||||
@@ -117,12 +105,6 @@
|
|||||||
parent: Firelock
|
parent: Firelock
|
||||||
name: firelock
|
name: firelock
|
||||||
components:
|
components:
|
||||||
- type: Door
|
|
||||||
occludes: false
|
|
||||||
inhibitCrush: false
|
|
||||||
crushDamage:
|
|
||||||
types:
|
|
||||||
Blunt: 15
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Doors/edge_door_hazard.rsi
|
sprite: Structures/Doors/edge_door_hazard.rsi
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
@@ -130,7 +112,6 @@
|
|||||||
noAirWhenFullyAirBlocked: false
|
noAirWhenFullyAirBlocked: false
|
||||||
airBlockedDirection:
|
airBlockedDirection:
|
||||||
- South
|
- South
|
||||||
- type: Physics
|
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
- shape:
|
- shape:
|
||||||
|
|||||||
@@ -30,12 +30,10 @@
|
|||||||
board: DoorElectronics
|
board: DoorElectronics
|
||||||
bumpOpen: false
|
bumpOpen: false
|
||||||
clickOpen: false
|
clickOpen: false
|
||||||
autoClose: false
|
|
||||||
closeTimeOne: 0.2
|
closeTimeOne: 0.2
|
||||||
closeTimeTwo: 1.2
|
closeTimeTwo: 1.2
|
||||||
openTimeOne: 1.2
|
openTimeOne: 1.2
|
||||||
openTimeTwo: 0.2
|
openTimeTwo: 0.2
|
||||||
inhibitCrush: false
|
|
||||||
crushDamage:
|
crushDamage:
|
||||||
types:
|
types:
|
||||||
Blunt: 5 # getting shutters closed on you probably doesn't hurt that much
|
Blunt: 5 # getting shutters closed on you probably doesn't hurt that much
|
||||||
|
|||||||
@@ -102,6 +102,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Document
|
id: Document
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: DoorBumpOpener
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: DoorElectronics
|
id: DoorElectronics
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user