From c94d2c8c1b320bf24f5dea63a4f65c3db953d325 Mon Sep 17 00:00:00 2001 From: Floral <49110090+floral-qua-floral@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:02:43 -0400 Subject: [PATCH] Implemented ClientSoundPlayer for client-side sound playback at any position --- .../java/com/floralquafloral/VoiceLine.java | 31 +++++++----- .../action/AirborneActionDefinition.java | 1 - .../action/GroundedActionDefinition.java | 9 ++-- .../baseactions/grounded/DuckSlide.java | 4 +- .../baseactions/grounded/DuckWaddle.java | 9 +++- .../registries/stomp/ParsedStomp.java | 24 ++++----- .../registries/stomp/StompDefinition.java | 5 +- .../registries/stomp/StompHandler.java | 2 +- .../stomp/basestomptypes/JumpStomp.java | 13 +---- .../util/ClientSoundPlayer.java | 47 ++++++++++++++++++ .../com/floralquafloral/util/MarioSFX.java | 5 +- .../qua_mario/sounds/sfx/action/unduck.ogg | Bin 5693 -> 5617 bytes 12 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/floralquafloral/util/ClientSoundPlayer.java diff --git a/src/main/java/com/floralquafloral/VoiceLine.java b/src/main/java/com/floralquafloral/VoiceLine.java index 0d5cad4..1f5a961 100644 --- a/src/main/java/com/floralquafloral/VoiceLine.java +++ b/src/main/java/com/floralquafloral/VoiceLine.java @@ -3,6 +3,7 @@ import com.floralquafloral.mariodata.MarioData; import com.floralquafloral.registries.RegistryManager; import com.floralquafloral.registries.states.character.ParsedCharacter; +import com.floralquafloral.util.ClientSoundPlayer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; @@ -68,25 +69,29 @@ public void play(MarioData data, long seed) { PlayerEntity mario = data.getMario(); if(mario.getWorld().isClient) { - PositionedSoundInstance prevVoiceSound = PLAYER_VOICE_LINES.get(mario); - SoundManager soundManager = MinecraftClient.getInstance().getSoundManager(); - soundManager.stop(prevVoiceSound); - - PositionedSoundInstance voiceSound = new PositionedSoundInstance( + ClientSoundPlayer.SOUND_MANAGER.stop(PLAYER_VOICE_LINES.get(mario)); + +// PositionedSoundInstance voiceSound = new PositionedSoundInstance( +// SOUND_EVENTS.get(data.getCharacter()), +// SoundCategory.VOICE, +// 1.0F, +// 1.0F, +// Random.create(seed), +// data.getMario().getX(), +// data.getMario().getY(), +// data.getMario().getZ() +// ); +// ClientSoundPlayer.SOUND_MANAGER.play(voiceSound); + PLAYER_VOICE_LINES.put(mario, ClientSoundPlayer.playSound( SOUND_EVENTS.get(data.getCharacter()), SoundCategory.VOICE, + mario, 1.0F, 1.0F, - Random.create(seed), - data.getMario().getX(), - data.getMario().getY(), - data.getMario().getZ() - ); - soundManager.play(voiceSound); - PLAYER_VOICE_LINES.put(mario, voiceSound); + seed + )); } else { - MarioQuaMario.LOGGER.info("Send voiceline packet!!!"); MarioPackets.sendPacketToTrackers((ServerPlayerEntity) data.getMario(), new PlayVoiceLineS2CPayload(data.getMario(), this, seed)); } } diff --git a/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java b/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java index cb0b5c1..f020451 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java +++ b/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java @@ -41,7 +41,6 @@ public abstract static class AerialTransitions { private final @NotNull OldCharaStat GRAVITY = getGravity(); private final @NotNull OldCharaStat JUMP_GRAVITY = getJumpGravity(); private final @Nullable OldCharaStat JUMP_CAP = getJumpCap(); - //TODO: Make Grounded states use CharaStats as well! protected abstract @NotNull OldCharaStat getGravity(); protected abstract @NotNull OldCharaStat getJumpGravity(); diff --git a/src/main/java/com/floralquafloral/registries/states/action/GroundedActionDefinition.java b/src/main/java/com/floralquafloral/registries/states/action/GroundedActionDefinition.java index 0045164..26211c1 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/GroundedActionDefinition.java +++ b/src/main/java/com/floralquafloral/registries/states/action/GroundedActionDefinition.java @@ -5,7 +5,9 @@ import com.floralquafloral.mariodata.client.Input; import com.floralquafloral.mariodata.client.MarioClientData; import com.floralquafloral.stats.CharaStat; +import com.floralquafloral.util.ClientSoundPlayer; import com.floralquafloral.util.MarioSFX; +import net.minecraft.sound.SoundCategory; import net.minecraft.util.math.BlockPos; import org.joml.Vector2d; @@ -32,13 +34,10 @@ public abstract static class GroundedTransitions { (data) -> Input.DUCK.isHeld(), (data, isSelf, seed) -> { // Play duck voiceline - data.getMario().playSound(MarioSFX.DUCK); + ClientSoundPlayer.playSound(MarioSFX.DUCK, data, seed); VoiceLine.DUCK.play(data, seed); - LOGGER.info("Ducking voiceline with seed {}", seed); }, - (data, seed) -> { - LOGGER.info("Entering duck_waddle on server with seed {}", seed); - } + null ); } diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckSlide.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckSlide.java index 5ac1d41..e7995fa 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckSlide.java +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckSlide.java @@ -91,7 +91,9 @@ public List getTransitionInjections() { && data.getMario().isOnGround() && !data.getAction().ID.equals(getID()) && Vector2d.lengthSquared(data.getForwardVel(), data.getStrafeVel()) > threshold * threshold; - } + }, + GroundedTransitions.DUCK_WADDLE.EXECUTOR_CLIENT, + GroundedTransitions.DUCK_WADDLE.EXECUTOR_SERVER ) ) ); diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckWaddle.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckWaddle.java index e0f4ff5..83888da 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckWaddle.java +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/DuckWaddle.java @@ -6,6 +6,9 @@ import com.floralquafloral.mariodata.client.MarioClientData; import com.floralquafloral.registries.states.action.GroundedActionDefinition; import com.floralquafloral.stats.CharaStat; +import com.floralquafloral.util.ClientSoundPlayer; +import com.floralquafloral.util.MarioSFX; +import net.minecraft.sound.SoundCategory; import net.minecraft.util.Identifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +27,11 @@ public class DuckWaddle extends GroundedActionDefinition { public static final ActionTransitionDefinition UNDUCK = new ActionTransitionDefinition( "qua_mario:basic", - (data) -> !Input.DUCK.isHeld() + (data) -> !Input.DUCK.isHeld(), + (data, isSelf, seed) -> { + ClientSoundPlayer.playSound(MarioSFX.UNDUCK, data, seed); + }, + null ); public static final CharaStat WADDLE_ACCEL = new CharaStat(0.06, DUCKING, FORWARD, ACCELERATION); diff --git a/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java b/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java index 37a282f..242d346 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java +++ b/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java @@ -3,6 +3,7 @@ import com.floralquafloral.MarioQuaMario; import com.floralquafloral.mariodata.MarioData; import com.floralquafloral.mariodata.MarioPlayerData; +import com.floralquafloral.util.ClientSoundPlayer; import net.minecraft.entity.Entity; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; @@ -38,7 +39,7 @@ public class ParsedStomp { private final boolean HITS_NONLIVING_ENTITIES; private final RegistryKey DAMAGE_TYPE; - private final RegistryEntry SOUND_ENTRY; + private final SoundEvent SOUND_EVENT; private final Identifier POST_STOMP_ACTION; public ParsedStomp(StompDefinition definition) { @@ -51,7 +52,7 @@ public ParsedStomp(StompDefinition definition) { this.HITS_NONLIVING_ENTITIES = definition.canHitNonLiving(); this.DAMAGE_TYPE = RegistryKey.of(RegistryKeys.DAMAGE_TYPE, definition.getDamageType()); - this.SOUND_ENTRY = Registries.SOUND_EVENT.getEntry(definition.getSoundEvent()); + this.SOUND_EVENT = definition.getSoundEvent(); this.POST_STOMP_ACTION = definition.getPostStompAction(); } @@ -71,23 +72,22 @@ public void executeServer(MarioPlayerData data, Entity target, boolean harmless, target.damage(damageSource, damage); - this.DEFINITION.executeServer(target.getWorld(), data, target, harmless, seed); + this.DEFINITION.executeServer(data, target, harmless, seed); } - public void executeClient(PlayerEntity hearingPlayer, MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed) { - if(this.SOUND_ENTRY != null) { - target.getWorld().playSound( - hearingPlayer, - target.getX(), - target.getY(), - target.getZ(), - this.SOUND_ENTRY, + public void executeClient(MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed) { + if(this.SOUND_EVENT != null) { + ClientSoundPlayer.playSound( + this.SOUND_EVENT, SoundCategory.PLAYERS, + data.getMario().getX(), + target.getY() + target.getHeight(), + data.getMario().getZ(), 1.0F, 1.0F, seed ); } - this.DEFINITION.executeClient(hearingPlayer.getWorld(), data, isSelf, target, harmless, seed); + this.DEFINITION.executeClient(data, isSelf, target, harmless, seed); data.applyModifiedVelocity(); } diff --git a/src/main/java/com/floralquafloral/registries/stomp/StompDefinition.java b/src/main/java/com/floralquafloral/registries/stomp/StompDefinition.java index 503a8e0..5bb4c11 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/StompDefinition.java +++ b/src/main/java/com/floralquafloral/registries/stomp/StompDefinition.java @@ -7,7 +7,6 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; -import net.minecraft.world.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,8 +26,8 @@ public interface StompDefinition { float calculateDamage(MarioData data, ServerPlayerEntity mario, ItemStack equipment, double equipmentArmor, double equipmentToughness, Entity target); - void executeServer(World world, MarioPlayerData data, Entity target, boolean harmless, long seed); - void executeClient(World world, MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed); + void executeServer(MarioPlayerData data, Entity target, boolean harmless, long seed); + void executeClient(MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed); enum PainfulStompResponse { INJURY, diff --git a/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java b/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java index 3c51cbf..cfb73f4 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java +++ b/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java @@ -74,7 +74,7 @@ public static void registerReceiver() { + "\nareEqual2: " + (context.player().getWorld().equals(mario.getWorld())) + "\nmarioDataMarioWorld: " + getMarioData(mario).getMario().getWorld() ); - stompType.executeClient(context.player(), (MarioPlayerData) getMarioData(mario), mario.isMainPlayer(), target, payload.harmless, payload.seed); + stompType.executeClient((MarioPlayerData) getMarioData(mario), mario.isMainPlayer(), target, payload.harmless, payload.seed); }); } diff --git a/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java index 150fe0c..3d3e398 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java +++ b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java @@ -6,22 +6,13 @@ import com.floralquafloral.registries.stomp.StompDefinition; import com.floralquafloral.registries.stomp.StompHandler; import com.floralquafloral.util.MarioSFX; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.sound.PositionedSoundInstance; -import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.MovementType; import net.minecraft.item.ItemStack; -import net.minecraft.registry.Registries; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvent; -import net.minecraft.sound.SoundEvents; import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.random.Random; -import net.minecraft.world.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -68,7 +59,7 @@ public float calculateDamage(MarioData data, ServerPlayerEntity mario, ItemStack } @Override - public void executeServer(World world, MarioPlayerData data, Entity target, boolean harmless, long seed) { + public void executeServer(MarioPlayerData data, Entity target, boolean harmless, long seed) { executeCommon(data, target); // world.playSound( // null, @@ -77,7 +68,7 @@ public void executeServer(World world, MarioPlayerData data, Entity target, bool } @Override - public void executeClient(World world, MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed) { + public void executeClient(MarioPlayerData data, boolean isSelf, Entity target, boolean harmless, long seed) { executeCommon(data, target); // world.playSoundFromEntity(null, target, Registries.SOUND_EVENT.getEntry(MarioSFX.STOMP), SoundCategory.PLAYERS, 1.0F, 1.0F, seed); diff --git a/src/main/java/com/floralquafloral/util/ClientSoundPlayer.java b/src/main/java/com/floralquafloral/util/ClientSoundPlayer.java new file mode 100644 index 0000000..5e7d41c --- /dev/null +++ b/src/main/java/com/floralquafloral/util/ClientSoundPlayer.java @@ -0,0 +1,47 @@ +package com.floralquafloral.util; + +import com.floralquafloral.mariodata.MarioData; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.client.sound.SoundManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.random.Random; + +@SuppressWarnings("UnusedReturnValue") +public abstract class ClientSoundPlayer { + public static final SoundManager SOUND_MANAGER = MinecraftClient.getInstance().getSoundManager(); + + public static PositionedSoundInstance playSound(SoundEvent event, SoundCategory category, double x, double y, double z, float volume, float pitch, long seed) { + PositionedSoundInstance sound = new PositionedSoundInstance( + event, + category, + volume, + pitch, + Random.create(seed), + x, + y, + z + ); + SOUND_MANAGER.play(sound); + return sound; + } + + public static PositionedSoundInstance playSound(SoundEvent event, SoundCategory category, Entity entity, float volume, float pitch, long seed) { + return playSound(event, category, entity.getX(), entity.getY(), entity.getZ(), volume, pitch, seed); + } + + public static PositionedSoundInstance playSound(SoundEvent event, MarioData data, float volume, float pitch, long seed) { + return playSound(event, SoundCategory.PLAYERS, data.getMario(), volume, pitch, seed); + } + + public static PositionedSoundInstance playSound(SoundEvent event, MarioData data, long seed) { + return playSound(event, data, 1.0F, 1.0F, seed); + } + + public static void kill(PositionedSoundInstance sound) { + SOUND_MANAGER.stop(sound); + } +} diff --git a/src/main/java/com/floralquafloral/util/MarioSFX.java b/src/main/java/com/floralquafloral/util/MarioSFX.java index c307cbe..cf414c7 100644 --- a/src/main/java/com/floralquafloral/util/MarioSFX.java +++ b/src/main/java/com/floralquafloral/util/MarioSFX.java @@ -6,7 +6,7 @@ import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; -public abstract class MarioSFX { +public final class MarioSFX { public static final SoundEvent JUMP = makeMovementSound("jump"); public static final SoundEvent FLIP = makeMovementSound("flip"); public static final SoundEvent SKID_BLOCK = makeMovementSound("skid"); @@ -24,7 +24,8 @@ public abstract class MarioSFX { public static final SoundEvent STOMP_HEAVY = makeStompSound("heavy"); public static final SoundEvent STOMP_YOSHI = makeStompSound("yoshi"); - public static final SoundEvent DUCK = makeAndRegisterSound("duck"); + public static final SoundEvent DUCK = makeActionSound("duck"); + public static final SoundEvent UNDUCK = makeActionSound("unduck"); private static SoundEvent makeMovementSound(String name) { return makeAndRegisterSound("sfx.movement." + name); diff --git a/src/main/resources/assets/qua_mario/sounds/sfx/action/unduck.ogg b/src/main/resources/assets/qua_mario/sounds/sfx/action/unduck.ogg index 94f9351241fa46747b1eced6a2ed5befe3a2d39e..411bff4027d09ea5d982d1fdc673dc4caedf1cbe 100644 GIT binary patch delta 1285 zcmV+g1^W8EEb%KEPiJRS00IC200000005C600000007Pjne34oE)0<$00031006`5 zuS~I2)ewI`3jhEB0001y9{>OX0000JZ?ESJQ&Rsp|13Q|JwsDctR;6C&v3?EF>|{$ zWjW(Hx{-?PY5EfgCtPHZr4}yx5@*!bmcFG6fI>yPj9LjZP$~#+o=$}#h+e~eyT`G; z8Zug#jFP%4b^>CxN{i=PeQ*GrHLpvF%gV?#aJPSD&MP)o42)hAXV)!4Py?i9VQSR| zD`RCa4Yr;Mi&-0J*t064MQjH-QJPSvwq0k6atVM%Y&X{~h7IB4*R=wQRh7I z+=S4KkpwJ2TyqmcGe#1y03XQOx;a%-$g(W6TAj8UkcndRvmK4FEZAsfI;Il_Anpa& zp7VeA9><>*=DDL)tW5@M)KoGWnQUXlNGvl5qKIV0V(Scx20{@c7$k?_l}HPTLOEgt zT1T~GjNnA8T1^l|1Q9THW|bBUIpblBLo+21*n$uNEl@6!KnfOxTGa!H0E#dUZH$e_ zp;03VO=tuTy@ow2V2lY62pch$BpE4s(ExuTK_J^erI1}$PlloS%us+8CiWMhaA?MXo36jh*5RfZ*LYyiL!B7OAU(hyb=%s#L2rEQl5%NdglZdIQjwmhtZZfQnTkqY8fj zw%FV#kbR2-I><64Ha7}nzaRiTWEp+{0CkHZ6R1*_Tb=mKAyU-T5#11DF=FwK7=TlO zESX3dt(s(mHK-%O7(xgM5wuQ6vt$$()|wKL-Y{gyXv^Hv>L?wtY8Avo7CmuHcBtul_NB1?Z6y;=v{*G3S?NrhqzF)vFOF;}TYm9jF&W!^%Ky;<;F zN7>TLI`Th506bQ&QOb}(55lP*t5=n=1<1iz_a~E}0LMZQtusc_C$d(B0&$C~jK*fs zAP1po;?++;OAA&syFO41mICB;uj7f0RjOCaKil_luVIO`%+3?%AMJm8y2r|fVg%{u zv9(IJQOm@#C|KEoDNyGEj$({VKro=Mh8WHELCy^l0RZG_uW<=>iTCwvd-{3Vt17|1 zsQEniVmqfRB2rox!dgh-xOA}=3lS5n8;WY7ioRmi)_{-NH{_74jfBA*O;`XjqF84Y ztYF3F)(u2at;H?T0PImI3$mxpfEmN0FX=O$wj_%ap*DLasZdf2xwX{UVYOHTJ6J0S v6;;t{)R0vf2cfJrh}G71JXQ2+ZM=kwWx2wjH3%b|3H3^|Qj8Pc87%<-G>8z1 delta 1362 zcmV-Y1+Dt=E4?flPiJRS00IC2000000016D00000007R6{;QE1E({(-00031003Sm z#k{dp)ewIW4gdfE0000UL;wH+0001xl7NW}QAz(F{}BHYI!#SSgeA+@pv{uy1Gg+$ zHU&n@3}1v)`v8e3r8%+%Q~}b;F>=r<`j*5TdM=;u7bR8E2%?ChLx;fqp~BD(V)^pLSH=vXzG91Z53?VJP-$ z6*QYiq_u=D1$|(xbt0kx-O5;1EfqmfQMG_P4$j3X0}67jSE>Pe2HoUb%D}@S+7J=S zpqrda8RbJSJD~8PT3cI2Q7J+cMNO7cnGt1D!#R3@cfFH_fD@*J#eD8m5`+zyV*&sG z0E~a#=Y$YL@M2P$5)iyqW7X>rv6aZqRzxg@TGiGl0001L#6kf8=oA!1RRIyf5D29l zTe6WZX%WGxh(&Bb0xKept-#UxpHos`972P^V6e5S0SIk_SBw!#m`5A|UbNZ*g~kZ* z7!&}EZ45X>3M3B5l3&+MY!RVGM58DQ5(Iw&JOC0mG>KBEG$y1@W|ux(@G z(Cck6)zGHx@WX=^KoYjH?~#mwi6z?rN`W*?s#?_+Kr|v61pqLtShJFCU_oyR01%Bu zT?+ufNLRRo5=tlmp#T6L`P@J$ff1yTp-AUO=SI{Tcp!)->cQ}airSJ?DYAlO?Wupf zBL|08rewsf)MYCx001Bic?VU)@$r~~siqUFfurdf761SM&=hYK0RR9n zt*A8v>tfi7X$>n@GOCG%g<=?nomhZXtF|bDZHwmNg|0t@HVTxW6gC))py(BmA`rwP zVgy-X!H6$W#hG^W#A^agQ3`nOkpzDNn+9V-Bf!{5*f9VW4Iu{XFh~o6(6-S!6tW*r z_U5#zQw19l%_jy%Hz@Kme^I+MPdyG=Mf?nglS*U{cu&t0R{LVFTtEoSpyx001&aNdjzKEHMWh@z~o~Y8D#+ z000q#jjF1uDgXd1r(8o{GF}A2n3u*iWjbs2C|hC`t11`<_LkNtqKIfLq7mC@1XUFj zELcQ09g?4Oi+$vv2}w)7Q{;awKoqj<(Anmwhf_tc0!jeJp&fV^nNij8uYd9(p0#56oP~>rkKnM(GRQW&^e+&QC&(5 zl^_ek7NrmZApnawwuDRx8-*4jMD{_pLSc{!1~9S|3u%oUBR@H-w}XEK9D>VaF0kiv z!*vh8r92m;YkP2=p538^K`PQ55}5K*v}QE{O;G|9VTY)QVprx#ZhMW!