using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.CCVar; 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.Configuration; using Robust.Shared.Timing; namespace Content.Shared.Security.Systems; public abstract class SharedGenpopSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _cfgManager = default!; [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!; // CCvar. private int _maxIdJobLength; /// public override void Initialize() { SubscribeLocalEvent(OnIdConfigured); SubscribeLocalEvent(OnCloseAttempt); SubscribeLocalEvent(OnLockToggleAttempt); SubscribeLocalEvent(OnLockToggled); SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent(OnExamine); Subs.CVar(_cfgManager, CCVars.MaxIdJobLength, value => _maxIdJobLength = value, true); } private void OnIdConfigured(Entity ent, ref GenpopLockerIdConfiguredMessage args) { // validation. if (string.IsNullOrWhiteSpace(args.Name) || args.Name.Length > _maxIdJobLength || 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) { } }