]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Genpop Closets & IDs (#36392)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Thu, 24 Apr 2025 14:32:11 +0000 (10:32 -0400)
committerGitHub <noreply@github.com>
Thu, 24 Apr 2025 14:32:11 +0000 (16:32 +0200)
* 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>
37 files changed:
Content.Client/Security/GenpopSystem.cs [new file with mode: 0644]
Content.Client/Security/Ui/GenpopLockerBoundUserInterface.cs [new file with mode: 0644]
Content.Client/Security/Ui/GenpopLockerMenu.xaml [new file with mode: 0644]
Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs [new file with mode: 0644]
Content.Server/Access/Systems/IdCardSystem.cs
Content.Server/Security/GenpopSystem.cs [new file with mode: 0644]
Content.Shared/Access/Components/ExpireIdCardComponent.cs [new file with mode: 0644]
Content.Shared/Access/Systems/SharedIdCardSystem.cs
Content.Shared/Lock/LockComponent.cs
Content.Shared/Lock/LockSystem.cs
Content.Shared/Security/Components/GenpopIdCardComponent.cs [new file with mode: 0644]
Content.Shared/Security/Components/GenpopLockerComponent.cs [new file with mode: 0644]
Content.Shared/Security/Systems/SharedGenpopSystem.cs [new file with mode: 0644]
Content.Shared/Storage/Components/SharedEntityStorageComponent.cs
Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
Resources/Locale/en-US/access/components/genpop.ftl [new file with mode: 0644]
Resources/Locale/en-US/prototypes/access/accesses.ftl
Resources/Prototypes/Access/misc.yml
Resources/Prototypes/Access/security.yml
Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
Resources/Prototypes/Entities/Structures/Doors/turnstile.yml
Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml
Resources/Prototypes/Entities/Structures/Wallmounts/Signs/signs.yml
Resources/Textures/Structures/Storage/closet.rsi/genpop.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_1.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_2.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_3.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_4.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_5.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_6.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_7.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_door_8.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png [new file with mode: 0644]
Resources/Textures/Structures/Storage/closet.rsi/meta.json
Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png [new file with mode: 0644]
Resources/Textures/Structures/Wallmounts/signs.rsi/meta.json

diff --git a/Content.Client/Security/GenpopSystem.cs b/Content.Client/Security/GenpopSystem.cs
new file mode 100644 (file)
index 0000000..2f537cd
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.Security.Systems;
+
+namespace Content.Client.Security;
+
+/// <inheritdoc/>
+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 (file)
index 0000000..a546fa6
--- /dev/null
@@ -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 (file)
index 0000000..4eb670d
--- /dev/null
@@ -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>
+
+
diff --git a/Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs b/Content.Client/Security/Ui/GenpopLockerMenu.xaml.cs
new file mode 100644 (file)
index 0000000..575b2f5
--- /dev/null
@@ -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);
+    }
+}
index 9057fade72214f3c967e16e073d18334d74323f4..05ee45b463b6cfe418f6080696cb767917616c32 100644 (file)
@@ -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<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);
+        }
+    }
 }
diff --git a/Content.Server/Security/GenpopSystem.cs b/Content.Server/Security/GenpopSystem.cs
new file mode 100644 (file)
index 0000000..0a42333
--- /dev/null
@@ -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);
+    }
+}
diff --git a/Content.Shared/Access/Components/ExpireIdCardComponent.cs b/Content.Shared/Access/Components/ExpireIdCardComponent.cs
new file mode 100644 (file)
index 0000000..68a2a97
--- /dev/null
@@ -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;
+}
index db7d9b38c87ea5ac7899323cebbc563fbb5846b2..69d77fe9ecdd3e65bf6fd40010abb1025789b8c9 100644 (file)
@@ -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<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));
+        }
+    }
 }
index 2689602ae8c9380eafb9f3986b46f3a89174b07d..0fdee2477f3d4b7df96d1d41d6ac3b7a8d9e6b1b 100644 (file)
@@ -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;
 
+    /// <summary>
+    /// Whether the lock requires access validation through <see cref="AccessReaderComponent"/>
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool UseAccess = true;
+
     /// <summary>
     /// The sound played when unlocked.
     /// </summary>
index 0b24bc67221675d7fdddc769d5b193bebe2c35cd..cbeceaf9e8be26240a76cdd85c805897e14a9e3e 100644 (file)
@@ -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 (file)
index 0000000..bf10bba
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/Security/Components/GenpopLockerComponent.cs b/Content.Shared/Security/Components/GenpopLockerComponent.cs
new file mode 100644 (file)
index 0000000..cfeb581
--- /dev/null
@@ -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
+}
diff --git a/Content.Shared/Security/Systems/SharedGenpopSystem.cs b/Content.Shared/Security/Systems/SharedGenpopSystem.cs
new file mode 100644 (file)
index 0000000..39fc87f
--- /dev/null
@@ -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)
+    {
+
+    }
+}
index 4100449f4e30ff94f1f30f6f584300626d1c24a3..06b1c15f2e1397a92996ef30a5c842fc94b417e6 100644 (file)
@@ -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<EntityUid> Contents, HashSet<EntityUid> BypassChecks);
index abd08c7459b4ea45f709c559a17abd09918c9467..75088bfeecb1d2ce6ed136de23d1edda93624601 100644 (file)
@@ -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 (file)
index 0000000..8964467
--- /dev/null
@@ -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)
index 44fd9adf0027cb9b804fb7465c72375e9183a301..4a9fa272a2579e19212134e774d9be886a5d282f 100644 (file)
@@ -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
index f79f1779c22e809b81ea2fb8bf315a2c7169b8f7..d3f6df775b104dddaad7b39511dcc38660454650 100644 (file)
@@ -32,3 +32,5 @@
   - Chapel
   - Hydroponics
   - Atmospherics
+  - GenpopEnter
+  - GenpopLeave
index cfe94dd78af6729d47e08ef61f170bc9a59f7a6c..45d8af61edd0cb23989bfded53a852a6c73003f8 100644 (file)
   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:
index 736642abb55f58532398dec14ed48fa8a18bccaa..72332010e6f8e518560a068c24ca0443be42fbfb 100644 (file)
   - 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
index 4f6762cf5f495c3f3ab6a44e973206eecd7afe34..188cbfc51b1fb7cd25e6301f384501148a7e9783 100644 (file)
     - SyndicateAgent
     - Wizard
     - Xenoborg
+    - GenpopEnter
+    - GenpopLeave
     privilegedIdSlot:
       name: id-card-console-privileged-id
       ejectSound: /Audio/Machines/id_swipe.ogg
index 6d675c59285b6a1d8152aac8eccc2d52fb07dab1..0827492d11b63f6be81b9ed545dafe78477bd523 100644 (file)
   - 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"]]
index 97ef052153bc17958e82ac97ac01aeac332c074d..df62854818ce55d82bdcf032534b8527f4c7a19d 100644 (file)
   - 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
index 90a6820daebb890a3f118de3b165ed5200a143d3..b7595baac9a1272d1a37e98c130ca068d45ee950 100644 (file)
   - 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 (file)
index 0000000..11e878a
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 (file)
index 0000000..35c3260
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 (file)
index 0000000..e7a8907
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 (file)
index 0000000..a4c82fd
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 (file)
index 0000000..ad11caa
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 (file)
index 0000000..429d432
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 (file)
index 0000000..35c4937
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 (file)
index 0000000..cd0c499
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 (file)
index 0000000..093cbc7
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 (file)
index 0000000..c6971a3
Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/genpop_open.png differ
index 5600268fde357c61388bc248b3f6f2cb083f61a5..6dbdb4dba3a2a16db6412de535827232c95a48a0 100644 (file)
 {
-  "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 (file)
index 0000000..b37bfbe
Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/signs.rsi/genpop.png differ
index cff9574b6a5c3d3e4451cc7fda48eea07907ac39..3a7caf068889a485eb94457ee86bac64fe1c8445 100644 (file)
@@ -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"
         {
             "name": "cloning"
         },
+        {
+            "name": "genpop"
+        },
         {
             "name": "gravi"
         },