Genpop Closets & IDs (#36392)
* Genpop IDs and Lockers * placeholder generation, no ui yet. * UI * Fix time offset * fix meta.jsons * big speller * Scarkyo review * Add turnstile prototypes * make IDs recyclable --------- Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
9
Content.Client/Security/GenpopSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Shared.Security.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Security;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class GenpopSystem : SharedGenpopSystem
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
36
Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Shared.Security.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Client.Security.Ui;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class GenpopLockerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
|
{
|
||||||
|
private GenpopLockerMenu? _menu;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_menu = new(Owner, EntMan);
|
||||||
|
|
||||||
|
_menu.OnConfigurationComplete += (name, time, crime) =>
|
||||||
|
{
|
||||||
|
SendMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
|
||||||
|
Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
_menu.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
_menu?.Orphan();
|
||||||
|
_menu = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
Content.Client/Security/Ui/GenpopLockerMenu.xaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<controls:FancyWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
MinSize="400 230"
|
||||||
|
SetSize="450 260">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="10 5 10 10">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalAlignment="Top" VerticalExpand="True" HorizontalExpand="True" Margin="20 0">
|
||||||
|
<RichTextLabel Name="NameLabel"/>
|
||||||
|
<LineEdit Name="NameEdit"/>
|
||||||
|
<Control MinWidth="5"/>
|
||||||
|
<RichTextLabel Name="SentenceLabel"/>
|
||||||
|
<LineEdit Name="SentenceEdit"/>
|
||||||
|
<Control MinWidth="5"/>
|
||||||
|
<RichTextLabel Name="CrimeLabel"/>
|
||||||
|
<LineEdit Name="CrimeEdit"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<Control VerticalExpand="True"/>
|
||||||
|
<BoxContainer VerticalExpand="True" VerticalAlignment="Bottom" HorizontalAlignment="Right">
|
||||||
|
<Button Name="DoneButton" Text="{Loc 'genpop-locket-ui-button-done'}" Disabled="True"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
|
|
||||||
|
|
||||||
49
Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Security.Components;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Security.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class GenpopLockerMenu : FancyWindow
|
||||||
|
{
|
||||||
|
public event Action<string, float, string>? OnConfigurationComplete;
|
||||||
|
|
||||||
|
public GenpopLockerMenu(EntityUid owner, IEntityManager entMan)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
Title = entMan.GetComponent<MetaDataComponent>(owner).EntityName;
|
||||||
|
|
||||||
|
NameLabel.SetMarkup(Loc.GetString("genpop-locker-ui-label-name"));
|
||||||
|
SentenceLabel.SetMarkup(Loc.GetString("genpop-locker-ui-label-sentence"));
|
||||||
|
CrimeLabel.SetMarkup(Loc.GetString("genpop-locker-ui-label-crime"));
|
||||||
|
|
||||||
|
SentenceEdit.Text = "5";
|
||||||
|
CrimeEdit.Text = Loc.GetString("genpop-prisoner-id-crime-default");
|
||||||
|
|
||||||
|
NameEdit.IsValid = val => !string.IsNullOrWhiteSpace(val) && val.Length <= IdCardConsoleComponent.MaxFullNameLength;
|
||||||
|
SentenceEdit.IsValid = val => float.TryParse(val, out var f) && f >= 0;
|
||||||
|
CrimeEdit.IsValid = val => !string.IsNullOrWhiteSpace(val) && val.Length <= GenpopLockerComponent.MaxCrimeLength;
|
||||||
|
|
||||||
|
NameEdit.OnTextChanged += _ => OnTextEdit();
|
||||||
|
SentenceEdit.OnTextChanged += _ => OnTextEdit();
|
||||||
|
CrimeEdit.OnTextChanged += _ => OnTextEdit();
|
||||||
|
|
||||||
|
DoneButton.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
OnConfigurationComplete?.Invoke(NameEdit.Text, float.Parse(SentenceEdit.Text), CrimeEdit.Text);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextEdit()
|
||||||
|
{
|
||||||
|
DoneButton.Disabled = string.IsNullOrWhiteSpace(NameEdit.Text) ||
|
||||||
|
!float.TryParse(SentenceEdit.Text, out var sentence) ||
|
||||||
|
sentence < 0 ||
|
||||||
|
string.IsNullOrWhiteSpace(CrimeEdit.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.Kitchen.Components;
|
using Content.Server.Kitchen.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
@@ -19,6 +20,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
|||||||
[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 IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
[Dependency] private readonly MicrowaveSystem _microwave = default!;
|
[Dependency] private readonly MicrowaveSystem _microwave = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -93,4 +95,22 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ExpireId(Entity<ExpireIdCardComponent> ent)
|
||||||
|
{
|
||||||
|
if (ent.Comp.Expired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.ExpireId(ent);
|
||||||
|
|
||||||
|
if (ent.Comp.ExpireMessage != null)
|
||||||
|
{
|
||||||
|
_chat.TrySendInGameICMessage(
|
||||||
|
ent,
|
||||||
|
Loc.GetString(ent.Comp.ExpireMessage),
|
||||||
|
InGameICChatType.Speak,
|
||||||
|
ChatTransmitRange.Normal,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
Content.Server/Security/GenpopSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.Security.Components;
|
||||||
|
using Content.Shared.Security.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.Security;
|
||||||
|
|
||||||
|
public sealed class GenpopSystem : SharedGenpopSystem
|
||||||
|
{
|
||||||
|
protected override void CreateId(Entity<GenpopLockerComponent> ent, string name, float sentence, string crime)
|
||||||
|
{
|
||||||
|
var xform = Transform(ent);
|
||||||
|
var uid = Spawn(ent.Comp.IdCardProto, xform.Coordinates);
|
||||||
|
ent.Comp.LinkedId = uid;
|
||||||
|
IdCard.TryChangeFullName(uid, name);
|
||||||
|
|
||||||
|
if (TryComp<GenpopIdCardComponent>(uid, out var id))
|
||||||
|
{
|
||||||
|
id.Crime = crime;
|
||||||
|
id.SentenceDuration = TimeSpan.FromMinutes(sentence);
|
||||||
|
Dirty(uid, id);
|
||||||
|
}
|
||||||
|
if (sentence <= 0)
|
||||||
|
IdCard.SetPermanent(uid, true);
|
||||||
|
IdCard.SetExpireTime(uid, TimeSpan.FromMinutes(sentence) + Timing.CurTime);
|
||||||
|
|
||||||
|
var metaData = MetaData(ent);
|
||||||
|
MetaDataSystem.SetEntityName(ent, Loc.GetString("genpop-locker-name-used", ("name", name)), metaData);
|
||||||
|
MetaDataSystem.SetEntityDescription(ent, Loc.GetString("genpop-locker-desc-used", ("name", name)), metaData);
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Content.Shared/Access/Components/ExpireIdCardComponent.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Access.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for an ID that expires and replaces its access after a certain period has passed.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||||
|
[Access(typeof(SharedIdCardSystem))]
|
||||||
|
public sealed partial class ExpireIdCardComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this ID has expired yet and had its accesses replaced.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool Expired;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this card will expire at all.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool Permanent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which this card will expire and the access will be removed.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
|
||||||
|
public TimeSpan ExpireTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Access the replaces current access once this card expires.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public HashSet<ProtoId<AccessLevelPrototype>> ExpiredAccess = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Line spoken by the card when it expires.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public LocId? ExpireMessage;
|
||||||
|
}
|
||||||
@@ -9,12 +9,15 @@ using Content.Shared.PDA;
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Access.Systems;
|
namespace Content.Shared.Access.Systems;
|
||||||
|
|
||||||
public abstract class SharedIdCardSystem : EntitySystem
|
public abstract class SharedIdCardSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly SharedAccessSystem _access = default!;
|
||||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
@@ -256,4 +259,49 @@ public abstract class SharedIdCardSystem : EntitySystem
|
|||||||
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})"
|
return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})"
|
||||||
.Trim();
|
.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetExpireTime(Entity<ExpireIdCardComponent?> ent, TimeSpan time)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
ent.Comp.ExpireTime = time;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPermanent(Entity<ExpireIdCardComponent?> ent, bool val)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
ent.Comp.Permanent = val;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an <see cref="ExpireIdCardComponent"/> as expired, setting the accesses.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ExpireId(Entity<ExpireIdCardComponent> ent)
|
||||||
|
{
|
||||||
|
if (ent.Comp.Expired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_access.TrySetTags(ent, ent.Comp.ExpiredAccess);
|
||||||
|
ent.Comp.Expired = true;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
var query = EntityQueryEnumerator<ExpireIdCardComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var comp))
|
||||||
|
{
|
||||||
|
if (comp.Expired || comp.Permanent)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_timing.CurTime < comp.ExpireTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ExpireId((uid, comp));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
@@ -33,6 +34,12 @@ public sealed partial class LockComponent : Component
|
|||||||
[DataField, AutoNetworkedField]
|
[DataField, AutoNetworkedField]
|
||||||
public bool UnlockOnClick = true;
|
public bool UnlockOnClick = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the lock requires access validation through <see cref="AccessReaderComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool UseAccess = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sound played when unlocked.
|
/// The sound played when unlocked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public sealed class LockSystem : EntitySystem
|
|||||||
if (!CanToggleLock(uid, user, quiet: false))
|
if (!CanToggleLock(uid, user, quiet: false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!HasUserAccess(uid, user, quiet: false))
|
if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!skipDoAfter && lockComp.LockTime != TimeSpan.Zero)
|
if (!skipDoAfter && lockComp.LockTime != TimeSpan.Zero)
|
||||||
@@ -145,6 +145,9 @@ public sealed class LockSystem : EntitySystem
|
|||||||
if (!Resolve(uid, ref lockComp))
|
if (!Resolve(uid, ref lockComp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (lockComp.Locked)
|
||||||
|
return;
|
||||||
|
|
||||||
if (user is { Valid: true })
|
if (user is { Valid: true })
|
||||||
{
|
{
|
||||||
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
|
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
|
||||||
@@ -175,6 +178,9 @@ public sealed class LockSystem : EntitySystem
|
|||||||
if (!Resolve(uid, ref lockComp))
|
if (!Resolve(uid, ref lockComp))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!lockComp.Locked)
|
||||||
|
return;
|
||||||
|
|
||||||
if (user is { Valid: true })
|
if (user is { Valid: true })
|
||||||
{
|
{
|
||||||
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-unlock-success",
|
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-unlock-success",
|
||||||
@@ -211,7 +217,7 @@ public sealed class LockSystem : EntitySystem
|
|||||||
if (!CanToggleLock(uid, user, quiet: false))
|
if (!CanToggleLock(uid, user, quiet: false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!HasUserAccess(uid, user, quiet: false))
|
if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!skipDoAfter && lockComp.UnlockTime != TimeSpan.Zero)
|
if (!skipDoAfter && lockComp.UnlockTime != TimeSpan.Zero)
|
||||||
|
|||||||
22
Content.Shared/Security/Components/GenpopIdCardComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Security.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for storing information about a Genpop ID in order to correctly display it on examine.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||||
|
public sealed partial class GenpopIdCardComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The crime committed, as a string.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public string Crime = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the sentence
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField, AutoPausedField]
|
||||||
|
public TimeSpan SentenceDuration;
|
||||||
|
}
|
||||||
47
Content.Shared/Security/Components/GenpopLockerComponent.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Security.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for a locker that automatically sets up and handles a <see cref="GenpopIdCardComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class GenpopLockerComponent : Component
|
||||||
|
{
|
||||||
|
public const int MaxCrimeLength = 48;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="GenpopIdCardComponent"/> that this locker is currently associated with.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public EntityUid? LinkedId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Prototype spawned.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId<GenpopIdCardComponent> IdCardProto = "PrisonerIDCard";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class GenpopLockerIdConfiguredMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public float Sentence;
|
||||||
|
public string Crime;
|
||||||
|
|
||||||
|
public GenpopLockerIdConfiguredMessage(string name, float sentence, string crime)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Sentence = sentence;
|
||||||
|
Crime = crime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum GenpopLockerUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
240
Content.Shared/Security/Systems/SharedGenpopSystem.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Lock;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Security.Components;
|
||||||
|
using Content.Shared.Storage.Components;
|
||||||
|
using Content.Shared.Storage.EntitySystems;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Shared.Security.Systems;
|
||||||
|
|
||||||
|
public abstract class SharedGenpopSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
|
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||||
|
[Dependency] private readonly SharedEntityStorageSystem _entityStorage = default!;
|
||||||
|
[Dependency] protected readonly SharedIdCardSystem IdCard = default!;
|
||||||
|
[Dependency] private readonly LockSystem _lock = default!;
|
||||||
|
[Dependency] protected readonly MetaDataSystem MetaDataSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<GenpopLockerComponent, GenpopLockerIdConfiguredMessage>(OnIdConfigured);
|
||||||
|
SubscribeLocalEvent<GenpopLockerComponent, StorageCloseAttemptEvent>(OnCloseAttempt);
|
||||||
|
SubscribeLocalEvent<GenpopLockerComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
|
||||||
|
SubscribeLocalEvent<GenpopLockerComponent, LockToggledEvent>(OnLockToggled);
|
||||||
|
SubscribeLocalEvent<GenpopLockerComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
|
||||||
|
SubscribeLocalEvent<GenpopIdCardComponent, ExaminedEvent>(OnExamine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIdConfigured(Entity<GenpopLockerComponent> ent, ref GenpopLockerIdConfiguredMessage args)
|
||||||
|
{
|
||||||
|
// validation.
|
||||||
|
if (string.IsNullOrWhiteSpace(args.Name) || args.Name.Length > IdCardConsoleComponent.MaxFullNameLength ||
|
||||||
|
args.Sentence < 0 ||
|
||||||
|
string.IsNullOrWhiteSpace(args.Crime) || args.Crime.Length > GenpopLockerComponent.MaxCrimeLength)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_accessReader.IsAllowed(args.Actor, ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We don't spawn the actual ID now because then the locker would eat it.
|
||||||
|
// Instead, we just fill in the spot temporarily til the checks pass.
|
||||||
|
ent.Comp.LinkedId = EntityUid.Invalid;
|
||||||
|
|
||||||
|
_lock.Lock(ent.Owner, null);
|
||||||
|
_entityStorage.CloseStorage(ent);
|
||||||
|
|
||||||
|
CreateId(ent, args.Name, args.Sentence, args.Crime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCloseAttempt(Entity<GenpopLockerComponent> ent, ref StorageCloseAttemptEvent args)
|
||||||
|
{
|
||||||
|
if (args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We cancel no matter what. Our second option is just opening the closet.
|
||||||
|
if (ent.Comp.LinkedId == null)
|
||||||
|
{
|
||||||
|
args.Cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.User is not { } user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_accessReader.IsAllowed(user, ent))
|
||||||
|
{
|
||||||
|
_popup.PopupClient(Loc.GetString("lock-comp-has-user-access-fail"), user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// my heart yearns for this to be predicted but for some reason opening an entitystorage via
|
||||||
|
// verb does not predict it properly.
|
||||||
|
_userInterface.TryOpenUi(ent.Owner, GenpopLockerUiKey.Key, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLockToggleAttempt(Entity<GenpopLockerComponent> ent, ref LockToggleAttemptEvent args)
|
||||||
|
{
|
||||||
|
if (args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ent.Comp.LinkedId == null)
|
||||||
|
{
|
||||||
|
args.Cancelled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we both have the linked ID on our person AND the ID has actually expired.
|
||||||
|
// That way, even if someone escapes early, they can't get ahold of their things.
|
||||||
|
if (!_accessReader.FindPotentialAccessItems(args.User).Contains(ent.Comp.LinkedId.Value))
|
||||||
|
{
|
||||||
|
if (!args.Silent)
|
||||||
|
_popup.PopupClient(Loc.GetString("lock-comp-has-user-access-fail"), ent, args.User);
|
||||||
|
args.Cancelled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryComp<ExpireIdCardComponent>(ent.Comp.LinkedId.Value, out var expireIdCard) ||
|
||||||
|
!expireIdCard.Expired)
|
||||||
|
{
|
||||||
|
if (!args.Silent)
|
||||||
|
_popup.PopupClient(Loc.GetString("genpop-prisoner-id-popup-not-served"), ent, args.User);
|
||||||
|
args.Cancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLockToggled(Entity<GenpopLockerComponent> ent, ref LockToggledEvent args)
|
||||||
|
{
|
||||||
|
if (args.Locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we unlock the door, then we're gonna reset the ID.
|
||||||
|
CancelIdCard(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVerbs(Entity<GenpopLockerComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||||
|
{
|
||||||
|
if (ent.Comp.LinkedId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!args.CanAccess || !args.CanComplexInteract || !args.CanInteract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<ExpireIdCardComponent>(ent.Comp.LinkedId, out var expire) ||
|
||||||
|
!TryComp<GenpopIdCardComponent>(ent.Comp.LinkedId, out var genpopId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = args.User;
|
||||||
|
var hasAccess = _accessReader.IsAllowed(args.User, ent);
|
||||||
|
args.Verbs.Add(new Verb // End sentence early.
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
IdCard.ExpireId((ent.Comp.LinkedId.Value, expire));
|
||||||
|
},
|
||||||
|
Priority = 13,
|
||||||
|
Text = Loc.GetString("genpop-locker-action-end-early"),
|
||||||
|
Impact = LogImpact.Medium,
|
||||||
|
DoContactInteraction = true,
|
||||||
|
Disabled = !hasAccess,
|
||||||
|
});
|
||||||
|
|
||||||
|
args.Verbs.Add(new Verb // Cancel Sentence.
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
CancelIdCard(ent, user);
|
||||||
|
},
|
||||||
|
Priority = 12,
|
||||||
|
Text = Loc.GetString("genpop-locker-action-clear-id"),
|
||||||
|
Impact = LogImpact.Medium,
|
||||||
|
DoContactInteraction = true,
|
||||||
|
Disabled = !hasAccess,
|
||||||
|
});
|
||||||
|
|
||||||
|
var servedTime = 1 - (expire.ExpireTime - Timing.CurTime).TotalSeconds / genpopId.SentenceDuration.TotalSeconds;
|
||||||
|
|
||||||
|
// Can't reset it after its expired.
|
||||||
|
if (expire.Expired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Verbs.Add(new Verb // Reset Sentence.
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
IdCard.SetExpireTime((ent.Comp.LinkedId.Value, expire), Timing.CurTime + genpopId.SentenceDuration);
|
||||||
|
},
|
||||||
|
Priority = 11,
|
||||||
|
Text = Loc.GetString("genpop-locker-action-reset-sentence", ("percent", Math.Clamp(servedTime, 0, 1) * 100)),
|
||||||
|
Impact = LogImpact.Medium,
|
||||||
|
DoContactInteraction = true,
|
||||||
|
Disabled = !hasAccess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelIdCard(Entity<GenpopLockerComponent> ent, EntityUid? user = null)
|
||||||
|
{
|
||||||
|
if (ent.Comp.LinkedId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var metaData = MetaData(ent);
|
||||||
|
MetaDataSystem.SetEntityName(ent, Loc.GetString("genpop-locker-name-default"), metaData);
|
||||||
|
MetaDataSystem.SetEntityDescription(ent, Loc.GetString("genpop-locker-desc-default"), metaData);
|
||||||
|
|
||||||
|
ent.Comp.LinkedId = null;
|
||||||
|
_lock.Unlock(ent.Owner, user);
|
||||||
|
_entityStorage.OpenStorage(ent.Owner);
|
||||||
|
|
||||||
|
if (TryComp<ExpireIdCardComponent>(ent.Comp.LinkedId, out var expire))
|
||||||
|
IdCard.ExpireId((ent.Comp.LinkedId.Value, expire));
|
||||||
|
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(Entity<GenpopIdCardComponent> ent, ref ExaminedEvent args)
|
||||||
|
{
|
||||||
|
// This component holds the contextual data for the sentence end time and other such things.
|
||||||
|
if (!TryComp<ExpireIdCardComponent>(ent, out var expireIdCard))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (expireIdCard.Permanent)
|
||||||
|
{
|
||||||
|
args.PushText(Loc.GetString("genpop-prisoner-id-examine-wait-perm",
|
||||||
|
("crime", ent.Comp.Crime)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (expireIdCard.Expired)
|
||||||
|
{
|
||||||
|
args.PushText(Loc.GetString("genpop-prisoner-id-examine-served",
|
||||||
|
("crime", ent.Comp.Crime)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var sentence = ent.Comp.SentenceDuration;
|
||||||
|
var served = ent.Comp.SentenceDuration - (expireIdCard.ExpireTime - Timing.CurTime);
|
||||||
|
|
||||||
|
args.PushText(Loc.GetString("genpop-prisoner-id-examine-wait",
|
||||||
|
("minutes", served.Minutes),
|
||||||
|
("seconds", served.Seconds),
|
||||||
|
("sentence", sentence.TotalMinutes),
|
||||||
|
("crime", ent.Comp.Crime)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void CreateId(Entity<GenpopLockerComponent> ent, string name, float sentence, string crime)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ public readonly record struct StorageBeforeOpenEvent;
|
|||||||
public readonly record struct StorageAfterOpenEvent;
|
public readonly record struct StorageAfterOpenEvent;
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public record struct StorageCloseAttemptEvent(bool Cancelled = false);
|
public record struct StorageCloseAttemptEvent(EntityUid? User, bool Cancelled = false);
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct StorageBeforeCloseEvent(HashSet<EntityUid> Contents, HashSet<EntityUid> BypassChecks);
|
public readonly record struct StorageBeforeCloseEvent(HashSet<EntityUid> Contents, HashSet<EntityUid> BypassChecks);
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
|||||||
|
|
||||||
if (component.Open)
|
if (component.Open)
|
||||||
{
|
{
|
||||||
TryCloseStorage(target);
|
TryCloseStorage(target, user);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -360,9 +360,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryCloseStorage(EntityUid target)
|
public bool TryCloseStorage(EntityUid target, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
if (!CanClose(target))
|
if (!CanClose(target, user))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -413,9 +413,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
|||||||
return !ev.Cancelled;
|
return !ev.Cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanClose(EntityUid target, bool silent = false)
|
public bool CanClose(EntityUid target, EntityUid? user = null, bool silent = false)
|
||||||
{
|
{
|
||||||
var ev = new StorageCloseAttemptEvent();
|
var ev = new StorageCloseAttemptEvent(user);
|
||||||
RaiseLocalEvent(target, ref ev, silent);
|
RaiseLocalEvent(target, ref ev, silent);
|
||||||
|
|
||||||
return !ev.Cancelled;
|
return !ev.Cancelled;
|
||||||
|
|||||||
28
Resources/Locale/en-US/access/components/genpop.ftl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
genpop-prisoner-id-expire = You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.
|
||||||
|
genpop-prisoner-id-popup-not-served = Sentence not yet served!
|
||||||
|
|
||||||
|
genpop-prisoner-id-crime-default = [Redacted]
|
||||||
|
genpop-prisoner-id-examine-wait = You have served {$minutes} {$minutes ->
|
||||||
|
[1] minute
|
||||||
|
*[other] minutes
|
||||||
|
} {$seconds} {$seconds ->
|
||||||
|
[1] second
|
||||||
|
*[other] seconds
|
||||||
|
} of your {$sentence} minute sentence for {$crime}.
|
||||||
|
genpop-prisoner-id-examine-wait-perm = You are serving a permanent sentence for {$crime}.
|
||||||
|
genpop-prisoner-id-examine-served = You have served your sentence for {$crime}.
|
||||||
|
|
||||||
|
genpop-locker-name-default = prisoner closet
|
||||||
|
genpop-locker-desc-default = It's a secure locker for an inmate's personal belongings during their time in prison.
|
||||||
|
|
||||||
|
genpop-locker-name-used = prisoner closet ({$name})
|
||||||
|
genpop-locker-desc-used = It's a secure locker for an inmate's personal belongings during their time in prison. It contains the personal effects of {$name}.
|
||||||
|
|
||||||
|
genpop-locker-ui-label-name = [bold]Convict Name:[/bold]
|
||||||
|
genpop-locker-ui-label-sentence = [bold]Sentence length in minutes:[/bold] [color=gray](0 for perma)[/color]
|
||||||
|
genpop-locker-ui-label-crime = [bold]Crime:[/bold]
|
||||||
|
genpop-locket-ui-button-done = Done
|
||||||
|
|
||||||
|
genpop-locker-action-end-early = End Sentence Early
|
||||||
|
genpop-locker-action-clear-id = Clear ID
|
||||||
|
genpop-locker-action-reset-sentence = Reset Sentence ({NATURALFIXED($percent, 0)}% served)
|
||||||
@@ -9,6 +9,8 @@ id-card-access-level-security = Security
|
|||||||
id-card-access-level-armory = Armory
|
id-card-access-level-armory = Armory
|
||||||
id-card-access-level-brig = Brig
|
id-card-access-level-brig = Brig
|
||||||
id-card-access-level-detective = Detective
|
id-card-access-level-detective = Detective
|
||||||
|
id-card-access-level-genpop-enter = Enter Genpop
|
||||||
|
id-card-access-level-genpop-leave = Leave Genpop
|
||||||
|
|
||||||
id-card-access-level-chief-engineer = Chief Engineer
|
id-card-access-level-chief-engineer = Chief Engineer
|
||||||
id-card-access-level-engineering = Engineering
|
id-card-access-level-engineering = Engineering
|
||||||
|
|||||||
@@ -32,3 +32,5 @@
|
|||||||
- Chapel
|
- Chapel
|
||||||
- Hydroponics
|
- Hydroponics
|
||||||
- Atmospherics
|
- Atmospherics
|
||||||
|
- GenpopEnter
|
||||||
|
- GenpopLeave
|
||||||
|
|||||||
@@ -18,6 +18,14 @@
|
|||||||
id: Detective
|
id: Detective
|
||||||
name: id-card-access-level-detective
|
name: id-card-access-level-detective
|
||||||
|
|
||||||
|
- type: accessLevel
|
||||||
|
id: GenpopEnter
|
||||||
|
name: id-card-access-level-genpop-enter
|
||||||
|
|
||||||
|
- type: accessLevel
|
||||||
|
id: GenpopLeave
|
||||||
|
name: id-card-access-level-genpop-leave
|
||||||
|
|
||||||
- type: accessGroup
|
- type: accessGroup
|
||||||
id: Security
|
id: Security
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -722,6 +722,40 @@
|
|||||||
- type: PresetIdCard
|
- type: PresetIdCard
|
||||||
job: Detective
|
job: Detective
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: IDCardStandard
|
||||||
|
id: PrisonerIDCard
|
||||||
|
name: prisoner ID card
|
||||||
|
description: A generically printed ID card for scummy prisoners.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: orange
|
||||||
|
- type: Item
|
||||||
|
heldPrefix: orange
|
||||||
|
- type: Access
|
||||||
|
tags:
|
||||||
|
- GenpopEnter
|
||||||
|
- type: GenpopIdCard
|
||||||
|
- type: IdCard
|
||||||
|
jobTitle: job-name-prisoner
|
||||||
|
jobIcon: JobIconPrisoner
|
||||||
|
canMicrowave: false
|
||||||
|
- type: ExpireIdCard
|
||||||
|
expireMessage: genpop-prisoner-id-expire
|
||||||
|
expiredAccess:
|
||||||
|
- GenpopLeave
|
||||||
|
- type: Speech
|
||||||
|
speechVerb: Robotic
|
||||||
|
- type: Tag
|
||||||
|
tags:
|
||||||
|
- DoorBumpOpener
|
||||||
|
- WhitelistChameleon
|
||||||
|
- WhitelistChameleonIdCard
|
||||||
|
- Recyclable
|
||||||
|
- type: StaticPrice # these are infinitely producible.
|
||||||
|
price: 0
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: CentcomIDCard
|
parent: CentcomIDCard
|
||||||
id: CBURNIDcard
|
id: CBURNIDcard
|
||||||
|
|||||||
@@ -126,6 +126,8 @@
|
|||||||
- SyndicateAgent
|
- SyndicateAgent
|
||||||
- Wizard
|
- Wizard
|
||||||
- Xenoborg
|
- Xenoborg
|
||||||
|
- GenpopEnter
|
||||||
|
- GenpopLeave
|
||||||
privilegedIdSlot:
|
privilegedIdSlot:
|
||||||
name: id-card-console-privileged-id
|
name: id-card-console-privileged-id
|
||||||
ejectSound: /Audio/Machines/id_swipe.ogg
|
ejectSound: /Audio/Machines/id_swipe.ogg
|
||||||
|
|||||||
@@ -75,3 +75,21 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- HideContextMenu
|
- HideContextMenu
|
||||||
|
|
||||||
|
# Genpop
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: TurnstileGenpopEnter
|
||||||
|
parent: Turnstile
|
||||||
|
suffix: Genpop Enter
|
||||||
|
components:
|
||||||
|
- type: AccessReader
|
||||||
|
access: [["GenpopEnter"]]
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: TurnstileGenpopLeave
|
||||||
|
parent: Turnstile
|
||||||
|
suffix: Genpop Leave
|
||||||
|
components:
|
||||||
|
- type: AccessReader
|
||||||
|
access: [["GenpopLeave"]]
|
||||||
|
|||||||
@@ -381,6 +381,111 @@
|
|||||||
- type: AccessReader
|
- type: AccessReader
|
||||||
access: [["Armory"]]
|
access: [["Armory"]]
|
||||||
|
|
||||||
|
# Genpop Storage
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner
|
||||||
|
parent: LockerBaseSecure
|
||||||
|
name: prisoner closet
|
||||||
|
description: It's a secure locker for an inmate's personal belongings during their time in prison.
|
||||||
|
suffix: 1
|
||||||
|
components:
|
||||||
|
- type: GenpopLocker
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateBaseClosed: genpop
|
||||||
|
stateDoorOpen: genpop_open
|
||||||
|
stateDoorClosed: genpop_door_1
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
enum.GenpopLockerUiKey.Key:
|
||||||
|
type: GenpopLockerBoundUserInterface
|
||||||
|
- type: AccessReader # note! this access is for the UI, not the door. door access is handled on GenpopLocker
|
||||||
|
access: [["Security"]]
|
||||||
|
- type: Lock
|
||||||
|
locked: false
|
||||||
|
useAccess: false
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
fix1:
|
||||||
|
shape: !type:PolygonShape
|
||||||
|
radius: 0.01
|
||||||
|
vertices:
|
||||||
|
- -0.25,-0.48
|
||||||
|
- 0.25,-0.48
|
||||||
|
- 0.25,0.48
|
||||||
|
- -0.25,0.48
|
||||||
|
mask:
|
||||||
|
- Impassable
|
||||||
|
- TableLayer
|
||||||
|
- LowImpassable
|
||||||
|
layer:
|
||||||
|
- BulletImpassable
|
||||||
|
- Opaque
|
||||||
|
density: 75
|
||||||
|
hard: True
|
||||||
|
restitution: 0
|
||||||
|
friction: 0.4
|
||||||
|
- type: EntityStorage
|
||||||
|
open: True
|
||||||
|
removedMasks: 20
|
||||||
|
- type: PlaceableSurface
|
||||||
|
isPlaceable: True
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner2
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 2
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_2
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner3
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 3
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_3
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner4
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 4
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_4
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner5
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 5
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_5
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner6
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 6
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_6
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner7
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 7
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_7
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: LockerPrisoner8
|
||||||
|
parent: LockerPrisoner
|
||||||
|
suffix: 8
|
||||||
|
components:
|
||||||
|
- type: EntityStorageVisuals
|
||||||
|
stateDoorClosed: genpop_door_8
|
||||||
|
|
||||||
# Detective
|
# Detective
|
||||||
- type: entity
|
- type: entity
|
||||||
id: LockerDetective
|
id: LockerDetective
|
||||||
|
|||||||
@@ -693,6 +693,15 @@
|
|||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: nosmoking
|
state: nosmoking
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseSign
|
||||||
|
id: SignGenpop
|
||||||
|
name: genpop sign
|
||||||
|
description: A sign indicating the genpop prison.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: genpop
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseSign
|
parent: BaseSign
|
||||||
id: SignPrison
|
id: SignPrison
|
||||||
|
|||||||
BIN
Resources/Textures/Structures/Storage/closet.rsi/genpop.png
Normal file
|
After Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 379 B |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 385 B |
BIN
Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png
Normal file
|
After Width: | Height: | Size: 303 B |
@@ -4,7 +4,7 @@
|
|||||||
"x": 32,
|
"x": 32,
|
||||||
"y": 32
|
"y": 32
|
||||||
},
|
},
|
||||||
"copyright": "Taken from tgstation, brigmedic locker is a resprited CMO locker by PuroSlavKing (Github), n2 sprites based on fire and emergency sprites",
|
"copyright": "Taken from tgstation, brigmedic locker is a resprited CMO locker by PuroSlavKing (Github), n2 sprites based on fire and emergency sprites, genpop lockers by EmoGarbage404 (github)",
|
||||||
"license": "CC-BY-SA-3.0",
|
"license": "CC-BY-SA-3.0",
|
||||||
"states": [
|
"states": [
|
||||||
{
|
{
|
||||||
@@ -329,6 +329,36 @@
|
|||||||
{
|
{
|
||||||
"name": "generic_icon"
|
"name": "generic_icon"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_door_8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop_open"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "green_door"
|
"name": "green_door"
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png
Normal file
|
After Width: | Height: | Size: 390 B |
@@ -258,6 +258,9 @@
|
|||||||
{
|
{
|
||||||
"name": "cloning"
|
"name": "cloning"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "genpop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "gravi"
|
"name": "gravi"
|
||||||
},
|
},
|
||||||
|
|||||||