From 35a142965da1d5b49146798b622c31574d0c0eb9 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:55:33 +1200 Subject: [PATCH] Firelock ECS + some other stuff. (#8366) * firelock rejig * dont error failed resolves * fix * less resolves * switch fire and pressure messages * update Co-authored-by: wrexbe --- Content.Client/Doors/AirlockVisualizer.cs | 50 +++-- .../AtmosphereSystem.Monstermos.cs | 7 +- .../Doors/Components/FirelockComponent.cs | 98 ++-------- .../Doors/Systems/FirelockSystem.cs | 178 +++++++++++++++--- .../Doors/Components/DoorComponent.cs | 1 + .../Glass/external.rsi/closed_unlit.png | Bin 83 -> 5527 bytes .../Glass/shuttle.rsi/bolted_unlit.png | Bin 5035 -> 4972 bytes .../Glass/shuttle.rsi/closed_unlit.png | Bin 4972 -> 5035 bytes .../Standard/external.rsi/closed_unlit.png | Bin 83 -> 5527 bytes .../Standard/shuttle.rsi/bolted_unlit.png | Bin 5035 -> 4972 bytes .../Standard/shuttle.rsi/closed_unlit.png | Bin 4972 -> 5035 bytes 11 files changed, 192 insertions(+), 142 deletions(-) diff --git a/Content.Client/Doors/AirlockVisualizer.cs b/Content.Client/Doors/AirlockVisualizer.cs index e2ff79362a..59aa77b97a 100644 --- a/Content.Client/Doors/AirlockVisualizer.cs +++ b/Content.Client/Doors/AirlockVisualizer.cs @@ -160,9 +160,6 @@ namespace Content.Client.Doors } var door = _entMan.GetComponent(component.Owner); - var unlitVisible = true; - var boltedVisible = false; - var emergencyLightsVisible = false; if (component.TryGetData(DoorVisuals.BaseRSI, out string baseRsi)) { @@ -184,7 +181,6 @@ namespace Content.Client.Doors { case DoorState.Open: sprite.LayerSetState(DoorVisualLayers.Base, "open"); - unlitVisible = _openUnlitVisible; if (_openUnlitVisible && !_simpleVisuals) { sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit"); @@ -219,33 +215,33 @@ namespace Content.Client.Doors throw new ArgumentOutOfRangeException(); } - if (component.TryGetData(DoorVisuals.Powered, out bool powered) && !powered) + if (_simpleVisuals) + return; + + var boltedVisible = false; + var emergencyLightsVisible = false; + var unlitVisible = false; + + if (component.TryGetData(DoorVisuals.Powered, out bool powered) && powered) { - unlitVisible = false; - } - if (component.TryGetData(DoorVisuals.BoltLights, out bool lights) && lights) - { - boltedVisible = true; + boltedVisible = component.TryGetData(DoorVisuals.BoltLights, out bool lights) && lights; + emergencyLightsVisible = component.TryGetData(DoorVisuals.EmergencyLights, out bool eaLights) && eaLights; + unlitVisible = state == DoorState.Closing + || state == DoorState.Opening + || state == DoorState.Denying + || state == DoorState.Open && _openUnlitVisible + || (component.TryGetData(DoorVisuals.ClosedLights, out bool closedLights) && closedLights); } - if (component.TryGetData(DoorVisuals.EmergencyLights, out bool eaLights) && eaLights) + sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible); + sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible); + if (_emergencyAccessLayer) { - emergencyLightsVisible = true; - } - - if (!_simpleVisuals) - { - sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && state != DoorState.Closed && state != DoorState.Welded); - sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible); - if (_emergencyAccessLayer) - { - sprite.LayerSetVisible(DoorVisualLayers.BaseEmergencyAccess, - emergencyLightsVisible - && state != DoorState.Open - && state != DoorState.Opening - && state != DoorState.Closing - && unlitVisible); - } + sprite.LayerSetVisible(DoorVisualLayers.BaseEmergencyAccess, + emergencyLightsVisible + && state != DoorState.Open + && state != DoorState.Opening + && state != DoorState.Closing); } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index b31485654b..7be214e184 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.Components; using Content.Server.Doors.Components; +using Content.Server.Doors.Systems; using Content.Shared.Atmos; using Content.Shared.Database; using Robust.Shared.Map; @@ -10,6 +11,8 @@ namespace Content.Server.Atmos.EntitySystems { public sealed partial class AtmosphereSystem { + [Dependency] private readonly FirelockSystem _firelockSystem = default!; + private readonly TileAtmosphereComparer _monstermosComparer = new(); private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; @@ -502,7 +505,7 @@ namespace Content.Server.Atmos.EntitySystems if (!TryComp(entity, out FirelockComponent? firelock)) continue; - reconsiderAdjacent |= firelock.EmergencyPressureStop(); + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices)) @@ -510,7 +513,7 @@ namespace Content.Server.Atmos.EntitySystems if (!TryComp(entity, out FirelockComponent? firelock)) continue; - reconsiderAdjacent |= firelock.EmergencyPressureStop(); + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } if (!reconsiderAdjacent) diff --git a/Content.Server/Doors/Components/FirelockComponent.cs b/Content.Server/Doors/Components/FirelockComponent.cs index 248d080948..7bb35ad2c9 100644 --- a/Content.Server/Doors/Components/FirelockComponent.cs +++ b/Content.Server/Doors/Components/FirelockComponent.cs @@ -1,103 +1,35 @@ -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Doors.Systems; using Content.Shared.Doors.Components; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Doors.Components { /// - /// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, - /// and not being openable on open-hand click. + /// Companion component to that handles firelock-specific behavior, including + /// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when + /// retaining pressure.. /// [RegisterComponent] public sealed class FirelockComponent : Component { - [Dependency] private readonly IEntityManager _entMan = default!; - /// /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure. /// /// - [DataField("lockedPryTimeModifier")] + [DataField("lockedPryTimeModifier"), ViewVariables(VVAccess.ReadWrite)] public float LockedPryTimeModifier = 1.5f; [DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f); - public bool EmergencyPressureStop() - { - var doorSys = EntitySystem.Get(); - if (_entMan.TryGetComponent(Owner, out var door) && - door.State == DoorState.Open && - doorSys.CanClose(Owner, door)) - { - doorSys.StartClosing(Owner, door); + /// + /// Maximum pressure difference before the firelock will refuse to open, in kPa. + /// + [DataField("pressureThreshold"), ViewVariables(VVAccess.ReadWrite)] + public float PressureThreshold = 20; - // Door system also sets airtight, but only after a delay. We want it to be immediate. - if (_entMan.TryGetComponent(Owner, out AirtightComponent? airtight)) - { - EntitySystem.Get().SetAirblocked(airtight, true); - } - return true; - } - return false; - } - - public bool IsHoldingPressure(float threshold = 20) - { - var transform = _entMan.GetComponent(Owner); - - if (transform.GridUid is not {} gridUid) - return false; - - var atmosphereSystem = _entMan.EntitySysManager.GetEntitySystem(); - var transformSystem = _entMan.EntitySysManager.GetEntitySystem(); - - var position = transformSystem.GetGridOrMapTilePosition(Owner, transform); - - var minMoles = float.MaxValue; - var maxMoles = 0f; - - foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(gridUid, position)) - { - var moles = adjacent.TotalMoles; - if (moles < minMoles) - minMoles = moles; - if (moles > maxMoles) - maxMoles = moles; - } - - return (maxMoles - minMoles) > threshold; - } - - public bool IsHoldingFire() - { - var atmosphereSystem = _entMan.EntitySysManager.GetEntitySystem(); - var transformSystem = _entMan.EntitySysManager.GetEntitySystem(); - - var transform = _entMan.GetComponent(Owner); - var position = transformSystem.GetGridOrMapTilePosition(Owner, transform); - - // No grid, no fun. - if (transform.GridUid is not {} gridUid) - return false; - - if (atmosphereSystem.GetTileMixture(gridUid, null, position) == null) - return false; - - if (atmosphereSystem.IsHotspotActive(gridUid, position)) - return true; - - foreach (var adjacent in atmosphereSystem.GetAdjacentTiles(gridUid, position)) - { - if (atmosphereSystem.IsHotspotActive(gridUid, adjacent)) - return true; - } - - return false; - } + /// + /// If true, and if this door has an , then it will only auto-close if the + /// alarm is set to danger. + /// + [DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)] + public bool AlarmAutoClose = true; } } diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index c066a06c61..cdc6be575f 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -1,68 +1,133 @@ -using Content.Server.Atmos.Monitor.Components; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Monitor.Systems; using Content.Server.Doors.Components; +using Content.Server.Popups; using Content.Server.Power.EntitySystems; +using Content.Server.Power.Components; using Content.Shared.Atmos.Monitor; using Content.Shared.Doors; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; -using Content.Shared.Popups; +using Robust.Server.GameObjects; +using Robust.Shared.Player; namespace Content.Server.Doors.Systems { public sealed class FirelockSystem : EntitySystem { + [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; + [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; + [Dependency] private readonly AirtightSystem _airtightSystem = default!; + + private static float _visualUpdateInterval = 0.2f; + private float _accumulatedFrameTime; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnBeforeDoorOpened); - SubscribeLocalEvent(OnBeforeDoorDenied); SubscribeLocalEvent(OnDoorGetPryTimeModifier); - SubscribeLocalEvent(OnBeforeDoorPry); SubscribeLocalEvent(OnUpdateState); SubscribeLocalEvent(OnBeforeDoorAutoclose); SubscribeLocalEvent(OnAtmosAlarm); + + // Visuals + SubscribeLocalEvent(UpdateVisuals); + } + + #region Visuals + private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid); + + public override void Update(float frameTime) + { + _accumulatedFrameTime += frameTime; + if (_accumulatedFrameTime < _visualUpdateInterval) + return; + + _accumulatedFrameTime -= _visualUpdateInterval; + + var powerQuery = GetEntityQuery(); + + foreach (var (_, door, appearance, xform) in EntityQuery()) + { + UpdateVisuals(door.Owner, door, appearance, powerQuery); + } + } + + private void UpdateVisuals(EntityUid uid, + DoorComponent? door = null, + AppearanceComponent? appearance = null, + EntityQuery? powerQuery = null) + { + if (!Resolve(uid, ref door, ref appearance, false)) + return; + + // only bother to check pressure on doors that are some variation of closed. + if (door.State != DoorState.Closed + && door.State != DoorState.Welded + && door.State != DoorState.Denying) + { + appearance.SetData(DoorVisuals.ClosedLights, false); + return; + } + + powerQuery ??= EntityManager.GetEntityQuery(); + if (powerQuery.Value.TryGetComponent(uid, out var receiver) && !receiver.Powered) + { + appearance.SetData(DoorVisuals.ClosedLights, false); + return; + } + + appearance.SetData(DoorVisuals.ClosedLights, + IsHoldingPressureOrFire(uid)); + } + #endregion + + public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null) + { + if (!Resolve(uid, ref firelock, ref door)) + return false; + + if (door.State == DoorState.Open) + { + if (_doorSystem.TryClose(door.Owner, door)) + { + return _doorSystem.OnPartialClose(door.Owner, door); + } + } + return false; } private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) { - if (!this.IsPowered(uid, EntityManager) || component.IsHoldingFire() || component.IsHoldingPressure()) + if (!this.IsPowered(uid, EntityManager) || IsHoldingPressureOrFire(uid)) args.Cancel(); } - private void OnBeforeDoorDenied(EntityUid uid, FirelockComponent component, BeforeDoorDeniedEvent args) - { - args.Cancel(); - } - private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args) { - if (component.IsHoldingFire() || component.IsHoldingPressure()) + var state = CheckPressureAndFire(uid); + + if (state.Fire) + { + _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"), + uid, Filter.Pvs(uid, entityManager: EntityManager)); + } + else if (state.Pressure) + { + _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"), + uid, Filter.Pvs(uid, entityManager: EntityManager)); + } + + if (state.Fire || state.Pressure) args.PryTimeModifier *= component.LockedPryTimeModifier; } - private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args) - { - if (!TryComp(uid, out var door) || door.State != DoorState.Closed) - { - return; - } - - if (component.IsHoldingPressure()) - { - component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-pressure-message")); - } - else if (component.IsHoldingFire()) - { - component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-fire-message")); - } - } - private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args) { var ev = new BeforeDoorAutoCloseEvent(); @@ -73,6 +138,7 @@ namespace Content.Server.Doors.Systems } _doorSystem.SetNextStateChange(uid, component.AutocloseDelay); + UpdateVisuals(uid, component, args); } private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) @@ -83,7 +149,8 @@ namespace Content.Server.Doors.Systems // Make firelocks autoclose, but only if the last alarm type it // remembers was a danger. This is to prevent people from // flooding hallways with endless bad air/fire. - if (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null) + if (component.AlarmAutoClose && + (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null)) args.Cancel(); } @@ -98,7 +165,58 @@ namespace Content.Server.Doors.Systems } else if (args.AlarmType == AtmosAlarmType.Danger) { - component.EmergencyPressureStop(); + EmergencyPressureStop(uid, component, doorComponent); + } + } + + public bool IsHoldingPressureOrFire(EntityUid uid) + { + var result = CheckPressureAndFire(uid); + return result.Pressure || result.Fire; + } + + public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid owner) + { + float threshold = 20; + var atmosphereSystem = EntityManager.EntitySysManager.GetEntitySystem(); + var transformSystem = EntityManager.EntitySysManager.GetEntitySystem(); + var transform = EntityManager.GetComponent(owner); + var position = transformSystem.GetGridOrMapTilePosition(owner, transform); + if (transform.GridUid is not {} gridUid) + return (false, false); + var minMoles = float.MaxValue; + var maxMoles = 0f; + + return (IsHoldingPressure(), IsHoldingFire()); + + bool IsHoldingPressure() + { + foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(gridUid, position)) + { + var moles = adjacent.TotalMoles; + if (moles < minMoles) + minMoles = moles; + if (moles > maxMoles) + maxMoles = moles; + } + + return (maxMoles - minMoles) > threshold; + } + + bool IsHoldingFire() + { + if (atmosphereSystem.GetTileMixture(gridUid, null, position) == null) + return false; + + if (atmosphereSystem.IsHotspotActive(gridUid, position)) + return true; + + foreach (var adjacent in atmosphereSystem.GetAdjacentTiles(gridUid, position)) + { + if (atmosphereSystem.IsHotspotActive(gridUid, adjacent)) + return true; + } + return false; } } } diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs index 20376b23f8..b2f8a6b585 100644 --- a/Content.Shared/Doors/Components/DoorComponent.cs +++ b/Content.Shared/Doors/Components/DoorComponent.cs @@ -231,6 +231,7 @@ public enum DoorVisuals Powered, BoltLights, EmergencyLights, + ClosedLights, BaseRSI, } diff --git a/Resources/Textures/Structures/Doors/Airlocks/Glass/external.rsi/closed_unlit.png b/Resources/Textures/Structures/Doors/Airlocks/Glass/external.rsi/closed_unlit.png index 6e3cb09bcf701bc53ecc7886c19b82d386469aa6..79125cbe2e941de31226793d59c9514305570d03 100644 GIT binary patch literal 5527 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 AaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K3lJq7F zhWDHzN3bM>#BumjRc?^u=f!q+zUk>KCdn$*rVJRcB=i85)B5B0NWbA?DkWt#FSV2d zSFX8oL&cBlYG+PIKJ)Y9y9>`>+S9Xx(IhD4dh~LSf3(w6z<*UV)btlYKJJeGwWH=+ zzP|{%70I?}TQP4z^%f+2brk;( zdxB{NG7C6u**jGv$EqG!3iB98M;cU#fh#D}q((vOEC&skn0X~ORG0?{?nW2m%0o84 zyb}c*7*27p9l$PV<)@vX0 zS+RaBJAc+tQ_-xdMNM6+wjw1LEu~o5hhj6aoJ_5lnOn8y;@Q=Uo4Z$Um8;;;)LN=l zueG)Yoeh#3-fkGsTIVi3cTK%?>)vZ`1IuURp`#2NKI&+br%aq5vrL;l>uie_meR^g zS6Q}v)ztz^EhI$_jS?0fHQJNfjrCjl0X4d*@qbBb540yWw3`LlV;HHM*`!Sj1oI-+pa;9e7tu}bUvW#8zi{J!N-k}5KP2~^ z+dFD=e}=|M?AnFfsfS?qp?`T}Z~IvMR;It}_-XkO^bzzC^bzzC^bz#`6BJ!a_tqkp zwtuYP=PqdGj<3GyS~jH+Is$?IoHho&&AJrdbo6#R@XcPFZ1hlZlTrgwzm14|Y(3mp z_1HfHx7PjKqSn<{A7_OT*9oz-k6xU|oOSO+mu?g)b5sX3O7Qbbml(bt-7+G==hzuAf%uIIyX=u;^EAQvJy z*+LC58)O1VkK7lcCei6@4Z_dWYIC4-qItFR^(}rBgoJiX9?1<+SOlEy7J~$V-6JY> zAy5PNB@qu-0KSZR0mWbDYnko}-k3H02@qNp{Vw5g2($A8+XH_vGh<^nIWROWWMyVG zEi_{?Vl84~H#aRdH)T0DWH&G~F*P$H3LqdLcx`Y^O*%<#b97;DV`WK1JtBB*a4uY(#Hkb#0SP2oEGQFf(I0G&o@`VK*^hEi__eVJ%@XHeoF{F*jjhW-w-BWMeUt zfCxtkIX5*nGBPqbHy{D4^000SaNLh0L01FZT z01FZU(%pXi00007bV*G`2jvV26A2?copmPGsxsFwd zfQ}|yEd#@RDm&oaDTWVMuH#h#lNS&^HFTmD01zJ=gNZSmZU6uP07*qoM6N<$f^;c| AaB^>EX>4U6ba`-PAZ2)IW&i+q+U-|clI$i7 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 AaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K3lJq7F zhWDHzN3bM>#BumjRc?^u=f!q+zUk>KCdn$*rVJRcB=i85)B5B0NWbA?DkWt#FSV2d zSFX8oL&cBlYG+PIKJ)Y9y9>`>+S9Xx(IhD4dh~LSf3(w6z<*UV)btlYKJJeGwWH=+ zzP|{%70I?}TQP4z^%f+2brk;( zdxB{NG7C6u**jGv$EqG!3iB98M;cU#fh#D}q((vOEC&skn0X~ORG0?{?nW2m%0o84 zyb}c*7*27p9l$PV<)@vX0 zS+RaBJAc+tQ_-xdMNM6+wjw1LEu~o5hhj6aoJ_5lnOn8y;@Q=Uo4Z$Um8;;;)LN=l zueG)Yoeh#3-fkGsTIVi3cTK%?>)vZ`1IuURp`#2NKI&+br%aq5vrL;l>uie_meR^g zS6Q}v)ztz^EhI$_jS?0fHQJNfjrCjl0X4d*@qbBb540yWw3`LlV;HHM*`!Sj1oI-+pa;9e7tu}bUvW#8zi{J!N-k}5KP2~^ z+dFD=e}=|M?AnFfsfS?qp?`T}Z~IvMR;It}_-XkO^bzzC^bzzC^bz#`6BJ!a_tqkp zwtuYP=PqdGj<3GyS~jH+Is$?IoHho&&AJrdbo6#R@XcPFZ1hlZlTrgwzm14|Y(3mp z_1HfHx7PjKqSn<{A7_OT*9oz-k6xU|oOSO+mu?g)b5sX3O7Qbbml(bt-7+G==hzuAf%uIIyX=u;^EAQvJy z*+LC58)O1VkK7lcCei6@4Z_dWYIC4-qItFR^(}rBgoJiX9?1<+SOlEy7J~$V-6JY> zAy5PNB@qu-0KSZR0mWbDYnko}-k3H02@qNp{Vw5g2($A8+XH_vGh<^nIWROWWMyVG zEi_{?Vl84~H#aRdH)T0DWH&G~F*P$H3LqdLcx`Y^O*%<#b97;DV`WK1JtBB*a4uY(#Hkb#0SP2oEGQFf(I0G&o@`VK*^hEi__eVJ%@XHeoF{F*jjhW-w-BWMeUt zfCxtkIX5*nGBPqbHy{D4^000SaNLh0L01FZT z01FZU(%pXi00007bV*G`2jvV26A2?copmPGsxsFwd zfQ}|yEd#@RDm&oaDTWVMuH#h#lNS&^HFTmD01zJ=gNZSmZU6uP07*qoM6N<$f^;c| AaB^>EX>4U6ba`-PAZ2)IW&i+q+U-|clI$i7 z{bv@kkb?3Dt`hs{Q%_sZfKtyYQFLP z1?cWbwnf{DaRb#GNcd`?8z8P>9i_T`973B{IUXbN>rFcoyN3P%dHv1??+t}en5U2& z$XWa{K=w;X0u7A(kP1Z-J?rI^KYRj*yz|rZsjz5mQ}bJ;Ppmv^^j@A`*~)yY$cvN9 zvwSO0r0-cdU4Q?f=j?ZlKg(yipwjW;{r#>re4S&)C?XfQy4-_$dTm#r*U-i8+S9QS z4X*;i&Qy^cv$}(|arC1hb*jYh{-R8Sf)>rQ?9^dm=9$<~VeVjX3tfyWciH^qooKO1 zl_3spJ7^b3`DqKC-PWV0vo}Z2gP_U;ust2Vozi!ISbu9T1){^BzJguM+u1N^szps*t45KMi`gIDEGJVdX69BcTs*saadY?TQMn2>O|7L`^;&BL z`V1fk{thTaG;Y#z)6`0{=B>8qm_9vs?WJ4yUV984GO&M)GHm#$BPLBurJ1MBGHv>- zGZv=WlC<*DRhBJZb;Xmlo7OMs8?4dI8lOyUMSpv;2C3Ntg7$Wzn==r`DH9mCO#vn} zZ_W;z%i62sXnu@DZlsVxTX0V7Z8Q?=D3(^eoj%}P2L7v){cxRa93 zR&^rASjuXMLnUa=k~^>$j_?QiY|%=j)^hY&O{L)y5u>*yYaGk#bI*6g#J#1Wr;=`zY=`?&4 zL}n&5G5fKp)5s|;^T8l{QG!?cDfS7Z2ZteR5fcHeRrroaffH6jJcRlBNXBe&! q0+{jY0dq>Q@6VlOxx#UL=Lb$(EfxA92M@9U0000