Feature/door remote radial (#36378)
* it works! kinda * so it works now * minor cleanup * central button now is useful too * more cleanup * minor cleanup * more cleanup * refactor: migrated code from toolbox (as it was rejected as too specific) * feat: moved border drawing for radial menu into RadialMenuTextureButton. Radial menu position setting into was moved to OverrideArrange to not being called on every frame * refactor: major reworks! * renamed DrawBagleSector to DrawAnnulusSector * Remove strange indexing * Regularize math * refactor: re-orienting segment elements to be Y-mirrored * refactor: extracted radial menu radius multiplier property, changed color pallet for radial menu button * refactor: removed icon backgrounds on textures used in current radial menu buttons with sectors, RadialContainer Radius renamed and now actually changed control radius. * refactor: in RadialMenuTextureButtonWithSector all sector colors are converted to and from sRGB in property getter-setters * refactor: renamed srgb to include Srgb suffix so devs gonna see that its srgb clearly * fix: enabled any functional keys pressed when pushing radial menu buttons * fix: radial menu sector now scales with UIScale * fix: accept only one event when clicking on radial menu ContextualButton * fix: now radial menu buttons accepts only click/alt-click, now clicks outside menu closes menu always * feat: simple radial menu prototype for easier creation * refactor: cleanup, restored emote filtering, button models now have class hierarchy * refactor: remove usage of closure from 'outside code' * refactor: remove non existing type from UiControlTest * refactor: remove unused using * refactor: revert ability to declare radial menu layers in xaml, scale 32px sprites using scale in radial menu * refactor: whitespaces * feat: now door remote have some kind of ui to switch mode * refactor: subscribe for dispose on existing radial menus * feat: now simple radial menu button models can have custom color for each sector background (and hover background color). Also added OpenOverMouseScreenPosition inside SimpleRadialMenu * fix: AI door menu now can be closed by verb if it gets unpowered * refactor: simplify code for DoorRemoteBoundUserInterface * fix open/close mode sprite * remove broken merge changes * refactor: changed DoorRemoteSystem to be fully in shared * refactor: localize DoorRemoteBoundUserInterface * refactor: fix multiple invocation for TryToggleDoor inside DoorRemoteSystem on prediction * refactor: extracted sprites and loc strings into prototype for cleaner code. Currently selected mode now have different background. * refactor: changed hover selected color to recommmended * refactor: reuse stylenano colors! * review * refactor: remove StyleNano reference * refactor: revert removal of item status for door remote * refactor: fix status control misprediction * refactor: remove invalid comments, rename client DoorRemoteSystem comp after handle method * refactor: fix DoorRemoteStatusControl not displaying status on entity pickup --------- Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets.Palette;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Changeling.Components;
|
using Content.Shared.Changeling.Components;
|
||||||
using Content.Shared.Changeling.Systems;
|
using Content.Shared.Changeling.Systems;
|
||||||
@@ -11,8 +11,8 @@ namespace Content.Client.Changeling.UI;
|
|||||||
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
{
|
{
|
||||||
private SimpleRadialMenu? _menu;
|
private SimpleRadialMenu? _menu;
|
||||||
private static readonly Color SelectedOptionBackground = StyleNano.ButtonColorGoodDefault.WithAlpha(128);
|
private static readonly Color SelectedOptionBackground = Palettes.Green.Element.WithAlpha(128);
|
||||||
private static readonly Color SelectedOptionHoverBackground = StyleNano.ButtonColorGoodHovered.WithAlpha(128);
|
private static readonly Color SelectedOptionHoverBackground = Palettes.Green.HoveredElement.WithAlpha(128);
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Content.Client.Remote.UI;
|
|
||||||
using Content.Client.Items;
|
using Content.Client.Items;
|
||||||
using Content.Shared.Remotes.EntitySystems;
|
using Content.Client.Remotes.UI;
|
||||||
using Content.Shared.Remotes.Components;
|
using Content.Shared.Remotes.Components;
|
||||||
|
using Content.Shared.Remotes.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Client.Remotes.EntitySystems;
|
namespace Content.Client.Remotes.Systems;
|
||||||
|
|
||||||
public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
|
public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
|
||||||
{
|
{
|
||||||
@@ -12,5 +12,11 @@ public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
Subs.ItemStatus<DoorRemoteComponent>(ent => new DoorRemoteStatusControl(ent));
|
Subs.ItemStatus<DoorRemoteComponent>(ent => new DoorRemoteStatusControl(ent));
|
||||||
|
SubscribeLocalEvent<DoorRemoteComponent, AfterAutoHandleStateEvent>(OnAutoHandleState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAutoHandleState(Entity<DoorRemoteComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||||
|
{
|
||||||
|
ent.Comp.IsStatusControlUpdateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
63
Content.Client/Remotes/UI/DoorRemoteBoundUserInterface.cs
Normal file
63
Content.Client/Remotes/UI/DoorRemoteBoundUserInterface.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using Content.Client.Stylesheets.Palette;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Remotes.Components;
|
||||||
|
using Content.Shared.Remotes.EntitySystems;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.Remotes.UI;
|
||||||
|
|
||||||
|
public sealed class DoorRemoteBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
|
{
|
||||||
|
private static readonly Color SelectedOptionColor = Palettes.Green.Element.WithAlpha(128);
|
||||||
|
private static readonly Color SelectedOptionHoverColor = Palettes.Green.HoveredElement.WithAlpha(128);
|
||||||
|
|
||||||
|
private SimpleRadialMenu? _menu;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
if (!EntMan.TryGetComponent<DoorRemoteComponent>(Owner, out var remote))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu = this.CreateWindow<SimpleRadialMenu>();
|
||||||
|
var models = CreateButtons(remote.Mode, remote.Options);
|
||||||
|
_menu.SetButtons(models);
|
||||||
|
|
||||||
|
_menu.OpenOverMouseScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<RadialMenuOptionBase> CreateButtons(OperatingMode selectedMode, List<DoorRemoteModeInfo> modeOptions)
|
||||||
|
{
|
||||||
|
var options = new List<RadialMenuOptionBase>();
|
||||||
|
for (var i = 0; i < modeOptions.Count; i++)
|
||||||
|
{
|
||||||
|
var modeOption = modeOptions[i];
|
||||||
|
|
||||||
|
Color? optionCustomColor = null;
|
||||||
|
Color? optionHoverCustomColor = null;
|
||||||
|
if (modeOption.Mode == selectedMode)
|
||||||
|
{
|
||||||
|
optionCustomColor = SelectedOptionColor;
|
||||||
|
optionHoverCustomColor = SelectedOptionHoverColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
var option = new RadialMenuActionOption<OperatingMode>(HandleRadialMenuClick, modeOption.Mode)
|
||||||
|
{
|
||||||
|
IconSpecifier = RadialMenuIconSpecifier.With(modeOption.Icon),
|
||||||
|
ToolTip = Loc.GetString(modeOption.Tooltip),
|
||||||
|
BackgroundColor = optionCustomColor,
|
||||||
|
HoverBackgroundColor = optionHoverCustomColor
|
||||||
|
};
|
||||||
|
options.Add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRadialMenuClick(OperatingMode mode)
|
||||||
|
{
|
||||||
|
var msg = new DoorRemoteModeChangeMessage { Mode = mode };
|
||||||
|
SendPredictedMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +1,36 @@
|
|||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.Remotes.Components;
|
using Content.Shared.Remotes.Components;
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.Remote.UI;
|
namespace Content.Client.Remotes.UI;
|
||||||
|
|
||||||
public sealed class DoorRemoteStatusControl : Control
|
public sealed class DoorRemoteStatusControl(Entity<DoorRemoteComponent> ent) : Control
|
||||||
{
|
{
|
||||||
private readonly Entity<DoorRemoteComponent> _entity;
|
private RichTextLabel? _label;
|
||||||
private readonly RichTextLabel _label;
|
|
||||||
|
|
||||||
// set to toggle bolts initially just so that it updates on first pickup of remote
|
|
||||||
private OperatingMode PrevOperatingMode = OperatingMode.placeholderForUiUpdates;
|
|
||||||
|
|
||||||
public DoorRemoteStatusControl(Entity<DoorRemoteComponent> entity)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
|
|
||||||
AddChild(_label);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
// only updates the UI if any of the details are different than they previously were
|
if (_label == null)
|
||||||
if (PrevOperatingMode == _entity.Comp.Mode)
|
{
|
||||||
|
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
|
||||||
|
AddChild(_label);
|
||||||
|
}
|
||||||
|
else if (!ent.Comp.IsStatusControlUpdateRequired)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PrevOperatingMode = _entity.Comp.Mode;
|
UpdateLabel(_label);
|
||||||
|
|
||||||
// Update current volume and injector state
|
ent.Comp.IsStatusControlUpdateRequired = false;
|
||||||
var modeStringLocalized = Loc.GetString(_entity.Comp.Mode switch
|
}
|
||||||
|
|
||||||
|
private void UpdateLabel(RichTextLabel label)
|
||||||
|
{
|
||||||
|
var modeStringLocalized = Loc.GetString(ent.Comp.Mode switch
|
||||||
{
|
{
|
||||||
OperatingMode.OpenClose => "door-remote-open-close-text",
|
OperatingMode.OpenClose => "door-remote-open-close-text",
|
||||||
OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
|
OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
|
||||||
@@ -41,6 +38,6 @@ public sealed class DoorRemoteStatusControl : Control
|
|||||||
_ => "door-remote-invalid-text"
|
_ => "door-remote-invalid-text"
|
||||||
});
|
});
|
||||||
|
|
||||||
_label.SetMarkup(Loc.GetString("door-remote-mode-label", ("modeString", modeStringLocalized)));
|
label.SetMarkup(Loc.GetString("door-remote-mode-label", ("modeString", modeStringLocalized)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,5 @@
|
|||||||
using Content.Server.Administration.Logs;
|
|
||||||
using Content.Server.Doors.Systems;
|
|
||||||
using Content.Server.Power.EntitySystems;
|
|
||||||
using Content.Shared.Access.Components;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.Doors.Components;
|
|
||||||
using Content.Shared.Examine;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Remotes.Components;
|
|
||||||
using Content.Shared.Remotes.EntitySystems;
|
using Content.Shared.Remotes.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Shared.Remotes
|
namespace Content.Server.Remotes;
|
||||||
{
|
|
||||||
public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
||||||
[Dependency] private readonly AirlockSystem _airlock = default!;
|
|
||||||
[Dependency] private readonly DoorSystem _doorSystem = default!;
|
|
||||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public sealed class DoorRemoteSystem : SharedDoorRemoteSystem;
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<DoorRemoteComponent, BeforeRangedInteractEvent>(OnBeforeInteract);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBeforeInteract(Entity<DoorRemoteComponent> entity, ref BeforeRangedInteractEvent args)
|
|
||||||
{
|
|
||||||
bool isAirlock = TryComp<AirlockComponent>(args.Target, out var airlockComp);
|
|
||||||
|
|
||||||
if (args.Handled
|
|
||||||
|| args.Target == null
|
|
||||||
|| !TryComp<DoorComponent>(args.Target, out var doorComp) // If it isn't a door we don't use it
|
|
||||||
// Only able to control doors if they are within your vision and within your max range.
|
|
||||||
// Not affected by mobs or machines anymore.
|
|
||||||
|| !_examine.InRangeUnOccluded(args.User,
|
|
||||||
args.Target.Value,
|
|
||||||
SharedInteractionSystem.MaxRaycastRange,
|
|
||||||
null))
|
|
||||||
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Handled = true;
|
|
||||||
|
|
||||||
if (!this.IsPowered(args.Target.Value, EntityManager))
|
|
||||||
{
|
|
||||||
Popup.PopupEntity(Loc.GetString("door-remote-no-power"), args.User, args.User);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessTarget = args.Used;
|
|
||||||
// This covers the accesses the REMOTE has, and is not effected by the user's ID card.
|
|
||||||
if (entity.Comp.IncludeUserAccess) // Allows some door remotes to inherit the user's access.
|
|
||||||
{
|
|
||||||
accessTarget = args.User;
|
|
||||||
// This covers the accesses the USER has, which always includes the remote's access since holding a remote acts like holding an ID card.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryComp<AccessReaderComponent>(args.Target, out var accessComponent)
|
|
||||||
&& !_doorSystem.HasAccess(args.Target.Value, accessTarget, doorComp, accessComponent))
|
|
||||||
{
|
|
||||||
if (isAirlock)
|
|
||||||
_doorSystem.Deny(args.Target.Value, doorComp, accessTarget);
|
|
||||||
Popup.PopupEntity(Loc.GetString("door-remote-denied"), args.User, args.User);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (entity.Comp.Mode)
|
|
||||||
{
|
|
||||||
case OperatingMode.OpenClose:
|
|
||||||
if (_doorSystem.TryToggleDoor(args.Target.Value, doorComp, accessTarget))
|
|
||||||
_adminLogger.Add(LogType.Action,
|
|
||||||
LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)}: {doorComp.State}");
|
|
||||||
break;
|
|
||||||
case OperatingMode.ToggleBolts:
|
|
||||||
if (TryComp<DoorBoltComponent>(args.Target, out var boltsComp))
|
|
||||||
{
|
|
||||||
if (!boltsComp.BoltWireCut)
|
|
||||||
{
|
|
||||||
_doorSystem.SetBoltsDown((args.Target.Value, boltsComp), !boltsComp.BoltsDown, accessTarget);
|
|
||||||
_adminLogger.Add(LogType.Action,
|
|
||||||
LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to {(boltsComp.BoltsDown ? "" : "un")}bolt it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OperatingMode.ToggleEmergencyAccess:
|
|
||||||
if (airlockComp != null)
|
|
||||||
{
|
|
||||||
_airlock.SetEmergencyAccess((args.Target.Value, airlockComp), !airlockComp.EmergencyAccess);
|
|
||||||
_adminLogger.Add(LogType.Action,
|
|
||||||
LogImpact.Medium,
|
|
||||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"{nameof(DoorRemoteComponent)} had invalid mode {entity.Comp.Mode}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,26 +1,74 @@
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Remotes.Components;
|
namespace Content.Shared.Remotes.Components;
|
||||||
|
|
||||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
/// <summary>
|
||||||
|
/// Component for door remote devices, that allow you to control doors from a distance.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||||
public sealed partial class DoorRemoteComponent : Component
|
public sealed partial class DoorRemoteComponent : Component
|
||||||
{
|
{
|
||||||
[AutoNetworkedField]
|
/// <summary>
|
||||||
[DataField]
|
/// Currently selected mode. The mode dictates what device would do upon
|
||||||
|
/// interaction with door.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
public OperatingMode Mode = OperatingMode.OpenClose;
|
public OperatingMode Mode = OperatingMode.OpenClose;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the remote allow the user to manipulate doors that they have access to, even if the remote itself does not?
|
/// Modes with metadata that could be displayed in the device mode change menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AutoNetworkedField]
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool IncludeUserAccess = false;
|
public List<DoorRemoteModeInfo> Options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the remote allow the user to control doors that they have access to, even if the remote itself does not?
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool IncludeUserAccess;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client-side only field for checking if StatusControl requires update.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// StatusControl is updated inside loop and cannot understand
|
||||||
|
/// when state is of component it looks for is restored, thus mispredicting. To avoid that,
|
||||||
|
/// client-side system basically controls behaviour of StatusControl updates using this field.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsStatusControlUpdateRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote door device mode with data that is required for menu display.
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition]
|
||||||
|
public sealed partial class DoorRemoteModeInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Icon that should represent the option in the radial menu.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public SpriteSpecifier Icon = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tooltip describing the option in the radial menu.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public LocId Tooltip;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mode option.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public OperatingMode Mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
public enum OperatingMode : byte
|
public enum OperatingMode : byte
|
||||||
{
|
{
|
||||||
OpenClose,
|
OpenClose,
|
||||||
ToggleBolts,
|
ToggleBolts,
|
||||||
ToggleEmergencyAccess,
|
ToggleEmergencyAccess
|
||||||
placeholderForUiUpdates
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,134 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Power.EntitySystems;
|
||||||
using Content.Shared.Remotes.Components;
|
using Content.Shared.Remotes.Components;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Remotes.EntitySystems;
|
namespace Content.Shared.Remotes.EntitySystems;
|
||||||
|
|
||||||
public abstract class SharedDoorRemoteSystem : EntitySystem
|
public abstract class SharedDoorRemoteSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
[Dependency] private readonly SharedAirlockSystem _airlock = default!;
|
||||||
|
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||||
|
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||||
|
[Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<DoorRemoteComponent, UseInHandEvent>(OnInHandActivation);
|
SubscribeLocalEvent<DoorRemoteComponent, DoorRemoteModeChangeMessage>(OnDoorRemoteModeChange);
|
||||||
|
SubscribeLocalEvent<DoorRemoteComponent, BeforeRangedInteractEvent>(OnBeforeInteract);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInHandActivation(Entity<DoorRemoteComponent> entity, ref UseInHandEvent args)
|
private void OnDoorRemoteModeChange(Entity<DoorRemoteComponent> ent, ref DoorRemoteModeChangeMessage args)
|
||||||
{
|
{
|
||||||
string switchMessageId;
|
ent.Comp.Mode = args.Mode;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeInteract(Entity<DoorRemoteComponent> entity, ref BeforeRangedInteractEvent args)
|
||||||
|
{
|
||||||
|
if (!Timing.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var isAirlock = TryComp<AirlockComponent>(args.Target, out var airlockComp);
|
||||||
|
|
||||||
|
if (args.Handled
|
||||||
|
|| args.Target == null
|
||||||
|
|| !TryComp<DoorComponent>(args.Target, out var doorComp) // If it isn't a door we don't use it
|
||||||
|
// Only able to control doors if they are within your vision and within your max range.
|
||||||
|
// Not affected by mobs or machines anymore.
|
||||||
|
|| !_examine.InRangeUnOccluded(args.User,
|
||||||
|
args.Target.Value,
|
||||||
|
SharedInteractionSystem.MaxRaycastRange,
|
||||||
|
null))
|
||||||
|
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
if (!_powerReceiver.IsPowered(args.Target.Value))
|
||||||
|
{
|
||||||
|
_popup.PopupClient(Loc.GetString("door-remote-no-power"), args.User, args.User);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessTarget = args.Used;
|
||||||
|
// This covers the accesses the REMOTE has, and is not effected by the user's ID card.
|
||||||
|
if (entity.Comp.IncludeUserAccess) // Allows some door remotes to inherit the user's access.
|
||||||
|
{
|
||||||
|
accessTarget = args.User;
|
||||||
|
// This covers the accesses the USER has, which always includes the remote's access since holding a remote acts like holding an ID card.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<AccessReaderComponent>(args.Target, out var accessComponent)
|
||||||
|
&& !_doorSystem.HasAccess(args.Target.Value, accessTarget, doorComp, accessComponent))
|
||||||
|
{
|
||||||
|
if (isAirlock)
|
||||||
|
_doorSystem.Deny(args.Target.Value, doorComp, user: args.User, predicted: true);
|
||||||
|
|
||||||
|
_popup.PopupClient(Loc.GetString("door-remote-denied"), args.User, args.User);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (entity.Comp.Mode)
|
switch (entity.Comp.Mode)
|
||||||
{
|
{
|
||||||
case OperatingMode.OpenClose:
|
case OperatingMode.OpenClose:
|
||||||
entity.Comp.Mode = OperatingMode.ToggleBolts;
|
if (_doorSystem.TryToggleDoor(args.Target.Value, doorComp, user: args.User, predicted: true))
|
||||||
switchMessageId = "door-remote-switch-state-toggle-bolts";
|
_adminLogger.Add(LogType.Action,
|
||||||
|
LogImpact.Medium,
|
||||||
|
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)}: {doorComp.State}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Skip toggle bolts mode and move on from there (to emergency access)
|
|
||||||
case OperatingMode.ToggleBolts:
|
case OperatingMode.ToggleBolts:
|
||||||
entity.Comp.Mode = OperatingMode.ToggleEmergencyAccess;
|
if (TryComp<DoorBoltComponent>(args.Target, out var boltsComp))
|
||||||
switchMessageId = "door-remote-switch-state-toggle-emergency-access";
|
{
|
||||||
break;
|
if (!boltsComp.BoltWireCut)
|
||||||
|
{
|
||||||
|
_doorSystem.SetBoltsDown((args.Target.Value, boltsComp), !boltsComp.BoltsDown, user: args.User, predicted: true);
|
||||||
|
_adminLogger.Add(LogType.Action,
|
||||||
|
LogImpact.Medium,
|
||||||
|
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to {(boltsComp.BoltsDown ? "" : "un")}bolt it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip ToggleEmergencyAccess mode and move on from there (to door toggle)
|
break;
|
||||||
case OperatingMode.ToggleEmergencyAccess:
|
case OperatingMode.ToggleEmergencyAccess:
|
||||||
entity.Comp.Mode = OperatingMode.OpenClose;
|
if (airlockComp != null)
|
||||||
switchMessageId = "door-remote-switch-state-open-close";
|
{
|
||||||
|
_airlock.SetEmergencyAccess((args.Target.Value, airlockComp), !airlockComp.EmergencyAccess, user: args.User, predicted: true);
|
||||||
|
_adminLogger.Add(LogType.Action,
|
||||||
|
LogImpact.Medium,
|
||||||
|
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}");
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"{nameof(DoorRemoteComponent)} had invalid mode {entity.Comp.Mode}");
|
$"{nameof(DoorRemoteComponent)} had invalid mode {entity.Comp.Mode}");
|
||||||
}
|
}
|
||||||
Dirty(entity);
|
|
||||||
Popup.PopupClient(Loc.GetString(switchMessageId), entity, args.User);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class DoorRemoteModeChangeMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public OperatingMode Mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum DoorRemoteUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,31 @@
|
|||||||
storedRotation: -90
|
storedRotation: -90
|
||||||
- type: Access
|
- type: Access
|
||||||
- type: DoorRemote
|
- type: DoorRemote
|
||||||
|
options:
|
||||||
|
- mode: OpenClose
|
||||||
|
tooltip: door-remote-open-close-text
|
||||||
|
icon:
|
||||||
|
sprite: /Textures/Structures/Doors/Airlocks/Standard/basic.rsi
|
||||||
|
state: assembly
|
||||||
|
- mode: ToggleBolts
|
||||||
|
tooltip: door-remote-toggle-bolt-text
|
||||||
|
icon:
|
||||||
|
sprite: /Textures/Interface/Actions/actions_ai.rsi
|
||||||
|
state: bolt_door
|
||||||
|
- mode: ToggleEmergencyAccess
|
||||||
|
tooltip: door-remote-emergency-access-text
|
||||||
|
icon:
|
||||||
|
sprite: /Textures/Interface/Actions/actions_ai.rsi
|
||||||
|
state: emergency_on
|
||||||
- type: StealTarget
|
- type: StealTarget
|
||||||
stealGroup: DoorRemote
|
stealGroup: DoorRemote
|
||||||
|
- type: ActivatableUI
|
||||||
|
inHandsOnly: true
|
||||||
|
key: enum.DoorRemoteUiKey.Key
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
enum.DoorRemoteUiKey.Key:
|
||||||
|
type: DoorRemoteBoundUserInterface
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: [DoorRemoteDefault, BaseCommandContraband]
|
parent: [DoorRemoteDefault, BaseCommandContraband]
|
||||||
|
|||||||
Reference in New Issue
Block a user