Code lock console (#3060)

Co-authored-by: KaiserMaus <kaiser.ratte@gmail.com>
This commit is contained in:
Hero010h 2025-12-25 19:47:18 +03:00 committed by GitHub
parent 0edc918beb
commit 78a63ae4b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 565 additions and 0 deletions

View file

@ -0,0 +1,62 @@
using Content.Shared._Sunrise.CodeConsole;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._Sunrise.CodeConsole;
[UsedImplicitly]
public sealed class CodeConsoleBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private CodeConsoleMenu? _menu;
public CodeConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<CodeConsoleMenu>();
_menu.OnKeypadButtonPressed += i =>
{
SendMessage(new CodeConsoleKeypadMessage(i));
};
_menu.OnEnterButtonPressed += () =>
{
SendMessage(new CodeConsoleKeypadEnterMessage());
};
_menu.OnClearButtonPressed += () =>
{
SendMessage(new CodeConsoleKeypadClearMessage());
};
_menu.ActivateButton.OnPressed += _ =>
{
SendMessage(new CodeConsoleActivateButtonMessage());
};
_menu.LockButton.OnPressed += _ =>
{
SendMessage(new CodeConsoleLockButtonMessage());
};
_menu.OnClose += Close;
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_menu == null)
return;
switch (state)
{
case CodeConsoleUiState msg:
_menu.UpdateState(msg);
break;
}
}
}

View file

@ -0,0 +1,42 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'code-console-user-interface-title'}"
MinSize="280 256"
SetSize="280 256">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Status label -->
<PanelContainer Margin="0 0 0 5">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#001c00" />
</PanelContainer.PanelOverride>
<Label Name="StatusLabel"/>
</PanelContainer>
<!-- Code label -->
<PanelContainer Margin="0 0 0 5">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#001c00" />
</PanelContainer.PanelOverride>
<Label Name="CodeEnteringLabel"/>
</PanelContainer>
<BoxContainer Orientation="Horizontal" >
<GridContainer Columns="3"
Name="KeypadGrid">
<!-- Keypad is filled by code -->
</GridContainer>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
Margin="5 0">
<Button Name="ActivateButton"
Text="{Loc 'code-console-user-interface-activate-button'}"
Margin="0 0 0 5"
Access="Public"/>
<Button Name="LockButton"
Text="{Loc 'code-console-user-interface-lock-button'}"
Access="Public"
StyleClasses="Caution"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View file

@ -0,0 +1,83 @@
using Content.Shared._Sunrise.CodeConsole;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Sunrise.CodeConsole;
[GenerateTypedNameReferences]
public sealed partial class CodeConsoleMenu : DefaultWindow
{
public event Action<int>? OnKeypadButtonPressed;
public event Action? OnClearButtonPressed;
public event Action? OnEnterButtonPressed;
public CodeConsoleMenu()
{
RobustXamlLoader.Load(this);
FillKeypadGrid();
}
private void FillKeypadGrid()
{
// add 3 rows of keypad buttons (1-9)
for (var i = 1; i <= 9; i++)
{
AddKeypadButton(i);
}
// clear button
var clearBtn = new Button()
{
Text = "C"
};
clearBtn.OnPressed += _ => OnClearButtonPressed?.Invoke();
KeypadGrid.AddChild(clearBtn);
// zero button
AddKeypadButton(0);
// enter button
var enterBtn = new Button()
{
Text = "E"
};
enterBtn.OnPressed += _ => OnEnterButtonPressed?.Invoke();
KeypadGrid.AddChild(enterBtn);
}
private void AddKeypadButton(int i)
{
var btn = new Button()
{
Text = i.ToString()
};
btn.OnPressed += _ => OnKeypadButtonPressed?.Invoke(i);
KeypadGrid.AddChild(btn);
}
public void UpdateState(CodeConsoleUiState state)
{
if (state.IsLocked)
StatusLabel.Text = Loc.GetString("code-console-interface-code-waiting");
else
StatusLabel.Text = Loc.GetString("code-console-interface-opened");
CodeEnteringLabel.Text = Loc.GetString("nuke-user-interface-second-status-current-code",
("code", VisualizeCode(state.EnteredCodeLength, state.MaxCodeLength)));
ActivateButton.Disabled = state.IsLocked;
LockButton.Disabled = state.IsLocked;
}
private static string VisualizeCode(int codeLength, int maxLength)
{
var code = new string('*', codeLength);
var blanksCount = maxLength - codeLength;
var blanks = new string('_', blanksCount);
return code + blanks;
}
}

View file

@ -0,0 +1,220 @@
using Content.Server.DeviceLinking.Systems;
using Content.Shared._Sunrise.CodeConsole;
using Content.Shared.Audio;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server._Sunrise.CodeConsole;
public sealed class CodeConsoleSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CodeConsoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CodeConsoleComponent, CodeConsoleActivateButtonMessage>(OnActivateButtonPressed);
SubscribeLocalEvent<CodeConsoleComponent, CodeConsoleLockButtonMessage>(OnLockButtonPressed);
SubscribeLocalEvent<CodeConsoleComponent, CodeConsoleKeypadMessage>(OnKeypadButtonPressed);
SubscribeLocalEvent<CodeConsoleComponent, CodeConsoleKeypadClearMessage>(OnClearButtonPressed);
SubscribeLocalEvent<CodeConsoleComponent, CodeConsoleKeypadEnterMessage>(OnEnterButtonPressed);
SubscribeLocalEvent<CodeConsoleComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<CodeConsoleComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
}
private void OnMapInit(Entity<CodeConsoleComponent> ent, ref MapInitEvent args)
{
if (string.IsNullOrWhiteSpace(ent.Comp.Code))
{
if (ent.Comp.CodeLength <= 0 || ent.Comp.CodeLength > 16)
ent.Comp.CodeLength = 16;
ent.Comp.Code = GetRandomCode(ent.Comp.CodeLength);
}
UpdateUserInterface(ent);
}
private void OnActivateButtonPressed(Entity<CodeConsoleComponent> ent, ref CodeConsoleActivateButtonMessage args)
{
_audio.PlayPvs(ent.Comp.KeypadPressSound, ent.Owner);
if (ent.Comp.IsLocked)
return;
_deviceLink.InvokePort(ent.Owner, ent.Comp.ActivatePort);
}
private void OnLockButtonPressed(Entity<CodeConsoleComponent> ent, ref CodeConsoleLockButtonMessage args)
{
if (ent.Comp.IsLocked)
return;
UpdateStatus(ent);
UpdateUserInterface(ent);
}
private void OnKeypadButtonPressed(Entity<CodeConsoleComponent> ent, ref CodeConsoleKeypadMessage args)
{
if (args.Value < 0 || args.Value > 9)
return;
PlayKeypadSound(ent, args.Value);
if (!ent.Comp.IsLocked)
return;
if (ent.Comp.EnteredCode.Length >= ent.Comp.CodeLength)
return;
ent.Comp.EnteredCode += args.Value.ToString();
UpdateUserInterface(ent);
}
private void OnClearButtonPressed(Entity<CodeConsoleComponent> ent, ref CodeConsoleKeypadClearMessage args)
{
_audio.PlayPvs(ent.Comp.KeypadPressSound, ent.Owner);
if (!ent.Comp.IsLocked)
return;
ent.Comp.EnteredCode = "";
UpdateUserInterface(ent);
}
private void OnEnterButtonPressed(Entity<CodeConsoleComponent> ent, ref CodeConsoleKeypadEnterMessage args)
{
if (!ent.Comp.IsLocked)
{
_audio.PlayPvs(ent.Comp.KeypadPressSound, ent.Owner);
return;
}
UpdateStatus(ent);
UpdateUserInterface(ent);
}
private void OnInteractUsing(Entity<CodeConsoleComponent> ent, ref InteractUsingEvent args)
{
if (TryComp<NetworkConfiguratorComponent>(args.Used, out _) && ent.Comp.IsSealed)
{
args.Handled = true;
}
}
private void OnGetVerbs(Entity<CodeConsoleComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (ent.Comp.IsSealed)
return;
if (!args.CanInteract || !args.CanAccess)
return;
var verb = new AlternativeVerb()
{
Text = Loc.GetString("code-console-verb-seal-name"),
Message = Loc.GetString("code-console-verb-seal-desc"),
Icon = new SpriteSpecifier.Texture(
new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")),
Act = () => { ent.Comp.IsSealed = true; }
};
args.Verbs.Add(verb);
}
private void UpdateStatus(Entity<CodeConsoleComponent> ent)
{
if (ent.Comp.IsLocked)
{
if (ent.Comp.EnteredCode == ent.Comp.Code)
{
ent.Comp.IsLocked = false;
_audio.PlayPvs(ent.Comp.AccessGrantedSound, ent.Owner);
}
else
{
if (ent.Comp.EnteredCode.Length == ent.Comp.CodeLength)
_deviceLink.InvokePort(ent.Owner, ent.Comp.WrongCodePort);
ent.Comp.EnteredCode = "";
_audio.PlayPvs(ent.Comp.AccessDeniedSound, ent.Owner);
}
}
else
{
ent.Comp.IsLocked = true;
ent.Comp.EnteredCode = "";
_audio.PlayPvs(ent.Comp.KeypadPressSound, ent.Owner);
}
}
private void PlayKeypadSound(Entity<CodeConsoleComponent> ent, int number)
{
// This is a C mixolydian blues scale.
// 1 2 3 C D Eb
// 4 5 6 E F F#
// 7 8 9 G A Bb
var semitoneShift = number switch
{
1 => 0,
2 => 2,
3 => 3,
4 => 4,
5 => 5,
6 => 6,
7 => 7,
8 => 9,
9 => 10,
0 => 8,
_ => 0
};
var opts = ent.Comp.KeypadPressSound.Params;
opts = AudioHelpers.ShiftSemitone(opts, semitoneShift).AddVolume(-5f);
_audio.PlayPvs(ent.Comp.KeypadPressSound, ent.Owner, opts);
}
private void UpdateUserInterface(Entity<CodeConsoleComponent> ent)
{
if (!_ui.HasUi(ent.Owner, CodeConsoleUiKey.Key))
return;
var state = new CodeConsoleUiState
{
IsLocked = ent.Comp.IsLocked,
EnteredCodeLength = ent.Comp.EnteredCode.Length,
MaxCodeLength = ent.Comp.CodeLength
};
_ui.SetUiState(ent.Owner, CodeConsoleUiKey.Key, state);
}
private string GetRandomCode(int codeLength)
{
if (codeLength < 0)
codeLength = 6;
var symbols = "1234567890".ToCharArray();
var code = new char[codeLength];
for (int i = 0; i < codeLength; i++)
{
code[i] = _random.Pick(symbols);
}
return new string(code);
}
}

View file

@ -0,0 +1,55 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using System.Linq;
namespace Content.Shared._Sunrise.CodeConsole;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CodeConsoleComponent : Component
{
[DataField, AutoNetworkedField]
public bool IsLocked = true;
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]
public int CodeLength = 6;
[DataField(serverOnly: true)]
public string Code
{
get => _code;
set
{
if (string.IsNullOrWhiteSpace(value) || !value.All(char.IsDigit))
return;
CodeLength = value.Length;
_code = value;
}
}
[DataField, AutoNetworkedField]
public bool IsSealed = false;
[DataField, AutoNetworkedField]
public string ActivatePort = "Pressed";
[DataField, AutoNetworkedField]
public string WrongCodePort = "WrongCode";
private string _code = string.Empty;
[DataField(serverOnly: true)]
public string EnteredCode = string.Empty;
[DataField]
public SoundSpecifier KeypadPressSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg");
[DataField]
public SoundSpecifier AccessGrantedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/confirm_beep.ogg");
[DataField]
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg");
}

View file

@ -0,0 +1,40 @@
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.CodeConsole;
[Serializable, NetSerializable]
public sealed class CodeConsoleKeypadMessage : BoundUserInterfaceMessage
{
public int Value;
public CodeConsoleKeypadMessage(int value)
{
Value = value;
}
}
[Serializable, NetSerializable]
public sealed class CodeConsoleKeypadEnterMessage : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public sealed class CodeConsoleKeypadClearMessage : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public sealed class CodeConsoleActivateButtonMessage : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public sealed class CodeConsoleLockButtonMessage : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public sealed class CodeConsoleUiState : BoundUserInterfaceState
{
public bool IsLocked;
public int EnteredCodeLength;
public int MaxCodeLength;
}
[Serializable, NetSerializable]
public enum CodeConsoleUiKey : byte
{
Key
}

View file

@ -0,0 +1,2 @@
ent-CodeConsole = кодовая консоль
.desc = Конcоль, предназначенная для ввода кода. Страшно пищит.

View file

@ -0,0 +1,13 @@
code-console-verb-seal-name = запечатать порты
code-console-verb-seal-desc = Закрывает доступ к портам, делая невозможным перепривязку устройства.
code-console-user-interface-title = Кодовая консоль
code-console-interface-code-waiting = ВВЕДИТЕ КОД
code-console-interface-opened = УСТРОЙСТВО АКТИВНО
code-console-user-interface-activate-button = Активировать
code-console-user-interface-lock-button = Заблокировать
signal-port-name-wrong-code = Неверный ввод
signal-port-description-wrong-code = Активируется каждый раз, когда код введён неверно.

View file

@ -0,0 +1,4 @@
- type: sourcePort
id: WrongCode
name: signal-port-name-wrong-code
description: signal-port-description-wrong-code

View file

@ -71,3 +71,47 @@
- type: Tag
tags:
- CantInteract
- type: entity
parent: BaseComputerAiAccess
id: CodeConsole
name: code input console
description: A computer used to enter the password.
components:
- type: CodeConsole
- type: DeviceNetwork
deviceNetId: Wireless
- type: WirelessNetworkConnection
range: 50 # This can be changed if necessary
- type: DeviceLinkSource
ports:
- Pressed
- WrongCode
- type: Sprite
layers:
- map: ["computerLayerBody"]
state: computer
- map: ["computerLayerKeyboard"]
state: generic_keyboard
- map: ["computerLayerScreen"]
state: generic
- map: ["computerLayerKeys"]
state: generic_keys
- map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
state: generic_panel_open
- type: GenericVisualizer
visuals:
enum.ComputerVisuals.Powered:
computerLayerScreen:
True: { visible: true, shader: unshaded }
False: { visible: false }
computerLayerKeys:
True: { visible: true, shader: unshaded }
False: { visible: true, shader: shaded }
- type: ActivatableUI
singleUser: true
key: enum.CodeConsoleUiKey.Key
- type: UserInterface
interfaces:
enum.CodeConsoleUiKey.Key:
type: CodeConsoleBoundUserInterface