From 159e298e8b1235811eb9255f56d1bbce7a71c06c Mon Sep 17 00:00:00 2001
From: Zelophed <zefren1@googlemail.com>
Date: Wed, 17 Feb 2021 02:34:55 +0100
Subject: [PATCH] paletted contraptions

- use minecraft's palette-map during serialization and deserialization to reduce compound size in most cases
- also update the placement_indicator sprite sheet
---
 .../structureMovement/Contraption.java        | 159 ++++++++++++------
 .../create/foundation/gui/AllGuiTextures.java |   2 +-
 .../resources/META-INF/accesstransformer.cfg  |   5 +-
 .../textures/gui/placement_indicator.png      | Bin 9385 -> 9488 bytes
 4 files changed, 110 insertions(+), 56 deletions(-)

diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java
index 6fc93a511..45638b9a3 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java
@@ -28,11 +28,7 @@ import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBl
 import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock;
 import com.simibubi.create.foundation.config.AllConfigs;
 import com.simibubi.create.foundation.fluid.CombinedTankWrapper;
-import com.simibubi.create.foundation.utility.BlockFace;
-import com.simibubi.create.foundation.utility.Iterate;
-import com.simibubi.create.foundation.utility.NBTHelper;
-import com.simibubi.create.foundation.utility.NBTProcessors;
-import com.simibubi.create.foundation.utility.VecHelper;
+import com.simibubi.create.foundation.utility.*;
 import com.simibubi.create.foundation.utility.worldWrappers.WrappedWorld;
 import net.minecraft.block.*;
 import net.minecraft.block.material.PushReaction;
@@ -41,6 +37,7 @@ import net.minecraft.fluid.Fluids;
 import net.minecraft.fluid.IFluidState;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.nbt.INBT;
 import net.minecraft.nbt.ListNBT;
 import net.minecraft.nbt.NBTUtil;
 import net.minecraft.state.properties.BlockStateProperties;
@@ -55,6 +52,7 @@ import net.minecraft.util.Rotation;
 import net.minecraft.util.math.AxisAlignedBB;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.palette.PaletteHashMap;
 import net.minecraft.world.IWorld;
 import net.minecraft.world.World;
 import net.minecraft.world.gen.feature.template.Template.BlockInfo;
@@ -67,6 +65,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
 import net.minecraftforge.fluids.capability.templates.FluidTank;
 import net.minecraftforge.items.IItemHandlerModifiable;
 import net.minecraftforge.items.wrapper.CombinedInvWrapper;
+import net.minecraftforge.registries.GameData;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 
@@ -595,51 +594,16 @@ public abstract class Contraption {
 		presentTileEntities.clear();
 		renderedTileEntities.clear();
 
-		nbt.getList("Blocks", 10)
-			.forEach(c -> {
-				CompoundNBT comp = (CompoundNBT) c;
-				BlockInfo info = new BlockInfo(NBTUtil.readBlockPos(comp.getCompound("Pos")),
-					NBTUtil.readBlockState(comp.getCompound("Block")),
-					comp.contains("Data") ? comp.getCompound("Data") : null);
-				blocks.put(info.pos, info);
-
-				if (world.isRemote) {
-					Block block = info.state.getBlock();
-					CompoundNBT tag = info.nbt;
-					MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block);
-					if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer()))
-						return;
-
-					tag.putInt("x", info.pos.getX());
-					tag.putInt("y", info.pos.getY());
-					tag.putInt("z", info.pos.getZ());
-
-					TileEntity te = TileEntity.create(tag);
-					if (te == null)
-						return;
-					te.setLocation(new WrappedWorld(world) {
-
-						@Override
-						public BlockState getBlockState(BlockPos pos) {
-							if (!pos.equals(te.getPos()))
-								return Blocks.AIR.getDefaultState();
-							return info.state;
-						}
-
-					}, te.getPos());
-					if (te instanceof KineticTileEntity)
-						((KineticTileEntity) te).setSpeed(0);
-					te.getBlockState();
-					presentTileEntities.put(info.pos, te);
-					renderedTileEntities.add(te);
-				}
-			});
+		INBT blocks = nbt.get("Blocks");
+		//used to differentiate between the 'old' and the paletted serialization
+		boolean usePalettedDeserialization = blocks != null && blocks.getId() == 10 && ((CompoundNBT) blocks).contains("Palette");
+		readBlocksCompound(blocks, world, usePalettedDeserialization);
 
 		actors.clear();
 		nbt.getList("Actors", 10)
 			.forEach(c -> {
 				CompoundNBT comp = (CompoundNBT) c;
-				BlockInfo info = blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos")));
+				BlockInfo info = this.blocks.get(NBTUtil.readBlockPos(comp.getCompound("Pos")));
 				MovementContext context = MovementContext.readNBT(world, info, comp, this);
 				getActors().add(MutablePair.of(info, context));
 			});
@@ -704,15 +668,8 @@ public abstract class Contraption {
 	public CompoundNBT writeNBT(boolean spawnPacket) {
 		CompoundNBT nbt = new CompoundNBT();
 		nbt.putString("Type", getType().id);
-		ListNBT blocksNBT = new ListNBT();
-		for (BlockInfo block : this.blocks.values()) {
-			CompoundNBT c = new CompoundNBT();
-			c.put("Block", NBTUtil.writeBlockState(block.state));
-			c.put("Pos", NBTUtil.writeBlockPos(block.pos));
-			if (block.nbt != null)
-				c.put("Data", block.nbt);
-			blocksNBT.add(c);
-		}
+
+		CompoundNBT blocksNBT = writeBlocksCompound();
 
 		ListNBT actorsNBT = new ListNBT();
 		for (MutablePair<BlockInfo, MovementContext> actor : getActors()) {
@@ -789,6 +746,100 @@ public abstract class Contraption {
 		return nbt;
 	}
 
+	private CompoundNBT writeBlocksCompound() {
+		CompoundNBT compound = new CompoundNBT();
+		PaletteHashMap<BlockState> palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState);
+		ListNBT blockList = new ListNBT();
+
+		for (BlockInfo block : this.blocks.values()) {
+			int id = palette.idFor(block.state);
+			CompoundNBT c = new CompoundNBT();
+			c.putLong("Pos", block.pos.toLong());
+			c.putInt("State", id);
+			if (block.nbt != null)
+				c.put("Data", block.nbt);
+			blockList.add(c);
+		}
+
+		ListNBT paletteNBT = new ListNBT();
+		palette.writePaletteToList(paletteNBT);
+		compound.put("Palette", paletteNBT);
+		compound.put("BlockList", blockList);
+
+		return compound;
+	}
+
+	private void readBlocksCompound(INBT compound, World world, boolean usePalettedDeserialization) {
+		PaletteHashMap<BlockState> palette = null;
+		ListNBT blockList;
+		if (usePalettedDeserialization) {
+			CompoundNBT c = ((CompoundNBT) compound);
+			palette = new PaletteHashMap<>(GameData.getBlockStateIDMap(), 16, (i, s) -> {throw new IllegalStateException("Palette Map index exceeded maximum");}, NBTUtil::readBlockState, NBTUtil::writeBlockState);
+			palette.read(c.getList("Palette", 10));
+
+			blockList = c.getList("BlockList", 10);
+		} else {
+			blockList = (ListNBT) compound;
+		}
+
+		PaletteHashMap<BlockState> finalPalette = palette;
+		blockList.forEach(e -> {
+			CompoundNBT c = (CompoundNBT) e;
+
+			BlockInfo info = usePalettedDeserialization ? readBlockInfo(c, finalPalette) : legacyReadBlockInfo(c);
+
+			this.blocks.put(info.pos, info);
+
+			if (world.isRemote) {
+				Block block = info.state.getBlock();
+				CompoundNBT tag = info.nbt;
+				MovementBehaviour movementBehaviour = AllMovementBehaviours.of(block);
+				if (tag == null || (movementBehaviour != null && movementBehaviour.hasSpecialMovementRenderer()))
+					return;
+
+				tag.putInt("x", info.pos.getX());
+				tag.putInt("y", info.pos.getY());
+				tag.putInt("z", info.pos.getZ());
+
+				TileEntity te = TileEntity.create(tag);
+				if (te == null)
+					return;
+				te.setLocation(new WrappedWorld(world) {
+
+					@Override
+					public BlockState getBlockState(BlockPos pos) {
+						if (!pos.equals(te.getPos()))
+							return Blocks.AIR.getDefaultState();
+						return info.state;
+					}
+
+				}, te.getPos());
+				if (te instanceof KineticTileEntity)
+					((KineticTileEntity) te).setSpeed(0);
+				te.getBlockState();
+				presentTileEntities.put(info.pos, te);
+				renderedTileEntities.add(te);
+			}
+
+		});
+	}
+
+	private static BlockInfo readBlockInfo(CompoundNBT blockListEntry, PaletteHashMap<BlockState> palette) {
+		return new BlockInfo(
+				BlockPos.fromLong(blockListEntry.getLong("Pos")),
+				Objects.requireNonNull(palette.get(blockListEntry.getInt("State"))),
+				blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null
+		);
+	}
+
+	private static BlockInfo legacyReadBlockInfo(CompoundNBT blockListEntry) {
+		return new BlockInfo(
+				NBTUtil.readBlockPos(blockListEntry.getCompound("Pos")),
+				NBTUtil.readBlockState(blockListEntry.getCompound("Block")),
+				blockListEntry.contains("Data") ? blockListEntry.getCompound("Data") : null
+		);
+	}
+
 	public void removeBlocksFromWorld(World world, BlockPos offset) {
 		storage.values()
 			.forEach(MountedStorage::removeStorageFromWorld);
diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java
index 520cde09e..c66f911bb 100644
--- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java
+++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java
@@ -79,7 +79,7 @@ public enum AllGuiTextures {
 	INDICATOR_RED("widgets.png", 72, 18, 18, 6),
 
 	// PlacementIndicator
-	PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 256, 256);
+	PLACEMENT_INDICATOR_SHEET("placement_indicator.png", 0, 0, 16, 256);
 
 	;
 
diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg
index 22c960e77..254548d6f 100644
--- a/src/main/resources/META-INF/accesstransformer.cfg
+++ b/src/main/resources/META-INF/accesstransformer.cfg
@@ -26,4 +26,7 @@ public net.minecraft.world.server.ServerTickList field_205374_d # pendingTickLis
 public net.minecraft.world.server.ServerTickList field_205375_e # pendingTickListEntriesTreeSet
 
 # GameRenderer
-public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier
\ No newline at end of file
+public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D #getFOVModifier
+
+# IResizeCallback
+public net.minecraft.util.palette.IResizeCallback
\ No newline at end of file
diff --git a/src/main/resources/assets/create/textures/gui/placement_indicator.png b/src/main/resources/assets/create/textures/gui/placement_indicator.png
index cb53a47fcb3c027831ef62e2a077a255fb5ddeaf..d433d9b2d532806e2c64c137b9dc8dd6ac94cb99 100644
GIT binary patch
delta 5670
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
MNkvXXt^-0~f*)C?UjP6A

delta 5465
zcmV-f6{hNtN~uYZBYzBMdQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+O3#tk|Vhd
zg#Xtmd<23I5IhbaV1#er<NHIYd%QC{8?g?1G%cys5{pD8G820IU;n(%KloQt*Qd5_
zI#d(?>Zl`6o>=<)oA14(`rd#2lD?n(^!oGTY38x;SU!I+{eOD@_v?~=T1h6)+8-9O
z{q=|S^B+e2JZAf4p|1;-*iq)m`C4dvEmZR75AwCZ{2VLku>Qv*EdM;7?<3mh=ZU-0
z-?e`%^6T%lQ@zu8vE=MUN_y|>X9FdE<SFxgGWmn%&&#va_db>Hqf(IH)hYd9SI^%?
z<*z^~`g#4~g@2!msXrcn`?|E>7yo%n{D&)-{o~3%y}t7ot9~hR{M>u&xz|6Zn^h^P
zejV!jDDN}gxF~sfKc?I|KaJ=8y$x>maGec0C+)PjZ5fh*<w}(!Q?B^?xP>j{nmu<O
zbKU?IuUYAN<XzGeFvd-8?q!ry4CFTO7GBLi_9E|f$A5ddEyjh9@XjnOCVV14arghY
z{pCXU2IWPPed;8EkK`aK+X&?1o_7I=+;1MWx4_@GU;hQJ1q9n$X5)Y}t<Msx$rrbR
zN96^&@W#gDr!AzPuLUR(*KTY^3J4^X(nKZooVy5hH1L@$WVzJ5fIwQyhh64c=$4)>
z$F=!Pmw)%Yx#iExXcj=UqNM}PC=;wI!UlhOH8?a|X|=Vctv73<O)XlrscP2{zu8N#
zy>;!qTOWN|v1-lKy4hpH=E$QUj&11Z!^RkMa?#1klTT0HFz3pvth(CL)t9ZY=7v7I
z?zVOJZO<Nio_xwdCyt(e?2I#A94dMB=IY(ehkwrt)o!`<wyU>ayW`FusLAuTo&4$c
z_feB?)bfi|ys`g4jgR*Jy@Z6Ew7?9+aupEofdGWgfmt%CH9rAeV3tOvD_UlkEx<Vv
zh=E{r$?eJy?EZt?Pq;m`{!h5&Z{(hU?*D<@6VUwya(~9{H>jPEyPeAOA{1>HMNNhF
zbAO><o2PN9>TdOD+IO<A(s`_HmCL(kYrbz@)pjhMA20cgy+-Qu)<y@RO1Q=V-F=N>
z9268QDpN-G&N@by9rbNq#h14|%-NC2xi}AioMi3fYei7C)h=5y%kK452M$i|M^4ab
zmXb%W^kUw%-!zApr<^r=E46R!S18T3MSuN7N7fOPpg2R#KsdExqn0&-OqnV*>oWWv
zb~|DAzSla%*&L@kKwm!-Xi_y>+0o^b>#mk&hn3dy+MsZu3}_zX!feVPK*E3~s7w^y
z*~Z+wOSFt}w)6|LF4cM+$z>E7dw9KBS1X=Yx6^CA>FMj-ao8((M+J!`-GmZ?5`W4y
z67{d3j<GD5wZO4mPPlOY3#;6q_+9zoqPlVmrs-Oi729!H6Zp3j7<RQ-gqtFr*V&q$
z<vHn;&e`B*Iuv=ckl1L^$2Z8n#^y?EuZ8vLE!)bn<i&QtFczh>zRL)FgL7QC!;Maj
z+UA4hjsl1G8C1GxFAGBx^9!)JB!4zY+umxI)eUx@(QcdIKWkPO)%t|nkJUQPGdP;;
zw<?r}%T+JqK&H4hyr(OCmm1wmKb+lcFMwpjB&i2YXc&o`L9G$X*m9Y7!x!k#=G-?m
zh}Dl?P5GRwJ~7ySx$b1iI!!}rgF!SWrnAFi=%V$W!$po$4(Es%Pzh>r9)F@+o)|aF
z;7)7pxk^8KKT-6QZ#yUWK@n+G%5AGbOhM-wTNoidGOK0chANDNGcVRVC{@6f8aZ#;
zu1kt%Xo8SkIcN_2->4+{*xGF41P4ez8#2=%5WU##?j>o^<GGgM2L}jzj?kj5v*QJJ
z#j-3b>OMNGv`Kk`lCmJXn12e)_XT<tVmyNqImDq&X6iU_+fMfYS(_YJAQDZqXpN0K
zKnt>1gjY#xq7di^y@%916eYv1iA2?9w{ZE1j5z)dP0;V%55naX04D3*4R#MZUUZ?S
z#rKx@{Bx9QitT$Y)UmUvjD$Sut?rHEv+<4vj!yB*;R)qL_|Ae=r++u#&&T&u6(%Xx
zft<M7Sx6EGOsOGfeIp-0lDvTeib2x=DmxL;wlD%#JxjG9Z28v6ZhgB{URa4ULo@ge
zX`Q;zrFUlzM4OT*d(%^HqeLnQ_p0gU+S7xIBDT4{%qR=9%KVD4oMog<ulc^hF&Dg|
zWAZ$!?jUD17b7JoihoV9akDg?Y)pq=zo~G#YbO#YV*Hi`fr@Uc3-RVHrD3rXhbVd(
zQ$q1Pw+CJuK92&Iu_Hs{-?)r<M~y!{cN!@kcb&{O6Rgob`f<?pAZtK^`pZMcmfPt-
zd}gFz3obAsQO}NCUs(q;&Y(k8Hz9<+%x;8nWA0QCz`*z@27f6F!Z%9pq3}&NMG5g3
zS{%52>s^FLfIGH=CLFUYL<_a(<~aF1yF5<WCBGN9NDp2B*%ddYZHMAz6RMFqd9GwF
z4_l4)m9z51;S5TWSRRY;^il>K(X7dWOn1|rX`x7_7O)PD8}5wImln!|Tsn?A*gHyd
z5V=n&p(EI@qJLwtEgMtu5!@dErv=K$omK;^9bLLe0*c>4<OJ*N9#o5tM@A%S0aLDS
zs95@%bqmRe*aBN@96qisr!f0ONl;5dhMdm-<6$pBl2=Bc2QM0%M?p?gWP}rQc>1@A
zpF#5)Q&SAIdBeHl8di_OBO=Jy?)JRPAdxHsWf#n4nSU`OAHbA$Xk5~lMHit8ls9Qe
z1IvuEZv`^in6)0xf-7EnCPayc$y++<tCWinSd82|2%p%)=}RzbnuyQ?hO2=JLZN9s
z$TUvt*We)nXX$MgPq6>4(4dUwg)uT}N~A51DzbMo;5swpuM<ZfRAQj%9K)i|`4o-1
zVpznP?tk7ha&iRMdj)-CV1b21JiHozIjA}PqH8{bD9MRiBqq8N2Cyn#o|zQEH4hmL
zKiVT$D`+*f@5Z{w2{u)F7ousChZ;gDeg;vSD-7}o6DO(~5ur7@d48O>kOhS<$)d!D
zP*MofogGCJKT0Avn~=Rp)E!phd0pATI}R)a8GnVpVH?2bL>(RRWd=!VqmjL;1?33r
znIuiLInkYdNNKx=;laIh4n<-Kt5@GhikQ;>3#qM#6o=8!#MBf~#g5noHbP;Y^g=Zl
z4%qdgO)PAi9;_?IpUy#0eA<l=Lqw|lI;fXmw|id(KO~g?sAQGE1$rp4mUhxjLMPb6
zjDMNw{u${M8ztcDVIHcSgbJP71LTK@l2{^fG(r6c>0m~SO=lJ$PZEWIa&ycbj!#s>
zyGW?a9DKZo_c7OyMo1Z-?C~Zra$n;qqirkjkpY1W6A6T*2^}PA$nRw{QX!0lZZrsS
z9v2=#enQ3wvPII2uv>JrUMAj2KB}+*4}U+5b2q}zV1W8G&g}s<{h|Yfq_!KELY-@l
z#+e{kgywZk74c?bD2WLsBIRC&sL;1}V5)>*+#+^CP<@EbI7L|=GCCO|HeG*k;mCn8
zn!WKDd8Nt`-N=<kVqZQv3C6WPS>|z)6etm9T7AO|a;&UD?=iZuAwq&hDnsZhQ-5SD
zCjuEd^erGC{doQx0pX7|Ocy3un1=Bgwl5(okFDrmxWz<@+tb_F9A-<_AWqQdi<2Ye
zwf7j{6ty|ED%NtCQ;8n)M9lVQ?@*%@bnFNqc+naiU!|K{^v8&1bD+dp7<2@mCX*80
zAOM{*X;Z4}53OjXnRcT`$Ib(MMt`L2h{{Sg5>oCPEQ!Vl4;hYhe8N+e#S-%|yPD@A
zS*9Mkfr}8wDKss^)W6#slNEFD-Dy@(=I0_JYd8OJ;mrsc*&F+TtoRBUA8intZwIDa
z|B@!C6a)tsX5eve*dwKDWB|lsF(A_mY?9uiX}hCdfQVD4P-?~=QW!IyPJh1?fWB32
zgc!h$uj$YiVkK=yU<bYUQVu0!M;88wtDzPYG#!YBMAVGnGXD{`U%^XejXn|g7(Ix#
z`$dDR@6Wi?A2LwPJM{$1m?=~-gSJCsu;krGi*GV1U(9cr=*@VvExz8M#UWKCUpw*O
zVMXRprX?WE%6Q^*b}LvS;eQIlx4XxPUM1wkNEeZpc^zH@CDp9)g(y?egYePfjO<8m
zbQU5U-D=FVd2E_5Mo!$MGp?8Vk%$^my@#7&@ua#lv_EUnQ6Qm(ekB~nH`FjPrnQJD
zbQlw9!#r8#GY2Eb$K*svCP<w$BIF=!PBFneCLEF_T)6Kn%A`^P^?&Ez&`3u6IT*&n
z0001HdQ@0+Qek%>aB^>EX>4U6ba`-PAZc)PV*mhn+C7Rv4uC)i1Mj@TCjiA&pR-~2
z;LU^okChlppiKukuRCQaMHB4dam3Kz{BpNc$Hv&<P_FrSo!8KcCh08N5mKgD`S4Xx
zc>whB7gYN@>5BjW0e^&PLqkwWLqi~Na&Km7Y-Iodc$|HaJxIeq9K~N#MUje%9aJ1L
zR3{6fA|0iQMX*rX3avVrT>1q~8j=(jN5Qq=;KyRs!Nplu2UkH5`~Y!tb5eAX691PJ
zTEuv8+>dwn9(V5mp<ZUH*)a*Inq?$oQ8ANU6}w&`pdSMmMt?+NrXEi%rr|lh?&0I>
zU6^NipZjz4Dmjw@K9P8i>4rtTK|H%@>74h8gRCei#OK5l23?T&k?XR{Z=4Gb3p_Ju
zq?7Z+L1MAc#&R38qM;Jc5Qh|1qkJLbvch?bvs$jQ<~{if137Ia#dVsah+zqFBp^aY
z6(y8mAxx`AihqeD?MFTQBaS~oE}2{<Fmf!Q0u_?u2mgcL-J1F7DK{w)1v+1B`(q5~
z+65Xl+x|Yb?Zye<e+I6!mcLR5BA=w!T3YA`=-UP^u3MVC2VCv|Lr=P7NRH&EDdh9O
z`x$*x78tw*de+?Dn)^6?0MgXe@(pls2#gmfd)?#R-4yM;{d=a_-w&=qa=)Mlb|e4*
z5PP#j54!??WjHc6G%+(VEjKY^F)cJUH!v+>G-5F=G-5F|Ffue{G%;dhA_^cNAb4$X
zO-(vUZgX^DZewLhL_H#SZE!AWX=FM%Ib$<1H8e0SG&W*pEi^DOGc93ZWi%~hV>e+n
zIbvZsIbvfX3LqdLcx`Y^O*&6<X=iC}VQfTiV|8tleG(5OVmM_sV`eyEEi*7?H!U<Y
zWiTybH#RdZIAb<6H(@Y2W;bRqlduv;B``EJFg7_gHZ(ReA_^cNAV*0}P&!s+a&u{K
zZapG0E-^4JF*LIr6Yc>dW@R%qV>x4CEj43gV=Xi`Gche;IW%G|Gc`D5WHV!BIAbt5
zlP(oa3^r6THaaphIx#jPFO#SgVkBl|VPP{iWn(R6HZd_RG%;l~En#6}H!WsnIb&up
zGBz|eHZ+qi6-^2@R5CC+G%z|aHnW8lUI_#jPhS(0=N>YD5ECc{3feN*000F#Nkl<Z
zc-rk-ZBFAb5FLAS3NEoL8Nn&I1S?f)dLD{`MDGJiBzg*tG5dqMa-5m*j1y=JYY7Rp
z^~84k-kT5m7b3#l-5rDw;OgoM007?K-{EW;9{$+oq|S&ReVudcZ(XVL@rR5Nr|B#i
zaoWzD5ohUtOc`m?FWSup006cR4`j#qecJqDFFb@0-#KzEet-DwGH+q_6dLVfam_D|
zo^&zuzjW~~s1Z{tFJjthwORopf(9qYPF>eYJ5gjHv2|T1z6o5nE%tpc8p%g7S%c@@
z4u)Z<+KKM<<s~j6+hMg@$qQMU0wRiVW})0}Ht^?v_iqRx!1eVtNom-19oadq$=iMp
zZ~HwD=ePZS^rW~J&$}JO^|`sdE%QNq((`VYkHY1z(Gj~5LI|TLJ#M!p8I<vGp**lS
zA9Cw4Tb(Cpe0q8^joMS2$YR8#jhZQ_oeau=x%0??#6r@V_!MoFDz}@BxUgQ5_W1wf
zcFP)naRjxGK1iU9>$X)v8`o`%>$atoNDb`^;sD2}v?d(j{R2dVzVCtH16`D|Zrh2R
z^?gqo^9HseJrz<HEUe^HF4Xu!obRBLbwr%(P@j4xHO;47P;zaCVGzMId3_O7ae5TM
zJoluO7o_QleDM160>dys{7lUSiNuXQ*KTKjG=6>|a$lc-D7pE*;9Nk0PAu&MvvXmN
z8>x}_iba7cN?R=S%@y3w={$K0xiY8ekW}hLH&1#p1?MK^{lgE)R^{?rdEisPbY3|`
z)a=AmACW*Rs<(2|L4>*JE9YvRcrA5=qKa5*;jDJ`^J%yoiCi*Lba8UzS(0&0hBlyo
zI3Y?tCG%|1#dXM`?HCA49Y+sur1=p9qswetobAiw1eG#zoS@=^5hgMtSW;&aP_YvS
zHWi$;jAR0lDt0y}B;(e#7&(0kq*-ze7Kxp4#@c<yrb>x694Dsx;jnqi@j?he6M(a+
zam^M6MC7$m+J$jsd3<f&iMiZO;B3Wz!lFr8tVw^6CDXt6_hf>SlKX~RsQ6>fm<Z>B
zf8aE@J}yRT%wj+ynR6!-YsvKVb>}lOV2BaxC{5+=YsjRSKah-+@&{t%!leL2cfXW;
z5aBG;8Pw23OCoZ%(|sAdV0M;}@ey1Vn4&sIKY|5Q>IZ1R)a2S%LZk?$nhd{xIh$7k
z)BK`ssm72;ZVe^vl$>+vfTrY;loqx&f8bOj;F1oO{J}R0E?A^;K^G}`D*u4V8IZe>
z1(TFBP-pkGN`3n5zPEOsMJS(Ar1_seICuQ3IKc~k!KA)mD%RE17XUtr_!Fl>+Kpy$
z6}|Pj5zA=!T2M8aYiA~vW)2&FnezvP*p>^Hsln2*b28+goj*to*5(g#gBM07vyN7&
zju?AQupwnK%&GAboKJW<aFG<qNh%!0`~hV!1yw$Rsxk=|`lm++s%{a?JA1GMn7*dX
zrZis&FfdA<>HY$BPLF#dO^u7s3X76R_~2U;ct>fc_6K=3lWNI%%{oGVNdxjpg+p^j
zQF2C()9CyDh)C5DOKF(xX2XummC}GUR}rXPharR?dq(logRaIJwdcjluMfV9Yw+d4
z_vf9E)T*<WI&=3KR2~ZDC#WkN`JDwRW>BTVeqf!!<V<-<0}t2m%=fz3tBFvmc!Utx
z%aPiPl0{WkOB*GsRGNHbWI(#R7}fZ2$Ip@Tw>TE0kJR@)48s6#`#pQmQ7lkA?{>%g
zi=3Pv?m+Sdig=GPo%b*d$B~JC;^Dr60|30ay;aegs@|9w*ZbJo`;Gqq;LY5YN7jMT
P00000NkvXXu0mjfTwE@e