From e2610e148f88be528dfe2de6c94666fc72ac5d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 28 Dec 2020 17:53:45 +0100 Subject: [PATCH] UI to toggle off computer, toggle full input capturing. --- src/main/java/li/cil/oc2/Constants.java | 4 + .../li/cil/oc2/client/gui/TerminalScreen.java | 89 +++++++++++++++++- .../li/cil/oc2/client/gui/widget/Sprite.java | 27 ++++++ .../client/gui/widget/ToggleImageButton.java | 89 ++++++++++++++++++ .../oc2/client/gui/widget/package-info.java | 7 ++ .../tile/ComputerTileEntityRenderer.java | 31 ++---- .../cil/oc2/common/block/ComputerBlock.java | 10 +- .../block/entity/ComputerTileEntity.java | 19 +++- .../li/cil/oc2/common/network/Network.java | 6 ++ .../message/ComputerBootErrorMessage.java | 2 +- .../message/ComputerBusStateMessage.java | 2 +- .../network/message/ComputerPowerMessage.java | 53 +++++++++++ .../message/ComputerRunStateMessage.java | 2 +- src/main/resources/assets/oc2/lang/en_us.json | 5 + .../oc2/textures/gui/screen/terminal.png | Bin 4195 -> 6082 bytes 15 files changed, 304 insertions(+), 42 deletions(-) create mode 100644 src/main/java/li/cil/oc2/client/gui/widget/Sprite.java create mode 100644 src/main/java/li/cil/oc2/client/gui/widget/ToggleImageButton.java create mode 100644 src/main/java/li/cil/oc2/client/gui/widget/package-info.java create mode 100644 src/main/java/li/cil/oc2/common/network/message/ComputerPowerMessage.java diff --git a/src/main/java/li/cil/oc2/Constants.java b/src/main/java/li/cil/oc2/Constants.java index f8a27789..ef3d9aae 100644 --- a/src/main/java/li/cil/oc2/Constants.java +++ b/src/main/java/li/cil/oc2/Constants.java @@ -29,6 +29,10 @@ public final class Constants { /////////////////////////////////////////////////////////////////// + public static final String COMPUTER_SCREEN_CAPTURE_INPUT_CAPTION = "gui.oc2.computer.capture_input.capt"; + public static final String COMPUTER_SCREEN_CAPTURE_INPUT_DESCRIPTION = "gui.oc2.computer.capture_input.desc"; + public static final String COMPUTER_SCREEN_POWER_CAPTION = "gui.oc2.computer.power.capt"; + public static final String COMPUTER_SCREEN_POWER_DESCRIPTION = "gui.oc2.computer.power.desc"; public static final String COMPUTER_BOOT_ERROR_UNKNOWN = "gui.oc2.computer.boot_error.unknown"; public static final String COMPUTER_BOOT_ERROR_NO_MEMORY = "gui.oc2.computer.boot_error.no_memory"; public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete"; diff --git a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java index 97ab39ba..d1acd5de 100644 --- a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java @@ -2,15 +2,21 @@ package li.cil.oc2.client.gui; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.systems.RenderSystem; +import li.cil.oc2.Constants; import li.cil.oc2.api.API; import li.cil.oc2.client.gui.terminal.TerminalInput; +import li.cil.oc2.client.gui.widget.Sprite; +import li.cil.oc2.client.gui.widget.ToggleImageButton; import li.cil.oc2.common.block.entity.ComputerTileEntity; import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.ComputerPowerMessage; import li.cil.oc2.common.network.message.TerminalBlockInputMessage; import li.cil.oc2.common.vm.Terminal; +import net.minecraft.client.entity.player.ClientPlayerEntity; import net.minecraft.client.gui.screen.Screen; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; import org.lwjgl.glfw.GLFW; import java.nio.ByteBuffer; @@ -21,13 +27,24 @@ public final class TerminalScreen extends Screen { private static final ResourceLocation BACKGROUND = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal.png"); private static final ResourceLocation BACKGROUND_TERMINAL_FOCUSED = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal_focused.png"); private static final int TEXTURE_SIZE = 512; - private static final int SCREEN_WIDTH = 8 + 80 * 8 / 2 + 8; - private static final int SCREEN_HEIGHT = 8 + 24 * 16 / 2 + 8; + + private static final Sprite CAPTURE_INPUT_BASE = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 15, 241); + private static final Sprite CAPTURE_INPUT_PRESSED = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 29, 241); + private static final Sprite CAPTURE_INPUT_ACTIVE = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 1, 241); + + private static final Sprite POWER_BASE = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 15, 255); + private static final Sprite POWER_PRESSED = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 29, 255); + private static final Sprite POWER_ACTIVE = new Sprite(BACKGROUND, TEXTURE_SIZE, 12, 12, 1, 255); + + private static final int SCREEN_WIDTH = 336; + private static final int SCREEN_HEIGHT = 228; private static final int TERMINAL_AREA_X = 8; private static final int TERMINAL_AREA_Y = 8; private static final int TERMINAL_AREA_WIDTH = 80 * 8 / 2; private static final int TERMINAL_AREA_HEIGHT = 24 * 16 / 2; + private static boolean enableInputCapture; + /////////////////////////////////////////////////////////////////// private final ComputerTileEntity tileEntity; @@ -55,7 +72,7 @@ public final class TerminalScreen extends Screen { requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND); blit(matrixStack, windowLeft, windowTop, 0, 0, windowWidth, windowHeight, TEXTURE_SIZE, TEXTURE_SIZE); - if (isMouseOverTerminal && tileEntity.isRunning()) { + if (shouldCaptureInput()) { requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND_TERMINAL_FOCUSED); blit(matrixStack, windowLeft, windowTop, 0, 0, windowWidth, windowHeight, TEXTURE_SIZE, TEXTURE_SIZE); } @@ -67,6 +84,18 @@ public final class TerminalScreen extends Screen { stack.translate(windowLeft + TERMINAL_AREA_X, windowTop + TERMINAL_AREA_Y, this.itemRenderer.zLevel); stack.scale(TERMINAL_AREA_WIDTH / (float) terminal.getWidth(), TERMINAL_AREA_HEIGHT / (float) terminal.getHeight(), 1f); terminal.render(stack); + } else { + final ITextComponent bootError = tileEntity.getBootError(); + if (bootError != null) { + final int textWidth = font.getStringPropertyWidth(bootError); + final int textOffsetX = (TERMINAL_AREA_WIDTH - textWidth) / 2; + final int textOffsetY = (TERMINAL_AREA_HEIGHT - font.FONT_HEIGHT) / 2; + font.func_243246_a(matrixStack, + bootError, + windowLeft + TERMINAL_AREA_X + textOffsetX, + windowTop + TERMINAL_AREA_Y + textOffsetY, + 0xEE3322); + } } } @@ -79,7 +108,10 @@ public final class TerminalScreen extends Screen { Network.INSTANCE.sendToServer(new TerminalBlockInputMessage(tileEntity, input)); } - if (!tileEntity.isRunning()) { + assert minecraft != null; + final ClientPlayerEntity player = minecraft.player; + assert player != null; + if (!player.isAlive() || !tileEntity.getPos().withinDistance(player.getPositionVec(), 8)) { closeScreen(); } } @@ -97,7 +129,7 @@ public final class TerminalScreen extends Screen { @Override public boolean keyPressed(final int keyCode, final int scanCode, final int modifiers) { - if ((!isMouseOverTerminal || !tileEntity.isRunning()) && keyCode == GLFW.GLFW_KEY_ESCAPE) { + if (!shouldCaptureInput() && keyCode == GLFW.GLFW_KEY_ESCAPE) { return super.keyPressed(keyCode, scanCode, modifiers); } @@ -135,6 +167,53 @@ public final class TerminalScreen extends Screen { this.windowTop = (this.height - this.windowHeight) / 2; requireNonNull(minecraft).keyboardListener.enableRepeatEvents(true); + + addButton(new ToggleImageButton( + this, windowLeft + 104, windowTop + 212, 12, 12, + new TranslationTextComponent(Constants.COMPUTER_SCREEN_CAPTURE_INPUT_CAPTION), + new TranslationTextComponent(Constants.COMPUTER_SCREEN_CAPTURE_INPUT_DESCRIPTION), + CAPTURE_INPUT_BASE, + CAPTURE_INPUT_PRESSED, + CAPTURE_INPUT_ACTIVE + ) { + @Override + public void onPress() { + super.onPress(); + enableInputCapture = !enableInputCapture; + } + + @Override + public boolean isToggled() { + return enableInputCapture; + } + }); + + addButton(new ToggleImageButton( + this, windowLeft + 220, windowTop + 212, 12, 12, + new TranslationTextComponent(Constants.COMPUTER_SCREEN_POWER_CAPTION), + new TranslationTextComponent(Constants.COMPUTER_SCREEN_POWER_DESCRIPTION), + POWER_BASE, + POWER_PRESSED, + POWER_ACTIVE + ) { + @Override + public void onPress() { + super.onPress(); + final ComputerPowerMessage message = new ComputerPowerMessage(tileEntity, !tileEntity.isRunning()); + Network.INSTANCE.sendToServer(message); + } + + @Override + public boolean isToggled() { + return tileEntity.isRunning(); + } + }); + } + + /////////////////////////////////////////////////////////////////// + + private boolean shouldCaptureInput() { + return isMouseOverTerminal && enableInputCapture && tileEntity.isRunning(); } private boolean isPointInRegion(final int x, final int y, final int width, final int height, double mouseX, double mouseY) { diff --git a/src/main/java/li/cil/oc2/client/gui/widget/Sprite.java b/src/main/java/li/cil/oc2/client/gui/widget/Sprite.java new file mode 100644 index 00000000..be5e672a --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/widget/Sprite.java @@ -0,0 +1,27 @@ +package li.cil.oc2.client.gui.widget; + +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.AbstractGui; +import net.minecraft.util.ResourceLocation; + +public final class Sprite extends AbstractGui { + public final ResourceLocation image; + public final int textureSize; + public final int width, height; + public final int u0, v0; + + public Sprite(final ResourceLocation atlas, final int textureSize, final int width, final int height, final int u0, final int v0) { + this.image = atlas; + this.textureSize = textureSize; + this.width = width; + this.height = height; + this.u0 = u0; + this.v0 = v0; + } + + public void draw(final MatrixStack stack, final int x, final int y) { + Minecraft.getInstance().getTextureManager().bindTexture(image); + blit(stack, x, y, u0, v0, width, height, textureSize, textureSize); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/widget/ToggleImageButton.java b/src/main/java/li/cil/oc2/client/gui/widget/ToggleImageButton.java new file mode 100644 index 00000000..edeeb81e --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/widget/ToggleImageButton.java @@ -0,0 +1,89 @@ +package li.cil.oc2.client.gui.widget; + +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.button.AbstractButton; +import net.minecraft.util.text.Color; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.fml.client.gui.GuiUtils; + +import java.util.Arrays; +import java.util.List; + +public abstract class ToggleImageButton extends AbstractButton { + private static final long PRESS_DURATION = 200; + private static final long TOOLTIP_DELAY = 250; + + private final Screen parent; + private final List tooltip; + private final Sprite baseImage; + private final Sprite pressedImage; + private final Sprite activeImage; + private boolean isToggled; + private long lastPressedAt; + private long hoveringStartedAt; + + public ToggleImageButton( + final Screen parent, + final int x, final int y, + final int width, final int height, + final ITextComponent caption, + final ITextComponent description, + final Sprite baseImage, + final Sprite pressedImage, + final Sprite activeImage) { + super(x, y, width, height, caption); + this.parent = parent; + this.tooltip = Arrays.asList(caption, new StringTextComponent("").modifyStyle(style -> style.setColor(Color.fromTextFormatting(TextFormatting.GRAY))).append(description)); + this.baseImage = baseImage; + this.pressedImage = pressedImage; + this.activeImage = activeImage; + } + + @Override + public void onPress() { + lastPressedAt = System.currentTimeMillis(); + } + + public boolean isToggled() { + return isToggled; + } + + public void setToggled(final boolean value) { + isToggled = value; + } + + @Override + public void renderButton(final MatrixStack stack, final int mouseX, final int mouseY, final float partialTicks) { + Sprite background = baseImage; + if ((System.currentTimeMillis() - lastPressedAt) < PRESS_DURATION) { + background = pressedImage; + } + + background.draw(stack, x, y); + + if (isToggled()) { + activeImage.draw(stack, x, y); + } + + if (isHovered()) { + if (hoveringStartedAt == 0) { + hoveringStartedAt = System.currentTimeMillis(); + } + + if ((System.currentTimeMillis() - hoveringStartedAt) > TOOLTIP_DELAY) { + renderToolTip(stack, mouseX, mouseY); + } + } else { + hoveringStartedAt = 0; + } + } + + @Override + public void renderToolTip(final MatrixStack stack, final int mouseX, final int mouseY) { + GuiUtils.drawHoveringText(stack, tooltip, mouseX, mouseY, parent.width, parent.height, 200, Minecraft.getInstance().fontRenderer); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/widget/package-info.java b/src/main/java/li/cil/oc2/client/gui/widget/package-info.java new file mode 100644 index 00000000..f94de5ad --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/widget/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.client.gui.widget; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/client/render/tile/ComputerTileEntityRenderer.java b/src/main/java/li/cil/oc2/client/render/tile/ComputerTileEntityRenderer.java index 31612e1f..be2c709c 100644 --- a/src/main/java/li/cil/oc2/client/render/tile/ComputerTileEntityRenderer.java +++ b/src/main/java/li/cil/oc2/client/render/tile/ComputerTileEntityRenderer.java @@ -3,7 +3,6 @@ package li.cil.oc2.client.render.tile; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.vertex.IVertexBuilder; -import li.cil.oc2.Constants; import li.cil.oc2.api.API; import li.cil.oc2.client.render.OpenComputersRenderType; import li.cil.oc2.common.block.ComputerBlock; @@ -24,7 +23,6 @@ import net.minecraft.util.math.vector.Vector3f; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextProperties; import net.minecraft.util.text.Style; -import net.minecraft.util.text.TranslationTextComponent; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -174,32 +172,15 @@ public final class ComputerTileEntityRenderer extends TileEntityRenderer void sendToClientsTrackingChunk(final T message, final Chunk chunk) { diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java index 19583659..82169c37 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBootErrorMessage.java @@ -9,7 +9,7 @@ import net.minecraftforge.fml.network.NetworkEvent; import java.util.function.Supplier; -public class ComputerBootErrorMessage { +public final class ComputerBootErrorMessage { private BlockPos pos; private ITextComponent value; diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java index 54e0aa14..3a0abfda 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerBusStateMessage.java @@ -9,7 +9,7 @@ import net.minecraftforge.fml.network.NetworkEvent; import java.util.function.Supplier; -public class ComputerBusStateMessage { +public final class ComputerBusStateMessage { private BlockPos pos; private AbstractDeviceBusController.BusState busState; diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerPowerMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerPowerMessage.java new file mode 100644 index 00000000..7ad816f8 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerPowerMessage.java @@ -0,0 +1,53 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.block.entity.ComputerTileEntity; +import li.cil.oc2.common.network.MessageUtils; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public final class ComputerPowerMessage { + private BlockPos pos; + private boolean power; + + /////////////////////////////////////////////////////////////////// + + public ComputerPowerMessage(final ComputerTileEntity computer, final boolean power) { + this.pos = computer.getPos(); + this.power = power; + } + + public ComputerPowerMessage(final PacketBuffer buffer) { + fromBytes(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public static boolean handleMessage(final ComputerPowerMessage message, final Supplier context) { + context.get().enqueueWork(() -> MessageUtils.withServerTileEntityAt(context, message.pos, ComputerTileEntity.class, + (computer) -> { + final ServerPlayerEntity player = context.get().getSender(); + if (player != null && computer.getPos().withinDistance(player.getPositionVec(), 8)) { + if (message.power) { + computer.start(); + } else { + computer.stop(); + } + } + })); + return true; + } + + public void fromBytes(final PacketBuffer buffer) { + pos = buffer.readBlockPos(); + power = buffer.readBoolean(); + } + + public static void toBytes(final ComputerPowerMessage message, final PacketBuffer buffer) { + buffer.writeBlockPos(message.pos); + buffer.writeBoolean(message.power); + } +} diff --git a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java index 47fe6129..d456e49f 100644 --- a/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java +++ b/src/main/java/li/cil/oc2/common/network/message/ComputerRunStateMessage.java @@ -8,7 +8,7 @@ import net.minecraftforge.fml.network.NetworkEvent; import java.util.function.Supplier; -public class ComputerRunStateMessage { +public final class ComputerRunStateMessage { private BlockPos pos; private ComputerTileEntity.RunState runState; diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index d3753886..dad5c0dc 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -19,6 +19,11 @@ "gui.oc2.computer.bus_state.too_complex": "Bus Too Complex", "gui.oc2.computer.bus_state.multiple_controllers": "Multiple Bus Controllers", + "gui.oc2.computer.capture_input.capt": "Capture Input", + "gui.oc2.computer.capture_input.desc": "When enabled, as long as the mouse cursor is hovering the terminal contents, all input will be captured, including the escape key.", + "gui.oc2.computer.power.capt": "Toggle Power", + "gui.oc2.computer.power.desc": "Toggles the power state of the computer.", + "gui.oc2.device_type.eeprom": "EEPROM", "gui.oc2.device_type.memory": "Memory", "gui.oc2.device_type.hard_drive": "Hard Drive", diff --git a/src/main/resources/assets/oc2/textures/gui/screen/terminal.png b/src/main/resources/assets/oc2/textures/gui/screen/terminal.png index ee71464867d1c230b1c96ba7525de608e2013019..2a0c656051ca3a1eb0a7ea0de4dd282e1f6ad082 100644 GIT binary patch delta 3705 zcma)8c~nz(7XF1zs1{&?r!m;3Jh?svcYdvD0{ zA*!}jeT6;RZYO%@p1d~ot;=@3OuZPf?BtO(aT=k#6k)xNRv7})Qyx07?*XTx^hhQJQqDS>#nc6x-o;|MCn(~Xc@&N}p5hzGBJ8D2EByHS zeTN0b-h#e--FfI$C^I$Q*0_vK!9dY8U5BGv%kqf|Ik!O!@cwS4pYt{|K1a`DCMG7j z8uB6tBRM%a)@AvXl`rQO%0&(GJ-QX!xKqb@XLl6eX^oq%O3Nc>3+-4_#w_zFPpac{ zY{Nx^%yeC|dvA-`?Nk>*eNLu#ncPzi-m})N6!w;;A_biq^IK3<8R(5gd+`~9R zbtmd|?6e|KN&0oZnI6T~JPJYpPg?*-_3!6M?^5wvB{#`5p@fXwp(AWfki%T)N zWI%W43$Jpdq)H2&6r zR6(S@M+16ObmG^%>*m)Vc;gLNwW2QpoYoF8Ad%E{qQNVTMjug&&2p)3Z!Fh(GqnM@ zY;s#};j9`d@dUzBcOwkt$XZau`>3`swtOno2=P~7px=OA0|Whm;A`emW1PtuUNr(V z*}!R51S83DWZ(|Bw8Ym3Sh_cwLW|=95c2(p`+gl+7kn414&qP3ICZbfemb#LHL%}$ zh2C3B1_ROWk=5w)8+U)ACCQ(g%(DZ)@cB2u=(!rc#fQp)DO=~SVqL|PivDhIy5Kxb zi)7E#B;irtESKrgAhB4i-$%D37^LRMwV7?usi!L9-!u3@HB1jwR@^syH=JeU@98WO=+dZDbLn5*_EZ0ejFj@(ISrRP2scbU@eu{V>I-2zIkD*JD?31yL%ftIC z1$>aS|1TJVcW?=3Z3yyDIR7^ve;NY3i+_~t?{avri$5Ciq_(A*uTxdN2UUpCW%+&q z0XxWQ@rs42;r9!c_8OKo8EpMznYkVfz&s9Uj*6U_hpIhULg)=_Zfk-smKF?63!NI} zP!IPYKuGsx#J~_MC&tzoTWJ6WT)hy#3*D5_#=(mNpM!I>PDnU3G<5La%{QdM9jF9n zGii5(v#83IJ)7omcOqg@yJYAM$3NXCjxUuPx5o0a75#Ma{_Kk_&#`uB{(9w84N~k| zX5t~Cq(u5cFebuB^UurisTXQka$`8y7?UTzR$b@T6j=N;`puTWITX30xTf#wMA+Cr zlF*;AzjT5s%l(cC!fhPz^!VGe>8&!m!d81$df0T`Wa_rhqVxp0n90t}hLz#HdYFYN zrvk5+$c$~a1zyii?~)X2`aIm=qB`oD{4SXw9H<>$uO(*%#QC_n`ANEeK!Aa5-NQva zhP4iOupnVsxW_GCI~ZPrWh7T>o|#7)@$L%tLk7ih1WIORW{Y6O^;PAdy2p_Uv@-V1 zNri{UX68GG@|BKMf`e(fNF=f~)eSmZQTgc(gUdZ)`Ll#(FG0G@`#6#uw%1%YbF_nu zs;I1dv#?M^XbB|{iMUsE*cEn=Rl%p~JS?2k@sf))K2A2t;rPqmj*M*CVE|o@goN}9 zP;<7lsa-Lw7!frpXiBAZS-BF|SZHwh;GA-Pk}aJ-E54?|nBM|IDiTY&YzsS|DHIng z#pqR;OqCPw;FXM}66uu4`eS2qLJYdNd@%ln?Vb9*+ZTv1c=+^z!E=((-Wd~(f753M1aTUfx9brQGc9lF=j;_d6}J32kx&=*Fe^qsqUkB_^? z7QyY=QrH|m9}7F3VtCh$b6BHFrL#Acla!j;ct;?gIZ=b*7Kk4&PR}b;V+yzN;EdYx z1p<4XNF*al#o*N%gyr@A=wuqdTw;;~vAj2)2nZk$2*+c#CvGgXAYaL^K7HreqiQ(e z>Q0`BbtZ!1zE0}#AlxR}_^GM+&qI=*ZF0qhospf|t}vLfkeb+C1ve@y10NokUEpvy zp4#90P`z(jF@H{X_Eiw+I;&ysIz``#w=)6b#0H>|pLY9dbXNA{A*&UDXCbV@1^TxU=gLOAFF|YOZ5v@d;Lsc)~$t zzqY6(73bCAmhO+Y+P_bC{h60vsMUd98=ZEiPBezsi7` zMPbz!tf)FtVmmr4F;21HanZtI1;P^MwEkJJ$jyG{ZdywytT*q{0?|X5j=n^87QTji zV*La=E4@U9;u;Otej7B1GPoRxIMufsP zIabAYtp;2KfK}JzCt4__mpH(1e3>gUK$~`>*{&2`g z4%aQ9g}U)IAi4nPKVdTW8)v!2kr6H(8l-K30lm*}F~-q$=PnB0w)%e*SVDhQ&Q9S> z#?=L%TL|fts9VPAgX*I=E1zEd*lummHefmg$Z=Ph85yUW&4Z^?X z@eQF7w#FBuX<7h}LR*gunnt>(IKCJ=%q=lNXr~}TLJ?gteIk*^5hlzge^LzLyqZFg zg{**pX6&b+=*81Ub){lP5GIE=c!4(An%0}5r>8e66S32{n&4b&y)t8QmcEFI_8jO= zdtq_U48%9ps6j5CHF^j!Q|Su8{Z%IFE}m{`U|=uL82G(3Q;l$S|fT0XjV>GxyK}Ij0<0sGd=<#)dgU`0hpb_GK9KDvRsm2&> z8FO{vA$5-;=Xgo`C~=0f)`p0Qdbk~9A9W29sUf?KKJp!-@RBHOOis6bQ^VB*wLtsuz~ n?Oot6TL9{(R}CPHNT~yu=*zKmqM9U1b@$Lgp99sd;s5$C^qxCu delta 1703 zcmb7EdrVVz82xTrc_<@3d2E0vGsH+jp@>>Ef{zu4;sWTzu24sWNu}sI9;3=F&43xF z5OITOTQn0eQ7c+$K_;*O^xZc_Ge5jv)M0}S+M+d$9gtLk*IJBPp$~W!1n57P} zHRz;S(h}fMe*=~2Og)N+!s_v4h4_)eO`~?L)`YQk-w#u_Qq(6mcC;!eTW!;-Y5L(l zX`!4|7j04`2d_?901OKA%M<1A_bS6>nb;>><7U*oJC|_@WaL5GI;><{sm6#^Kw(+X8+g`@tBmOgsGxA7 zf5O__WOEw17Ff0Qp})9U-^=`j{Q@{5$MlbmyjbrLA=Ep`3)xZ^_V{<%_kSuUz2S z=Y)vIev=L?OlkRK0xWu86b5%0;c+IkzNjesGe31Um*{XKuP0 zvt%+o&szApy8BOx%Pso8kHuGP`N+JCl zTfqjtP$yu-4xZ+bbw_0fgH+M7lnX2d`fHpvvy{SH;c-Z$7&<&0K3V^gXdtNv% zsq3SaUv}>=3s)Nrql5j%>?-qwo@5}c