diff --git a/Content.Client/Security/GenpopSystem.cs b/Content.Client/Security/GenpopSystem.cs
new file mode 100644
index 0000000000..2f537cd96e
--- /dev/null
+++ b/Content.Client/Security/GenpopSystem.cs
@@ -0,0 +1,9 @@
+using Content.Shared.Security.Systems;
+
+namespace Content.Client.Security;
+
+///
+public sealed class GenpopSystem : SharedGenpopSystem
+{
+
+}
diff --git a/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs b/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
new file mode 100644
index 0000000000..a546fa6fc6
--- /dev/null
+++ b/Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs
@@ -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;
+ }
+}
+
diff --git a/Content.Client/Security/Ui/GenpopLockerMenu.xaml b/Content.Client/Security/Ui/GenpopLockerMenu.xaml
new file mode 100644
index 0000000000..4eb670d25d
--- /dev/null
+++ b/Content.Client/Security/Ui/GenpopLockerMenu.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs b/Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs
new file mode 100644
index 0000000000..575b2f50df
--- /dev/null
+++ b/Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs
@@ -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? OnConfigurationComplete;
+
+ public GenpopLockerMenu(EntityUid owner, IEntityManager entMan)
+ {
+ RobustXamlLoader.Load(this);
+
+ Title = entMan.GetComponent(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);
+ }
+}
diff --git a/Content.Server/Access/Systems/IdCardSystem.cs b/Content.Server/Access/Systems/IdCardSystem.cs
index 9057fade72..05ee45b463 100644
--- a/Content.Server/Access/Systems/IdCardSystem.cs
+++ b/Content.Server/Access/Systems/IdCardSystem.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Server.Administration.Logs;
+using Content.Server.Chat.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Access;
@@ -19,6 +20,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly MicrowaveSystem _microwave = default!;
public override void Initialize()
@@ -93,4 +95,22 @@ public sealed class IdCardSystem : SharedIdCardSystem
}
}
+
+ public override void ExpireId(Entity 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);
+ }
+ }
}
diff --git a/Content.Server/Security/GenpopSystem.cs b/Content.Server/Security/GenpopSystem.cs
new file mode 100644
index 0000000000..0a4233308e
--- /dev/null
+++ b/Content.Server/Security/GenpopSystem.cs
@@ -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 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(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);
+ }
+}
diff --git a/Content.Shared/Access/Components/ExpireIdCardComponent.cs b/Content.Shared/Access/Components/ExpireIdCardComponent.cs
new file mode 100644
index 0000000000..68a2a97531
--- /dev/null
+++ b/Content.Shared/Access/Components/ExpireIdCardComponent.cs
@@ -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;
+
+///
+/// This is used for an ID that expires and replaces its access after a certain period has passed.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(SharedIdCardSystem))]
+public sealed partial class ExpireIdCardComponent : Component
+{
+ ///
+ /// Whether this ID has expired yet and had its accesses replaced.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Expired;
+
+ ///
+ /// Whether this card will expire at all.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Permanent;
+
+ ///
+ /// The time at which this card will expire and the access will be removed.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
+ public TimeSpan ExpireTime = TimeSpan.Zero;
+
+ ///
+ /// Access the replaces current access once this card expires.
+ ///
+ [DataField]
+ public HashSet> ExpiredAccess = new();
+
+ ///
+ /// Line spoken by the card when it expires.
+ ///
+ [DataField]
+ public LocId? ExpireMessage;
+}
diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs
index db7d9b38c8..69d77fe9ec 100644
--- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs
+++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs
@@ -9,12 +9,15 @@ using Content.Shared.PDA;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
namespace Content.Shared.Access.Systems;
public abstract class SharedIdCardSystem : EntitySystem
{
[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 MetaDataSystem _metaSystem = 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)})"
.Trim();
}
+
+ public void SetExpireTime(Entity ent, TimeSpan time)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+ ent.Comp.ExpireTime = time;
+ Dirty(ent);
+ }
+
+ public void SetPermanent(Entity ent, bool val)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+ ent.Comp.Permanent = val;
+ Dirty(ent);
+ }
+
+ ///
+ /// Marks an as expired, setting the accesses.
+ ///
+ public virtual void ExpireId(Entity 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();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (comp.Expired || comp.Permanent)
+ continue;
+
+ if (_timing.CurTime < comp.ExpireTime)
+ continue;
+
+ ExpireId((uid, comp));
+ }
+ }
}
diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs
index 2689602ae8..0fdee2477f 100644
--- a/Content.Shared/Lock/LockComponent.cs
+++ b/Content.Shared/Lock/LockComponent.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Access.Components;
using Content.Shared.DoAfter;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
@@ -33,6 +34,12 @@ public sealed partial class LockComponent : Component
[DataField, AutoNetworkedField]
public bool UnlockOnClick = true;
+ ///
+ /// Whether the lock requires access validation through
+ ///
+ [DataField, AutoNetworkedField]
+ public bool UseAccess = true;
+
///
/// The sound played when unlocked.
///
diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs
index 0b24bc6722..cbeceaf9e8 100644
--- a/Content.Shared/Lock/LockSystem.cs
+++ b/Content.Shared/Lock/LockSystem.cs
@@ -118,7 +118,7 @@ public sealed class LockSystem : EntitySystem
if (!CanToggleLock(uid, user, quiet: false))
return false;
- if (!HasUserAccess(uid, user, quiet: false))
+ if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
return false;
if (!skipDoAfter && lockComp.LockTime != TimeSpan.Zero)
@@ -145,6 +145,9 @@ public sealed class LockSystem : EntitySystem
if (!Resolve(uid, ref lockComp))
return;
+ if (lockComp.Locked)
+ return;
+
if (user is { Valid: true })
{
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
@@ -175,6 +178,9 @@ public sealed class LockSystem : EntitySystem
if (!Resolve(uid, ref lockComp))
return;
+ if (!lockComp.Locked)
+ return;
+
if (user is { Valid: true })
{
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-unlock-success",
@@ -211,7 +217,7 @@ public sealed class LockSystem : EntitySystem
if (!CanToggleLock(uid, user, quiet: false))
return false;
- if (!HasUserAccess(uid, user, quiet: false))
+ if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
return false;
if (!skipDoAfter && lockComp.UnlockTime != TimeSpan.Zero)
diff --git a/Content.Shared/Security/Components/GenpopIdCardComponent.cs b/Content.Shared/Security/Components/GenpopIdCardComponent.cs
new file mode 100644
index 0000000000..bf10bbab90
--- /dev/null
+++ b/Content.Shared/Security/Components/GenpopIdCardComponent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Security.Components;
+
+///
+/// This is used for storing information about a Genpop ID in order to correctly display it on examine.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class GenpopIdCardComponent : Component
+{
+ ///
+ /// The crime committed, as a string.
+ ///
+ [DataField, AutoNetworkedField]
+ public string Crime = string.Empty;
+
+ ///
+ /// The length of the sentence
+ ///
+ [DataField, AutoNetworkedField, AutoPausedField]
+ public TimeSpan SentenceDuration;
+}
diff --git a/Content.Shared/Security/Components/GenpopLockerComponent.cs b/Content.Shared/Security/Components/GenpopLockerComponent.cs
new file mode 100644
index 0000000000..cfeb581814
--- /dev/null
+++ b/Content.Shared/Security/Components/GenpopLockerComponent.cs
@@ -0,0 +1,47 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Security.Components;
+
+///
+/// This is used for a locker that automatically sets up and handles a
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GenpopLockerComponent : Component
+{
+ public const int MaxCrimeLength = 48;
+
+ ///
+ /// The that this locker is currently associated with.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? LinkedId;
+
+ ///
+ /// The Prototype spawned.
+ ///
+ [DataField]
+ public EntProtoId 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
+}
diff --git a/Content.Shared/Security/Systems/SharedGenpopSystem.cs b/Content.Shared/Security/Systems/SharedGenpopSystem.cs
new file mode 100644
index 0000000000..39fc87f665
--- /dev/null
+++ b/Content.Shared/Security/Systems/SharedGenpopSystem.cs
@@ -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!;
+
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnIdConfigured);
+ SubscribeLocalEvent(OnCloseAttempt);
+ SubscribeLocalEvent(OnLockToggleAttempt);
+ SubscribeLocalEvent(OnLockToggled);
+ SubscribeLocalEvent>(OnGetVerbs);
+ SubscribeLocalEvent(OnExamine);
+ }
+
+ private void OnIdConfigured(Entity 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 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 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(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 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 ent, ref GetVerbsEvent args)
+ {
+ if (ent.Comp.LinkedId == null)
+ return;
+
+ if (!args.CanAccess || !args.CanComplexInteract || !args.CanInteract)
+ return;
+
+ if (!TryComp(ent.Comp.LinkedId, out var expire) ||
+ !TryComp(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 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(ent.Comp.LinkedId, out var expire))
+ IdCard.ExpireId((ent.Comp.LinkedId.Value, expire));
+
+ Dirty(ent);
+ }
+
+ private void OnExamine(Entity ent, ref ExaminedEvent args)
+ {
+ // This component holds the contextual data for the sentence end time and other such things.
+ if (!TryComp(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 ent, string name, float sentence, string crime)
+ {
+
+ }
+}
diff --git a/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs b/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
index 4100449f4e..06b1c15f2e 100644
--- a/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
+++ b/Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
@@ -159,7 +159,7 @@ public readonly record struct StorageBeforeOpenEvent;
public readonly record struct StorageAfterOpenEvent;
[ByRefEvent]
-public record struct StorageCloseAttemptEvent(bool Cancelled = false);
+public record struct StorageCloseAttemptEvent(EntityUid? User, bool Cancelled = false);
[ByRefEvent]
public readonly record struct StorageBeforeCloseEvent(HashSet Contents, HashSet BypassChecks);
diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
index abd08c7459..75088bfeec 100644
--- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
+++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
@@ -181,7 +181,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
if (component.Open)
{
- TryCloseStorage(target);
+ TryCloseStorage(target, user);
}
else
{
@@ -360,9 +360,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
return true;
}
- public bool TryCloseStorage(EntityUid target)
+ public bool TryCloseStorage(EntityUid target, EntityUid? user = null)
{
- if (!CanClose(target))
+ if (!CanClose(target, user))
{
return false;
}
@@ -413,9 +413,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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);
return !ev.Cancelled;
diff --git a/Resources/Locale/en-US/access/components/genpop.ftl b/Resources/Locale/en-US/access/components/genpop.ftl
new file mode 100644
index 0000000000..8964467688
--- /dev/null
+++ b/Resources/Locale/en-US/access/components/genpop.ftl
@@ -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)
diff --git a/Resources/Locale/en-US/prototypes/access/accesses.ftl b/Resources/Locale/en-US/prototypes/access/accesses.ftl
index 44fd9adf00..4a9fa272a2 100644
--- a/Resources/Locale/en-US/prototypes/access/accesses.ftl
+++ b/Resources/Locale/en-US/prototypes/access/accesses.ftl
@@ -9,6 +9,8 @@ id-card-access-level-security = Security
id-card-access-level-armory = Armory
id-card-access-level-brig = Brig
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-engineering = Engineering
@@ -50,4 +52,4 @@ id-card-access-level-station-ai = Artifical Intelligence
id-card-access-level-borg = Cyborg
id-card-access-level-basic-silicon = Robot
-id-card-access-level-basic-xenoborg = Xenoborg
\ No newline at end of file
+id-card-access-level-basic-xenoborg = Xenoborg
diff --git a/Resources/Prototypes/Access/misc.yml b/Resources/Prototypes/Access/misc.yml
index f79f1779c2..d3f6df775b 100644
--- a/Resources/Prototypes/Access/misc.yml
+++ b/Resources/Prototypes/Access/misc.yml
@@ -32,3 +32,5 @@
- Chapel
- Hydroponics
- Atmospherics
+ - GenpopEnter
+ - GenpopLeave
diff --git a/Resources/Prototypes/Access/security.yml b/Resources/Prototypes/Access/security.yml
index cfe94dd78a..45d8af61ed 100644
--- a/Resources/Prototypes/Access/security.yml
+++ b/Resources/Prototypes/Access/security.yml
@@ -18,6 +18,14 @@
id: 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
id: Security
tags:
diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
index 736642abb5..72332010e6 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
@@ -722,6 +722,40 @@
- type: PresetIdCard
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
parent: CentcomIDCard
id: CBURNIDcard
diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
index 4f6762cf5f..188cbfc51b 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
@@ -126,6 +126,8 @@
- SyndicateAgent
- Wizard
- Xenoborg
+ - GenpopEnter
+ - GenpopLeave
privilegedIdSlot:
name: id-card-console-privileged-id
ejectSound: /Audio/Machines/id_swipe.ogg
diff --git a/Resources/Prototypes/Entities/Structures/Doors/turnstile.yml b/Resources/Prototypes/Entities/Structures/Doors/turnstile.yml
index 6d675c5928..0827492d11 100644
--- a/Resources/Prototypes/Entities/Structures/Doors/turnstile.yml
+++ b/Resources/Prototypes/Entities/Structures/Doors/turnstile.yml
@@ -75,3 +75,21 @@
- type: Tag
tags:
- 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"]]
diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml
index 97ef052153..df62854818 100644
--- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml
+++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml
@@ -381,6 +381,111 @@
- type: AccessReader
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
- type: entity
id: LockerDetective
diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/signs.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/signs.yml
index 90a6820dae..b7595baac9 100644
--- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/signs.yml
+++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/signs.yml
@@ -693,6 +693,15 @@
- type: Sprite
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
parent: BaseSign
id: SignPrison
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop.png
new file mode 100644
index 0000000000..11e878a6fe
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_1.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_1.png
new file mode 100644
index 0000000000..35c32606bd
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_1.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_2.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_2.png
new file mode 100644
index 0000000000..e7a8907900
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_2.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_3.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_3.png
new file mode 100644
index 0000000000..a4c82fdb08
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_3.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_4.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_4.png
new file mode 100644
index 0000000000..ad11caa8f9
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_4.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_5.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_5.png
new file mode 100644
index 0000000000..429d432cd9
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_5.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_6.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_6.png
new file mode 100644
index 0000000000..35c49375d9
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_6.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_7.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_7.png
new file mode 100644
index 0000000000..cd0c499f08
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_7.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_8.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_8.png
new file mode 100644
index 0000000000..093cbc75aa
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_door_8.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png b/Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png
new file mode 100644
index 0000000000..c6971a35d1
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png differ
diff --git a/Resources/Textures/Structures/Storage/closet.rsi/meta.json b/Resources/Textures/Structures/Storage/closet.rsi/meta.json
index 5600268fde..6dbdb4dba3 100644
--- a/Resources/Textures/Structures/Storage/closet.rsi/meta.json
+++ b/Resources/Textures/Structures/Storage/closet.rsi/meta.json
@@ -1,566 +1,596 @@
{
- "version": 1,
- "size": {
- "x": 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",
- "license": "CC-BY-SA-3.0",
- "states": [
- {
- "name": "abductor"
- },
- {
- "name": "abductor_door"
- },
- {
- "name": "abductor_open"
- },
- {
- "name": "agentbox"
- },
- {
- "name": "alien"
- },
- {
- "name": "alien_door"
- },
- {
- "name": "alien_open"
- },
- {
- "name": "brigmedic_door"
- },
- {
- "name": "brigmedic"
- },
- {
- "name": "armory"
- },
- {
- "name": "armory_door"
- },
- {
- "name": "armory_open"
- },
- {
- "name": "atmos"
- },
- {
- "name": "atmos_door"
- },
- {
- "name": "atmos_open"
- },
- {
- "name": "atmos_wardrobe_door"
- },
- {
- "name": "bio"
- },
- {
- "name": "bio_door"
- },
- {
- "name": "bio_jan"
- },
- {
- "name": "bio_jan_door"
- },
- {
- "name": "bio_jan_open"
- },
- {
- "name": "bio_open"
- },
- {
- "name": "bio_sec"
- },
- {
- "name": "bio_sec_door"
- },
- {
- "name": "bio_sec_open"
- },
- {
- "name": "bio_viro"
- },
- {
- "name": "bio_viro_door"
- },
- {
- "name": "bio_viro_open"
- },
- {
- "name": "black_door"
- },
- {
- "name": "blue_door"
- },
- {
- "name": "bomb"
- },
- {
- "name": "bomb_door"
- },
- {
- "name": "bomb_open"
- },
- {
- "name": "janitor_bomb"
- },
- {
- "name": "janitor_bomb_door"
- },
- {
- "name": "janitor_bomb_open"
- },
- {
- "name": "cabinet"
- },
- {
- "name": "cabinet_door"
- },
- {
- "name": "cabinet_open"
- },
- {
- "name": "cap"
- },
- {
- "name": "cap_door"
- },
- {
- "name": "cap_open"
- },
- {
- "name": "cardboard"
- },
- {
- "name": "cardboard_open"
- },
- {
- "name": "cardboard_special"
- },
- {
- "name": "cargo"
- },
- {
- "name": "cargo_door"
- },
- {
- "name": "cargo_open"
- },
- {
- "name": "ce"
- },
- {
- "name": "ce_door"
- },
- {
- "name": "ce_open"
- },
- {
- "name": "chemical_door"
- },
- {
- "name": "cmo"
- },
- {
- "name": "cmo_door"
- },
- {
- "name": "cmo_open"
- },
- {
- "name": "cursed",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "cursed_door",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "cursed_open"
- },
- {
- "name": "cursed_whole",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "decursed",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "decursed_door",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "decursed_open"
- },
- {
- "name": "ecase"
- },
- {
- "name": "ecase_door"
- },
- {
- "name": "ecase_open"
- },
- {
- "name": "egun"
- },
- {
- "name": "emergency"
- },
- {
- "name": "emergency_door"
- },
- {
- "name": "emergency_open"
- },
- {
- "name": "eng"
- },
- {
- "name": "eng_elec_door"
- },
- {
- "name": "eng_open"
- },
- {
- "name": "eng_rad_door"
- },
- {
- "name": "eng_secure"
- },
- {
- "name": "eng_secure_door"
- },
- {
- "name": "eng_secure_open"
- },
- {
- "name": "eng_tool_door"
- },
- {
- "name": "eng_weld_door"
- },
- {
- "name": "fire"
- },
- {
- "name": "fire_door"
- },
- {
- "name": "fire_open"
- },
- {
- "name": "freezer"
- },
- {
- "name": "freezer_icon"
- },
- {
- "name": "freezer_door"
- },
- {
- "name": "freezer_open"
- },
- {
- "name": "generic"
- },
- {
- "name": "generic_door"
- },
- {
- "name": "generic_open"
- },
- {
- "name": "generic_icon"
- },
- {
- "name": "green_door"
- },
- {
- "name": "grey_door"
- },
- {
- "name": "hop"
- },
- {
- "name": "hop_door"
- },
- {
- "name": "hop_open"
- },
- {
- "name": "hos"
- },
- {
- "name": "hos_door"
- },
- {
- "name": "hos_open"
- },
- {
- "name": "hydro"
- },
- {
- "name": "hydro_door"
- },
- {
- "name": "hydro_open"
- },
- {
- "name": "locked"
- },
- {
- "name": "med"
- },
- {
- "name": "med_door"
- },
- {
- "name": "med_open"
- },
- {
- "name": "med_secure"
- },
- {
- "name": "med_secure_door"
- },
- {
- "name": "med_secure_open"
- },
- {
- "name": "metalbox"
- },
- {
- "name": "metalbox_open"
- },
- {
- "name": "mining"
- },
- {
- "name": "mining_door"
- },
- {
- "name": "mining_open"
- },
- {
- "name": "mixed_door"
- },
- {
- "name": "n2"
- },
- {
- "name": "n2_open"
- },
- {
- "name": "n2_door"
- },
- {
- "name": "oldcloset"
- },
- {
- "name": "orange_door"
- },
- {
- "name": "paramed"
- },
- {
- "name": "paramed_door"
- },
- {
- "name": "paramed_open"
- },
- {
- "name": "pink_door"
- },
- {
- "name": "qm"
- },
- {
- "name": "qm_door"
- },
- {
- "name": "qm_open"
- },
- {
- "name": "rd"
- },
- {
- "name": "rd_door"
- },
- {
- "name": "rd_open"
- },
- {
- "name": "red_door"
- },
- {
- "name": "science"
- },
- {
- "name": "science_door"
- },
- {
- "name": "science_open"
- },
- {
- "name": "sec"
- },
- {
- "name": "sec_door"
- },
- {
- "name": "sec_open"
- },
- {
- "name": "secure"
- },
- {
- "name": "secure_door"
- },
- {
- "name": "secure_icon"
- },
- {
- "name": "secure_open"
- },
- {
- "name": "shotgun"
- },
- {
- "name": "shotguncase"
- },
- {
- "name": "shotguncase_door"
- },
- {
- "name": "shotguncase_open"
- },
- {
- "name": "sparking",
- "delays": [
- [
- 0.1,
- 0.1,
- 0.1,
- 0.1
- ]
- ]
- },
- {
- "name": "syndicate"
- },
- {
- "name": "syndicate_door"
- },
- {
- "name": "syndicate_open"
- },
- {
- "name": "tac"
- },
- {
- "name": "tac_door"
- },
- {
- "name": "tac_open"
- },
- {
- "name": "unlocked"
- },
- {
- "name": "warden"
- },
- {
- "name": "warden_door"
- },
- {
- "name": "warden_open"
- },
- {
- "name": "welded"
- },
- {
- "name": "white_door"
- },
- {
- "name": "yellow_door"
- },
- {
- "name": "clown"
- },
- {
- "name": "clown_door"
- },
- {
- "name": "clown_open"
- },
- {
- "name": "mime"
- },
- {
- "name": "mime_door"
- },
- {
- "name": "mime_open"
- },
- {
- "name": "representative_door"
- }
- ]
+ "version": 1,
+ "size": {
+ "x": 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, genpop lockers by EmoGarbage404 (github)",
+ "license": "CC-BY-SA-3.0",
+ "states": [
+ {
+ "name": "abductor"
+ },
+ {
+ "name": "abductor_door"
+ },
+ {
+ "name": "abductor_open"
+ },
+ {
+ "name": "agentbox"
+ },
+ {
+ "name": "alien"
+ },
+ {
+ "name": "alien_door"
+ },
+ {
+ "name": "alien_open"
+ },
+ {
+ "name": "brigmedic_door"
+ },
+ {
+ "name": "brigmedic"
+ },
+ {
+ "name": "armory"
+ },
+ {
+ "name": "armory_door"
+ },
+ {
+ "name": "armory_open"
+ },
+ {
+ "name": "atmos"
+ },
+ {
+ "name": "atmos_door"
+ },
+ {
+ "name": "atmos_open"
+ },
+ {
+ "name": "atmos_wardrobe_door"
+ },
+ {
+ "name": "bio"
+ },
+ {
+ "name": "bio_door"
+ },
+ {
+ "name": "bio_jan"
+ },
+ {
+ "name": "bio_jan_door"
+ },
+ {
+ "name": "bio_jan_open"
+ },
+ {
+ "name": "bio_open"
+ },
+ {
+ "name": "bio_sec"
+ },
+ {
+ "name": "bio_sec_door"
+ },
+ {
+ "name": "bio_sec_open"
+ },
+ {
+ "name": "bio_viro"
+ },
+ {
+ "name": "bio_viro_door"
+ },
+ {
+ "name": "bio_viro_open"
+ },
+ {
+ "name": "black_door"
+ },
+ {
+ "name": "blue_door"
+ },
+ {
+ "name": "bomb"
+ },
+ {
+ "name": "bomb_door"
+ },
+ {
+ "name": "bomb_open"
+ },
+ {
+ "name": "janitor_bomb"
+ },
+ {
+ "name": "janitor_bomb_door"
+ },
+ {
+ "name": "janitor_bomb_open"
+ },
+ {
+ "name": "cabinet"
+ },
+ {
+ "name": "cabinet_door"
+ },
+ {
+ "name": "cabinet_open"
+ },
+ {
+ "name": "cap"
+ },
+ {
+ "name": "cap_door"
+ },
+ {
+ "name": "cap_open"
+ },
+ {
+ "name": "cardboard"
+ },
+ {
+ "name": "cardboard_open"
+ },
+ {
+ "name": "cardboard_special"
+ },
+ {
+ "name": "cargo"
+ },
+ {
+ "name": "cargo_door"
+ },
+ {
+ "name": "cargo_open"
+ },
+ {
+ "name": "ce"
+ },
+ {
+ "name": "ce_door"
+ },
+ {
+ "name": "ce_open"
+ },
+ {
+ "name": "chemical_door"
+ },
+ {
+ "name": "cmo"
+ },
+ {
+ "name": "cmo_door"
+ },
+ {
+ "name": "cmo_open"
+ },
+ {
+ "name": "cursed",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "cursed_door",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "cursed_open"
+ },
+ {
+ "name": "cursed_whole",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "decursed",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "decursed_door",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "decursed_open"
+ },
+ {
+ "name": "ecase"
+ },
+ {
+ "name": "ecase_door"
+ },
+ {
+ "name": "ecase_open"
+ },
+ {
+ "name": "egun"
+ },
+ {
+ "name": "emergency"
+ },
+ {
+ "name": "emergency_door"
+ },
+ {
+ "name": "emergency_open"
+ },
+ {
+ "name": "eng"
+ },
+ {
+ "name": "eng_elec_door"
+ },
+ {
+ "name": "eng_open"
+ },
+ {
+ "name": "eng_rad_door"
+ },
+ {
+ "name": "eng_secure"
+ },
+ {
+ "name": "eng_secure_door"
+ },
+ {
+ "name": "eng_secure_open"
+ },
+ {
+ "name": "eng_tool_door"
+ },
+ {
+ "name": "eng_weld_door"
+ },
+ {
+ "name": "fire"
+ },
+ {
+ "name": "fire_door"
+ },
+ {
+ "name": "fire_open"
+ },
+ {
+ "name": "freezer"
+ },
+ {
+ "name": "freezer_icon"
+ },
+ {
+ "name": "freezer_door"
+ },
+ {
+ "name": "freezer_open"
+ },
+ {
+ "name": "generic"
+ },
+ {
+ "name": "generic_door"
+ },
+ {
+ "name": "generic_open"
+ },
+ {
+ "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": "grey_door"
+ },
+ {
+ "name": "hop"
+ },
+ {
+ "name": "hop_door"
+ },
+ {
+ "name": "hop_open"
+ },
+ {
+ "name": "hos"
+ },
+ {
+ "name": "hos_door"
+ },
+ {
+ "name": "hos_open"
+ },
+ {
+ "name": "hydro"
+ },
+ {
+ "name": "hydro_door"
+ },
+ {
+ "name": "hydro_open"
+ },
+ {
+ "name": "locked"
+ },
+ {
+ "name": "med"
+ },
+ {
+ "name": "med_door"
+ },
+ {
+ "name": "med_open"
+ },
+ {
+ "name": "med_secure"
+ },
+ {
+ "name": "med_secure_door"
+ },
+ {
+ "name": "med_secure_open"
+ },
+ {
+ "name": "metalbox"
+ },
+ {
+ "name": "metalbox_open"
+ },
+ {
+ "name": "mining"
+ },
+ {
+ "name": "mining_door"
+ },
+ {
+ "name": "mining_open"
+ },
+ {
+ "name": "mixed_door"
+ },
+ {
+ "name": "n2"
+ },
+ {
+ "name": "n2_open"
+ },
+ {
+ "name": "n2_door"
+ },
+ {
+ "name": "oldcloset"
+ },
+ {
+ "name": "orange_door"
+ },
+ {
+ "name": "paramed"
+ },
+ {
+ "name": "paramed_door"
+ },
+ {
+ "name": "paramed_open"
+ },
+ {
+ "name": "pink_door"
+ },
+ {
+ "name": "qm"
+ },
+ {
+ "name": "qm_door"
+ },
+ {
+ "name": "qm_open"
+ },
+ {
+ "name": "rd"
+ },
+ {
+ "name": "rd_door"
+ },
+ {
+ "name": "rd_open"
+ },
+ {
+ "name": "red_door"
+ },
+ {
+ "name": "science"
+ },
+ {
+ "name": "science_door"
+ },
+ {
+ "name": "science_open"
+ },
+ {
+ "name": "sec"
+ },
+ {
+ "name": "sec_door"
+ },
+ {
+ "name": "sec_open"
+ },
+ {
+ "name": "secure"
+ },
+ {
+ "name": "secure_door"
+ },
+ {
+ "name": "secure_icon"
+ },
+ {
+ "name": "secure_open"
+ },
+ {
+ "name": "shotgun"
+ },
+ {
+ "name": "shotguncase"
+ },
+ {
+ "name": "shotguncase_door"
+ },
+ {
+ "name": "shotguncase_open"
+ },
+ {
+ "name": "sparking",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "syndicate"
+ },
+ {
+ "name": "syndicate_door"
+ },
+ {
+ "name": "syndicate_open"
+ },
+ {
+ "name": "tac"
+ },
+ {
+ "name": "tac_door"
+ },
+ {
+ "name": "tac_open"
+ },
+ {
+ "name": "unlocked"
+ },
+ {
+ "name": "warden"
+ },
+ {
+ "name": "warden_door"
+ },
+ {
+ "name": "warden_open"
+ },
+ {
+ "name": "welded"
+ },
+ {
+ "name": "white_door"
+ },
+ {
+ "name": "yellow_door"
+ },
+ {
+ "name": "clown"
+ },
+ {
+ "name": "clown_door"
+ },
+ {
+ "name": "clown_open"
+ },
+ {
+ "name": "mime"
+ },
+ {
+ "name": "mime_door"
+ },
+ {
+ "name": "mime_open"
+ },
+ {
+ "name": "representative_door"
+ }
+ ]
}
diff --git a/Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png b/Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png
new file mode 100644
index 0000000000..b37bfbec45
Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png differ
diff --git a/Resources/Textures/Structures/Wallmounts/signs.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/signs.rsi/meta.json
index cff9574b6a..3a7caf0688 100644
--- a/Resources/Textures/Structures/Wallmounts/signs.rsi/meta.json
+++ b/Resources/Textures/Structures/Wallmounts/signs.rsi/meta.json
@@ -1,11 +1,11 @@
{
- "version": 1,
- "size": {
- "x": 32,
- "y": 32
- },
- "license": "CC-BY-SA-3.0",
- "copyright": "Taken from https://github.com/discordia-space/CEV-Eris at commit 4e0bbe682d0a00192d24708fdb7031008aa03f18 and bee station at commit https://github.com/BeeStation/BeeStation-Hornet/commit/13dd5ac712385642574138f6d7b30eea7c2fab9c, Job signs by EmoGarbage404 (github) with inspiration from yogstation and tgstation, 'direction_exam' and 'direction_icu' made by rosieposieeee (github), 'direction_atmos' made by SlamBamActionman, 'vox' based on sprites taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/e7f005f8b8d3f7d89cbee3b87f76c23f9e951c27/icons/obj/decals.dmi, 'direction_pods' derived by WarPigeon from existing directional signs.",
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/discordia-space/CEV-Eris at commit 4e0bbe682d0a00192d24708fdb7031008aa03f18 and bee station at commit https://github.com/BeeStation/BeeStation-Hornet/commit/13dd5ac712385642574138f6d7b30eea7c2fab9c, Job signs by EmoGarbage404 (github) with inspiration from yogstation and tgstation, 'direction_exam' and 'direction_icu' made by rosieposieeee (github), 'direction_atmos' made by SlamBamActionman, 'vox' based on sprites taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/e7f005f8b8d3f7d89cbee3b87f76c23f9e951c27/icons/obj/decals.dmi, 'direction_pods' derived by WarPigeon from existing directional signs.",
"states": [
{
"name": "ai"
@@ -258,6 +258,9 @@
{
"name": "cloning"
},
+ {
+ "name": "genpop"
+ },
{
"name": "gravi"
},