using Content.Client.Wires.Visualizers;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
+using Content.Shared.Prying.Components;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
base.Initialize();
SubscribeLocalEvent<AirlockComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<AirlockComponent, AppearanceChangeEvent>(OnAppearanceChange);
+ SubscribeLocalEvent<AirlockComponent, BeforePryEvent>(OnAirlockPryAttempt);
+ }
+
+ private void OnAirlockPryAttempt(EntityUid uid, AirlockComponent component, ref BeforePryEvent args)
+ {
+ // TODO: Temporary until airlocks predicted.
+ args.Cancelled = true;
}
private void OnComponentStartup(EntityUid uid, AirlockComponent comp, ComponentStartup args)
using Content.Server.DeviceLinking.Events;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
-using Content.Shared.Tools.Components;
using Content.Server.Wires;
using Content.Shared.Doors;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Content.Shared.Wires;
+using Content.Shared.Prying.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Doors.Systems;
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<AirlockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
- SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new [] {typeof(DoorSystem)});
- SubscribeLocalEvent<AirlockComponent, DoorGetPryTimeModifierEvent>(OnGetPryMod);
- SubscribeLocalEvent<AirlockComponent, BeforeDoorPryEvent>(OnDoorPry);
+ SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new[] { typeof(DoorSystem) });
+ SubscribeLocalEvent<AirlockComponent, GetPryTimeModifierEvent>(OnGetPryMod);
+ SubscribeLocalEvent<AirlockComponent, BeforePryEvent>(OnBeforePry);
}
}
}
- private void OnGetPryMod(EntityUid uid, AirlockComponent component, DoorGetPryTimeModifierEvent args)
+ private void OnGetPryMod(EntityUid uid, AirlockComponent component, ref GetPryTimeModifierEvent args)
{
if (_power.IsPowered(uid))
args.PryTimeModifier *= component.PoweredPryModifier;
}
- private void OnDoorPry(EntityUid uid, AirlockComponent component, BeforeDoorPryEvent args)
+ private void OnBeforePry(EntityUid uid, AirlockComponent component, ref BeforePryEvent args)
{
- if (this.IsPowered(uid, EntityManager))
+ if (this.IsPowered(uid, EntityManager) && !args.PryPowered)
{
- if (HasComp<ToolForcePoweredComponent>(args.Tool))
- return;
- Popup.PopupEntity(Loc.GetString("airlock-component-cannot-pry-is-powered-message"), uid, args.User);
- args.Cancel();
+ Popup.PopupClient(Loc.GetString("airlock-component-cannot-pry-is-powered-message"), uid, args.User);
+ args.Cancelled = true;
}
}
using Content.Server.Access;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
-using Content.Server.Construction;
using Content.Shared.Database;
using Content.Shared.Doors;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Emag.Systems;
using Content.Shared.Interaction;
-using Content.Shared.Tools.Components;
-using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Content.Server.Administration.Logs;
using Content.Server.Power.EntitySystems;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Content.Shared.DoAfter;
+using Content.Shared.Prying.Systems;
+using Content.Shared.Prying.Components;
using Content.Shared.Tools.Systems;
namespace Content.Server.Doors.Systems;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly PryingSystem _pryingSystem = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ConstructionSystem) });
- // Mob prying doors
- SubscribeLocalEvent<DoorComponent, GetVerbsEvent<AlternativeVerb>>(OnDoorAltVerb);
-
- SubscribeLocalEvent<DoorComponent, DoorPryDoAfterEvent>(OnPryFinished);
+ SubscribeLocalEvent<DoorComponent, BeforePryEvent>(OnBeforeDoorPry);
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
+ SubscribeLocalEvent<DoorComponent, PriedEvent>(OnAfterPry);
}
protected override void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args)
if (args.Handled || !door.ClickOpen)
return;
- TryToggleDoor(uid, door, args.User);
+ if (!TryToggleDoor(uid, door, args.User))
+ _pryingSystem.TryPry(uid, args.User, out _);
+
args.Handled = true;
}
Audio.PlayPvs(soundSpecifier, uid, audioParams);
}
-#region DoAfters
- /// <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))
- {
- args.Handled = TryPryDoor(uid, args.Used, args.User, door, out _);
- }
- }
-
+ #region DoAfters
private void OnWeldAttempt(EntityUid uid, DoorComponent component, WeldableAttemptEvent args)
{
if (component.CurrentlyCrushing.Count > 0)
SetState(uid, DoorState.Closed, component);
}
- private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEvent<AlternativeVerb> args)
+ private void OnBeforeDoorPry(EntityUid id, DoorComponent door, ref BeforePryEvent args)
{
- if (!args.CanInteract || !args.CanAccess)
- return;
-
- if (!TryComp<ToolComponent>(args.User, out var tool) || !tool.Qualities.Contains(component.PryingQuality))
- return;
-
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString("door-pry"),
- Impact = LogImpact.Low,
- Act = () => TryPryDoor(uid, args.User, args.User, component, out _, force: true),
- });
+ if (door.State == DoorState.Welded || !door.CanPry)
+ args.Cancelled = true;
}
-
-
- /// <summary>
- /// Pry open a door. This does not check if the user is holding the required tool.
- /// </summary>
- public bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, out DoAfterId? id, bool force = false)
- {
- id = null;
-
- if (door.State == DoorState.Welded)
- return false;
-
- if (!force)
- {
- var canEv = new BeforeDoorPryEvent(user, tool);
- RaiseLocalEvent(target, canEv, false);
-
- if (!door.CanPry || canEv.Cancelled)
- // mark handled, as airlock component will cancel after generating a pop-up & you don't want to pry a tile
- // under a windoor.
- return true;
- }
-
- var modEv = new DoorGetPryTimeModifierEvent(user);
- RaiseLocalEvent(target, modEv, false);
-
- _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is using {ToPrettyString(tool)} to pry {ToPrettyString(target)} while it is {door.State}"); // TODO move to generic tool use logging in a way that includes door state
- _toolSystem.UseTool(tool, user, target, TimeSpan.FromSeconds(modEv.PryTimeModifier * door.PryTime), new[] {door.PryingQuality}, new DoorPryDoAfterEvent(), out id);
- return true; // we might not actually succeeded, but a do-after has started
- }
-
- private void OnPryFinished(EntityUid uid, DoorComponent door, DoAfterEvent args)
- {
- if (args.Cancelled)
- return;
-
- if (door.State == DoorState.Closed)
- {
- _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} open"); // TODO move to generic tool use logging in a way that includes door state
- StartOpening(uid, door);
- }
- else if (door.State == DoorState.Open)
- {
- _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} closed"); // TODO move to generic tool use logging in a way that includes door state
- StartClosing(uid, door);
- }
- }
-#endregion
+ #endregion
/// <summary>
}
private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent args)
{
- if(TryComp<AirlockComponent>(uid, out var airlockComponent))
+ if (TryComp<AirlockComponent>(uid, out var airlockComponent))
{
if (_bolts.IsBolted(uid) || !this.IsPowered(uid, EntityManager))
return;
if (door.OpenSound != null)
PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted);
- if(lastState == DoorState.Emagging && TryComp<DoorBoltComponent>(uid, out var doorBoltComponent))
+ if (lastState == DoorState.Emagging && TryComp<DoorBoltComponent>(uid, out var doorBoltComponent))
_bolts.SetBoltsWithAudio(uid, doorBoltComponent, !doorBoltComponent.BoltsDown);
}
+ /// <summary>
+ /// Open or close a door after it has been successfuly pried.
+ /// </summary>
+ private void OnAfterPry(EntityUid uid, DoorComponent door, ref PriedEvent args)
+ {
+ if (door.State == DoorState.Closed)
+ {
+ _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} open");
+ StartOpening(uid, door, args.User);
+ }
+ else if (door.State == DoorState.Open)
+ {
+ _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} closed");
+ StartClosing(uid, door, args.User);
+ }
+ }
+
protected override void CheckDoorBump(DoorComponent component, PhysicsComponent body)
{
var uid = body.Owner;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
+using Content.Shared.Prying.Components;
namespace Content.Server.Doors.Systems
{
base.Initialize();
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
- SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
+ SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
args.Cancel();
}
- private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args)
+ private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
{
var state = CheckPressureAndFire(uid, component);
List<AtmosDirection> directions = new(4);
for (var i = 0; i < Atmospherics.Directions; i++)
{
- var dir = (AtmosDirection) (1 << i);
+ var dir = (AtmosDirection)(1 << i);
if (airtight.AirBlockedDirection.HasFlag(dir))
{
directions.Add(dir);
// TODO: Use the verb.
if (door.State != DoorState.Opening)
- _doors.TryPryDoor(ent, uid, uid, door, out id, force: true);
+ _pryingSystem.TryPry(ent, uid, out id, uid);
component.DoAfterId = id;
return SteeringObstacleStatus.Continuing;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
+using Content.Shared.Prying.Systems;
namespace Content.Server.NPC.Systems;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
+ [Dependency] private readonly PryingSystem _pryingSystem = default!;
private EntityQuery<FixturesComponent> _fixturesQuery;
private EntityQuery<MovementSpeedModifierComponent> _modifierQuery;
private void OnDebugRequest(RequestNPCSteeringDebugEvent msg, EntitySessionEventArgs args)
{
- if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
+ if (!_admin.IsAdmin((IPlayerSession)args.SenderSession))
return;
if (msg.Enabled)
if (targetPoly != null &&
steering.Coordinates.Position.Equals(Vector2.Zero) &&
TryComp<PhysicsComponent>(uid, out var physics) &&
- _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup) physics.CollisionMask))
+ _interaction.InRangeUnobstructed(uid, steering.Coordinates.EntityId, range: 30f, (CollisionGroup)physics.CollisionMask))
{
steering.CurrentPath.Clear();
steering.CurrentPath.Enqueue(targetPoly);
using Content.Shared.Weapons.Melee;
using Content.Shared.Zombies;
using Robust.Shared.Audio;
+using Content.Shared.Prying.Components;
namespace Content.Server.Zombies
{
melee.Damage = dspec;
// humanoid zombies get to pry open doors and shit
- var tool = EnsureComp<ToolComponent>(target);
- tool.SpeedModifier = 0.75f;
- tool.Qualities = new ("Prying");
- tool.UseSound = new SoundPathSpecifier("/Audio/Items/crowbar.ogg");
- Dirty(tool);
+ var pryComp = EnsureComp<PryingComponent>(target);
+ pryComp.SpeedModifier = 0.75f;
+ pryComp.PryPowered = true;
+ pryComp.Force = true;
+
+ Dirty(target, pryComp);
}
Dirty(melee);
else
{
var htn = EnsureComp<HTNComponent>(target);
- htn.RootTask = new HTNCompoundTask() {Task = "SimpleHostileCompound"};
+ htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" };
htn.Blackboard.SetValue(NPCBlackboard.Owner, target);
_npc.WakeNPC(target, htn);
}
}
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
- return (float) (NextStateChange.Value - curTime).TotalSeconds;
+ return (float)(NextStateChange.Value - curTime).TotalSeconds;
}
set
{
public bool ClickOpen = true;
[DataField("openDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
- public int OpenDrawDepth = (int) DrawDepth.DrawDepth.Doors;
+ public int OpenDrawDepth = (int)DrawDepth.DrawDepth.Doors;
[DataField("closedDrawDepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
- public int ClosedDrawDepth = (int) DrawDepth.DrawDepth.Doors;
+ public int ClosedDrawDepth = (int)DrawDepth.DrawDepth.Doors;
}
[Serializable, NetSerializable]
public sealed class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs
{
}
-
- /// <summary>
- /// Raised to determine how long the door's pry time should be modified by.
- /// Multiply PryTimeModifier by the desired amount.
- /// </summary>
- public sealed class DoorGetPryTimeModifierEvent : EntityEventArgs
- {
- public readonly EntityUid User;
- public float PryTimeModifier = 1.0f;
-
- public DoorGetPryTimeModifierEvent(EntityUid user)
- {
- User = user;
- }
- }
-
- /// <summary>
- /// Raised when an attempt to pry open the door is made.
- /// Cancel to stop the door from being pried open.
- /// </summary>
- public sealed class BeforeDoorPryEvent : CancellableEntityEventArgs
- {
- public readonly EntityUid User;
- public readonly EntityUid Tool;
-
- public BeforeDoorPryEvent(EntityUid user, EntityUid tool)
- {
- User = user;
- Tool = tool;
- }
- }
}
using Content.Shared.Doors.Components;
using Content.Shared.Popups;
+using Content.Shared.Prying.Components;
namespace Content.Shared.Doors.Systems;
SubscribeLocalEvent<DoorBoltComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<DoorBoltComponent, BeforeDoorClosedEvent>(OnBeforeDoorClosed);
SubscribeLocalEvent<DoorBoltComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
- SubscribeLocalEvent<DoorBoltComponent, BeforeDoorPryEvent>(OnDoorPry);
+ SubscribeLocalEvent<DoorBoltComponent, BeforePryEvent>(OnDoorPry);
}
- private void OnDoorPry(EntityUid uid, DoorBoltComponent component, BeforeDoorPryEvent args)
+ private void OnDoorPry(EntityUid uid, DoorBoltComponent component, ref BeforePryEvent args)
{
- if (component.BoltsDown)
+ if (component.BoltsDown && !args.Force)
{
- Popup.PopupEntity(Loc.GetString("airlock-component-cannot-pry-is-bolted-message"), uid, args.User);
- args.Cancel();
+ Popup.PopupClient(Loc.GetString("airlock-component-cannot-pry-is-bolted-message"), uid, args.User);
+ args.Cancelled = true;
}
}
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
+using Content.Shared.Prying.Components;
namespace Content.Shared.Doors.Systems;
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] protected readonly SharedPhysicsSystem PhysicsSystem = default!;
- [Dependency] private readonly DamageableSystem _damageableSystem = default!;
- [Dependency] private readonly SharedStunSystem _stunSystem = default!;
+ [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+ [Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] protected readonly TagSystem Tags = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!;
- [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
- [Dependency] private readonly OccluderSystem _occluder = default!;
- [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
+ [Dependency] private readonly OccluderSystem _occluder = default!;
+ [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
/// <summary>
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
SubscribeLocalEvent<DoorComponent, StartCollideEvent>(HandleCollide);
SubscribeLocalEvent<DoorComponent, PreventCollideEvent>(PreventCollision);
+ SubscribeLocalEvent<DoorComponent, GetPryTimeModifierEvent>(OnPryTimeModifier);
+
}
protected virtual void OnComponentInit(EntityUid uid, DoorComponent door, ComponentInit args)
args.Handled = true;
}
+ private void OnPryTimeModifier(EntityUid uid, DoorComponent door, ref GetPryTimeModifierEvent args)
+ {
+ args.BaseTime = door.PryTime;
+ }
+
/// <summary>
/// Update the door state/visuals and play an access denied sound when a user without access interacts with the
/// door.
PlaySound(uid, door.DenySound, AudioParams.Default.WithVolume(-3), user, predicted);
}
+
public bool TryToggleDoor(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
{
if (!Resolve(uid, ref door))
if (door.State == DoorState.Welded)
return false;
- var ev = new BeforeDoorOpenedEvent(){User=user};
+ var ev = new BeforeDoorOpenedEvent() { User = user };
RaiseLocalEvent(uid, ev, false);
if (ev.Cancelled)
return false;
return true;
}
+ /// <summary>
+ /// Immediately start opening a door
+ /// </summary>
+ /// <param name="uid"> The uid of the door</param>
+ /// <param name="door"> The doorcomponent of the door</param>
+ /// <param name="user"> The user (if any) opening the door</param>
+ /// <param name="predicted">Whether the interaction would have been
+ /// predicted. See comments in the PlaySound method on the Server system for details</param>
public virtual void StartOpening(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
{
if (!Resolve(uid, ref door))
return true;
}
+ /// <summary>
+ /// Immediately start closing a door
+ /// </summary>
+ /// <param name="uid"> The uid of the door</param>
+ /// <param name="door"> The doorcomponent of the door</param>
+ /// <param name="user"> The user (if any) opening the door</param>
+ /// <param name="predicted">Whether the interaction would have been
+ /// predicted. See comments in the PlaySound method on the Server system for details</param>
public bool CanClose(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool quiet = true)
{
if (!Resolve(uid, ref door))
//TODO: Make only shutters ignore these objects upon colliding instead of all airlocks
// Excludes Glasslayer for windows, GlassAirlockLayer for windoors, TableLayer for tables
- if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int) CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int) CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int) CollisionGroup.TableLayer)
+ if (!otherPhysics.CanCollide || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.GlassAirlockLayer || otherPhysics.CollisionLayer == (int)CollisionGroup.TableLayer)
continue;
//If the colliding entity is a slippable item ignore it by the airlock
- if (otherPhysics.CollisionLayer == (int) CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int) CollisionGroup.ItemMask)
+ if (otherPhysics.CollisionLayer == (int)CollisionGroup.SlipLayer && otherPhysics.CollisionMask == (int)CollisionGroup.ItemMask)
continue;
if ((physics.CollisionMask & otherPhysics.CollisionLayer) == 0 && (otherPhysics.CollisionMask & physics.CollisionLayer) == 0)
}
}
- protected virtual void CheckDoorBump(DoorComponent component, PhysicsComponent body) {}
+ protected virtual void CheckDoorBump(DoorComponent component, PhysicsComponent body) { }
/// <summary>
/// Makes a door proceed to the next state (if applicable).
#endregion
protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
-
- [Serializable, NetSerializable]
- protected sealed partial class DoorPryDoAfterEvent : SimpleDoAfterEvent
- {
- }
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Prying.Components;
+
+///<summary>
+/// Applied to entities that can be pried open without tools while unpowered
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PryUnpoweredComponent : Component
+{
+}
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Prying.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PryingComponent : Component
+{
+ /// <summary>
+ /// Whether the entity can pry open powered doors
+ /// </summary>
+ [DataField("pryPowered")]
+ public bool PryPowered = false;
+
+ /// <summary>
+ /// Whether the tool can bypass certain restrictions when prying.
+ /// For example door bolts.
+ /// </summary>
+ [DataField("force")]
+ public bool Force = false;
+ /// <summary>
+ /// Modifier on the prying time.
+ /// Lower values result in more time.
+ /// </summary>
+ [DataField("speedModifier")]
+ public float SpeedModifier = 1.0f;
+
+ /// <summary>
+ /// What sound to play when prying is finished.
+ /// </summary>
+ [DataField("useSound")]
+ public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Items/crowbar.ogg");
+
+ /// <summary>
+ /// Whether the entity can currently pry things.
+ /// </summary>
+ [DataField("enabled")]
+ public bool Enabled = true;
+}
+
+/// <summary>
+/// Raised directed on an entity before prying it.
+/// Cancel to stop the entity from being pried open.
+/// </summary>
+[ByRefEvent]
+public record struct BeforePryEvent(EntityUid User, bool PryPowered, bool Force)
+{
+ public readonly EntityUid User = User;
+
+ public readonly bool PryPowered = PryPowered;
+
+ public readonly bool Force = Force;
+
+ public bool Cancelled;
+}
+
+/// <summary>
+/// Raised directed on an entity that has been pried.
+/// </summary>
+[ByRefEvent]
+public readonly record struct PriedEvent(EntityUid User)
+{
+ public readonly EntityUid User = User;
+}
+
+/// <summary>
+/// Raised to determine how long the door's pry time should be modified by.
+/// Multiply PryTimeModifier by the desired amount.
+/// </summary>
+[ByRefEvent]
+public record struct GetPryTimeModifierEvent
+{
+ public readonly EntityUid User;
+ public float PryTimeModifier = 1.0f;
+ public float BaseTime = 5.0f;
+
+ public GetPryTimeModifierEvent(EntityUid user)
+ {
+ User = user;
+ }
+}
+
--- /dev/null
+using Content.Shared.Prying.Components;
+using Content.Shared.Verbs;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Doors.Components;
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Interaction;
+using PryUnpoweredComponent = Content.Shared.Prying.Components.PryUnpoweredComponent;
+
+namespace Content.Shared.Prying.Systems;
+
+/// <summary>
+/// Handles prying of entities (e.g. doors)
+/// </summary>
+public sealed class PryingSystem : EntitySystem
+{
+ [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // Mob prying doors
+ SubscribeLocalEvent<DoorComponent, GetVerbsEvent<AlternativeVerb>>(OnDoorAltVerb);
+ SubscribeLocalEvent<DoorComponent, DoorPryDoAfterEvent>(OnDoAfter);
+ SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(TryPryDoor);
+ }
+
+ private void TryPryDoor(EntityUid uid, DoorComponent comp, InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = TryPry(uid, args.User, out _, args.Used);
+ }
+
+ private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ if (!TryComp<PryingComponent>(args.User, out var tool))
+ return;
+
+ args.Verbs.Add(new AlternativeVerb()
+ {
+ Text = Loc.GetString("door-pry"),
+ Impact = LogImpact.Low,
+ Act = () => TryPry(uid, args.User, out _, args.User),
+ });
+ }
+
+ /// <summary>
+ /// Attempt to pry an entity.
+ /// </summary>
+ public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id, EntityUid tool)
+ {
+ id = null;
+
+ PryingComponent? comp = null;
+ if (!Resolve(tool, ref comp, false))
+ return false;
+
+ if (!comp.Enabled)
+ return false;
+
+ if (!CanPry(target, user, comp))
+ {
+ // If we have reached this point we want the event that caused this
+ // to be marked as handled as a popup would be generated on failure.
+ return true;
+ }
+
+ StartPry(target, user, tool, comp.SpeedModifier, out id);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Try to pry an entity.
+ /// </summary>
+ public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id)
+ {
+ id = null;
+
+ if (!CanPry(target, user))
+ // If we have reached this point we want the event that caused this
+ // to be marked as handled as a popup would be generated on failure.
+ return true;
+
+ return StartPry(target, user, null, 1.0f, out id);
+ }
+
+ private bool CanPry(EntityUid target, EntityUid user, PryingComponent? comp = null)
+ {
+ BeforePryEvent canev;
+
+ if (comp != null)
+ {
+ canev = new BeforePryEvent(user, comp.PryPowered, comp.Force);
+ }
+ else
+ {
+ if (!TryComp<PryUnpoweredComponent>(target, out _))
+ return false;
+ canev = new BeforePryEvent(user, false, false);
+ }
+
+ RaiseLocalEvent(target, ref canev);
+
+ if (canev.Cancelled)
+ return false;
+ return true;
+ }
+
+ private bool StartPry(EntityUid target, EntityUid user, EntityUid? tool, float toolModifier, [NotNullWhen(true)] out DoAfterId? id)
+ {
+ var modEv = new GetPryTimeModifierEvent(user);
+
+ RaiseLocalEvent(target, ref modEv);
+ var doAfterArgs = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(modEv.BaseTime * modEv.PryTimeModifier / toolModifier), new DoorPryDoAfterEvent(), target, target, tool)
+ {
+ BreakOnDamage = true,
+ BreakOnUserMove = true,
+ };
+
+ if (tool != null)
+ {
+ _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is using {ToPrettyString(tool.Value)} to pry {ToPrettyString(target)}");
+ }
+ else
+ {
+ _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user)} is prying {ToPrettyString(target)}");
+ }
+ return _doAfterSystem.TryStartDoAfter(doAfterArgs, out id);
+ }
+
+ private void OnDoAfter(EntityUid uid, DoorComponent door, DoorPryDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+ if (args.Target is null)
+ return;
+
+ PryingComponent? comp = null;
+
+ if (args.Used != null && Resolve(args.Used.Value, ref comp))
+ _audioSystem.PlayPredicted(comp.UseSound, args.Used.Value, args.User);
+
+ var ev = new PriedEvent(args.User);
+ RaiseLocalEvent(uid, ref ev);
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class DoorPryDoAfterEvent : SimpleDoAfterEvent
+{
+}
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
using Robust.Shared.GameStates;
+using Content.Shared.Prying.Components;
namespace Content.Shared.Tools;
private void OnMultipleToolStartup(EntityUid uid, MultipleToolComponent multiple, ComponentStartup args)
{
// Only set the multiple tool if we have a tool component.
- if(EntityManager.TryGetComponent(uid, out ToolComponent? tool))
+ if (EntityManager.TryGetComponent(uid, out ToolComponent? tool))
SetMultipleTool(uid, multiple, tool);
}
if (multiple.Entries.Length == 0)
return false;
- multiple.CurrentEntry = (uint) ((multiple.CurrentEntry + 1) % multiple.Entries.Length);
+ multiple.CurrentEntry = (uint)((multiple.CurrentEntry + 1) % multiple.Entries.Length);
SetMultipleTool(uid, multiple, playSound: true, user: user);
return true;
tool.UseSound = current.Sound;
tool.Qualities = current.Behavior;
+ // TODO: Replace this with a better solution later
+ if (TryComp<PryingComponent>(uid, out var pcomp))
+ {
+ if (current.Behavior.Contains("Prying"))
+ {
+ pcomp.Enabled = true;
+ }
+ else
+ {
+ pcomp.Enabled = false;
+ }
+ }
+
if (playSound && current.ChangeSound != null)
_audioSystem.PlayPredicted(current.ChangeSound, uid, user);
- type: Tool
qualities:
- Prying
+ - type: Prying
- type: MultipleTool
statusShowBehavior: true
entries:
speed: 1.5
qualities:
- Prying
+ - type: Prying
+ pryPowered: !type:Bool
+ true
+ force: !type:Bool
+ true
useSound:
path: /Audio/Items/crowbar.ogg
- type: Reactive
-- type: entity
+- type: entity
name: haycutters
parent: BaseItem
id: Haycutters
path: /Audio/Items/crowbar.ogg
speed: 0.05
- type: TilePrying
+ - type: Prying
- type: entity
name: mooltitool
- Prying
speed: 1.5
useSound: /Audio/Items/jaws_pry.ogg
+ - type: Prying
+ pryPowered: !type:Bool
+ true
+ force: !type:Bool
+ true
+ useSound: /Audio/Items/jaws_pry.ogg
- type: ToolForcePowered
- type: MultipleTool
statusShowBehavior: true
- type: MeleeWeapon
damage:
types:
- Blunt: 14
\ No newline at end of file
+ Blunt: 14
Steel: 100
- type: StaticPrice
price: 22
+ - type: Prying
- type: entity
parent: Crowbar
- type: Tool\r
qualities:\r
- Prying\r
+ - type: Prying\r
- Prying
- type: TilePrying
advanced: true
+ - type: Prying
- type: entity
id: FireAxeFlaming
- type: Tool
qualities:
- Prying
+ - type: Prying
- type: entity
name: crusher dagger
--- /dev/null
+- type: entity
+ parent: AirlockExternal
+ id: AirlockExternalEasyPry
+ suffix: External, EasyPry
+ description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockExternalGlass
+ id: AirlockExternalGlassEasyPry
+ suffix: External, Glass, EasyPry
+ description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockGlassShuttle
+ id: AirlockGlassShuttleEasyPry
+ suffix: EasyPry, Docking
+ description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockShuttle
+ id: AirlockShuttleEasyPry
+ suffix: EasyPry, Docking
+ description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockExternalLocked
+ id: AirlockExternalEasyPryLocked
+ suffix: External, EasyPry, Locked
+ description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockExternalGlassLocked
+ id: AirlockExternalGlassEasyPryLocked
+ suffix: External, Glass, EasyPry, Locked
+ description: It opens, it closes, it might crush you, and there might be only space behind it. Has to be manually activated. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockExternalGlassShuttleLocked
+ id: AirlockGlassShuttleEasyPryLocked
+ suffix: EasyPry, Docking, Locked
+ description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered
+
+- type: entity
+ parent: AirlockExternalShuttleLocked
+ id: AirlockShuttleEasyPryLocked
+ suffix: EasyPry, Docking, Locked
+ description: Necessary for connecting two space craft together. Has a valve labelled "TURN TO OPEN"
+ components:
+ - type: PryUnpowered