From 47b54b1ae6c1c416e38b961e17d6acbd9f0922f4 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Sun, 7 Mar 2021 17:22:18 +0100 Subject: [PATCH] Ponder me this - auto-backtracking is now more generic and works for tag screens - Ponder entries for water wheel, motor, hand crank and valves --- .../foundation/gui/AbstractSimiScreen.java | 4 + .../create/foundation/gui/ScreenOpener.java | 30 ++- .../create/foundation/ponder/PonderUI.java | 9 +- .../ponder/content/KineticsScenes.java | 241 +++++++++++++++++- .../ponder/content/PonderIndex.java | 7 + .../ponder/content/PonderTagScreen.java | 7 + src/main/resources/ponder/creative_motor.nbt | Bin 0 -> 550 bytes src/main/resources/ponder/hand_crank.nbt | Bin 0 -> 470 bytes src/main/resources/ponder/valve_handle.nbt | Bin 0 -> 480 bytes src/main/resources/ponder/water_wheel.nbt | Bin 0 -> 734 bytes 10 files changed, 282 insertions(+), 16 deletions(-) create mode 100644 src/main/resources/ponder/creative_motor.nbt create mode 100644 src/main/resources/ponder/hand_crank.nbt create mode 100644 src/main/resources/ponder/valve_handle.nbt create mode 100644 src/main/resources/ponder/water_wheel.nbt diff --git a/src/main/java/com/simibubi/create/foundation/gui/AbstractSimiScreen.java b/src/main/java/com/simibubi/create/foundation/gui/AbstractSimiScreen.java index fcfba6f34..874bfeba7 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AbstractSimiScreen.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AbstractSimiScreen.java @@ -275,5 +275,9 @@ public abstract class AbstractSimiScreen extends Screen { double mouseY = minecraft.mouseHelper.getMouseY() * w.getScaledHeight() / w.getHeight(); centerScalingOn((int) mouseX, (int) mouseY); } + + public boolean isEquivalentTo(AbstractSimiScreen other) { + return false; + } } diff --git a/src/main/java/com/simibubi/create/foundation/gui/ScreenOpener.java b/src/main/java/com/simibubi/create/foundation/gui/ScreenOpener.java index 0a3f77489..82f736791 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/ScreenOpener.java +++ b/src/main/java/com/simibubi/create/foundation/gui/ScreenOpener.java @@ -7,7 +7,6 @@ import java.util.List; import javax.annotation.Nullable; -import com.simibubi.create.foundation.ponder.PonderUI; import com.simibubi.create.foundation.utility.LerpedFloat; import net.minecraft.client.Minecraft; @@ -49,24 +48,27 @@ public class ScreenOpener { // transitions are only supported in simiScreens atm. they take care of all the // rendering for it public static void transitionTo(AbstractSimiScreen screen) { - - List screenHistory = getScreenHistory(); - if (!screenHistory.isEmpty()) { - Screen previouslyRenderedScreen = screenHistory.get(0); - if (screen instanceof PonderUI && previouslyRenderedScreen instanceof PonderUI) { - if (((PonderUI) screen).getSubject() - .isItemEqual(((PonderUI) previouslyRenderedScreen).getSubject())) { - openPreviousScreen(Minecraft.getInstance().currentScreen); - return; - } - } - } - + if (tryBackTracking(screen)) + return; screen.transition.startWithValue(0.1) .chase(1, .4f, LerpedFloat.Chaser.EXP); open(screen); } + private static boolean tryBackTracking(AbstractSimiScreen screen) { + List screenHistory = getScreenHistory(); + if (screenHistory.isEmpty()) + return false; + Screen previouslyRenderedScreen = screenHistory.get(0); + if (!(previouslyRenderedScreen instanceof AbstractSimiScreen)) + return false; + if (!screen.isEquivalentTo((AbstractSimiScreen) previouslyRenderedScreen)) + return false; + + openPreviousScreen(Minecraft.getInstance().currentScreen); + return true; + } + public static void clearStack() { backStack.clear(); } diff --git a/src/main/java/com/simibubi/create/foundation/ponder/PonderUI.java b/src/main/java/com/simibubi/create/foundation/ponder/PonderUI.java index 6118475b1..5a41bc869 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/PonderUI.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/PonderUI.java @@ -97,7 +97,7 @@ public class PonderUI extends AbstractSimiScreen { stack = new ItemStack(ForgeRegistries.ITEMS.getValue(component)); else stack = new ItemStack(ForgeRegistries.BLOCKS.getValue(component)); - + tags = new ArrayList<>(PonderRegistry.tags.getTags(component)); this.scenes = scenes; if (scenes.isEmpty()) { @@ -727,4 +727,11 @@ public class PonderUI extends AbstractSimiScreen { return stack; } + @Override + public boolean isEquivalentTo(AbstractSimiScreen other) { + if (other instanceof PonderUI) + return stack.isItemEqual(((PonderUI) other).stack); + return super.isEquivalentTo(other); + } + } diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/KineticsScenes.java b/src/main/java/com/simibubi/create/foundation/ponder/content/KineticsScenes.java index b79949c81..28ff3e051 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/KineticsScenes.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/KineticsScenes.java @@ -1,6 +1,8 @@ package com.simibubi.create.foundation.ponder.content; import com.simibubi.create.AllBlocks; +import com.simibubi.create.content.contraptions.components.crank.ValveHandleBlock; +import com.simibubi.create.content.contraptions.components.waterwheel.WaterWheelBlock; import com.simibubi.create.content.contraptions.relays.elementary.CogWheelBlock; import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock; import com.simibubi.create.content.contraptions.relays.encased.EncasedShaftBlock; @@ -15,9 +17,12 @@ import com.tterrag.registrate.util.entry.BlockEntry; import net.minecraft.block.BlockState; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.util.Direction; import net.minecraft.util.Direction.Axis; +import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; public class KineticsScenes { @@ -431,7 +436,7 @@ public class KineticsScenes { scene.effects.rotationDirectionIndicator(gearshift.east(2)); scene.effects.rotationDirectionIndicator(gearshift.west(2)); scene.idle(30); - + scene.overlay.showText(50) .colored(PonderPalette.RED) .placeNearTarget() @@ -448,4 +453,238 @@ public class KineticsScenes { } } + public static void creativeMotor(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("creative_motor", "Generating Rotational Force using Creative Motors"); + scene.configureBasePlate(0, 0, 5); + scene.world.showSection(util.select.layer(0), Direction.UP); + + BlockPos motor = util.grid.at(3, 1, 2); + + for (int i = 0; i < 3; i++) { + scene.idle(5); + scene.world.showSection(util.select.position(1 + i, 1, 2), Direction.DOWN); + } + + scene.idle(10); + scene.effects.rotationSpeedIndicator(motor); + scene.overlay.showText(50) + .text("Creative motors are a compact and configurable source of Rotational Force") + .placeNearTarget() + .pointAt(util.vector.topOf(motor)); + scene.idle(50); + + scene.rotateCameraY(90); + scene.idle(20); + + Vec3d blockSurface = util.vector.blockSurface(motor, Direction.EAST); + AxisAlignedBB point = new AxisAlignedBB(blockSurface, blockSurface); + AxisAlignedBB expanded = point.grow(1 / 16f, 1 / 5f, 1 / 5f); + + scene.overlay.chaseBoundingBoxOutline(PonderPalette.WHITE, blockSurface, point, 1); + scene.idle(1); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.WHITE, blockSurface, expanded, 60); + scene.overlay.showControls(new InputWindowElement(blockSurface, Pointing.DOWN).scroll(), 60); + scene.idle(20); + + scene.overlay.showText(50) + .text("Scrolling on the back panel changes the RPM of the motors' rotational output") + .placeNearTarget() + .pointAt(blockSurface); + scene.idle(10); + scene.world.modifyKineticSpeed(util.select.fromTo(1, 1, 2, 3, 1, 2), f -> 4 * f); + scene.idle(50); + + scene.effects.rotationSpeedIndicator(motor); + scene.rotateCameraY(-90); + } + + public static void waterWheel(SceneBuilder scene, SceneBuildingUtil util) { + scene.title("water_wheel", "Generating Rotational Force using Water Wheels"); + scene.configureBasePlate(0, 0, 5); + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + scene.world.showSection(util.select.fromTo(4, 1, 1, 4, 3, 3) + .add(util.select.fromTo(3, 1, 3, 3, 2, 3)), Direction.DOWN); + scene.world.setKineticSpeed(util.select.everywhere(), 0); + + BlockPos gaugePos = util.grid.at(0, 2, 2); + + for (int i = 0; i < 4; i++) { + scene.idle(5); + scene.world.showSection(util.select.fromTo(gaugePos.east(i) + .down(), gaugePos.east(i)), Direction.DOWN); + } + + scene.idle(10); + + for (int i = 0; i < 3; i++) { + scene.idle(5); + scene.world.showSection(util.select.position(3, 3, 3 - i), Direction.DOWN); + } + scene.world.setKineticSpeed(util.select.everywhere(), -12); + scene.effects.indicateSuccess(gaugePos); + for (int i = 0; i < 2; i++) { + scene.idle(5); + scene.world.showSection(util.select.position(3, 2 - i, 1), Direction.DOWN); + } + + BlockPos wheel = util.grid.at(3, 2, 2); + scene.effects.rotationSpeedIndicator(wheel); + scene.overlay.showText(50) + .text("Water Wheels draw force from adjacent Water Currents") + .placeNearTarget() + .pointAt(util.vector.topOf(wheel)); + scene.idle(50); + + AxisAlignedBB bb = new AxisAlignedBB(wheel).grow(.125f, 0, 0); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, new Object(), bb.offset(0, 1.2, 0) + .contract(0, .75, 0), 80); + scene.idle(5); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, new Object(), bb.offset(0, 0, 1.2) + .contract(0, 0, .75), 80); + scene.idle(5); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, new Object(), bb.offset(0, -1.2, 0) + .contract(0, -.75, 0), 80); + scene.idle(5); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, new Object(), bb.offset(0, 0, -1.2) + .contract(0, 0, -.75), 80); + scene.idle(5); + scene.overlay.showText(50) + .text("The more faces are powered, the faster the Water Wheel will rotate") + .colored(PonderPalette.MEDIUM) + .placeNearTarget() + .pointAt(util.vector.topOf(wheel)); + + scene.idle(80); + scene.rotateCameraY(-30); + scene.overlay.showText(70) + .text("The Wheels' blades should be oriented against the flow") + .placeNearTarget() + .pointAt(util.vector.topOf(wheel)); + scene.idle(80); + + ElementLink water = scene.world.makeSectionIndependent(util.select.fromTo(3, 1, 1, 3, 3, 1) + .add(util.select.fromTo(3, 3, 2, 3, 3, 3))); + ElementLink wheelElement = scene.world.makeSectionIndependent(util.select.position(wheel)); + + scene.world.setKineticSpeed(util.select.everywhere(), 0); + scene.world.moveSection(water, util.vector.of(0, 2, -2), 10); + scene.world.moveSection(wheelElement, util.vector.of(0, 1, -1), 10); + scene.idle(10); + scene.world.rotateSection(wheelElement, 0, 180, 0, 5); + scene.idle(10); + scene.world.modifyBlock(wheel, s -> s.with(WaterWheelBlock.HORIZONTAL_FACING, Direction.WEST), false); + scene.world.rotateSection(wheelElement, 0, -180, 0, 0); + scene.idle(1); + scene.world.moveSection(water, util.vector.of(0, -2, 2), 10); + scene.world.moveSection(wheelElement, util.vector.of(0, -1, 1), 10); + scene.idle(10); + scene.world.setKineticSpeed(util.select.everywhere(), -8); + + scene.overlay.showText(70) + .colored(PonderPalette.RED) + .text("Facing the opposite way, they will not be as effective") + .placeNearTarget() + .pointAt(util.vector.topOf(wheel)); + scene.idle(80); + + scene.world.setKineticSpeed(util.select.everywhere(), 0); + scene.world.moveSection(water, util.vector.of(0, 2, -2), 10); + scene.world.moveSection(wheelElement, util.vector.of(0, 1, -1), 10); + scene.idle(10); + scene.rotateCameraY(30); + scene.world.rotateSection(wheelElement, 0, 180, 0, 5); + scene.idle(10); + scene.world.modifyBlock(wheel, s -> s.with(WaterWheelBlock.HORIZONTAL_FACING, Direction.EAST), false); + scene.world.rotateSection(wheelElement, 0, -180, 0, 0); + scene.idle(1); + scene.world.moveSection(water, util.vector.of(0, -2, 2), 10); + scene.world.moveSection(wheelElement, util.vector.of(0, -1, 1), 10); + scene.idle(10); + scene.world.setKineticSpeed(util.select.everywhere(), -12); + scene.effects.indicateSuccess(gaugePos); + } + + public static void handCrank(SceneBuilder scene, SceneBuildingUtil util) { + manualSource(scene, util, true); + } + + public static void valveHandle(SceneBuilder scene, SceneBuildingUtil util) { + manualSource(scene, util, false); + scene.world.setKineticSpeed(util.select.everywhere(), 0); + scene.idle(20); + Vec3d centerOf = util.vector.centerOf(2, 2, 2); + scene.overlay.showControls(new InputWindowElement(centerOf, Pointing.DOWN).rightClick() + .withItem(new ItemStack(Items.BLUE_DYE)), 40); + scene.idle(7); + scene.world.modifyBlock(util.grid.at(2, 2, 2), s -> AllBlocks.DYED_VALVE_HANDLES[11].getDefaultState() + .with(ValveHandleBlock.FACING, Direction.UP), true); + scene.idle(10); + scene.overlay.showText(70) + .text("Valve handles can be dyed for aesthetic purposes") + .placeNearTarget() + .pointAt(centerOf); + } + + private static void manualSource(SceneBuilder scene, SceneBuildingUtil util, boolean handCrank) { + String name = handCrank ? "Hand Cranks" : "Valve Handles"; + scene.title(handCrank ? "hand_crank" : "valve_handle", "Generating Rotational Force using " + name); + scene.configureBasePlate(0, 0, 5); + scene.world.showSection(util.select.layer(0), Direction.UP); + scene.idle(5); + + BlockPos gaugePos = util.grid.at(1, 3, 3); + BlockPos handlePos = util.grid.at(2, 2, 2); + Selection handleSelect = util.select.position(handlePos); + + scene.world.showSection(util.select.layersFrom(1) + .substract(handleSelect), Direction.DOWN); + scene.idle(10); + scene.world.showSection(handleSelect, Direction.DOWN); + scene.idle(20); + + Vec3d centerOf = util.vector.centerOf(handlePos); + scene.overlay.showText(70) + .text(name + " can be used by players to apply rotational force manually") + .placeNearTarget() + .pointAt(centerOf); + scene.idle(80); + + scene.overlay.showControls(new InputWindowElement(centerOf, Pointing.DOWN).rightClick(), 40); + scene.idle(7); + scene.world.setKineticSpeed(util.select.everywhere(), handCrank ? 32 : 16); + scene.world.modifyKineticSpeed(util.select.column(1, 3), f -> f * -2); + scene.effects.rotationDirectionIndicator(handlePos); + scene.effects.indicateSuccess(gaugePos); + scene.idle(10); + scene.overlay.showText(50) + .text("Hold Right-Click to rotate it Counter-Clockwise") + .placeNearTarget() + .pointAt(centerOf); + scene.idle(70); + scene.overlay.showText(50) + .colored(handCrank ? PonderPalette.MEDIUM : PonderPalette.SLOW) + .text("Its conveyed speed is " + (handCrank ? "relatively high" : "slow and precise")) + .placeNearTarget() + .pointAt(centerOf); + scene.idle(70); + + scene.world.setKineticSpeed(util.select.everywhere(), 0); + scene.idle(10); + + scene.overlay.showControls(new InputWindowElement(centerOf, Pointing.DOWN).rightClick() + .whileSneaking(), 40); + scene.idle(7); + scene.world.setKineticSpeed(util.select.everywhere(), handCrank ? -32 : -16); + scene.world.modifyKineticSpeed(util.select.column(1, 3), f -> f * -2); + scene.effects.rotationDirectionIndicator(handlePos); + scene.effects.indicateSuccess(gaugePos); + scene.idle(10); + scene.overlay.showText(90) + .text("Sneak and Hold Right-Click to rotate it Clockwise") + .placeNearTarget() + .pointAt(centerOf); + scene.idle(90); + } + } diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java index d6e096029..8b763c330 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderIndex.java @@ -36,6 +36,13 @@ public class PonderIndex { PonderRegistry.addStoryBoard(AllBlocks.CLUTCH, "clutch", KineticsScenes::clutch); PonderRegistry.addStoryBoard(AllBlocks.GEARSHIFT, "gearshift", KineticsScenes::gearshift); + PonderRegistry.addStoryBoard(AllBlocks.CREATIVE_MOTOR, "creative_motor", KineticsScenes::creativeMotor); + PonderRegistry.addStoryBoard(AllBlocks.WATER_WHEEL, "water_wheel", KineticsScenes::waterWheel); + PonderRegistry.addStoryBoard(AllBlocks.HAND_CRANK, "hand_crank", KineticsScenes::handCrank); + PonderRegistry.addStoryBoard(AllBlocks.COPPER_VALVE_HANDLE, "valve_handle", KineticsScenes::valveHandle); + PonderRegistry.forComponents(AllBlocks.DYED_VALVE_HANDLES) + .addStoryBoard("valve_handle", KineticsScenes::valveHandle); + PonderRegistry.addStoryBoard(AllBlocks.ENCASED_CHAIN_DRIVE, "chain_drive/relay", ChainDriveScenes::chainDriveAsRelay); PonderRegistry.forComponents(AllBlocks.ENCASED_CHAIN_DRIVE, AllBlocks.ADJUSTABLE_CHAIN_GEARSHIFT) diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderTagScreen.java b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderTagScreen.java index 7d78287f2..822f0e59b 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/PonderTagScreen.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/PonderTagScreen.java @@ -285,4 +285,11 @@ public class PonderTagScreen extends AbstractSimiScreen { return super.mouseClicked(x, y, button); } + @Override + public boolean isEquivalentTo(AbstractSimiScreen other) { + if (other instanceof PonderTagScreen) + return tag == ((PonderTagScreen) other).tag; + return super.isEquivalentTo(other); + } + } diff --git a/src/main/resources/ponder/creative_motor.nbt b/src/main/resources/ponder/creative_motor.nbt new file mode 100644 index 0000000000000000000000000000000000000000..58a94c9d1c80beb7244526f282becf24d514f9da GIT binary patch literal 550 zcmV+>0@?i^iwFP!000000Iik5j?*v@h9|buO|u|X2qX@i;T?KFLW>YwDx``PwIZ7lmx`0ZY-O>C89&|BRdzU<|^l@5lkbUol^cohLY^8lZZad-en1aP_~0(=$$J|E-o0FD^s zq`>P77ehR65R03wuba&1*6tpBY6{Bq<8-D=ZD8?hk;2Ig-A0>r49S^nWUkz|_gmjY z%;^yD?F6t71#R*tjGAc-qq#F^_eTJ=I+taOuiM+}1ehw|Np6suOs#LID|Bd5D$%XU z=|qV>Q_)L`3yM>UUns7IjEs6i!~(?y2x3PB?L|E6M5wAMFc3 zpN=So$8U-Pi|+4tTR#{NYU6M^&y6n2-7BL9lfBwUcWNu`Xej=k#l8eVIVX)Q(K+-- zf1H4r$qM0lMMmXDF5Prw)=jNBk4z@8#MmYlE7$5YU&lR zwn<Yx{_{8c0ql`Z0{iX{8l=7j>alo(@gz<-=Zmhe^v(o02NdOhyVZp literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/hand_crank.nbt b/src/main/resources/ponder/hand_crank.nbt new file mode 100644 index 0000000000000000000000000000000000000000..2057ec7005da01003870d90dffe794d73db0c05d GIT binary patch literal 470 zcmV;{0V)0;iwFP!000000JW9RYTPgo#>bMp@ooxDLJK|hCH9tUd+8+Gsf=gk|L&DNxan@!>TiI)5mS=RR#^^*#6& zL-$K4;POB^!TP$UedSwiBT~KoX(D9LBWkeD>QHw|^`SwuQ`TauE8}VqPwiMa^ZEQ$ z2k(2%TiQIzyI}?mx$;)~x$_}41zdI2rMBMH<<^9ldhSS@o2Z1#uR7@m4AFQ;v;PIZ M0dbK5Tb>610HJ~Dp8x;= literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/valve_handle.nbt b/src/main/resources/ponder/valve_handle.nbt new file mode 100644 index 0000000000000000000000000000000000000000..fe6af755d6d520bed8e4c7eda9b5eb2b5583938e GIT binary patch literal 480 zcmV<60U!P!iwFP!000000JW9DYTPgohR2q?@ooxDOADokK1i; z?&^kqhW^2Ax24Lggj{doeeIF%u7f^w2CKcc7^VINMY^?L!>66K!d<0xrL( WsP51Q?JT_nzu^z~#ma@J2LJ#~Q}6!( literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/water_wheel.nbt b/src/main/resources/ponder/water_wheel.nbt new file mode 100644 index 0000000000000000000000000000000000000000..8c45937dd040b505a3f66cb5d481e743521bc9c4 GIT binary patch literal 734 zcmV<40wMh$iwFP!000000L@n2ZqqOnK8c++%|MJl2?=-yKwM0lggOl&aZ`aNCdN(W zCa0~nG>&YS46gQ6T*LG55a4Wa7osHf1Q%FJo!Z~|&iQ*Blj;Z3Kf^D&jL0nLjT_Kysn1(WVgylx7+$i#o<@!FUaf@4!PGc&HF3RuSXYkz}V(Hea2sZ`jt;r#!`NI9hg|gW*hU@p z1$EdLeLTd0hdS_B?X*Wbalp`4OavsG=>US`95MMZE@RxCm{)Z<@F7nOvOH>zL@Fel z{yL3dI6++qc@{u;#BvsM{pDy}lG9^z8I>+5_-_+{;TQ(vg8&A{TB5qTij1ohmag&D z-us^+_&kAou|(r!UzHBU3N^~en25EE&CTY0G)9k2e`@*z(|H#Bt=_m_gM zL_D-2_$p6vdd4%5X$uTGnXL zaS+!p`(X#qHVkQC!VXM5o>2#;e#k}!Gt60vT4VWrxf24Nuoc3im8qUsvbo+bmR#dB z7MWQ$V`LEzt`RB|#q{zcbu)YSdUnR76}ZGV5;T%I+B({sL8i;ne0?77MMUVXQ@JCUGjo=z9G70 ziIyhru8^*(u<5&2OijZe#m{Ec+uIo3sVy#RiPl=YxUt3a8(Tc