From f1e711fe4c191eb41cecb56f6eb1efa103aa4621 Mon Sep 17 00:00:00 2001 From: vladmarica Date: Fri, 9 Oct 2020 14:54:16 -0300 Subject: [PATCH 1/5] Progress on automatic ping colors --- gradle.properties | 2 +- .../betterpingdisplay/hud/PingColors.java | 19 +++++++++++++++++++ src/main/resources/fabric.mod.json | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java diff --git a/gradle.properties b/gradle.properties index 0c962ea..4242fb6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.9.2+build.206 # Mod Properties - mod_version = 1.0.0 + mod_version = 1.1.0 maven_group = com.vladmarica archives_base_name = BetterPingDisplay diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java new file mode 100644 index 0000000..ddfbf93 --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java @@ -0,0 +1,19 @@ +package com.vladmarica.betterpingdisplay.hud; + + +// start: #00e676 +// mid #d6cd30 +// end #e53935 +public class PingColors { + public static int getColor(int ping) { + if (ping < 0) { + return 0x535353; + } + if (ping < 150) { + return 0x00E676; + } + if (ping < 300) { + return 0xC6FF00; + } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2c32f27..9d753fc 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "modid", - "version": "1.0", + "version": "1.1", "name": "Better Ping Display", "description": "Shows the actual ping number instead of just bars in the player list.!", From 44e2df0b32a3ea1093d2b1b9b1977740b1e2b872 Mon Sep 17 00:00:00 2001 From: vladmarica Date: Sat, 24 Oct 2020 23:29:42 -0300 Subject: [PATCH 2/5] v1.1 - add auto ping text color based on latency --- .gitignore | 2 + build.gradle | 21 +++++--- gradle.properties | 2 +- .../BetterPingDisplayMod.java | 1 + .../vladmarica/betterpingdisplay/Config.java | 37 ++++++++++---- .../betterpingdisplay/hud/ColorUtil.java | 32 +++++++++++++ .../hud/CustomPlayerListHud.java | 32 ++++++++++--- .../betterpingdisplay/hud/PingColors.java | 38 +++++++++++---- .../hud/PlayerListHudUtil.java | 2 + src/main/resources/fabric.mod.json | 2 +- .../betterpingdisplay/hud/ColorUtilTest.java | 48 +++++++++++++++++++ 11 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java create mode 100644 src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java diff --git a/.gitignore b/.gitignore index c303bcb..2f33118 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/ out/ .idea/ run/ +logs/ +src/main/java/net/fabricmc/* \ No newline at end of file diff --git a/build.gradle b/build.gradle index ba9e972..8f22a16 100644 --- a/build.gradle +++ b/build.gradle @@ -11,13 +11,11 @@ version = project.mod_version group = project.maven_group dependencies { - //to change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + testImplementation 'junit:junit:4.13' } processResources { @@ -33,9 +31,6 @@ processResources { } } -// ensure that the encoding is set to UTF-8, no matter what the system default is -// this fixes some edge cases with special characters not displaying correctly -// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html tasks.withType(JavaCompile) { options.encoding = "UTF-8" } @@ -72,3 +67,17 @@ publishing { // mavenLocal() } } + +sourceSets { + main.java { + include 'com/vladmarica/betterpingdisplay/**/*' + exclude 'net/fabricmc/*' + } + test.java { + include 'com/vladmarica/betterpingdisplay/**/*' + } +} + +test { + useJUnit() +} diff --git a/gradle.properties b/gradle.properties index 4242fb6..76ef33a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G # Mod Properties mod_version = 1.1.0 maven_group = com.vladmarica - archives_base_name = BetterPingDisplay + archives_base_name = BetterPingDisplay-Fabric # Dependencies # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api diff --git a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java index 0a2e622..b9b3450 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java @@ -26,6 +26,7 @@ public class BetterPingDisplayMod implements ModInitializer { try { ConfigData data = Config.loadConfigFile(configFile); config = new Config(data); + Config.writeConfigFile(configFile, data); } catch (Exception ex) { LOGGER.error("Failed to load config file, using default. Error: {}", ex.getMessage()); } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/Config.java b/src/main/java/com/vladmarica/betterpingdisplay/Config.java index 0fba95b..3b65a9f 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/Config.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/Config.java @@ -13,28 +13,33 @@ public class Config { private static final int DEFAULT_PING_TEXT_COLOR = 0xA0A0A0; private static final String DEFAULT_PING_TEXT_FORMAT = "%dms"; + private boolean autoColorPingText; + private boolean renderPingBars; private int textColor = DEFAULT_PING_TEXT_COLOR; private String textFormatString = DEFAULT_PING_TEXT_FORMAT; public Config(ConfigData confileFileFormat) { - if (confileFileFormat.textColor.startsWith("#")) { + if (confileFileFormat.pingTextColor.startsWith("#")) { try { - textColor = Integer.parseInt(confileFileFormat.textColor.substring(1), 16); + textColor = Integer.parseInt(confileFileFormat.pingTextColor.substring(1), 16); } catch (NumberFormatException ex) { - BetterPingDisplayMod.LOGGER.error("Config option 'textColor' is invalid - it must be a hex color code"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); } } else { - BetterPingDisplayMod.LOGGER.error("Config option 'textColor' is invalid - it must be a hex color code"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); } - if (confileFileFormat.textFormatString.contains("%d")) { - textFormatString = confileFileFormat.textFormatString; + if (confileFileFormat.pingTextFormatString.contains("%d")) { + textFormatString = confileFileFormat.pingTextFormatString; } else { - BetterPingDisplayMod.LOGGER.error("Config option 'textFormatString' is invalid - it needs to contain %d"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextFormatString' is invalid - it needs to contain %d"); } + + autoColorPingText = confileFileFormat.autoColorPingText; + renderPingBars = confileFileFormat.renderPingBars; } public Config() { @@ -49,6 +54,14 @@ public class Config { return this.textFormatString; } + public boolean shouldAutoColorPingText() { + return this.autoColorPingText; + } + + public boolean shouldRenderPingBars() { + return this.renderPingBars; + } + public static ConfigData loadConfigFile(File configFile) throws IOException { FileReader reader = null; try { @@ -78,9 +91,15 @@ public class Config { public static class ConfigData implements Serializable { @Expose - private String textColor = "#A0A0A0"; + private boolean autoColorPingText = true; @Expose - private String textFormatString = "%dms"; + private boolean renderPingBars = false; + + @Expose + private String pingTextColor = "#A0A0A0"; + + @Expose + private String pingTextFormatString = "%dms"; } } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java new file mode 100644 index 0000000..599506e --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java @@ -0,0 +1,32 @@ +package com.vladmarica.betterpingdisplay.hud; + +public class ColorUtil { + + public static int interpolate(int colorStart, int colorEnd, float offset) { + if (offset < 0 || offset > 1) { + throw new IllegalArgumentException("Offset must be between 0.0 and 1.0"); + } + + int redDiff = getRed(colorEnd) - getRed(colorStart); + int greenDiff = getGreen(colorEnd) - getGreen(colorStart); + int blueDiff = getBlue(colorEnd) - getBlue(colorStart); + + int newRed = Math.round(getRed(colorStart) + (redDiff * offset)); + int newGreen = Math.round(getGreen(colorStart) + (greenDiff * offset)); + int newBlue = Math.round(getBlue(colorStart) + (blueDiff * offset)); + + return (newRed << 16) | (newGreen << 8) | newBlue; + } + + static int getRed(int color) { + return (color >> 16) & 0xFF; + } + + static int getGreen(int color) { + return (color >> 8) & 0xFF; + } + + static int getBlue(int color) { + return color & 0xFF; + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java index f68749e..6cb9637 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java @@ -4,8 +4,8 @@ import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; import com.mojang.authlib.GameProfile; import com.mojang.blaze3d.systems.RenderSystem; -import com.vladmarica.betterpingdisplay.Config; import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; +import com.vladmarica.betterpingdisplay.Config; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -33,6 +33,7 @@ public final class CustomPlayerListHud { private static final int PING_TEXT_RENDER_OFFSET = -13; private static final int PLAYER_SLOT_EXTRA_WIDTH = 45; private static final int PLAYER_ICON_WIDTH = 9; + private static final int PING_BARS_WIDTH = 11; public static void render(PlayerListHud hud, int width, Scoreboard scoreboard, ScoreboardObjective obj) { MinecraftClient mc = MinecraftClient.getInstance(); @@ -175,13 +176,30 @@ public final class CustomPlayerListHud { // Here is the magic, rendering the ping text String pingString = String.format(config.getTextFormatString(), player.getLatency()); int pingStringWidth = textRenderer.getStringWidth(pingString); - textRenderer.drawWithShadow( - pingString, - (float) r + aa - pingStringWidth + PING_TEXT_RENDER_OFFSET - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), - (float) ab, - config.getTextColor()); + int textX = r + aa - pingStringWidth + PING_TEXT_RENDER_OFFSET; - PlayerListHudUtil.renderLatencyIcon(hud, r, aa - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), ab, player); + if (displayPlayerIcons) { + textX -= PLAYER_ICON_WIDTH; + } + + if (!config.shouldRenderPingBars()) { + textX += PING_BARS_WIDTH; + } + + int pingTextColor = config.shouldAutoColorPingText() + ? PingColors.getColor(player.getLatency()) + : config.getTextColor(); + + textRenderer.drawWithShadow(pingString, (float) textX, (float) ab, pingTextColor); + + if (config.shouldRenderPingBars()) { + PlayerListHudUtil.renderLatencyIcon( + hud, r, aa - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), ab, player); + } else { + // If we don't render ping bars, we need to reset the render system color so the rest + // of the player list renders properly + RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + } } } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java index ddfbf93..e981567 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java @@ -1,19 +1,37 @@ package com.vladmarica.betterpingdisplay.hud; +import net.minecraft.util.math.MathHelper; -// start: #00e676 -// mid #d6cd30 -// end #e53935 public class PingColors { + public static final int PING_START = 0; + public static final int PING_MID = 150; + public static final int PING_END = 300; + + public static final int COLOR_GREY = 0x535353; + public static final int COLOR_START = 0x00E676; + public static final int COLOR_MID = 0xD6CD30; + public static final int COLOR_END = 0xE53935; + public static int getColor(int ping) { - if (ping < 0) { - return 0x535353; + if (ping < PING_START) { + return COLOR_GREY; } - if (ping < 150) { - return 0x00E676; - } - if (ping < 300) { - return 0xC6FF00; + + if (ping < PING_MID) { + return ColorUtil.interpolate( + COLOR_START, + COLOR_MID, + computeOffset(PING_START, PING_MID, ping)); } + + return ColorUtil.interpolate( + COLOR_MID, + COLOR_END, + computeOffset(PING_MID, PING_END, Math.min(ping, PING_END))); + } + + static float computeOffset(int start, int end, int value) { + float offset = (value - start) / (float) ( end - start); + return MathHelper.clamp(offset, 0.0F, 1.0F); } } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java index 5b5af27..aeb376c 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java @@ -7,10 +7,12 @@ import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.text.Text; public class PlayerListHudUtil { + /** Calls {@link net.minecraft.client.gui.hud.PlayerListHud#renderLatencyIcon}. */ static void renderLatencyIcon(PlayerListHud hud, int x, int offsetX, int y, PlayerListEntry player) { ((PlayerListHudAccessor) hud).invokeRenderLatencyIcon(x, offsetX, y, player); } + /** Calls {@link net.minecraft.client.gui.hud.PlayerListHud#renderScoreboardObjective} */ static void renderScoreboardObjective(PlayerListHud hud, ScoreboardObjective obj, int i, String str, int j, int k, PlayerListEntry player) { ((PlayerListHudAccessor) hud).invokeRenderScoreboardObjective(obj, i, str, j, k, player); } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 9d753fc..6073435 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,7 +4,7 @@ "version": "1.1", "name": "Better Ping Display", - "description": "Shows the actual ping number instead of just bars in the player list.!", + "description": "Shows the actual ping number instead of just bars in the player list!", "authors": [ "Quintinity" ], diff --git a/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java b/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java new file mode 100644 index 0000000..65a94c0 --- /dev/null +++ b/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java @@ -0,0 +1,48 @@ +package com.vladmarica.betterpingdisplay.hud; + +import static com.vladmarica.betterpingdisplay.hud.PingColors.COLOR_MID; +import static com.vladmarica.betterpingdisplay.hud.PingColors.COLOR_START; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ColorUtilTest { + + @Test + public void testExtractComponents() { + int color = 0xD6C130; + assertEquals(0xD6, ColorUtil.getRed(color)); + assertEquals(0xC1, ColorUtil.getGreen(color)); + assertEquals(0x30, ColorUtil.getBlue(color)); + } + + @Test + public void testInterpolation_zero() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 0F); + assertEquals(COLOR_START, color); + } + + @Test + public void testInterpolation_middle() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 0.5F); + assertEquals(0x6b, ColorUtil.getRed(color)); + assertEquals(0xda, ColorUtil.getGreen(color)); + assertEquals(0x53, ColorUtil.getBlue(color)); + } + + @Test + public void testInterpolation_one() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 1F); + assertEquals(COLOR_MID, color); + } + + @Test + public void testInterpolation_invalidOffset() { + assertThrows(IllegalArgumentException.class, () -> ColorUtil.interpolate(0, 1, -0.1F)); + assertThrows(IllegalArgumentException.class, () -> ColorUtil.interpolate(0, 1, 1.1F)); + } +} From 76ebf87a36941f4b917326fcf449d623e93e6651 Mon Sep 17 00:00:00 2001 From: vladmarica Date: Sat, 24 Oct 2020 23:40:37 -0300 Subject: [PATCH 3/5] Add config file documentation to README --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4a5ce7b..208fa95 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [![](http://cf.way2muchnoise.eu/better-ping-display-fabric.svg)](https://curseforge.com/minecraft/mc-mods/better-ping-display-fabric) [![](http://cf.way2muchnoise.eu/versions/better-ping-display-fabric.svg)](https://curseforge.com/minecraft/mc-mods/better-ping-display-fabric) -A [Fabric](https://fabricmc.net/) mod for Minecraft to display each player's ping in the player list as a number. There is also a config file -for changing the text color and format. +A [Fabric](https://fabricmc.net/) mod for Minecraft to display each player's ping in the player list as a number. Go [**here**](https://github.com/vladmarica/better-ping-display) for the Minecraft Forge edition of this mod. @@ -11,9 +10,19 @@ Go [**here**](https://github.com/vladmarica/better-ping-display) for the Minecra This is a client-side mod. The server doesn't need to have it installed. It works even when playing on vanilla servers. +## Configuration +This mod's config file is `betterpingdisplay.json`. It contains the following options: + +| Option | Default Value | Description | +|---|---|---| +| autoColorPingText | `true` | Whether to color a player's ping based on their latency. E.g, low latency = green, high latency = red | +| renderPingBars | `false` | Whether to also draw the default Minecraft ping bars | +| pingTextColor | `#A0A0A0` | The ping text color to use. Only works whens `autoColorPingText` is false | +| pingTextFormatString | `%dms` | The format string for ping text. Must include a `%d`, which will be replaced dynamically by the actual ping value. + ## Supported Minecraft Versions * **1.15.x** - +* **1.16.x** ## Requirements * [Fabric](https://fabricmc.net/) From 4ef3db98249cef19d1c4a0037366c09a95411a56 Mon Sep 17 00:00:00 2001 From: vladmarica Date: Sun, 25 Oct 2020 00:17:09 -0300 Subject: [PATCH 4/5] Fix spacing issue in player list --- .../vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java index 6cb9637..f980b34 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java @@ -133,7 +133,7 @@ public final class CustomPlayerListHud { ai = x % m; int aa = s + y * r + y * 5; int ab = t + ai * 9; - DrawableHelper.fill(aa, ab, aa + r, ab + 8, w); + DrawableHelper.fill(aa, ab, aa + r - 1, ab + 8, w); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); RenderSystem.enableAlphaTest(); RenderSystem.enableBlend(); From 0b320d23bd3e9561f47d3fdf07b0b3e790fbe505 Mon Sep 17 00:00:00 2001 From: vladmarica Date: Sun, 25 Oct 2020 00:34:41 -0300 Subject: [PATCH 5/5] Update mod icon and image --- README.md | 2 +- .../assets/betterpingdisplay/icon.png | Bin 4238 -> 2422 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 208fa95..142dd9a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A [Fabric](https://fabricmc.net/) mod for Minecraft to display each player's pin Go [**here**](https://github.com/vladmarica/better-ping-display) for the Minecraft Forge edition of this mod. -![](https://i.imgur.com/LYB3o4h.png) +![](https://i.imgur.com/HTrH0i2.png) This is a client-side mod. The server doesn't need to have it installed. It works even when playing on vanilla servers. diff --git a/src/main/resources/assets/betterpingdisplay/icon.png b/src/main/resources/assets/betterpingdisplay/icon.png index 87f1b2e018b63351b0194c65db5a8101da62c84f..de82d520cbda4f081d5d9724a95bde3176bd9b66 100644 GIT binary patch literal 2422 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL985qF{<{lj139gk&dvdz&dv%2Mfqu&IjIZ` z8WU?L+Ik#zkU1J3yfjFQuPmVOM1bs(MJqZMhb`m~^;&yjRrATE@fVgTdKMOn#BlS~ z>4eF~%qcA`5M?>YFQD$O?kcYNvEFl`YY+SJzZK>03%~OTyh>d*OZIWYsz^62Ibq|Q zp@tv26n-ftup|o^f8Cj4#CZN=DZhOD`$aaVf8UwEIQryFo)4WzHv5?CJmP)y(%1X> z9)FoMS5HMdbve@)O-K4;rpMMjjwy(EFf&nk?ksga;qF$mX}4#H@=mSjvRJf2>M6J6 zbkTFsbFLOmHGO|FUgA`y_UV}AmK_#i8(UOdPOMneVx;4edDX*k;-bGZ+UpN|R6nUY zc{Ph$A8W`0r=mX(>Obs`Hnz8XsMea~@Vq;>o`5(ej@)Wnk16ovB4k_-iRPv3y> zMm}+%ws=n$$B>F!Z|`o*lW~x&?l3ky1$gyI;4%tc&`Jn9%ZoE9!TysW5@ z)pga6DwPQ`_Kd6(ofwZE&3EwPiVEN=U$SH&_ix>KJ9o~#`}^;$%RkGmPv4(5{k-${ ztL5RG34C$IoD2t<1aurWFtBg~2@o4dfY~hEU^X$5L@UH=1zk*OIQg~SCVDrQLumee z+ii!K?g%k)h&3cIRxd2)OxRx%7QxWOst|GD&zJmryLQe`|9AGd_`%ivb-!}l+@(+3 zO`pWSHf8qy>F4bK=l=S9bgC#wD@X$aoBK-{U9fJjaXBX^$?uxZlV$(E)bz`x-STzO zuN(N;`F~uToN?&)PyN*&*xlEs%|G}2_DReHaRZ8vhR(0tbpew8?d-`zUh(tyXF`z|FtRL=I^-s7h$ZIZt zEct1u+s4P{|I9W`tl9VZ>(OiHReN)t=Ko!&TKwT)wfe3fXa733ii$m$CIEF3BDvh~ z-|Ky$uR_E++Tie9uC(LX=WO}qKAdRzo;Wr4nYW!u-TO$XJ#EU>yTs>Y{+8YzoyeHS z$%vZl7DzQO{v2SoRI*orZF2AP*BMrY*FLvjx0xH7d^~uIYtd5K6>A|Ofk?LCWOHLt zrT?z}9OY;QnNR2L7h2Sr-Z^dY-{<(T>EV_6Ysx=HOIykRHg4|b+;^aKn@1n}i|o9v z+*Z~dsw`mb(++(zIv@s#eQ^9OnX*fNU!4BeJ6_-Ru2ZQF+i`Dah*bQ#k^`#l)+Z&( zzqR{)daJvWqkiuWuDHFm&u?&X7kG1k!xF3=7OwwG+vl%KtNBvbXwY8Ix!vC2B_G(z z2Bp2n9nN&VUky$`VAZ;>@9!wR9C~Bs_5FKt-cI=U@1NgPhYp`bYh_-vGHiAZmkv}d?S10C?y$=9ptrv>TY+gDnAADM z4oL9dZLBX9-V02WFfZ%0x4+{(Iq$!o^wE1)eq310&i-(>`V86r?Pu((Y<^ri{h{K~ zYk~dWkA0|qY;GreQ^Y>Hr1PEWhK2iJo;t{sF}MBQ=L`Qhij>v6p<%MY!`J5Ur!z@c zdYfJzsW@RUhporXWZq$&iMEF$J!=>r3*MaAJ>Tzh-Q(oqotnRIC&8WQF!3I9{sC~F z0hMPBZ#P6A77{tL>3Yz&h0`n3qjLXMZ~o04k>CB~+P?`vbue$B#GKaTcRXFs=QTIJ zoD#H0zUJU_KZ6oGv#s;%mwi0;_Ttg?#+UCunC}D3LeMnaF!SRD*>x*#Uw{05*?yg{ zh=ynpIz@8&#g46nrL-jL|f;8t|B-RHW=Jh{~o^X&{w^g z-VE2jT>E_-W}(K#Y9(ew#@-gTe~DWM4flAo@O literal 4238 zcmeHK`BxKJ7ES~zELlJh0wf^_vM&{O1d=XUbrcYEY-C?VXpwyLM@P|^+EgTvxR zi;5CinoVS1A|kFRX%GbwP|`pMbWpY_+WDc+oHPHx^t^N5t5^5Fy6?WP?sx0dd3RT5 zoV1EG3WdV$b#d@Oq0sEDE-8j+8UlMakc;?n=m3O5Rp-mB1xlb$Vkr0hha9)`65AvI zF+frbBPA{gh)DwClF|}VSP5wyAcF^RvKah!NqK^l+zzSjiqZ>zwez_dLmkE&W2>PW)4&7TVIXsB9w zdm#BEHv_}*6?}TvCsszEWt=F?ol_3U!T$(hgV>cb?sg?q4yS!!eYp8==MSz2w9nOs z(SmYL&bTX2wo`qJn$*0L)O#*M&0rj(er``rskRwcMyx)4-IDxo+n@;G({mLyZVaEp z4>dvQzs}CCs9;STS^ZQDKy%qATi^PMlMlwJCC>Fu&xrKFuEbsKw4-@ODwDi3(2Xz< z%KaX4z0QJmSI1e>of)aNu25t9FUdplJ{3=tI4h%>#)K8p`b7T#u5Ww3B`c0XICeL#1KGuDE=lkR>cl$ zLNsnWt*DWB`Is#11E=WiKJ);Rb}G7N-!rWYGeZN|x>L8d@G?s!hJb$QXhSDH{Soy( zHEH)~v9Lpq;+)HjBZUj4Hq0NiP^{YFwu$kaTa3wtg?2`MbgH88j<63+ zzTHN$#&>QcS>Nfq%&#tl1_NYa;-mu712HK|OJSLyC@|`xYDrm-?X2wrwT`G--PAPFYfQiYqy)jT=yzMQqJX!*LH+YGdYcV~jALOA1bE;| zhxn?BI7u~xbtIf!N4@iGQ$o*`gYO(=iPB1<%G?VdBsWfcoIcy6a%D|qjkdBCPmuw< zI6iDUTus+Y;8I7WsSA@W>i8TQ;M(TK%6V}W>Sq|$O!G3%$2F3&6g6ON_L+YsRnu1)@$72HM<9cn!y2Kcm2lkN zMJvj4JTupn2R=)&mWwLVy946}+HQ|cEX1=j=aZ6~*3SB;H~)e-@etfYa^bKOJA>0+ zc~Vdj7Tw(O(R(e>K67nWdTK4Jix*6fr%N*KuRdP1Az^Fq-uXo@sHRp?Y+b*TxeQ16 z18OHCbHmnUTYG;ueW-)N}XcO?;QP?mu%jiHnc(rP%F(qv2#$ zGK&;k_2_yZQ+7<4Fkbl$rbXsJh*Pe`B+`Uy|LPsVi6J`S0t^@TC zAI?5~ADRiix5JNc4qY$@Y&q%vBQ)37!a5=?LG5T>FVuke>`z#sH{G|5|K{r!{aa7p zEjn2ci#QI^4;?A@LH6Y3@GOv$S07aW_^a9Ut1*wFB;Cn07dNGB8yF9NENt|{bq4l~ zE^ofLMIgOjG6R#5z_pK$XAF-E_OiEMzrup5IuK&Q zA&sRGn4<<$e!UU7!>cXSx5O4{xMb^Qg!=9&)wC#F&drjGlNcZR#fqVN?!|%Z#kU#6 z=w_+Tz}5ETAJGUClk7rI+>1&=;c?rheDirK{1dX#5p1&G&k(kzPY|p)`E!R`)ruUQ zLJVR3+3Eoo_I(?|5Aq&__h3SoMUWfJkN$L82wa*NsxNv>&9`SaU`i~w z7m;i&xG*f-i=7~wT8@pb(U(DYDg>#Ov?QlpA{pwA_bw&Qb3K=6jKsf zDYYhynd-Fl=lP#_l^W9<_fGUa!n3})arBB#^?& z3!^RKd4b4w&SC7ggcf_JI%2Q_!8kjKn@vF@s5mVx;F~hHg6n{a61&$72Q6m=l7RKH zCrKJ6h6J$X44sxh?&+-+SD`WM&Vls}Kqd%DevEkZ{0e>-QNCiexr z{bwXNU@KhE1@j-k48BT0qD6ozy*sP&huy~ewK?Jvzdy0Q>crFcF?f@UDe^`92;bL; z7X&q4a~1RlinR&82p$Nk$7h=XyPx3EBMNX9&4u`v8yuQ4HaA7PcHVB;&+^^+iQMbx K>QJ>OAoE|SZNNeR