From 6db30ff6932020d40564261232c83ec37ccbd2cd Mon Sep 17 00:00:00 2001 From: Sollace Date: Wed, 12 Oct 2022 15:46:15 +0200 Subject: [PATCH] Added weather vanes --- .../com/minelittlepony/unicopia/Unicopia.java | 4 +- .../unicopia/block/UBlockEntities.java | 16 +++ .../unicopia/block/UBlocks.java | 5 + .../unicopia/block/WeatherVaneBlock.java | 119 ++++++++++++++++++ .../block/data/WeatherConditions.java | 83 +++++++++++- .../unicopia/client/URenderers.java | 6 +- .../WeatherVaneBlockEntityRenderer.java | 58 +++++++++ .../unicopia/blockstates/weather_vane.json | 5 + .../resources/assets/unicopia/lang/en_us.json | 1 + .../unicopia/models/block/weather_vane.json | 6 + .../unicopia/models/item/weather_vane.json | 6 + .../unicopia/textures/entity/weather_vane.png | Bin 0 -> 314 bytes .../unicopia/textures/item/weather_vane.png | Bin 0 -> 1408 bytes .../advancements/recipes/weather_vane.json | 30 +++++ .../data/unicopia/recipes/weather_vane.json | 16 +++ weather_vane.bbmodel | 1 + 16 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java create mode 100644 src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/entity/WeatherVaneBlockEntityRenderer.java create mode 100644 src/main/resources/assets/unicopia/blockstates/weather_vane.json create mode 100644 src/main/resources/assets/unicopia/models/block/weather_vane.json create mode 100644 src/main/resources/assets/unicopia/models/item/weather_vane.json create mode 100644 src/main/resources/assets/unicopia/textures/entity/weather_vane.png create mode 100644 src/main/resources/assets/unicopia/textures/item/weather_vane.png create mode 100644 src/main/resources/data/unicopia/advancements/recipes/weather_vane.json create mode 100644 src/main/resources/data/unicopia/recipes/weather_vane.json create mode 100644 weather_vane.bbmodel diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index e968fc3a..58c39dd3 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -18,8 +18,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitLoader; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.block.UTreeGen; -import com.minelittlepony.unicopia.block.data.BlockDestructionManager; -import com.minelittlepony.unicopia.block.data.ZapAppleStageStore; +import com.minelittlepony.unicopia.block.data.*; import com.minelittlepony.unicopia.block.state.StateMapLoader; import com.minelittlepony.unicopia.command.Commands; import com.minelittlepony.unicopia.container.SpellbookChapterLoader; @@ -63,6 +62,7 @@ public class Unicopia implements ModInitializer { ServerTickEvents.END_WORLD_TICK.register(w -> { ((BlockDestructionManager.Source)w).getDestructionManager().tick(); ZapAppleStageStore.get(w).tick(); + WeatherConditions.get(w).tick(); if (Debug.DEBUG_SPELLBOOK_CHAPTERS) { SpellbookChapterLoader.INSTANCE.sendUpdate(w.getServer()); } diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java b/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java new file mode 100644 index 00000000..dad1d265 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java @@ -0,0 +1,16 @@ +package com.minelittlepony.unicopia.block; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.block.entity.BlockEntityType.Builder; +import net.minecraft.util.registry.Registry; + +public interface UBlockEntities { + BlockEntityType WEATHER_VANE = create("weather_vane", BlockEntityType.Builder.create(WeatherVaneBlock.WeatherVane::new, UBlocks.WEATHER_VANE)); + + static BlockEntityType create(String id, Builder builder) { + return Registry.register(Registry.BLOCK_ENTITY_TYPE, id, builder.build(null)); + } + + static void bootstrap() {} +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java index 8ef9805a..8b568b5a 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java +++ b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java @@ -42,6 +42,8 @@ public interface UBlocks { Block ZAP_BULB = register("zap_bulb", new FruitBlock(FabricBlockSettings.of(Material.GOURD, MapColor.GRAY).strength(500, 1200).sounds(BlockSoundGroup.AZALEA_LEAVES), Direction.DOWN, ZAP_LEAVES, FruitBlock.DEFAULT_SHAPE, false)); Block ZAP_APPLE = register("zap_apple", new FruitBlock(FabricBlockSettings.of(Material.GOURD, MapColor.GRAY).sounds(BlockSoundGroup.AZALEA_LEAVES), Direction.DOWN, ZAP_LEAVES, FruitBlock.DEFAULT_SHAPE, false)); + Block WEATHER_VANE = register("weather_vane", new WeatherVaneBlock(FabricBlockSettings.of(Material.METAL, MapColor.BLACK).requiresTool().strength(3.0f, 6.0f).sounds(BlockSoundGroup.METAL).nonOpaque()), ItemGroup.DECORATIONS); + Block GREEN_APPLE_LEAVES = register("green_apple_leaves", new FruitBearingBlock(FabricBlockSettings.copy(Blocks.OAK_LEAVES), 0xE5FFFF88, () -> UBlocks.GREEN_APPLE, @@ -98,6 +100,9 @@ public interface UBlocks { static void bootstrap() { StrippableBlockRegistry.register(ZAP_LOG, STRIPPED_ZAP_LOG); StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD); + TRANSLUCENT_BLOCKS.add(WEATHER_VANE); + + UBlockEntities.bootstrap(); } static boolean never(BlockState state, BlockView world, BlockPos pos) { diff --git a/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java new file mode 100644 index 00000000..068fb951 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java @@ -0,0 +1,119 @@ +package com.minelittlepony.unicopia.block; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.block.data.WeatherConditions; + +import net.minecraft.block.*; +import net.minecraft.block.entity.*; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.Packet; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.util.math.*; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; + +public class WeatherVaneBlock extends BlockWithEntity { + /*private static final VoxelShape SHAPE = VoxelShapes.union( + Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F), + Block.createCuboidShape(7, 0, 7, 9, 1, 9) + );*/ + + protected WeatherVaneBlock(Settings settings) { + super(settings); + } + + @Deprecated + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return VoxelShapes.union( + Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F), + Block.createCuboidShape(7, 0, 7, 9, 1, 9) + ); + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new WeatherVane(pos, state); + } + + @Override + @Nullable + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return BellBlock.checkType(type, UBlockEntities.WEATHER_VANE, world.isClient ? WeatherVane::clientTick : WeatherVane::serverTick); + } + + public static class WeatherVane extends BlockEntity { + private float angle; + + private float clientAngle; + private float prevAngle; + + public WeatherVane(BlockPos pos, BlockState state) { + super(UBlockEntities.WEATHER_VANE, pos, state); + } + + public float getAngle(float tickDelta) { + return MathHelper.lerp(tickDelta, prevAngle, clientAngle); + } + + @Override + public void readNbt(NbtCompound nbt) { + angle = nbt.getFloat("angle"); + } + + @Override + protected void writeNbt(NbtCompound nbt) { + nbt.putFloat("angle", angle); + } + + @Override + public Packet toUpdatePacket() { + return BlockEntityUpdateS2CPacket.create(this); + } + + @Override + public NbtCompound toInitialChunkDataNbt() { + return createNbt(); + } + + public static void serverTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { + Vec3d airflow = WeatherConditions.get(world).getWindDirection(); + float angle = (float)Math.atan2(airflow.x, airflow.z) + MathHelper.PI; + if (Math.signum(entity.angle) != Math.signum(angle)) { + angle = MathHelper.PI - angle; + } + angle %= MathHelper.PI; + + if (angle != entity.angle) { + entity.angle = angle; + entity.markDirty(); + if (world instanceof ServerWorld serverWorld) { + serverWorld.getChunkManager().markForUpdate(pos); + } + + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), USounds.BLOCK_WEATHER_VANE_ROTATE, SoundCategory.BLOCKS, 1, 0.5F + (float)world.random.nextGaussian()); + } + } + + public static void clientTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { + entity.prevAngle = entity.clientAngle; + + float angle = entity.angle + (float)Math.sin(world.getTime() / 70F) * (world.isThundering() ? 30 : 1); + + float step = (Math.abs(entity.clientAngle) - Math.abs(angle)) / 7F; + + if (entity.clientAngle < angle) { + entity.clientAngle += step; + } else if (entity.clientAngle > angle) { + entity.clientAngle -= step; + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/data/WeatherConditions.java b/src/main/java/com/minelittlepony/unicopia/block/data/WeatherConditions.java index dcae7ed2..c3b15ad3 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/data/WeatherConditions.java +++ b/src/main/java/com/minelittlepony/unicopia/block/data/WeatherConditions.java @@ -1,13 +1,19 @@ package com.minelittlepony.unicopia.block.data; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.util.Tickable; + import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.nbt.NbtCompound; import net.minecraft.tag.BlockTags; +import net.minecraft.util.Identifier; import net.minecraft.util.math.*; import net.minecraft.world.Heightmap.Type; +import net.minecraft.world.PersistentState; import net.minecraft.world.World; -public class WeatherConditions { +public class WeatherConditions extends PersistentState implements Tickable { public static final TwoDimensionalField HEIGHT_MAP_FIELD = (world, pos) -> { return world.getTopY(Type.WORLD_SURFACE_WG, pos.getX(), pos.getZ()); }; @@ -29,16 +35,87 @@ public class WeatherConditions { public static final float MAX_UPDRAFT_HEIGHT = 20; public static final float MAX_TERRAIN_HEIGHT = 50; + public static final float MAX_WIND_HEIGHT = 70; + + private static final Identifier ID = Unicopia.id("weather_conditions"); + + public static WeatherConditions get(World world) { + return WorldOverlay.getPersistableStorage(world, ID, WeatherConditions::new, WeatherConditions::new); + } + + private final World world; + + private float windYaw; + private float prevWindYaw; + private int interpolation; + private int maxInterpolation = 100; + + private boolean prevDayState; + + private WeatherConditions(World world, NbtCompound compound) { + this(world); + windYaw = compound.getFloat("windYaw"); + prevWindYaw = compound.getFloat("prevWindYaw"); + prevDayState = compound.getBoolean("prevDayState"); + interpolation = compound.getInt("interpolation"); + maxInterpolation = compound.getInt("maxInterpolation"); + } + + private WeatherConditions(World world) { + this.world = world; + } + + @Override + public void tick() { + if (interpolation < maxInterpolation) { + interpolation++; + markDirty(); + } + + boolean isDay = world.isDay(); + if (isDay != prevDayState + || world.random.nextInt(1200) == 0 + || (world.isRaining() && world.random.nextInt(120) == 0) + || (world.isThundering() && world.random.nextInt(90) == 0)) { + prevDayState = isDay; + prevWindYaw = getWindYaw(); + windYaw = world.random.nextFloat() * 360; + interpolation = 0; + maxInterpolation = world.isRaining() || world.isThundering() ? 50 : 100; + markDirty(); + } + } + + public float getWindYaw() { + return MathHelper.lerp(interpolation / (float)maxInterpolation, prevWindYaw, windYaw); + } + + public Vec3d getWindDirection() { + return Vec3d.fromPolar(0, windYaw).normalize(); + } + + @Override + public NbtCompound writeNbt(NbtCompound compound) { + compound.putFloat("windYaw", windYaw); + compound.putFloat("prevWindYaw", prevWindYaw); + compound.putBoolean("prevDayState", prevDayState); + compound.putInt("interpolation", interpolation); + compound.putInt("maxInterpolation", maxInterpolation); + return compound; + } public static Vec3d getAirflow(BlockPos pos, World world) { BlockPos.Mutable probedPosition = new BlockPos.Mutable(); - final float terrainFactor = Math.min(MAX_TERRAIN_HEIGHT, LOCAL_ALTITUDE_FIELD.getValue(world, probedPosition.set(pos))) / MAX_TERRAIN_HEIGHT; + final float localAltitude = LOCAL_ALTITUDE_FIELD.getValue(world, probedPosition.set(pos)); + final float terrainFactor = Math.min(MAX_TERRAIN_HEIGHT, localAltitude) / MAX_TERRAIN_HEIGHT; + final float windFactor = Math.min(MAX_WIND_HEIGHT, localAltitude) / MAX_WIND_HEIGHT; Vec3d terrainGradient = LOCAL_ALTITUDE_FIELD.computeAverage(world, pos, probedPosition).multiply(1 - terrainFactor); Vec3d thermalGradient = THERMAL_FIELD.computeAverage(world, pos, probedPosition).multiply(terrainFactor); + Vec3d wind = get(world).getWindDirection().multiply(1 - windFactor); - return terrainGradient.add(thermalGradient).normalize().add(0, getUpdraft(probedPosition.set(pos), world), 0); + return terrainGradient.add(thermalGradient).add(wind).normalize().add(0, getUpdraft(probedPosition.set(pos), world), 0); } public static double getUpdraft(BlockPos.Mutable pos, World world) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 903b8ad3..3e108f08 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -24,9 +24,7 @@ import com.minelittlepony.unicopia.particle.UParticles; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry.PendingParticleFactory; -import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; -import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; -import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.*; import net.minecraft.block.Block; import net.minecraft.client.MinecraftClient; import net.minecraft.client.color.block.BlockColorProvider; @@ -77,6 +75,8 @@ public interface URenderers { EntityRendererRegistry.register(UEntities.SPELLBOOK, SpellbookEntityRenderer::new); EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new); + BlockEntityRendererRegistry.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new); + ColorProviderRegistry.ITEM.register((stack, i) -> i > 0 ? -1 : ((DyeableItem)stack.getItem()).getColor(stack), UItems.FRIENDSHIP_BRACELET); BuiltinItemRendererRegistry.INSTANCE.register(UItems.FILLED_JAR, (stack, mode, matrices, vertexConsumers, light, overlay) -> { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/WeatherVaneBlockEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/WeatherVaneBlockEntityRenderer.java new file mode 100644 index 00000000..24ad2bc7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/WeatherVaneBlockEntityRenderer.java @@ -0,0 +1,58 @@ +package com.minelittlepony.unicopia.client.render.entity; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.block.WeatherVaneBlock; +import com.minelittlepony.unicopia.block.WeatherVaneBlock.WeatherVane; +import com.minelittlepony.unicopia.client.render.RenderLayers; + +import net.minecraft.client.model.*; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.block.entity.BlockEntityRenderer; +import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; + +public class WeatherVaneBlockEntityRenderer implements BlockEntityRenderer { + private static final Identifier TEXTURE = Unicopia.id("textures/entity/weather_vane.png"); + + private final ModelPart root; + private final ModelPart pole; + + public WeatherVaneBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) { + root = getTexturedModelData().createModel(); + pole = root.getChild("pole"); + } + + private static TexturedModelData getTexturedModelData() { + ModelData modelData = new ModelData(); + ModelPartData root = modelData.getRoot(); + + root.addChild("base", ModelPartBuilder.create() + .uv(30, 14).mirrored().cuboid(-9, -1, 7, 2, 1, 2, Dilation.NONE), ModelTransform.pivot(8, 0, -8)); + + ModelPartData pole = root.addChild("pole", ModelPartBuilder.create(), ModelTransform.NONE); + + pole.addChild("ew_arrow", ModelPartBuilder.create() + .uv(0, -16).cuboid(0, -12, -8, 0, 5, 16, Dilation.NONE), ModelTransform.rotation(0, 0.7854F, 0)); + pole.addChild("apple", ModelPartBuilder.create() + .uv(0, 2).cuboid(0, -27, -7, 0, 15, 15, Dilation.NONE) + .uv(0, -11).cuboid(0, -9, -8, 0, 5, 16, Dilation.NONE) + .uv(32, 0).cuboid(-0.5F, -14, -0.5F, 1, 14, 1, Dilation.NONE), ModelTransform.NONE); + + + return TexturedModelData.of(modelData, 64, 32); + } + + + @Override + public void render(WeatherVane entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light, int overlay) { + matrices.push(); + matrices.scale(1, -1, -1); + matrices.translate(0.5F, 0, -0.5F); + + pole.yaw = entity.getAngle(tickDelta); + root.render(matrices, vertices.getBuffer(RenderLayers.getEntityCutoutNoCull(TEXTURE, true)), light, overlay); + + matrices.pop(); + } +} diff --git a/src/main/resources/assets/unicopia/blockstates/weather_vane.json b/src/main/resources/assets/unicopia/blockstates/weather_vane.json new file mode 100644 index 00000000..e4a9f4e3 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/weather_vane.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "unicopia:block/weather_vane" } + } +} diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 04e7cfae..831b7e95 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -119,6 +119,7 @@ "block.unicopia.zap_apple": "Zap Apple", "block.unicopia.zap_bulb": "Unripened Zap Apple", "block.unicopia.apple_pie": "Apple Pie", + "block.unicopia.weather_vane": "Weather Vane", "block.unicopia.green_apple_leaves": "Granny Smith Leaves", "block.unicopia.green_apple_sapling": "Granny Smith Sapling", diff --git a/src/main/resources/assets/unicopia/models/block/weather_vane.json b/src/main/resources/assets/unicopia/models/block/weather_vane.json new file mode 100644 index 00000000..824dcce2 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/weather_vane.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "particle": "unicopia:item/weather_vane" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/item/weather_vane.json b/src/main/resources/assets/unicopia/models/item/weather_vane.json new file mode 100644 index 00000000..0c105c42 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/item/weather_vane.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "unicopia:item/weather_vane" + } +} diff --git a/src/main/resources/assets/unicopia/textures/entity/weather_vane.png b/src/main/resources/assets/unicopia/textures/entity/weather_vane.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa6fdd7ef005386fa75e232912a17eedfe4d5aa GIT binary patch literal 314 zcmV-A0mc4_P)U z8CHPCWx(JEF&rGjCIFaXI5_^3;~^>s!hi#^3PCCb!oU`SFi$M`@q_3BjT}A03Jj+* zU@*iWx~J*tCMJ4XHWAIEpkU*K`2QOx z#1E?;AYOp%+2QPYqN_pwf`ucmR36ST4+B_Jz-3m}|Cd==|0|U)L*diaWpn}<$(oQy zb$|aJR#{=hH-$%h0ANkdxU#`;Oa2TDqbUG)j|)8&49D0X1q=)f0L&nU5gPuI@&Et; M07*qoM6N<$f~paC4*&oF literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/unicopia/textures/item/weather_vane.png b/src/main/resources/assets/unicopia/textures/item/weather_vane.png new file mode 100644 index 0000000000000000000000000000000000000000..1e79431fad28c1ca54c8a0aac0ee86e1ae154a46 GIT binary patch literal 1408 zcmV-`1%LX9P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1b@cIzk%{m&|92?zla%i;5!*}+@>Tx{&5iIcRQ zXPiQXkS@9iu<3vP9p)cA&T2G1h{hD7#p9DtE~BCL_ViUJYsYMR{AkJO)FShx<7g4e zmE56b)VvE%e>@HG-GF%3bS0^go2l)ykBCoP zlRBodwV+rK7~Y0O6u~c`En7(_YO}3|yluAHf>J4wBesJ)HOe%oZ*`>#h~9N4sGX~h zH<_}y(Ue;kXer9l;o3#8%AF_`l`S&=_@w zh%oyjR>0-lq=9A>dH#hK2%&MTqp$_|D31?_g%J;}8D_-Hc$dRLuA;u_i=3*8WFF~S+s3ghC>NRLo zQPrf`B$pVu8d6M>G^La?oMu?g7@ajkP+Z~SODM5O(UMB8SU%O)P-B&mQwdU^w_0qPdyJTwZX%WFyfG*BaM8rwy|oyaQ4jHd)8>N2Exq7nTOKB z8pI-p37XqUn4Ezz=Lw9CUqCgJ@ai*V-S}hO743 z%0oTF#gcOqw~I^R8a|sd?-jUzgu)%+bZW9Io$QFZ1Hb}%s7Lx1Hp6|4Z+Ly(g+`+I-i`~E@s|BbvBL(}RKItdh z%}OKX-rsusfO{DIIQ)CH`IY}ug-#aDMq}7n#14#qc`d#YxHtb@T;?%wb#aX|gL|mU zA=0bRZP&DQzWIIynBUTUN&l0LTvw`S=8l?uARlnN;R4;zqR^c9)P=SV$yp43*bCPG z5%1oIBm1G2YF~CWNoF@uISt!yV^nr|tAq%=GH^3w%VDUb;ku+6HsC$uNKE6HM^C9I z-MVnic&Mq{>D4SN3>YIV7`S-*KJi|`eF58i#C;BggKb>W`922Le2lD=yw7*AO2tQo z?DMS-Um|7kX``{9A`Bo)UIm{gHOAc#t|*9zEGNMLWTZA$kR#!(_c2bmSG4OepW(*7 z8_W^bn z*;@R*&bOG~Y_WZ;4FcbGc)hIKNX}?CM zx#;_f{FGk0I(5xAl;7aPg*0Jz{0~C<1}Ag(o4No10C7n~K~y-)V_={qIK5Q&KOr@Y z#Ar5+zRXCH&94|3{=Z^i_k;7dnv=KYn2xO O0000