From 20f94ef3a6b02383de586726b9414fb917b466ea Mon Sep 17 00:00:00 2001 From: Aleksey Date: Tue, 7 Jul 2020 16:59:11 +0300 Subject: [PATCH] API for custom skins and icons --- .../justmap/client/config/ConfigFactory.java | 2 +- .../render/Image.java} | 43 ++++++--- .../justmap/client/render/MapRenderer.java | 2 +- .../bulldog/justmap/config/ConfigWriter.java | 5 +- .../ru/bulldog/justmap/map/data/MapCache.java | 26 +++-- .../justmap/map/icon/EntityHeadIcon.java | 70 ++++++++------ .../map/minimap/{ => skin}/MapSkin.java | 70 +++++++++----- .../justmap/map/minimap/skin/SkinLoader.java | 91 ++++++++++++++++++ .../justmap/map/waypoint/Waypoint.java | 20 ++-- .../justmap/mixins/client/HudMixin.java | 7 +- .../ru/bulldog/justmap/util/ImageUtil.java | 24 +++-- .../ru/bulldog/justmap/util/RenderUtil.java | 65 ++++++------- .../ru/bulldog/justmap/util/StorageUtil.java | 35 ++++++- .../textures/skin/skin_def_gui_2_round.png | Bin 21664 -> 0 bytes 14 files changed, 313 insertions(+), 147 deletions(-) rename src/main/java/ru/bulldog/justmap/{map/icon/AbstractIcon.java => client/render/Image.java} (57%) rename src/main/java/ru/bulldog/justmap/map/minimap/{ => skin}/MapSkin.java (86%) create mode 100644 src/main/java/ru/bulldog/justmap/map/minimap/skin/SkinLoader.java delete mode 100644 src/main/resources/assets/justmap/textures/skin/skin_def_gui_2_round.png diff --git a/src/main/java/ru/bulldog/justmap/client/config/ConfigFactory.java b/src/main/java/ru/bulldog/justmap/client/config/ConfigFactory.java index a1af8f07..6c244f7b 100644 --- a/src/main/java/ru/bulldog/justmap/client/config/ConfigFactory.java +++ b/src/main/java/ru/bulldog/justmap/client/config/ConfigFactory.java @@ -15,8 +15,8 @@ import ru.bulldog.justmap.client.JustMapClient; import ru.bulldog.justmap.config.ConfigKeeper.EnumEntry; import ru.bulldog.justmap.map.DirectionArrow; -import ru.bulldog.justmap.map.minimap.MapSkin; import ru.bulldog.justmap.map.minimap.Minimap; +import ru.bulldog.justmap.map.minimap.skin.MapSkin; import ru.bulldog.justmap.util.ScreenPosition; public final class ConfigFactory { diff --git a/src/main/java/ru/bulldog/justmap/map/icon/AbstractIcon.java b/src/main/java/ru/bulldog/justmap/client/render/Image.java similarity index 57% rename from src/main/java/ru/bulldog/justmap/map/icon/AbstractIcon.java rename to src/main/java/ru/bulldog/justmap/client/render/Image.java index d195058e..f0ee54df 100644 --- a/src/main/java/ru/bulldog/justmap/map/icon/AbstractIcon.java +++ b/src/main/java/ru/bulldog/justmap/client/render/Image.java @@ -1,30 +1,49 @@ -package ru.bulldog.justmap.map.icon; +package ru.bulldog.justmap.client.render; import net.minecraft.client.MinecraftClient; import net.minecraft.client.texture.NativeImage; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.texture.TextureManager; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; + import ru.bulldog.justmap.util.RenderUtil; -public abstract class AbstractIcon extends Sprite { +public abstract class Image { protected static TextureManager textureManager = MinecraftClient.getInstance().getTextureManager(); - protected AbstractIcon(SpriteAtlasTexture spriteAtlasTexture, Info info, int i, int j, int k, int l, int m, NativeImage nativeImage) { - super(spriteAtlasTexture, info, i, j, k, l, m, nativeImage); + protected final NativeImage image; + protected Identifier textureId; + protected int width; + protected int height; + + protected Image(Identifier id, NativeImage image) { + this.width = image.getWidth(); + this.height = image.getHeight(); + this.textureId = id; + this.image = image; } - public abstract void draw(double x, double y, int w, int h); public abstract void draw(MatrixStack matrix, double x, double y, int w, int h); - public void draw(double x, double y) { - MatrixStack matrix = new MatrixStack(); - this.draw(matrix, x, y, this.getWidth(), this.getHeight()); + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; } - public void draw(MatrixStack matrix, double x, double y) { + public Identifier getId() { + return this.textureId; + } + + public void bindTexture() { + textureManager.bindTexture(textureId); + } + + public void draw(double x, double y) { + MatrixStack matrix = new MatrixStack(); this.draw(matrix, x, y, this.getWidth(), this.getHeight()); } @@ -38,6 +57,6 @@ public void draw(MatrixStack matrix, double x, double y, int size) { } protected void draw(MatrixStack matrix, double x, double y, float w, float h) { - RenderUtil.drawSprite(matrix, this, x, y, w, h); + RenderUtil.drawImage(matrix, this, x, y, w, h); } } diff --git a/src/main/java/ru/bulldog/justmap/client/render/MapRenderer.java b/src/main/java/ru/bulldog/justmap/client/render/MapRenderer.java index 9f0b6827..6e2f699c 100644 --- a/src/main/java/ru/bulldog/justmap/client/render/MapRenderer.java +++ b/src/main/java/ru/bulldog/justmap/client/render/MapRenderer.java @@ -16,8 +16,8 @@ import ru.bulldog.justmap.map.icon.PlayerIcon; import ru.bulldog.justmap.map.icon.WaypointIcon; import ru.bulldog.justmap.map.minimap.MapPlayerManager; -import ru.bulldog.justmap.map.minimap.MapSkin; import ru.bulldog.justmap.map.minimap.Minimap; +import ru.bulldog.justmap.map.minimap.skin.MapSkin; import ru.bulldog.justmap.util.RenderUtil.TextAlignment; import ru.bulldog.justmap.util.Colors; import ru.bulldog.justmap.util.RenderUtil; diff --git a/src/main/java/ru/bulldog/justmap/config/ConfigWriter.java b/src/main/java/ru/bulldog/justmap/config/ConfigWriter.java index 2dfdb80f..342349bc 100644 --- a/src/main/java/ru/bulldog/justmap/config/ConfigWriter.java +++ b/src/main/java/ru/bulldog/justmap/config/ConfigWriter.java @@ -5,14 +5,13 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.fabricmc.loader.api.FabricLoader; import ru.bulldog.justmap.JustMap; import ru.bulldog.justmap.util.JsonFactory; +import ru.bulldog.justmap.util.StorageUtil; public class ConfigWriter extends JsonFactory { - private final static File CONFIG_DIR = FabricLoader.getInstance().getConfigDirectory(); - private final static File CONFIG_FILE = new File(CONFIG_DIR, "/" + JustMap.MODID + ".json"); + private final static File CONFIG_FILE = new File(StorageUtil.configDir(), JustMap.MODID + ".json"); private static JsonObject configObject; diff --git a/src/main/java/ru/bulldog/justmap/map/data/MapCache.java b/src/main/java/ru/bulldog/justmap/map/data/MapCache.java index 29e358a3..f61100c5 100644 --- a/src/main/java/ru/bulldog/justmap/map/data/MapCache.java +++ b/src/main/java/ru/bulldog/justmap/map/data/MapCache.java @@ -48,15 +48,17 @@ public static void setLayerLevel(int level) { public static MapCache get() { World world = minecraft.world; - if (minecraft.isIntegratedServerRunning() && world != null) { - world = minecraft.getServer().getWorld(minecraft.world.getRegistryKey()); - } - if (currentWorld == null || (world != null && - world != currentWorld)) { - - currentWorld = world; + if (world != null) { + if (minecraft.isIntegratedServerRunning()) { + World serverWorld = minecraft.getServer().getWorld(world.getRegistryKey()); + if (serverWorld != null) { + world = serverWorld; + } + } + if (!world.equals(currentWorld)) { + currentWorld = world; + } } - if (currentWorld == null) return null; Identifier dimId = currentWorld.getDimensionRegistryKey().getValue(); @@ -207,17 +209,13 @@ private Map getChunks() { return this.chunks; } - public MapChunk getCurrentChunk(ChunkPos chunkPos) { - return this.getChunk(currentLayer, currentLevel, chunkPos.x, chunkPos.z); - } - public MapChunk getCurrentChunk(int posX, int posZ) { return this.getChunk(currentLayer, currentLevel, posX, posZ); } public MapChunk getChunk(Layer.Type layer, int level, int posX, int posZ) { - ChunkPos chunkPos = new ChunkPos(posX, posZ); - + ChunkPos chunkPos = new ChunkPos(posX, posZ); + MapChunk mapChunk; if (chunks.containsKey(chunkPos)) { mapChunk = this.chunks.get(chunkPos); diff --git a/src/main/java/ru/bulldog/justmap/map/icon/EntityHeadIcon.java b/src/main/java/ru/bulldog/justmap/map/icon/EntityHeadIcon.java index 92543fbd..8dcf16d7 100644 --- a/src/main/java/ru/bulldog/justmap/map/icon/EntityHeadIcon.java +++ b/src/main/java/ru/bulldog/justmap/map/icon/EntityHeadIcon.java @@ -1,22 +1,22 @@ package ru.bulldog.justmap.map.icon; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import ru.bulldog.justmap.client.config.ClientParams; +import ru.bulldog.justmap.client.render.Image; import ru.bulldog.justmap.util.ImageUtil; -import ru.bulldog.justmap.util.SpriteAtlas; +import ru.bulldog.justmap.util.StorageUtil; import ru.bulldog.justmap.util.ColorUtil; import ru.bulldog.justmap.util.Colors; import ru.bulldog.justmap.util.RenderUtil; import ru.bulldog.justmap.util.math.Line.Point; -import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.texture.NativeImageBackedTexture; -import net.minecraft.client.texture.Sprite; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -24,7 +24,7 @@ import net.minecraft.entity.passive.TameableEntity; import net.minecraft.util.Identifier; -public class EntityHeadIcon extends AbstractIcon { +public class EntityHeadIcon extends Image { private final static Map ICONS = new HashMap<>(); @@ -33,9 +33,15 @@ public static EntityHeadIcon getIcon(Entity entity) { if (ICONS.containsKey(id)) { return ICONS.get(id); } else { - Identifier iconId = iconId(id); - if (ImageUtil.imageExists(iconId)) { - return registerIcon(entity, id, iconId); + File iconsDir = StorageUtil.iconsDir(); + File iconPng = new File(iconsDir, String.format("%s/%s.png", id.getNamespace(), id.getPath())); + if (iconPng.exists()) { + return registerIcon(entity, id, iconPng); + } else { + Identifier iconId = iconId(id); + if (ImageUtil.imageExists(iconId)) { + return registerIcon(entity, id, iconId); + } } } @@ -48,18 +54,16 @@ public static EntityHeadIcon getIcon(Entity entity) { private boolean solid; private EntityHeadIcon(Identifier id, Identifier texture, int w, int h) { - super(SpriteAtlas.ENTITY_HEAD_ICONS, new Sprite.Info(texture, w, h, AnimationResourceMetadata.EMPTY), 0, w, h, 0, 0, ImageUtil.loadImage(texture, w, h)); + this(id, texture, ImageUtil.loadImage(texture, w, h)); + } + private EntityHeadIcon(Identifier id, Identifier texture, NativeImage image) { + super(texture, image); + this.solid = this.isSolid(); this.id = id; } - @Override - public void draw(double x, double y, int w, int h) { - MatrixStack matrix = new MatrixStack(); - this.draw(matrix, x, y, (float) w, (float) h); - } - @Override public void draw(MatrixStack matrix, double x, double y, int w, int h) { if (ClientParams.showIconsOutline) { @@ -71,7 +75,6 @@ public void draw(MatrixStack matrix, double x, double y, int w, int h) { RenderUtil.draw(x - thickness / 2, y - thickness / 2, (float) (w + thickness), (float) (h + thickness)); } } - textureManager.bindTexture(this.getId()); this.draw(matrix, x, y, (float) w, (float) h); } @@ -84,7 +87,7 @@ private void bindOutline() { } private boolean isSolid() { - NativeImage icon = this.images[0]; + NativeImage icon = this.image; int width = icon.getWidth(); int height = icon.getHeight(); @@ -102,11 +105,6 @@ private boolean isSolid() { } private NativeImage generateOutline() { - NativeImage icon = this.images[0]; - - int width = icon.getWidth(); - int height = icon.getHeight(); - NativeImage outline = new NativeImage(width + 4, height + 4, false); ImageUtil.fillImage(outline, Colors.TRANSPARENT); @@ -120,7 +118,7 @@ private NativeImage generateOutline() { int left = x - 1; int right = x + 1; for (int y = 0; y < height; y++) { - int alpha = (icon.getPixelColor(x, y) >> 24) & 255; + int alpha = (image.getPixelColor(x, y) >> 24) & 255; if (alpha == 0) continue; outlinePixels.add(new Point(x + 2, y + 2)); @@ -128,7 +126,7 @@ private NativeImage generateOutline() { int top = y - 1; int bottom = y + 1; if (top >= 0) { - alpha = (icon.getPixelColor(x, top) >> 24) & 255; + alpha = (image.getPixelColor(x, top) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(x + 2, y); if (!outlinePixels.contains(pixel)) { @@ -137,7 +135,7 @@ private NativeImage generateOutline() { } } if (left >= 0) { - alpha = (icon.getPixelColor(left, top) >> 24) & 255; + alpha = (image.getPixelColor(left, top) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(x, y); if (!outlinePixels.contains(pixel)) { @@ -149,7 +147,7 @@ private NativeImage generateOutline() { } } if (right < width) { - alpha = (icon.getPixelColor(right, top) >> 24) & 255; + alpha = (image.getPixelColor(right, top) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(right + 2, y); if (!outlinePixels.contains(pixel)) { @@ -168,7 +166,7 @@ private NativeImage generateOutline() { } } if (bottom < height) { - alpha = (icon.getPixelColor(x, bottom) >> 24) & 255; + alpha = (image.getPixelColor(x, bottom) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(x + 2, bottom + 1); if (!outlinePixels.contains(pixel)) { @@ -177,7 +175,7 @@ private NativeImage generateOutline() { } } if (left >= 0) { - alpha = (icon.getPixelColor(left, bottom) >> 24) & 255; + alpha = (image.getPixelColor(left, bottom) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(x, bottom + 2); if (!outlinePixels.contains(pixel)) { @@ -189,7 +187,7 @@ private NativeImage generateOutline() { } } if (right < width) { - alpha = (icon.getPixelColor(right, bottom) >> 24) & 255; + alpha = (image.getPixelColor(right, bottom) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(right + 2, bottom + 2); if (!outlinePixels.contains(pixel)) { @@ -208,7 +206,7 @@ private NativeImage generateOutline() { } } if (left >= 0) { - alpha = (icon.getPixelColor(left, y) >> 24) & 255; + alpha = (image.getPixelColor(left, y) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(x, y + 2); if (!outlinePixels.contains(pixel)) { @@ -224,7 +222,7 @@ private NativeImage generateOutline() { } } if (right < width) { - alpha = (icon.getPixelColor(right, y) >> 24) & 255; + alpha = (image.getPixelColor(right, y) >> 24) & 255; if (alpha == 0) { Point pixel = new Point(right + 1, y + 2); if (!outlinePixels.contains(pixel)) { @@ -255,6 +253,18 @@ private static Identifier iconId(Identifier id) { private static EntityHeadIcon registerIcon(Entity entity, Identifier entityId, Identifier texture) { EntityHeadIcon icon = new EntityHeadIcon(entityId, texture, 32, 32); + return registerIcon(entity, entityId, icon); + } + + private static EntityHeadIcon registerIcon(Entity entity, Identifier entityId, File image) { + NativeImage iconImage = ImageUtil.loadImage(image, 32, 32); + String prefix = String.format("icon_%s_%s", entityId.getNamespace(), entityId.getPath()); + Identifier textureId = textureManager.registerDynamicTexture(prefix, new NativeImageBackedTexture(iconImage)); + EntityHeadIcon icon = new EntityHeadIcon(entityId, textureId, iconImage); + return registerIcon(entity, entityId, icon); + } + + private static EntityHeadIcon registerIcon(Entity entity, Identifier entityId, EntityHeadIcon icon) { if (entity instanceof HostileEntity) { icon.color = Colors.DARK_RED; } else if (entity instanceof TameableEntity) { diff --git a/src/main/java/ru/bulldog/justmap/map/minimap/MapSkin.java b/src/main/java/ru/bulldog/justmap/map/minimap/skin/MapSkin.java similarity index 86% rename from src/main/java/ru/bulldog/justmap/map/minimap/MapSkin.java rename to src/main/java/ru/bulldog/justmap/map/minimap/skin/MapSkin.java index f07ada93..e2c4b430 100644 --- a/src/main/java/ru/bulldog/justmap/map/minimap/MapSkin.java +++ b/src/main/java/ru/bulldog/justmap/map/minimap/skin/MapSkin.java @@ -1,4 +1,4 @@ -package ru.bulldog.justmap.map.minimap; +package ru.bulldog.justmap.map.minimap.skin; import java.util.ArrayList; import java.util.Collections; @@ -9,6 +9,8 @@ import ru.bulldog.justmap.JustMap; import ru.bulldog.justmap.client.config.ClientParams; +import ru.bulldog.justmap.client.render.Image; +import ru.bulldog.justmap.map.minimap.Minimap; import ru.bulldog.justmap.util.ImageUtil; import ru.bulldog.justmap.util.RenderUtil; @@ -16,11 +18,8 @@ import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.texture.NativeImageBackedTexture; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.texture.TextureManager; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.LiteralText; @@ -28,14 +27,13 @@ import net.minecraft.util.Identifier; @Environment(EnvType.CLIENT) -public class MapSkin extends Sprite { +public class MapSkin extends Image { public static enum SkinType { UNIVERSAL, SQUARE, ROUND } - private final static SpriteAtlasTexture ATLAS = new SpriteAtlasTexture(new Identifier(JustMap.MODID, "textures/atlas/map_skins")); private final static List SKINS = new ArrayList<>(); private final static TextureManager textureManager = MinecraftClient.getInstance().getTextureManager(); @@ -48,14 +46,20 @@ public static enum SkinType { public final String name; private MapSkin(int id, String name, SkinType type, Identifier texture, int w, int h, int border, boolean resize, boolean repeat) { - super(ATLAS, new Sprite.Info(texture, w, h, AnimationResourceMetadata.EMPTY), 0, w, h, 0, 0, ImageUtil.loadImage(texture, w, h)); + this(id, name, type, texture, ImageUtil.loadImage(texture, w, h), w, h, border, resize, repeat); + } + private MapSkin(int id, String name, SkinType type, Identifier texture, NativeImage image, int w, int h, int border, boolean resize, boolean repeat) { + super(texture, image); + this.id = id; this.type = type; this.border = border; this.resizable = resize; this.repeating = repeat; this.name = name; + this.width = w; + this.height = h; this.renderData = new RenderData(); } @@ -65,20 +69,37 @@ public static void addSkin(SkinType type, String name, Identifier texture, int w SKINS.add(id, new MapSkin(id, name, type, texture, w, h, border, resizable, repeat)); } + public static void addSkin(SkinType type, String name, Identifier texture, NativeImage image, int w, int h, int border, boolean resizable, boolean repeat) { + int id = SKINS.size(); + SKINS.add(id, new MapSkin(id, name, type, texture, image, w, h, border, resizable, repeat)); + } + public static void addSquareSkin(String name, Identifier texture, int w, int h, int border, boolean resizable, boolean repeat) { addSkin(SkinType.SQUARE, name, texture, w, h, border, resizable, repeat); } + public static void addSquareSkin(String name, Identifier texture, NativeImage image, int w, int h, int border, boolean resizable, boolean repeat) { + addSkin(SkinType.SQUARE, name, texture, image, w, h, border, resizable, repeat); + } + public static void addSquareSkin(String name, Identifier texture, int w, int h, int border, boolean resizable) { addSkin(SkinType.SQUARE, name, texture, w, h, border, resizable, false); } + public static void addRoundSkin(String name, Identifier texture, int w, int h, int border) { + addSkin(SkinType.ROUND, name, texture, w, h, border, true, false); + } + + public static void addRoundSkin(String name, Identifier texture, NativeImage image, int w, int h, int border) { + addSkin(SkinType.ROUND, name, texture, image, w, h, border, true, false); + } + public static void addUniversalSkin(String name, Identifier texture, int w, int h, int border) { addSkin(SkinType.UNIVERSAL, name, texture, w, h, border, false, false); } - public static void addRoundSkin(String name, Identifier texture, int w, int h, int border) { - addSkin(SkinType.ROUND, name, texture, w, h, border, true, false); + public static void addUniversalSkin(String name, Identifier texture, NativeImage image, int w, int h, int border) { + addSkin(SkinType.UNIVERSAL, name, texture, image, w, h, border, false, false); } public static MapSkin getSkin(int id) { @@ -91,41 +112,36 @@ public static MapSkin getSkin(int id) { return SKINS.get(0); } - public Identifier getTexture() { - return this.getId(); - } - - private void bindPavedTexture(int w, int h) { + private void registerPavedTexture(int w, int h) { int border = (int) (this.border * renderData.scaleFactor); String pattern = "skin_%d_%dx%d_%d"; if (Minimap.isRound()) pattern += "_round"; - Identifier id = new Identifier(JustMap.MODID, String.format(pattern, this.id, w, h, border)); - if (textureManager.getTexture(id) == null) { + this.textureId = new Identifier(JustMap.MODID, String.format(pattern, this.id, w, h, border)); + if (textureManager.getTexture(textureId) == null) { NativeImage pavedImage; if (Minimap.isRound()) { - pavedImage = ImageUtil.createRoundSkin(images[0], w, h, border); + pavedImage = ImageUtil.createRoundSkin(image, w, h, border); } else { - pavedImage = ImageUtil.createSquareSkin(images[0], w, h, border); + pavedImage = ImageUtil.createSquareSkin(image, w, h, border); } - textureManager.registerTexture(id, new NativeImageBackedTexture(pavedImage)); + textureManager.registerTexture(textureId, new NativeImageBackedTexture(pavedImage)); } - textureManager.bindTexture(id); } - - public void draw(MatrixStack matrixStack, int x, int y, int w, int h) { + + @Override + public void draw(MatrixStack matrixStack, double x, double y, int w, int h) { RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); float hMult = (float) this.getWidth() / this.getHeight(); if (resizable || repeating) { - textureManager.bindTexture(this.getTexture()); if (type != SkinType.ROUND && (w > this.getWidth() || h > this.getHeight())) { RenderUtil.drawSkin(matrixStack, this, x, y, w, h); } else { - RenderUtil.drawSprite(matrixStack, x, y, 0, w, (int) (h / hMult), this); + RenderUtil.drawImage(matrixStack, this, x, y, w, h / hMult); } } else { - this.bindPavedTexture(w, h); - RenderUtil.drawSprite(matrixStack, x, y, 0, w, h, this); + this.registerPavedTexture(w, h); + RenderUtil.drawImage(matrixStack, this, x, y, w, h); } } @@ -343,5 +359,7 @@ public void calculate(double x, double y, float w, float h) { addUniversalSkin("Podzol", new Identifier("textures/block/podzol_top.png"), 256, 256, 8); addUniversalSkin("Nether Wart", new Identifier("textures/block/nether_wart_block.png"), 256, 256, 8); addUniversalSkin("Sponge", new Identifier("textures/block/sponge.png"), 256, 256, 8); + + SkinLoader.loadSkins(); } } \ No newline at end of file diff --git a/src/main/java/ru/bulldog/justmap/map/minimap/skin/SkinLoader.java b/src/main/java/ru/bulldog/justmap/map/minimap/skin/SkinLoader.java new file mode 100644 index 00000000..fd13396c --- /dev/null +++ b/src/main/java/ru/bulldog/justmap/map/minimap/skin/SkinLoader.java @@ -0,0 +1,91 @@ +package ru.bulldog.justmap.map.minimap.skin; + +import java.io.File; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; +import net.minecraft.client.texture.TextureManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; + +import ru.bulldog.justmap.JustMap; +import ru.bulldog.justmap.map.minimap.skin.MapSkin.SkinType; +import ru.bulldog.justmap.util.ImageUtil; +import ru.bulldog.justmap.util.JsonFactory; +import ru.bulldog.justmap.util.StorageUtil; + +public final class SkinLoader extends JsonFactory { + + private final static File SKINS_FOLDER = StorageUtil.skinsDir(); + private final static TextureManager textureManager = MinecraftClient.getInstance().getTextureManager(); + + private SkinLoader() {} + + public static void loadSkins() { + File[] skinFolders = SKINS_FOLDER.listFiles(); + for (File folder : skinFolders) { + if (folder.isFile()) continue; + File skinFile = new File(folder, "skin_data.json"); + if (!skinFile.exists()) continue; + loadSkin(folder, skinFile); + } + } + + private static void loadSkin(File dir, File skinFile) { + JsonObject skinData = loadJson(skinFile); + try { + String name = JsonHelper.getString(skinData, "name"); + int width = JsonHelper.getInt(skinData, "width"); + int height = JsonHelper.getInt(skinData, "height"); + int border = JsonHelper.getInt(skinData, "border"); + SkinType shape = getSkinType(JsonHelper.getString(skinData, "shape", "universal")); + boolean resizable = JsonHelper.getBoolean(skinData, "resizable", false); + boolean repeating = JsonHelper.getBoolean(skinData, "repeating", false); + String textureType = JsonHelper.getString(skinData, "texture_type"); + if (textureType.equals("source")) { + Identifier texture = new Identifier(JsonHelper.getString(skinData, "texture")); + MapSkin.addUniversalSkin(name, texture, width, height, border); + } else if (textureType.equals("image")) { + String imageName = JsonHelper.getString(skinData, "image"); + File imageFile = new File(dir, imageName); + NativeImage skinImage = ImageUtil.loadImage(imageFile, width, height); + String prefix = String.format("%s_%s", JustMap.MODID, imageName); + Identifier textureId = textureManager.registerDynamicTexture(prefix, new NativeImageBackedTexture(skinImage)); + switch (shape) { + case ROUND: + MapSkin.addRoundSkin(name, textureId, skinImage, width, height, border); + break; + case SQUARE: + MapSkin.addSquareSkin(name, textureId, skinImage, width, height, border, resizable, repeating); + break; + case UNIVERSAL: + MapSkin.addUniversalSkin(imageName, textureId, skinImage, width, height, border); + break; + } + } else { + throw new JsonParseException("Invalid skin texture type: '" + textureType + "'"); + } + } catch (Exception ex) { + JustMap.LOGGER.logWarning("Can't load skin: " + skinFile.getPath()); + JustMap.LOGGER.logWarning(ex.getLocalizedMessage()); + } + } + + private static SkinType getSkinType(String shape) throws JsonParseException { + switch(shape.toLowerCase()) { + case "round": + case "circle": + return SkinType.ROUND; + case "universal": + return SkinType.UNIVERSAL; + case "square": + return SkinType.SQUARE; + default: + throw new JsonParseException("Invalid skin shape: '" + shape + "'"); + } + } +} diff --git a/src/main/java/ru/bulldog/justmap/map/waypoint/Waypoint.java b/src/main/java/ru/bulldog/justmap/map/waypoint/Waypoint.java index e666202e..97aec1fd 100644 --- a/src/main/java/ru/bulldog/justmap/map/waypoint/Waypoint.java +++ b/src/main/java/ru/bulldog/justmap/map/waypoint/Waypoint.java @@ -7,18 +7,15 @@ import com.google.gson.JsonObject; import ru.bulldog.justmap.JustMap; -import ru.bulldog.justmap.map.icon.AbstractIcon; +import ru.bulldog.justmap.client.render.Image; import ru.bulldog.justmap.util.ColorUtil; import ru.bulldog.justmap.util.Colors; import ru.bulldog.justmap.util.Dimension; import ru.bulldog.justmap.util.ImageUtil; -import ru.bulldog.justmap.util.SpriteAtlas; import ru.bulldog.justmap.util.math.RandomUtil; -import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.texture.NativeImageBackedTexture; -import net.minecraft.client.texture.Sprite; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Identifier; import net.minecraft.util.JsonHelper; @@ -185,7 +182,7 @@ public static Waypoint fromJson(JsonObject jsonObject) { return waypoint; } - public static class Icon extends AbstractIcon { + public static class Icon extends Image { public final static Identifier DEFAULT_ICON = new Identifier(JustMap.MODID, "textures/icon/default.png"); private final static NativeImage DEFAULT_TEXTURE = ImageUtil.loadImage(DEFAULT_ICON, 18, 18); @@ -196,13 +193,13 @@ public static class Icon extends AbstractIcon { private final static Map coloredIcons = new HashMap<>(); private Icon(int key, Identifier icon, int color, int w, int h) { - super(SpriteAtlas.WAYPOINT_ICONS, new Sprite.Info(icon, w, h, AnimationResourceMetadata.EMPTY), 0, w, h, 0, 0, ImageUtil.loadImage(icon, w, h)); + super(icon, ImageUtil.loadImage(icon, w, h)); this.key = key; this.color = color; } private Icon(int key, Identifier icon, NativeImage texture, int color, int w, int h) { - super(SpriteAtlas.WAYPOINT_ICONS, new Sprite.Info(icon, w, h, AnimationResourceMetadata.EMPTY), 0, w, h, 0, 0, texture); + super(icon, texture); this.key = key; this.color = color; } @@ -223,16 +220,11 @@ private static Icon coloredIcon(int color) { return icon; } + @Override public void bindTexture() { textureManager.bindTexture(this.getTexture()); } - @Override - public void draw(double x, double y, int w, int h) { - MatrixStack matrix = new MatrixStack(); - this.draw(matrix, x, y, w, h); - } - @Override public void draw(MatrixStack matrix, double x, double y, int w, int h) { this.bindTexture(); @@ -242,7 +234,7 @@ public void draw(MatrixStack matrix, double x, double y, int w, int h) { private Identifier getColoredTexture() { Identifier id = new Identifier(JustMap.MODID, String.format("wp_icon_%d", this.color)); if (textureManager.getTexture(id) == null) { - textureManager.registerTexture(id, new NativeImageBackedTexture(this.images[0])); + textureManager.registerTexture(id, new NativeImageBackedTexture(this.image)); } return id; } diff --git a/src/main/java/ru/bulldog/justmap/mixins/client/HudMixin.java b/src/main/java/ru/bulldog/justmap/mixins/client/HudMixin.java index ee76b1e2..c41da595 100644 --- a/src/main/java/ru/bulldog/justmap/mixins/client/HudMixin.java +++ b/src/main/java/ru/bulldog/justmap/mixins/client/HudMixin.java @@ -9,6 +9,7 @@ import ru.bulldog.justmap.client.render.MapRenderer; import ru.bulldog.justmap.util.Colors; import ru.bulldog.justmap.util.ScreenPosition; + import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.hud.InGameHud; @@ -41,8 +42,10 @@ abstract class HudMixin extends DrawableHelper { @Inject(at = @At("RETURN"), method = "render") public void draw(MatrixStack matrixStack, float delta, CallbackInfo info) { - MapRenderer.getInstance().draw(matrixStack); - AdvancedInfo.getInstance().draw(matrixStack); + if (!client.options.debugEnabled) { + MapRenderer.getInstance().draw(matrixStack); + AdvancedInfo.getInstance().draw(matrixStack); + } } @Inject(at = @At("HEAD"), method = "renderStatusEffectOverlay", cancellable = true) diff --git a/src/main/java/ru/bulldog/justmap/util/ImageUtil.java b/src/main/java/ru/bulldog/justmap/util/ImageUtil.java index 724acae3..d0fc91dc 100644 --- a/src/main/java/ru/bulldog/justmap/util/ImageUtil.java +++ b/src/main/java/ru/bulldog/justmap/util/ImageUtil.java @@ -1,6 +1,9 @@ package ru.bulldog.justmap.util; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import ru.bulldog.justmap.JustMap; import ru.bulldog.justmap.util.math.Line; @@ -23,8 +26,7 @@ private static void checkResourceManager() { } public static boolean imageExists(Identifier image) { - if (image == null) return false; - + if (image == null) return false; try { return resourceManager.containsResource(image); } catch(Exception ex) { @@ -33,9 +35,20 @@ public static boolean imageExists(Identifier image) { } } + public static NativeImage loadImage(File image, int w, int h) { + if (image.exists()) { + try (InputStream fis = new FileInputStream(image)) { + return NativeImage.read(fis); + } catch (IOException ex) { + JustMap.LOGGER.logWarning(String.format("Can't load texture image: %s. Will be created empty image.", image)); + JustMap.LOGGER.logWarning(String.format("Cause: %s.", ex.getMessage())); + } + } + return new NativeImage(w, h, false); + } + public static NativeImage loadImage(Identifier image, int w, int h) { - checkResourceManager(); - + checkResourceManager(); if (imageExists(image)) { try (Resource resource = resourceManager.getResource(image)) { return NativeImage.read(resource.getInputStream()); @@ -43,8 +56,7 @@ public static NativeImage loadImage(Identifier image, int w, int h) { JustMap.LOGGER.logWarning(String.format("Can't load texture image: %s. Will be created empty image.", image)); JustMap.LOGGER.logWarning(String.format("Cause: %s.", e.getMessage())); } - } - + } return new NativeImage(w, h, false); } diff --git a/src/main/java/ru/bulldog/justmap/util/RenderUtil.java b/src/main/java/ru/bulldog/justmap/util/RenderUtil.java index 0ddee0c4..6f736c22 100644 --- a/src/main/java/ru/bulldog/justmap/util/RenderUtil.java +++ b/src/main/java/ru/bulldog/justmap/util/RenderUtil.java @@ -11,18 +11,18 @@ import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexFormat; import net.minecraft.client.render.VertexFormats; -import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.TextureManager; import net.minecraft.client.util.math.AffineTransformation; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Identifier; import net.minecraft.util.math.Matrix3f; import net.minecraft.util.math.Matrix4f; - -import ru.bulldog.justmap.map.minimap.MapSkin; -import ru.bulldog.justmap.map.minimap.MapSkin.RenderData; import net.minecraft.text.Text; +import ru.bulldog.justmap.client.render.Image; +import ru.bulldog.justmap.map.minimap.skin.MapSkin; +import ru.bulldog.justmap.map.minimap.skin.MapSkin.RenderData; + import org.lwjgl.opengl.GL11; public class RenderUtil extends DrawableHelper { @@ -277,10 +277,10 @@ public static void drawSkin(MatrixStack matrix, MapSkin skin, double x, double y renderData.calculate(x, y, w, h); } - float sMinU = skin.getMinU(); - float sMaxU = skin.getMaxU(); - float sMinV = skin.getMinV(); - float sMaxV = skin.getMaxV(); + float sMinU = 0.0F; + float sMaxU = 1.0F; + float sMinV = 0.0F; + float sMaxV = 1.0F; float scaledBrd = renderData.scaledBorder; float hSide = renderData.hSide; float vSide = renderData.vSide; @@ -297,43 +297,42 @@ public static void drawSkin(MatrixStack matrix, MapSkin skin, double x, double y RenderSystem.enableAlphaTest(); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + skin.bindTexture(); startDrawNormal(); - VertexConsumer vertexConsumer = skin.getTextureSpecificVertexConsumer(vertexBuffer); - - draw(matrix, vertexConsumer, x, y, scaledBrd, scaledBrd, sMinU, sMinV, leftU, topV); - draw(matrix, vertexConsumer, rightC, y, scaledBrd, scaledBrd, rightU, sMinV, sMaxU, topV); - draw(matrix, vertexConsumer, x, bottomC, scaledBrd, scaledBrd, sMinU, bottomV, leftU, sMaxV); - draw(matrix, vertexConsumer, rightC, bottomC, scaledBrd, scaledBrd, rightU, bottomV, sMaxU, sMaxV); + draw(matrix, vertexBuffer, x, y, scaledBrd, scaledBrd, sMinU, sMinV, leftU, topV); + draw(matrix, vertexBuffer, rightC, y, scaledBrd, scaledBrd, rightU, sMinV, sMaxU, topV); + draw(matrix, vertexBuffer, x, bottomC, scaledBrd, scaledBrd, sMinU, bottomV, leftU, sMaxV); + draw(matrix, vertexBuffer, rightC, bottomC, scaledBrd, scaledBrd, rightU, bottomV, sMaxU, sMaxV); if (skin.resizable) { - draw(matrix, vertexConsumer, rightC, topC, scaledBrd, vSide, rightU, topV, sMaxU, bottomV); - draw(matrix, vertexConsumer, x, topC, scaledBrd, vSide, sMinU, topV, leftU, bottomV); - draw(matrix, vertexConsumer, leftC, topC, hSide, vSide, leftU, topV, rightU, bottomV); + draw(matrix, vertexBuffer, rightC, topC, scaledBrd, vSide, rightU, topV, sMaxU, bottomV); + draw(matrix, vertexBuffer, x, topC, scaledBrd, vSide, sMinU, topV, leftU, bottomV); + draw(matrix, vertexBuffer, leftC, topC, hSide, vSide, leftU, topV, rightU, bottomV); if (skin.repeating) { float tail = renderData.tail; float tailU = renderData.tailU; hSide = vSide; - draw(matrix, vertexConsumer, leftC + hSide, y, tail, scaledBrd, leftU, sMinV, tailU, topV); - draw(matrix, vertexConsumer, leftC + hSide, bottomC, tail, scaledBrd, leftU, bottomV, tailU, sMaxV); + draw(matrix, vertexBuffer, leftC + hSide, y, tail, scaledBrd, leftU, sMinV, tailU, topV); + draw(matrix, vertexBuffer, leftC + hSide, bottomC, tail, scaledBrd, leftU, bottomV, tailU, sMaxV); } - draw(matrix, vertexConsumer, leftC, y, hSide, scaledBrd, leftU, sMinV, rightU, topV); - draw(matrix, vertexConsumer, leftC, bottomC, hSide, scaledBrd, leftU, bottomV, rightU, sMaxV); + draw(matrix, vertexBuffer, leftC, y, hSide, scaledBrd, leftU, sMinV, rightU, topV); + draw(matrix, vertexBuffer, leftC, bottomC, hSide, scaledBrd, leftU, bottomV, rightU, sMaxV); } else { double left = leftC; int segments = renderData.hSegments; for (int i = 0; i < segments; i++) { - draw(matrix, vertexConsumer, left, y, hSide, scaledBrd, leftU, sMinV, rightU, topV); - draw(matrix, vertexConsumer, left, bottomC, hSide, scaledBrd, leftU, bottomV, rightU, sMaxV); + draw(matrix, vertexBuffer, left, y, hSide, scaledBrd, leftU, sMinV, rightU, topV); + draw(matrix, vertexBuffer, left, bottomC, hSide, scaledBrd, leftU, bottomV, rightU, sMaxV); left += hSide; } double top = topC; segments = renderData.vSegments; for (int i = 0; i < segments; i++) { - draw(matrix, vertexConsumer, x, top, scaledBrd, vSide, sMinU, topV, leftU, bottomV); - draw(matrix, vertexConsumer, rightC, top, scaledBrd, vSide, rightU, topV, sMaxU, bottomV); + draw(matrix, vertexBuffer, x, top, scaledBrd, vSide, sMinU, topV, leftU, bottomV); + draw(matrix, vertexBuffer, rightC, top, scaledBrd, vSide, rightU, topV, sMaxU, bottomV); top += vSide; } @@ -342,25 +341,23 @@ public static void drawSkin(MatrixStack matrix, MapSkin skin, double x, double y float hTailU = renderData.hTailU; float vTailV = renderData.vTailV; - draw(matrix, vertexConsumer, left, y, hTail, scaledBrd, leftU, sMinV, hTailU, topV); - draw(matrix, vertexConsumer, left, bottomC, hTail, scaledBrd, leftU, bottomV, hTailU, sMaxV); - draw(matrix, vertexConsumer, x, top, scaledBrd, vTail, sMinU, topV, leftU, vTailV); - draw(matrix, vertexConsumer, rightC, top, scaledBrd, vTail, rightU, topV, sMaxU, vTailV); + draw(matrix, vertexBuffer, left, y, hTail, scaledBrd, leftU, sMinV, hTailU, topV); + draw(matrix, vertexBuffer, left, bottomC, hTail, scaledBrd, leftU, bottomV, hTailU, sMaxV); + draw(matrix, vertexBuffer, x, top, scaledBrd, vTail, sMinU, topV, leftU, vTailV); + draw(matrix, vertexBuffer, rightC, top, scaledBrd, vTail, rightU, topV, sMaxU, vTailV); } endDraw(); } - public static void drawSprite(MatrixStack matrix, Sprite sprite, double x, double y, float w, float h) { + public static void drawImage(MatrixStack matrix, Image image, double x, double y, float w, float h) { RenderSystem.enableBlend(); RenderSystem.enableAlphaTest(); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + image.bindTexture(); startDrawNormal(); - - VertexConsumer vertexConsumer = sprite.getTextureSpecificVertexConsumer(vertexBuffer); - - draw(matrix, vertexConsumer, x, y, w, h, sprite.getMinU(), sprite.getMinV(), sprite.getMaxU(), sprite.getMaxV()); + draw(matrix, vertexBuffer, x, y, w, h, 0.0F, 0.0F, 1.0F, 1.0F); endDraw(); } diff --git a/src/main/java/ru/bulldog/justmap/util/StorageUtil.java b/src/main/java/ru/bulldog/justmap/util/StorageUtil.java index ca53620d..f9314242 100644 --- a/src/main/java/ru/bulldog/justmap/util/StorageUtil.java +++ b/src/main/java/ru/bulldog/justmap/util/StorageUtil.java @@ -2,6 +2,7 @@ import java.io.File; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ServerInfo; import net.minecraft.nbt.CompoundTag; @@ -9,16 +10,21 @@ import net.minecraft.util.math.ChunkPos; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.dimension.DimensionType; +import ru.bulldog.justmap.JustMap; import ru.bulldog.justmap.map.data.ChunkStorage; public class StorageUtil { private static MinecraftClient minecraft = MinecraftClient.getInstance(); - public final static File MAP_DIR = new File(minecraft.runDirectory, "justmap/"); + private final static File MAP_DATA_DIR = new File(minecraft.runDirectory, JustMap.MODID + "/"); + private final static File GAME_CONFIG_DIR = FabricLoader.getInstance().getConfigDirectory(); + private final static File MAP_CONFIG_DIR = new File(GAME_CONFIG_DIR, String.format("/%s/", JustMap.MODID)); + private final static File MAP_SKINS_DIR = new File(MAP_CONFIG_DIR, "skins/"); + private final static File MAP_ICONS_DIR = new File(MAP_CONFIG_DIR, "icons/"); private static ChunkStorage storage; private static File storageDir; - private static File filesDir = new File(MAP_DIR, "undefined/"); + private static File filesDir = new File(MAP_DATA_DIR, "undefined/"); private static String currentDim = "unknown"; public static synchronized CompoundTag getCache(ChunkPos pos) { @@ -47,6 +53,27 @@ public static void updateCacheStorage() { if (storage == null) storage = new ChunkStorage(); } + public static File configDir() { + if (!MAP_CONFIG_DIR.exists()) { + MAP_CONFIG_DIR.mkdirs(); + } + return MAP_CONFIG_DIR; + } + + public static File skinsDir() { + if (!MAP_SKINS_DIR.exists()) { + MAP_SKINS_DIR.mkdirs(); + } + return MAP_SKINS_DIR; + } + + public static File iconsDir() { + if (!MAP_ICONS_DIR.exists()) { + MAP_ICONS_DIR.mkdirs(); + } + return MAP_ICONS_DIR; + } + public static File cacheDir() { RegistryKey dimKey = null; if (minecraft.world != null) { @@ -82,10 +109,10 @@ public static File filesDir() { if (client.isIntegratedServerRunning()) { MinecraftServer server = client.getServer(); String name = scrubNameFile(server.getSaveProperties().getLevelName()); - filesDir = new File(MAP_DIR, String.format("local/%s/", name)); + filesDir = new File(MAP_DATA_DIR, String.format("local/%s/", name)); } else if (serverInfo != null) { String name = scrubNameFile(serverInfo.name); - filesDir = new File(MAP_DIR, String.format("servers/%s/", name)); + filesDir = new File(MAP_DATA_DIR, String.format("servers/%s/", name)); } if (!filesDir.exists()) { diff --git a/src/main/resources/assets/justmap/textures/skin/skin_def_gui_2_round.png b/src/main/resources/assets/justmap/textures/skin/skin_def_gui_2_round.png deleted file mode 100644 index 35d25a605d94dfb56606af8981839bdf1e2a3999..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21664 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 A~E(*OA1osXg zpMwt};$&chb*)=cL3OSk>scJEWV*mOZ5|O3p{S;&rXeL<{+E{5@u;XnZ7Uo-yRi!8 zi>Wz&Ww#(*p8F>^j5QP^Ocu$QLQ^1KwD}Vro;P=vP&6D=Pw+r>&`mHTXraK1yvAI$ zEEDk#DBIh84VnZBhOyp~wL^Ml+c!0e>V=jNI(7Tvu~RGa@!Pvj*xL^}fn4C_==hCG zGK>N~5e;7WMW}rF7D=ONOG-%{z*X;|%THU!j+9qlRg9_R5**xL!&z9EScGH5;^N|u zrP2E9O)#L@f@*W&vM+Pa)TVPEQY2a6R?hN&x=)u{IjPgpCZo*ktzp}o+aG6(W;RbT zYsQ?Mo!ef5yl1#~?{i10Tt$ixvP?!-PcI)<%$0Rhv_tzfpR`@0WhxhiR@3g@O|=~< zI~aDqTQ=30aiG`HZW_egU>NEAhi z93CPgE$s)&`OE_oBGnL`uD^qZyv!e+01ng*(qNo|+`2mE^|T zbmm04rwwdBeB)32#sRle)~$GhwwC%fWj*uln<2%q=a$2*=VxwDPbrQeNl9z5doHeq zL3mO}7)r9pbk`H*)=G>#613;t1(AC>&CEEIMv$eT8nm&*_ z%U6~?Gbl~LWSl>&s~aYXHq{^R;!rdu&O2kRGz`4O6fCbKr-lNIX~gw0xYmPT&8Bc6 z!VY!s@tPYjC25w>sRp9wX|qC{O-(@y5`xDPe#z%_iKs|3kw+-p-EmItR^qms>gq8S z<4f#oM+s!g(_>k#8+I->R&L?}b@H zc!Z?)?~U(1plH5_5)ik8MB>UEK3lsG>+*ptQXk5vyHl$kXN3A3vN9j-oRo;kdBM}N zXMC0NMdNM{o)ocNUb<{XpyU#zn=?P}vH-5#)T?~88|GfStq_z#K=R{q3kufenwzzK z%kLq&5dzW^JYvy7{>TgJ(wSKG?)oe&YLi!eXCKWUrjK}9C zz)Q#)cDm(D*}Z>%?DGvUH$+$=;<8F)4j*X2So^v1O0nWkf>SB9y$%@ZW!$wvJnnRfax9!8;&20fF$U#R5R|+42^tr`u%ZFsFI~`m5 z``C}Y{G+9}za6q&+I#W6f|bl|cq{Xq={_e+6d?rHu4tlG9v7}8G5_uF|D2Scf1(s@ z@44XgFVY4|2#J#GUuT(H5aSHyMgZP{7yQGCO8r|y-D{ap*7=sjYHR|zvQGcrQ#>+dJ5fsV9Tx*Ehftw7&QP>(Z5`@7ewK=UZV;;0R|f#jY1 EKkLmZ;Q#;t