From 231927a57722b0b921204e4ba35d5e2ba3bf956c Mon Sep 17 00:00:00 2001 From: Aviu00 <93730715+Aviu00@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:28:21 +0000 Subject: [PATCH] Security update (#323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add: security hud update * access & fix names * translates * fix * - fix: Fixes. * - fix: Условно-досрочно. --------- Co-authored-by: CaYpeN1 --- .../_White/SecurityHud/SecurityHudBUI.cs | 122 ++++++++++++++ .../SecurityHud/SecurityHudComponent.cs | 20 +++ .../_White/SecurityHud/SecurityHudSystem.cs | 153 ++++++++++++++++++ .../_White/SecurityHud/SecurityHudBUIState.cs | 44 +++++ .../criminal-records/criminal-records.ftl | 12 +- .../ru-RU/white/securityhud/securityhud.ftl | 6 + .../Prototypes/Entities/Clothing/Eyes/hud.yml | 14 ++ .../_White/StatusEffects/criminal_records.yml | 47 ------ .../Misc/security_icons.rsi/hud_suspected.png | Bin 139 -> 141 bytes .../Interface/securityhud.rsi/discharged.png | Bin 0 -> 225 bytes .../securityhud.rsi/incarcerated.png | Bin 0 -> 232 bytes .../White/Interface/securityhud.rsi/meta.json | 23 +++ .../Interface/securityhud.rsi/paroled.png | Bin 0 -> 236 bytes .../Interface/securityhud.rsi/released.png | Bin 0 -> 202 bytes .../Interface/securityhud.rsi/remove.png | Bin 0 -> 288 bytes .../Interface/securityhud.rsi/suspected.png | Bin 0 -> 217 bytes .../Interface/securityhud.rsi/wanted.png | Bin 0 -> 204 bytes 17 files changed, 390 insertions(+), 51 deletions(-) create mode 100644 Content.Client/_White/SecurityHud/SecurityHudBUI.cs create mode 100644 Content.Server/_White/SecurityHud/SecurityHudComponent.cs create mode 100644 Content.Server/_White/SecurityHud/SecurityHudSystem.cs create mode 100644 Content.Shared/_White/SecurityHud/SecurityHudBUIState.cs create mode 100644 Resources/Locale/ru-RU/white/securityhud/securityhud.ftl delete mode 100644 Resources/Prototypes/_White/StatusEffects/criminal_records.yml create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/discharged.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/incarcerated.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/meta.json create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/paroled.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/released.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/remove.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/suspected.png create mode 100644 Resources/Textures/White/Interface/securityhud.rsi/wanted.png diff --git a/Content.Client/_White/SecurityHud/SecurityHudBUI.cs b/Content.Client/_White/SecurityHud/SecurityHudBUI.cs new file mode 100644 index 0000000000..9e6e1de437 --- /dev/null +++ b/Content.Client/_White/SecurityHud/SecurityHudBUI.cs @@ -0,0 +1,122 @@ +using System.Linq; +using Content.Client._White.UserInterface.Radial; +using Content.Shared._White.SecurityHud; +using Content.Shared.Security; +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; + +namespace Content.Client._White.SecurityHud; + +public sealed class SecurityHudBUI : BoundUserInterface +{ + private RadialContainer? _radialContainer; + + private bool _updated; + + private readonly Dictionary _names = new() + { + { "SecurityIconDischarged", Loc.GetString("criminal-records-status-discharged")}, + { "SecurityIconParoled", Loc.GetString("criminal-records-status-paroled")}, + { "SecurityIconSuspected", Loc.GetString("criminal-records-status-suspected")}, + { "SecurityIconWanted", Loc.GetString("criminal-records-status-wanted")}, + { "SecurityIconIncarcerated", Loc.GetString("criminal-records-status-detained")}, + { "CriminalRecordIconRemove", Loc.GetString("security-hud-remove-status") } + }; + + private readonly Dictionary _icons = new() + { + { "SecurityIconDischarged", "/Textures/White/Interface/securityhud.rsi/discharged.png" }, + { "SecurityIconParoled", "/Textures/White/Interface/securityhud.rsi/paroled.png" }, + { "SecurityIconSuspected", "/Textures/White/Interface/securityhud.rsi/suspected.png" }, + { "SecurityIconWanted", "/Textures/White/Interface/securityhud.rsi/wanted.png" }, + { "SecurityIconIncarcerated", "/Textures/White/Interface/securityhud.rsi/incarcerated.png" }, + { "CriminalRecordIconRemove", "/Textures/White/Interface/securityhud.rsi/remove.png" } + }; + + private readonly Dictionary _status = new() + { + { "SecurityIconDischarged", SecurityStatus.Discharged }, + { "SecurityIconParoled", SecurityStatus.Paroled }, + { "SecurityIconSuspected", SecurityStatus.Suspected }, + { "SecurityIconWanted", SecurityStatus.Wanted }, + { "SecurityIconIncarcerated", SecurityStatus.Detained }, + { "CriminalRecordIconRemove", SecurityStatus.None } + }; + + + public SecurityHudBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + if (_radialContainer != null) + UIReset(); + + _radialContainer = new RadialContainer(); + + _radialContainer.Closed += Close; + + if (State != null) + UpdateState(State); + } + + private void UIReset() + { + _radialContainer?.Close(); + _radialContainer = null; + _updated = false; + } + + private void PopulateRadial(IReadOnlyCollection ids, NetEntity user, NetEntity target) + { + foreach (var id in ids) + { + if (_radialContainer == null) + continue; + + if(!_names.TryGetValue(id, out var name) || !_icons.TryGetValue(id, out var icon) || !_status.TryGetValue(id, out var status)) + return; + + var button = _radialContainer.AddButton(name, icon); + button.Controller.OnPressed += _ => + { + Select(status, user, target); + }; + } + } + + private void Select(SecurityStatus status, NetEntity user, NetEntity target) + { + SendMessage(new SecurityHudStatusSelectedMessage(status, user, target)); + UIReset(); + Close(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + UIReset(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (_updated) + return; + + if (state is SecurityHudBUIState newState) + { + PopulateRadial(newState.Ids, newState.User, newState.Target); + } + + if (_radialContainer == null) + return; + + _radialContainer?.OpenAttachedLocalPlayer(); + _updated = true; + } +} diff --git a/Content.Server/_White/SecurityHud/SecurityHudComponent.cs b/Content.Server/_White/SecurityHud/SecurityHudComponent.cs new file mode 100644 index 0000000000..f68034ffab --- /dev/null +++ b/Content.Server/_White/SecurityHud/SecurityHudComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Radio; +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server._White.SecurityHud; + +[RegisterComponent] +public sealed partial class SecurityHudComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + [DataField("criminalrecords", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public IReadOnlyCollection Status = ArraySegment.Empty; + + [ViewVariables(VVAccess.ReadOnly)] + public ProtoId SecurityChannel = "Security"; + + [ViewVariables(VVAccess.ReadOnly)] + public string Reason = "Изменено с помощью визора"; +} diff --git a/Content.Server/_White/SecurityHud/SecurityHudSystem.cs b/Content.Server/_White/SecurityHud/SecurityHudSystem.cs new file mode 100644 index 0000000000..22c36b4fd6 --- /dev/null +++ b/Content.Server/_White/SecurityHud/SecurityHudSystem.cs @@ -0,0 +1,153 @@ +using Content.Server.Access.Systems; +using Content.Server.CriminalRecords.Systems; +using Content.Server.Popups; +using Content.Server.Radio.EntitySystems; +using Content.Server.StationRecords.Systems; +using Content.Shared._Miracle.Components; +using Content.Shared._White.SecurityHud; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.CriminalRecords; +using Content.Shared.Humanoid; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Security; +using Content.Shared.Security.Components; +using Content.Shared.StationRecords; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Player; + +namespace Content.Server._White.SecurityHud; + +public sealed class SecurityHudSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly CriminalRecordsSystem _criminalRecordsSystem = default!; + [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsoleSystem = default!; + [Dependency] private readonly StationRecordsSystem _stationRecordsSystem = default!; + [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly InventorySystem _invSlotsSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnAltVerb); + SubscribeLocalEvent(OnStatusSelected); + } + + private void OnAltVerb(GetVerbsEvent args) + { + if(!HasComp(args.Target)) + return; + + if(!_invSlotsSystem.TryGetSlotEntity(args.User, "eyes", out var ent)) + return; + + if(!TryComp(ent, out var component)) + return; + + if(!TryComp(ent, out var accessReaderComponent)) + return; + + if (!_accessReaderSystem.IsAllowed(args.User, (EntityUid) ent, accessReaderComponent)) + { + _popupSystem.PopupEntity(Loc.GetString("security-hud-not-allowed"), args.User, args.User, PopupType.Medium); + return; + } + + AlternativeVerb verb = new() + { + Act = () => + { + SetWanted(args.User, args.Target, ent.Value, component); + }, + Disabled = false, + Priority = 0, + Text = Loc.GetString("security-hud-verb"), + }; + + args.Verbs.Add(verb); + } + + private void SetWanted(EntityUid uid, EntityUid target, EntityUid hud, SecurityHudComponent component) + { + if (!TryComp(uid, out var actor)) + return; + + if (_ui.TryGetUi(hud, SecurityHudUiKey.Key, out var bui)) + { + _ui.SetUiState(bui, new SecurityHudBUIState(component.Status, GetNetEntity(uid), GetNetEntity(target))); + _ui.OpenUi(bui, actor.PlayerSession); + } + } + + private void OnStatusSelected(EntityUid uid, SecurityHudComponent component, SecurityHudStatusSelectedMessage args) + { + var user = GetEntity(args.User); + var target = GetEntity(args.Target); + + if (!_idCardSystem.TryFindIdCard(target, out var idCard)) + { + _popupSystem.PopupEntity(Loc.GetString("security-hud-id-unknown"), user, user, PopupType.Medium); + return; + } + + if(!TryComp(idCard, out var stationRecordKeyComp)) + return; + + if (stationRecordKeyComp.Key == null) + { + _popupSystem.PopupEntity(Loc.GetString("security-hud-key-null"), user, user, PopupType.Medium); + return; + } + + var key = stationRecordKeyComp.Key.Value; + + if (!SetCriminalStatus(key, args.Status, uid, user, idCard.Comp, component.Reason, component.SecurityChannel)) + { + _popupSystem.PopupEntity(Loc.GetString("security-hud-cant-set-status"), user, user, PopupType.Medium); + } + } + + private bool SetCriminalStatus(StationRecordKey key, SecurityStatus status, EntityUid hud, EntityUid officer, + IdCardComponent idCard, string reason, string securityChannel) + { + if (!_stationRecordsSystem.TryGetRecord(key, out var generalRecord)) + return false; + + if (!_stationRecordsSystem.TryGetRecord(key, out var record) || record.Status == status) + return false; + + var name = generalRecord.Name; + var officerName = Loc.GetString("criminal-records-console-unknown-officer"); + if (_idCardSystem.TryFindIdCard(officer, out var id) && id.Comp.FullName is { } fullName) + officerName = fullName; + + _criminalRecordsSystem.TryChangeStatus(key, status, reason); + + var locArgs = new (string, object)[] { ("name", name), ("officer", officerName), ("reason", reason) }; + + var statusString = (record.Status, status) switch + { + (_, SecurityStatus.Detained) => "detained", + (_, SecurityStatus.Suspected) => "suspected", + (_, SecurityStatus.Paroled) => "paroled", + (_, SecurityStatus.Discharged) => "released", + (_, SecurityStatus.Wanted) => "wanted", + (SecurityStatus.Suspected, SecurityStatus.None) => "not-suspected", + (SecurityStatus.Wanted, SecurityStatus.None) => "not-wanted", + (SecurityStatus.Detained, SecurityStatus.None) => "released", + (SecurityStatus.Paroled, SecurityStatus.None) => "not-parole", + _ => "not-wanted" + }; + + _radio.SendRadioMessage(hud, Loc.GetString($"criminal-records-console-{statusString}", locArgs), securityChannel, hud); + _criminalRecordsConsoleSystem.UpdateCriminalIdentity(name, status); + + return true; + } +} diff --git a/Content.Shared/_White/SecurityHud/SecurityHudBUIState.cs b/Content.Shared/_White/SecurityHud/SecurityHudBUIState.cs new file mode 100644 index 0000000000..5ec4dc9a4e --- /dev/null +++ b/Content.Shared/_White/SecurityHud/SecurityHudBUIState.cs @@ -0,0 +1,44 @@ +using Content.Shared.Security; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.SecurityHud; + +[Serializable, NetSerializable] +public enum SecurityHudUiKey +{ + Key +} + +[Serializable, NetSerializable] +public sealed class SecurityHudBUIState : BoundUserInterfaceState +{ + public IReadOnlyCollection Ids { get; set; } + + public NetEntity User { get; set; } + + public NetEntity Target { get; private set; } + + public SecurityHudBUIState(IReadOnlyCollection ids, NetEntity user, NetEntity target) + { + Ids = ids; + User = user; + Target = target; + } +} + +[Serializable, NetSerializable] +public class SecurityHudStatusSelectedMessage : BoundUserInterfaceMessage +{ + public SecurityStatus Status { get; private set; } + + public NetEntity User { get; private set; } + + public NetEntity Target { get; private set; } + + public SecurityHudStatusSelectedMessage(SecurityStatus status, NetEntity user, NetEntity target) + { + Status = status; + User = user; + Target = target; + } +} diff --git a/Resources/Locale/ru-RU/criminal-records/criminal-records.ftl b/Resources/Locale/ru-RU/criminal-records/criminal-records.ftl index 78f3c1e5c7..698d9ba4d8 100644 --- a/Resources/Locale/ru-RU/criminal-records/criminal-records.ftl +++ b/Resources/Locale/ru-RU/criminal-records/criminal-records.ftl @@ -10,11 +10,12 @@ criminal-records-console-status = Статус criminal-records-status-none = Нет criminal-records-status-wanted = В розыске criminal-records-status-detained = В заключении - criminal-records-status-suspected = Подозреваемый -criminal-records-status-released = Отпущен +criminal-records-status-discharged = Освобождён +criminal-records-status-paroled = Досрочно освобождён criminal-records-console-wanted-reason = [color=gray]Причина розыска[/color] +criminal-records-console-suspected-reason = [color=gray]Причина подозрения[/color] criminal-records-console-reason = Причина criminal-records-console-reason-placeholder = Например: {$placeholder} @@ -31,12 +32,15 @@ criminal-records-permission-denied = Доступ воспрещен ## Security channel notifications criminal-records-console-wanted = {$name} отправлен в розыск по указу {$officer} по причине: {$reason}. +criminal-records-console-suspected = {$name} подозревается по указу {$officer} по причине: {$reason}. +criminal-records-console-not-suspected = {$name} больше не под подозрением. criminal-records-console-detained = {$name} был задержан {$officer}. -criminal-records-console-released = {$name} был задержан {$officer}. +criminal-records-console-released = {$name} был освобожден {$officer}. criminal-records-console-not-wanted = {$name} больше не в розыске. +criminal-records-console-paroled = {$name} был отпущен условно-досрочно {$officer}. +criminal-records-console-not-parole = {$name} больше не условно-досрочно освобождённый. criminal-records-console-unknown-officer = <неизвестный офицер> -criminal-records-console-suspected = {$name} подозревается по указу {$officer} по причине: {$reason}. ## Filters diff --git a/Resources/Locale/ru-RU/white/securityhud/securityhud.ftl b/Resources/Locale/ru-RU/white/securityhud/securityhud.ftl new file mode 100644 index 0000000000..33d6592335 --- /dev/null +++ b/Resources/Locale/ru-RU/white/securityhud/securityhud.ftl @@ -0,0 +1,6 @@ +security-hud-key-null = Визор не может установить личность человека! +security-hud-id-unknown = Визор не может установить идентификационную карту человека! +security-hud-verb = Изменить статус +security-hud-cant-set-status = Произошла ошибка при попытке установить статус! +security-hud-remove-status = Убрать статус. +security-hud-not-allowed = Недостаточный доступ для взаимодействия. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml index b53ef94bef..3fcad2d428 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml @@ -44,6 +44,20 @@ - type: Clothing sprite: Clothing/Eyes/Hud/sec.rsi - type: ShowSecurityIcons + - type: AccessReader + access: [["Security"]] + - type: SecurityHud + criminalrecords: + - SecurityIconDischarged + - SecurityIconParoled + - SecurityIconSuspected + - SecurityIconWanted + - SecurityIconIncarcerated + - CriminalRecordIconRemove + - type: UserInterface + interfaces: + - key: enum.SecurityHudUiKey.Key + type: SecurityHudBUI - type: Tag tags: - HudSecurity diff --git a/Resources/Prototypes/_White/StatusEffects/criminal_records.yml b/Resources/Prototypes/_White/StatusEffects/criminal_records.yml deleted file mode 100644 index 108360cd17..0000000000 --- a/Resources/Prototypes/_White/StatusEffects/criminal_records.yml +++ /dev/null @@ -1,47 +0,0 @@ -- type: statusIcon - id: CriminalRecordIcon - abstract: true - priority: 2 - locationPreference: Right - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconReleased - icon: - sprite: /Textures/White/Interface/records.rsi - state: released - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconDischarged - icon: - sprite: /Textures/White/Interface/records.rsi - state: discharged - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconParolled - icon: - sprite: /Textures/White/Interface/records.rsi - state: parolled - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconSuspected - icon: - sprite: /Textures/White/Interface/records.rsi - state: suspected - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconWanted - icon: - sprite: /Textures/White/Interface/records.rsi - state: wanted - -- type: statusIcon - parent: CriminalRecordIcon - id: CriminalRecordIconIncarcerated - icon: - sprite: /Textures/White/Interface/records.rsi - state: incarcerated diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_suspected.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_suspected.png index cfb34742bfb577bc33f5cf6c7463f8e8623e6823..8e04eb21f43459330ca7109ee238472ef61f0f7a 100644 GIT binary patch delta 99 zcmeBX>}8x_7~}5g;us<^H92Qu!mslU>>CV#fW@sKVQS3&N9@ePn;OFYhqJByw_c+w zKqP^gMRbEg@jB6>$L5T)r6nXKBqRzdw>~>+@5XTKzQC@sVy5>DK;Y@>=d#Wzp$Pyf CHY73t delta 97 zcmV-n0G|Ji0gC~UF_ITaRpM8W$f1EeKlD3J( diff --git a/Resources/Textures/White/Interface/securityhud.rsi/discharged.png b/Resources/Textures/White/Interface/securityhud.rsi/discharged.png new file mode 100644 index 0000000000000000000000000000000000000000..3206bf0693fadc99bcd295be284cdc3ed130fa9d GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}vproLLn2y} zQ+obR|p00i_>zopr0MgJAyYwDzLbF(2)K7O?3|{-b$@-4Iaht~Zf)03S2wb0{9)v83}o-IO1Q$< z^P<%8^&@VRtp!_TBue-Wtl-wrea(4taWg{6Vm^sdhUCy5JCK3Q=Il$Ne*AjO@B6ze zh-+344-lM}i%CyNkYG_fpmgN;xdTA(;oW8bT@f6gclJpxxhC^LfPvvtzWUTvk66@z Pu4V9a^>bP0l+XkKT;Nf+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/securityhud.rsi/meta.json b/Resources/Textures/White/Interface/securityhud.rsi/meta.json new file mode 100644 index 0000000000..f5ffc7b5b7 --- /dev/null +++ b/Resources/Textures/White/Interface/securityhud.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/blob/8e49222b72f6fdcbe741d2a6ce0a8425d95010b7/icons/mob/huds/hud.dmi", + "version": 1, + "size": { "y": 32, "x": 32 }, + "states": [ + { + "name": "wanted" + }, + { + "name": "suspected" + }, + { + "name": "released" + }, + { + "name": "incarcerated" + }, + { + "name": "remove" + } + ] +} diff --git a/Resources/Textures/White/Interface/securityhud.rsi/paroled.png b/Resources/Textures/White/Interface/securityhud.rsi/paroled.png new file mode 100644 index 0000000000000000000000000000000000000000..df2f1898dec0375b30844ee5b631fb9e89ef0cee GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}%RF5iLn2y} zdEN^AIp4tIa&)F-;18iQJX0F3>;GHyDgK}9#{N#F%PSe&WDhjyDQF%1BR|`zh_Onl z>aUQgFHZ$Wj}?P5uSvz81*=$jtyMmp;)e!Vl~=EN-$6RZnz3MMhNkiWKhV}z1 am>B}>RJL_%;4$j=d#Wzp$P!6WltOc literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/securityhud.rsi/released.png b/Resources/Textures/White/Interface/securityhud.rsi/released.png new file mode 100644 index 0000000000000000000000000000000000000000..3b58b7ccd0dda0187f1aa281a2ccb8cb5ac017bd GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}EuJopArY;~ zDLsF_&rf7vyzCHV_lkiH2*gSlOM~5JMOAz@$(khnK|rFE!A)vMoJTCT$?uP+`71bj ztQeGe1(;eMiP-Wk{5*5fg&#|-8g3x3;bKz+(p`xCbKT0D|D va7g<^gV0NJ|NqM~;|1^TW8`lPWM^P_#3rSfzhF}*&^-*Eu6{1-oD!MPx#+DSw~R9J=8m)#MBAPj^*?8zSJ%ckt%it--R84pk(BxWv;PS{-u`3oP|R(6ZP z3;{z+`N9I*)xlE4mHf0Rby^R{H^>2|Soo+d;*LCWT=kQ~XgF%vq1DlX91>tn5v^Dm zsD_9hj3VHOh~BfeU=k6MGirY$y|rpgTYyJGHvFhyFI*DxW&uwkO4gGxD!DBs?-o!K zAt_i{H+$E+>*RSM7A4hlh;IK-Oy?| mogtLgz@OdFdOc4?MBoe4ESTPkg1?6U&_D?1l+qE5?I)Mbz}BFV&|$q;GA%UQ6jTJ;KHl_UE%+t6wN)K z@m6s3SOJ9>^GTE*Si$VEj@v``HD@Cue`6qf&o*{9*#j2$r?e`5{36c=1Z_W;>G1f= z|6iOL9~d>QQ}CvpTM6rycWbWg`O%*AyuqFEuB^fRDXgu$42pI#dyhwcC`_pS9EAfug}HhNe=+m8rioVhx_i9N59) m`@pXEcUKn=k47g%8^e!YiRjKn$Iby=#Ng@b=d#Wzp$P!&r$=1? literal 0 HcmV?d00001