From 82528dce377ed76399fedf570a96459288354c95 Mon Sep 17 00:00:00 2001
From: keronshb <54602815+keronshb@users.noreply.github.com>
Date: Wed, 4 Dec 2024 11:49:54 -0500
Subject: [PATCH] Adds Store on Collide and Wand of the Locker (#33710)
* Adds wand of locker and locker projectile
* Adds IsOpen method to check if storage is open
* Adds store on collide
* Adds Store On Collide to Wizard Locker
* Adds Lock API
* Adds locking support
* Adds resist override and custom visual layers
* Fixes decursed states, adds comment for a future visualizer
* adds locker wand visuals and descriptions
* shrinks locker radius, moves TODO for throw support
* Adds whitelist and moves storage and lock logic into their own methods
* Adds support to disable store on collide after the first open. Fixes prediction issues with disabling.
* Adds wand of locker to the grimoire
* Adds wizard access prototype
* Adds Wizard to universal access
* Moves Lock on collide to on collide method
* Comments
* Changes layer order
* Fixes prediction issues when locking.
* Adds Wiz access to universal ID
---
Content.Shared/Lock/LockSystem.cs | 19 ++++-
.../Components/StoreOnCollideComponent.cs | 34 ++++++++
.../SharedEntityStorageSystem.cs | 8 ++
.../EntitySystems/StoreOnCollideSystem.cs | 71 ++++++++++++++++
.../en-US/prototypes/access/accesses.ftl | 2 +
.../Locale/en-US/store/spellbook-catalog.ftl | 3 +
Resources/Prototypes/Access/wizard.yml | 3 +
.../Prototypes/Catalog/spellbook_catalog.yml | 13 +++
.../Objects/Misc/identification_cards.yml | 1 +
.../Objects/Tools/access_configurator.yml | 1 +
.../Objects/Weapons/Guns/Basic/wands.yml | 36 ++++++++
.../Weapons/Guns/Projectiles/magic.yml | 80 ++++++++++++++++++
.../Guns/Basic/wands.rsi/locker-effect.png | Bin 0 -> 260 bytes
.../Basic/wands.rsi/locker-inhand-left.png | Bin 0 -> 794 bytes
.../Basic/wands.rsi/locker-inhand-right.png | Bin 0 -> 803 bytes
.../Weapons/Guns/Basic/wands.rsi/locker.png | Bin 0 -> 624 bytes
.../Weapons/Guns/Basic/wands.rsi/meta.json | 16 +++-
17 files changed, 284 insertions(+), 3 deletions(-)
create mode 100644 Content.Shared/Storage/Components/StoreOnCollideComponent.cs
create mode 100644 Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
create mode 100644 Resources/Prototypes/Access/wizard.yml
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png
diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs
index 3349034f32..411766d8c1 100644
--- a/Content.Shared/Lock/LockSystem.cs
+++ b/Content.Shared/Lock/LockSystem.cs
@@ -131,8 +131,24 @@ public sealed class LockSystem : EntitySystem
});
}
- _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
+ Lock(uid, user, lockComp);
+ return true;
+ }
+
+ ///
+ /// Forces a given entity to be locked, does not activate a do-after.
+ ///
+ public void Lock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null)
+ {
+ if (!Resolve(uid, ref lockComp))
+ return;
+
+ if (user is { Valid: true })
+ {
+ _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success",
("entityName", Identity.Name(uid, EntityManager))), uid, user);
+ }
+
_audio.PlayPredicted(lockComp.LockSound, uid, user);
lockComp.Locked = true;
@@ -141,7 +157,6 @@ public sealed class LockSystem : EntitySystem
var ev = new LockToggledEvent(true);
RaiseLocalEvent(uid, ref ev, true);
- return true;
}
///
diff --git a/Content.Shared/Storage/Components/StoreOnCollideComponent.cs b/Content.Shared/Storage/Components/StoreOnCollideComponent.cs
new file mode 100644
index 0000000000..d8fff31938
--- /dev/null
+++ b/Content.Shared/Storage/Components/StoreOnCollideComponent.cs
@@ -0,0 +1,34 @@
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Storage.Components;
+
+// Use where you want an entity to store other entities on collide
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(StoreOnCollideSystem))]
+public sealed partial class StoreOnCollideComponent : Component
+{
+ ///
+ /// Entities that are allowed in the storage on collide
+ ///
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// Should this storage lock on collide, provided they have a lock component?
+ ///
+ [DataField]
+ public bool LockOnCollide;
+
+ ///
+ /// Should the behavior be disabled when the storage is first opened?
+ ///
+ [DataField]
+ public bool DisableWhenFirstOpened;
+
+ ///
+ /// If the behavior is disabled or not
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Disabled;
+}
diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
index 309ac0a2e0..829f574ad1 100644
--- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
+++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
@@ -344,6 +344,14 @@ public abstract class SharedEntityStorageSystem : EntitySystem
return true;
}
+ public bool IsOpen(EntityUid target, SharedEntityStorageComponent? component = null)
+ {
+ if (!ResolveStorage(target, ref component))
+ return false;
+
+ return component.Open;
+ }
+
public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, SharedEntityStorageComponent? component = null)
{
if (!ResolveStorage(target, ref component))
diff --git a/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs b/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
new file mode 100644
index 0000000000..000e19faf3
--- /dev/null
+++ b/Content.Shared/Storage/EntitySystems/StoreOnCollideSystem.cs
@@ -0,0 +1,71 @@
+using Content.Shared.Lock;
+using Content.Shared.Storage.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+internal sealed class StoreOnCollideSystem : EntitySystem
+{
+ [Dependency] private readonly SharedEntityStorageSystem _storage = default!;
+ [Dependency] private readonly LockSystem _lock = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly INetManager _netMan = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnCollide);
+ SubscribeLocalEvent(AfterOpen);
+ // TODO: Add support to stop colliding after throw, wands will need a WandComp
+ }
+
+ // We use Collide instead of Projectile to support different types of interactions
+ private void OnCollide(Entity ent, ref StartCollideEvent args)
+ {
+ TryStoreTarget(ent, args.OtherEntity);
+
+ TryLockStorage(ent);
+ }
+
+ private void AfterOpen(Entity ent, ref StorageAfterOpenEvent args)
+ {
+ var comp = ent.Comp;
+
+ if (comp is { DisableWhenFirstOpened: true, Disabled: false })
+ comp.Disabled = true;
+ }
+
+ private void TryStoreTarget(Entity ent, EntityUid target)
+ {
+ var storageEnt = ent.Owner;
+ var comp = ent.Comp;
+
+ if (_netMan.IsClient || _gameTiming.ApplyingState)
+ return;
+
+ if (ent.Comp.Disabled || storageEnt == target || Transform(target).Anchored || _storage.IsOpen(storageEnt) || _whitelist.IsWhitelistFail(comp.Whitelist, target))
+ return;
+
+ _storage.Insert(target, storageEnt);
+
+ }
+
+ private void TryLockStorage(Entity ent)
+ {
+ var storageEnt = ent.Owner;
+ var comp = ent.Comp;
+
+ if (_netMan.IsClient || _gameTiming.ApplyingState)
+ return;
+
+ if (ent.Comp.Disabled)
+ return;
+
+ if (comp.LockOnCollide && !_lock.IsLocked(storageEnt))
+ _lock.Lock(storageEnt, storageEnt);
+ }
+}
diff --git a/Resources/Locale/en-US/prototypes/access/accesses.ftl b/Resources/Locale/en-US/prototypes/access/accesses.ftl
index 0e8b1d9ac7..5e54fcad22 100644
--- a/Resources/Locale/en-US/prototypes/access/accesses.ftl
+++ b/Resources/Locale/en-US/prototypes/access/accesses.ftl
@@ -42,3 +42,5 @@ id-card-access-level-nuclear-operative = Nuclear Operative
id-card-access-level-syndicate-agent = Syndicate Agent
id-card-access-level-central-command = Central Command
+
+id-card-access-level-wizard = Wizard
diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl
index ed62c6fa82..dff918b0bc 100644
--- a/Resources/Locale/en-US/store/spellbook-catalog.ftl
+++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl
@@ -28,6 +28,9 @@ spellbook-wand-polymorph-door-description = For when you need a get-away route.
spellbook-wand-polymorph-carp-name = Wand of Carp Polymorph
spellbook-wand-polymorph-carp-description = For when you need a carp filet quick and the clown is looking juicy.
+spellbook-wand-locker-name = Wand of the Locker
+spellbook-wand-locker-description = Shoot cursed lockers at your enemies and lock em away!
+
# Events
spellbook-event-summon-ghosts-name = Summon Ghosts
diff --git a/Resources/Prototypes/Access/wizard.yml b/Resources/Prototypes/Access/wizard.yml
new file mode 100644
index 0000000000..cacb889936
--- /dev/null
+++ b/Resources/Prototypes/Access/wizard.yml
@@ -0,0 +1,3 @@
+- type: accessLevel
+ id: Wizard
+ name: id-card-access-level-wizard
diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml
index 6e9bba87a6..57172dc665 100644
--- a/Resources/Prototypes/Catalog/spellbook_catalog.yml
+++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml
@@ -118,6 +118,19 @@
- !type:ListingLimitedStockCondition
stock: 1
+- type: listing
+ id: SpellbookWandLocker
+ name: spellbook-wand-locker-name
+ description: spellbook-wand-locker-description
+ productEntity: WeaponWandLocker
+ cost:
+ WizCoin: 3
+ categories:
+ - SpellbookEquipment
+ conditions:
+ - !type:ListingLimitedStockCondition
+ stock: 1
+
# Event
- type: listing
id: SpellbookEventSummonGhosts
diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
index ccb83b6aaa..a0e144d0ac 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
@@ -810,3 +810,4 @@
- CentralCommand
- NuclearOperative
- SyndicateAgent
+ - Wizard
diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
index 66f4689099..d55437696a 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
@@ -119,6 +119,7 @@
- CentralCommand
- NuclearOperative
- SyndicateAgent
+ - Wizard
privilegedIdSlot:
name: id-card-console-privileged-id
ejectSound: /Audio/Machines/id_swipe.ogg
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml
index e723a1db5f..d3745288f6 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml
@@ -57,6 +57,42 @@
capacity: 5
count: 5
+- type: entity
+ name: wand of the locker
+ description: Stuff nerds at a distance!
+ parent: WeaponWandBase
+ id: WeaponWandLocker
+ components:
+ - type: Sprite
+ layers:
+ - state: locker
+ map: ["base"]
+ - state: locker-effect
+ map: ["effect"]
+ - type: Gun
+ soundGunshot:
+ path: /Audio/Weapons/Guns/Gunshots/Magic/staff_animation.ogg
+ - type: BasicEntityAmmoProvider
+ proto: ProjectileLocker
+ capacity: 5
+ count: 5
+ - type: Item
+ size: Normal
+ inhandVisuals:
+ left:
+ - state: locker-inhand-left
+ right:
+ - state: locker-inhand-right
+ - type: GenericVisualizer
+ visuals:
+ enum.AmmoVisuals.HasAmmo:
+ effect:
+ True: { visible: False }
+ False: { visible: True }
+ base:
+ True: { visible: True }
+ False: { visible: False }
+
- type: entity
name: magical wand of instant death
parent: WeaponWandBase
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml
index d11f96d875..cd736a33d0 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml
@@ -79,6 +79,86 @@
totalIntensity: 0.3
maxTileBreak: 0
+- type: entity
+ id: ProjectileLocker
+ name: cursed locker
+ description: A cursed magical locker! Can you resist?
+ parent: ClosetSteelBase
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: ResistLocker
+ resistTime: 30
+ - type: StoreOnCollide
+ lockOnCollide: true
+ disableWhenFirstOpened: true
+ whitelist:
+ components:
+ - Body
+ - type: LockVisuals
+ stateLocked: cursed_door
+ stateUnlocked: decursed_door
+ - type: Lock
+ breakOnEmag: false
+ - type: AccessReader
+ access: [["Wizard"]]
+ breakOnEmag: false
+ - type: Projectile
+ deleteOnCollide: false
+ onlyCollideWhenShot: true
+ damage:
+ types:
+ Brute: 0
+ - type: Sprite
+ noRot: true
+ sprite: Structures/Storage/closet.rsi
+ layers:
+ - state: cursed
+ map: [ "enum.StorageVisualLayers.Base" ]
+ - state: decursed_door
+ map: [ "enum.StorageVisualLayers.Door" ]
+ - state: paper
+ visible: false
+ sprite: Structures/Storage/closet_labels.rsi
+ map: [ "enum.PaperLabelVisuals.Layer" ]
+ - state: cursed_door
+ map: [ "enum.LockVisualLayers.Lock" ]
+ - state: welded
+ visible: false
+ map: [ "enum.WeldableLayers.BaseWelded" ]
+ #TODO: Will have to eventually make a custom visualizer for cursed lockers
+ - type: EntityStorageVisuals
+ stateBaseClosed: decursed
+ stateDoorOpen: decursed_open
+ stateDoorClosed: decursed_door
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.25,-0.48,0.25,0.48"
+ density: 75
+ mask:
+ - MachineMask
+ layer:
+ - MachineLayer
+ projectile:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.15,-0.45,0.15,0.15"
+ hard: false
+ mask:
+ - Impassable
+ - BulletImpassable
+ fly-by: &flybyfixture
+ shape: !type:PhysShapeCircle
+ radius: 0.6
+ layer:
+ - Impassable
+ - MidImpassable
+ - HighImpassable
+ - LowImpassable
+ hard: false
+
- type: entity
id: ProjectilePolyboltBase
parent: BaseBullet
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-effect.png
new file mode 100644
index 0000000000000000000000000000000000000000..77bcb4b36404837aec35c9e89a2a143875590b28
GIT binary patch
literal 260
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4jBOuH;Rhv&5D7ekj
z#WAE}&fBYoT+Id|?H|=ECcP;;v!Zyf$GU~bPlUXl;rx_qt9pmsL5GXmX3Y}kde5)4
zqh|k$1isGW3~UAsj64Yk>X~mXD-815ajn#i@4$~_i31YM`*k=r9GQQ)^!2U>m)RN5
z^~kf$k+o;(5WLUZA$p&+L-IdYhwOij1s(fY1CE+APWdR_AaLm6-+SK!rmA}0VK!Jd
zWtM!#+LMpu64sv8bA70A@Q?a|c&&Y$M^qRetaz;L(A6s&)IRw{&I%Er4;VaM{an^L
HB{Ts5nq6hw
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-left.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcb330b9ed1042b744c5db5101d8524a91b5ec60
GIT binary patch
literal 794
zcmV+#1LgdQP)O|5?CrI)^&j|C{0Fvn
z+S^+Qf~{bqSZF08B3ASE3}ksMt2ml-dCS}b%gi##9r7iUtFzfZ5_5R~E^pT9bb|4C
zoOsv9QqBN$yWQY+yFG0?8jYMlZ~_1U<@0&j@AnTgjB6hs9|Buf5Sg5PeplFQ{D
z+QG)9=m0ePo6RQty<9HewZ*l=;UIWaaPFcCoK*ly8`+=F=XY&z4K}cKB{~3o_HjI&
zPGM$ev)PZfjYcE1$z(ELK5Wqep!7APtJMm@-!19EV324wn*l(mFI#i~2p)|snM_Kl
zRJxme6>Lxi@WDA-bN~pvQmKUJ&ongH^m@HOeb}M{Sg+S|KA&Z|Tt3~+hr?l_Uatq~
zbXwS=0_gYq0h-w>TN!|18^jvu4MGwX0G{RlVU0(k0eC0-D)_RifCj)abIdBB0IWs(=Cj+nD8g%r8X#m6-K{me2pc(y!IleZU+5vtu*XHU+@SKE&D$OaZVz
zwK48t3c$Mie;1>&{4O9dmj@s*mj@s*mj@s*mj@s*mj~eDChc}Rm`o;-KF7b}^NQ#I
zTCG;_j_-6j{k`)62%uOj%5Jxdlu_7TE*F6>8v?xo5Ih1o9*>dy6dYSL0GfRbJe$ov
z?W?w_R4P1YRsp>NP|8pg01&{zH>zJ$(Ew-(uc5&!>y+-zjk4}j&n;{KAtycSbgZs+MOdXZcwLY7mFhm2eSS%us
z#(bBHQ5hP5LZJ{IKLNb8PPJMUru=+>kYqXK$($Ia0kF)&
zLyQ>#Qvl51A#I43YnlPj?7NWVj$j6Woqd&c75JB;RtvfMH#h5h`3jJj%L9;@EA{~X
Y0-ctjx)}pDaR2}S07*qoM6N<$g3AP3(*OVf
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker-inhand-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..4be7ef205918ca5d5813819d912ae8c6e9c9800c
GIT binary patch
literal 803
zcmV+;1Kj+HP)Jm>twcn`Dt9k1hdHxxH*k&1NZz
zA^f=cI$$suIMeAgRw97Y^Z9&x?hybArPFCDl}fP^0bIc8bUMAc;LQPt!-3)mkO#M7
zvFLg35ujG9(fNEPSOxcSZ~@Q$e!q8r2d`In1X!(Bu0mJ_aRWGk8#ungBS5>|rt9@e
z`F!5B43v`&Ui-L$8yv7)E`Q&91Q?IUsZOWkKnQ?sBN^bpj^1v!ARk-?tJSKj6cPsR
zr32)_C6mdxd|qcCcoP6gZ#Wz}^?IG+01#&`ehJ<8i_l~;Nwr!nCzs2SbnJFJy4`NH
z*=&5PnQ||H0VpA((a1q=Ab~*aNPt15{NrW_`9LakVh!qU?e&u^9;v^=e>}{Ta3jP{LsapoGH;KnaHxfD#TX03{q&0M(0LujkC?^GJX9
z*F_MQKnHZYU1zaah!z1Hg5LWI0LAY2J5?$b(IOyra5x^1N*5>{zyqZN9Khvrx%j^K
z6`)WkxYukp>)uOr0na`YXHI{ibN~io8K?$I0XP800$>1s*h134OZZdl%KiW}NuZ?!
z%Rse$q70519MB-4IxbT>;PH6;c2-DChFrw}a8W@i0JqBEzS(TjPZA_aK+Jqf9J)b5
za;waGy$)=RVSL;RG#U+(h;q5?UVj7#{G9C%(D{QBJ4SV5))D|S4^>GJbls|b9xxWv
zBxMq-(Sb}ri(*CK**Ar;08w>8nAr#Hp=g*vQEZe58l002ovPDHLkV1mA2Sn&V=
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png b/Resources/Textures/Objects/Weapons/Guns/Basic/wands.rsi/locker.png
new file mode 100644
index 0000000000000000000000000000000000000000..f638769226a28fdf0546e0428d3f824be5eb4169
GIT binary patch
literal 624
zcmV-$0+0QPP)Kp2G&Gc=*H8!9S=%s9iC%ius@Fd;(~3Le3i
ziXwgolXq|ms$fb=ULe@qq2kiQ8^ngvV54X?Xh#SMR;(<}41=^FzHk5iwSW@`&btW+
zf*>l#lgY$ks{ny}JRW=$1Z7AxlCWHWz`rGvYh$Z(T?tDC6!^pABUmLY6Hw3GjD#fu
zMr9`we!m~bM@NPd+yYv-dgexzP=`xE51vRQ(C&2bH5y%$_2w;eiv^XKOUL8!7~DOuZO!BHIJ(^~d_Er^*Xrsfo*Ul-&3iL#xsJO5@3NV(e?)ScYzkZl*(GG)$rx)
z3@(?;@VZ@aCIKz{Wj;r**OQyA)oLMmey)4na5&rvXA)R_2$g8J+X#olXf~S&1OmDn
z4a>}#1xPM0E^uEeAsUI$+lj#Y{XQ3*S%4&)&7xANAfl2`!~Z=!VKf?