From 3f82b150d206a0547ba035cecb635859c5aa3a94 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Thu, 14 Mar 2024 16:33:08 +0100 Subject: [PATCH] Handle CanPlaceOn and CanDestroy tags --- .../viaversion/api/minecraft/HolderSet.java | 3 + .../api/minecraft/data/StructuredDataKey.java | 2 +- .../data/MappingData.java | 35 +++---- .../BlockItemPacketRewriter1_20_5.java | 88 ++++++++++++++++-- .../rewriter/StructuredDataConverter.java | 3 +- .../viaversion/util/KeyMappings.java | 11 +++ .../viaversion/data/items-blocks-1.20.3.nbt | Bin 0 -> 40707 bytes 7 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 common/src/main/resources/assets/viaversion/data/items-blocks-1.20.3.nbt diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/HolderSet.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/HolderSet.java index 59f09922a..b9796f734 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/HolderSet.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/HolderSet.java @@ -24,6 +24,9 @@ package com.viaversion.viaversion.api.minecraft; import com.viaversion.viaversion.util.EitherImpl; +/** + * Set of ids that either holds a string tag key or an array of ids. + */ public final class HolderSet extends EitherImpl { public HolderSet(final String tagKey) { diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataKey.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataKey.java index ea34f2cd9..e3b98bfdb 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataKey.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/data/StructuredDataKey.java @@ -65,7 +65,7 @@ public final class StructuredDataKey { public static final StructuredDataKey CREATIVE_SLOT_LOCK = new StructuredDataKey<>("creative_slot_lock", Type.EMPTY); public static final StructuredDataKey ENCHANTMENT_GLINT_OVERRIDE = new StructuredDataKey<>("enchantment_glint_override", Type.BOOLEAN); public static final StructuredDataKey INTANGIBLE_PROJECTILE = new StructuredDataKey<>("intangible_projectile", Type.EMPTY); - public static final StructuredDataKey STORED_ENCHANTMENTS = new StructuredDataKey<>("storded_enchantments", Enchantments.TYPE); + public static final StructuredDataKey STORED_ENCHANTMENTS = new StructuredDataKey<>("stored_enchantments", Enchantments.TYPE); public static final StructuredDataKey DYED_COLOR = new StructuredDataKey<>("dyed_color", DyedColor.TYPE); public static final StructuredDataKey MAP_COLOR = new StructuredDataKey<>("map_color", Type.INT); public static final StructuredDataKey MAP_ID = new StructuredDataKey<>("map_id", Type.VAR_INT); diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/data/MappingData.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/data/MappingData.java index 818ff8ba4..ad52a4a80 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/data/MappingData.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/data/MappingData.java @@ -18,19 +18,16 @@ package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.data; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.viaversion.viaversion.api.data.MappingDataBase; import com.viaversion.viaversion.api.data.MappingDataLoader; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.ArrayList; -import java.util.List; +import com.viaversion.viaversion.util.KeyMappings; +import org.checkerframework.checker.nullness.qual.Nullable; public class MappingData extends MappingDataBase { - private final Object2IntMap byId = new Object2IntOpenHashMap<>(); - private final List itemIds = new ArrayList<>(); + private KeyMappings items; + private KeyMappings blocks; public MappingData() { super("1.20.3", "1.20.5"); @@ -40,20 +37,24 @@ public class MappingData extends MappingDataBase { protected void loadExtras(final CompoundTag data) { super.loadExtras(data); - final ListTag items = MappingDataLoader.loadNBT("itemIds-1.20.3.nbt").getListTag("items", StringTag.class); - for (int i = 0; i < items.size(); i++) { - final StringTag tag = items.get(i); - itemIds.add(tag.getValue()); - byId.put(tag.getValue(), i); - } - byId.defaultReturnValue(-1); + final CompoundTag extraMappings = MappingDataLoader.loadNBT("items-blocks-1.20.3.nbt"); + items = new KeyMappings(extraMappings.getListTag("items", StringTag.class)); + blocks = new KeyMappings(extraMappings.getListTag("blocks", StringTag.class)); } public int itemId(final String name) { - return byId.getInt(name); + return items.keyToId(name); } - public String itemName(final int id) { - return itemIds.get(id); + public @Nullable String itemName(final int id) { + return items.idToKey(id); + } + + public int blockId(final String name) { + return blocks.keyToId(name); + } + + public @Nullable String blockName(final int id) { + return blocks.idToKey(id); } } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java index 6d9c8af1e..f9d2cd4e2 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_20_5to1_20_3/rewriter/BlockItemPacketRewriter1_20_5.java @@ -17,16 +17,19 @@ */ package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.rewriter; +import com.github.steveice10.opennbt.stringified.SNBT; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.NumberTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.minecraft.GameProfile; import com.viaversion.viaversion.api.minecraft.GlobalPosition; import com.viaversion.viaversion.api.minecraft.Holder; +import com.viaversion.viaversion.api.minecraft.HolderSet; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.data.StructuredData; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; @@ -34,6 +37,7 @@ import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.StructuredItem; +import com.viaversion.viaversion.api.minecraft.item.data.AdventureModePredicate; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrim; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimMaterial; import com.viaversion.viaversion.api.minecraft.item.data.ArmorTrimPattern; @@ -41,6 +45,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifier; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers; import com.viaversion.viaversion.api.minecraft.item.data.BannerPatternLayer; import com.viaversion.viaversion.api.minecraft.item.data.Bee; +import com.viaversion.viaversion.api.minecraft.item.data.BlockPredicate; import com.viaversion.viaversion.api.minecraft.item.data.BlockStateProperties; import com.viaversion.viaversion.api.minecraft.item.data.DyedColor; import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; @@ -53,6 +58,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.ModifierData; import com.viaversion.viaversion.api.minecraft.item.data.PotionContents; import com.viaversion.viaversion.api.minecraft.item.data.PotionEffect; import com.viaversion.viaversion.api.minecraft.item.data.PotionEffectData; +import com.viaversion.viaversion.api.minecraft.item.data.StatePropertyMatcher; import com.viaversion.viaversion.api.minecraft.item.data.SuspiciousStewEffect; import com.viaversion.viaversion.api.minecraft.item.data.Unbreakable; import com.viaversion.viaversion.api.minecraft.item.data.WrittenBook; @@ -79,6 +85,7 @@ import com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.packet.Serverb import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.ItemRewriter; import com.viaversion.viaversion.util.ComponentUtil; +import com.viaversion.viaversion.util.Either; import com.viaversion.viaversion.util.Key; import com.viaversion.viaversion.util.UUIDUtil; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; @@ -88,12 +95,14 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.logging.Level; import org.checkerframework.checker.nullness.qual.Nullable; public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter { public static final String[] MOB_TAGS = {"NoAI", "Silent", "NoGravity", "Glowing", "Invulnerable", "Health", "Age", "Variant", "HuntingCooldown", "BucketVariantTag"}; private static final GameProfile.Property[] EMPTY_PROPERTIES = new GameProfile.Property[0]; + private static final StatePropertyMatcher[] EMPTY_PROPERTY_MATCHERS = new StatePropertyMatcher[0]; public BlockItemPacketRewriter1_20_5(final Protocol1_20_5To1_20_3 protocol) { super(protocol, Type.ITEM1_20_2, Type.ITEM1_20_2_ARRAY, Types1_20_5.ITEM, Types1_20_5.ITEM_ARRAY); @@ -392,8 +401,6 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter canPlaceOnTag = tag.getListTag("CanPlaceOn", StringTag.class); + if (canPlaceOnTag != null) { + data.set(StructuredDataKey.CAN_PLACE_ON, updateBlockPredicates(canPlaceOnTag, (hideFlagsValue & StructuredDataConverter.HIDE_CAN_PLACE_ON) == 0)); + } + + final ListTag canDestroyTag = tag.getListTag("CanDestroy", StringTag.class); + if (canDestroyTag != null) { + data.set(StructuredDataKey.CAN_BREAK, updateBlockPredicates(canDestroyTag, (hideFlagsValue & StructuredDataConverter.HIDE_CAN_DESTROY) == 0)); + } + + // TODO MAP_POST_PROCESSING data.set(StructuredDataKey.CUSTOM_DATA, tag); return item; } + private AdventureModePredicate updateBlockPredicates(final ListTag tag, final boolean showInTooltip) { + final BlockPredicate[] predicates = tag.stream() + .map(StringTag::getValue) + .map(this::deserializeBlockPredicate) + .filter(Objects::nonNull) + .toArray(BlockPredicate[]::new); + return new AdventureModePredicate(predicates, showInTooltip); + } + + private @Nullable BlockPredicate deserializeBlockPredicate(final String rawPredicate) { + final int propertiesStartIndex = rawPredicate.indexOf('['); + final int tagStartIndex = rawPredicate.indexOf('{'); + int idLength = rawPredicate.length(); + if (propertiesStartIndex != -1) { + idLength = propertiesStartIndex; + } + if (tagStartIndex != -1) { + idLength = Math.min(propertiesStartIndex, tagStartIndex); + } + + final String identifier = rawPredicate.substring(0, idLength); + final int id = Protocol1_20_5To1_20_3.MAPPINGS.blockId(identifier); + if (id == -1) { + return null; + } + + final int propertiesEndIndex = rawPredicate.indexOf(']'); + final List propertyMatchers = new ArrayList<>(); + if (propertiesStartIndex != -1 && propertiesEndIndex != -1) { + for (final String property : rawPredicate.substring(propertiesStartIndex + 1, propertiesEndIndex).split(",")) { + final int propertySplitIndex = property.indexOf('='); + if (propertySplitIndex == -1) { + continue; + } + + final String propertyId = property.substring(0, propertySplitIndex).trim(); + final String propertyValue = property.substring(propertySplitIndex + 1).trim(); + propertyMatchers.add(new StatePropertyMatcher(propertyId, Either.left(propertyValue))); // TODO Also handle ranged matchers + } + } + + final int tagEndIndex = rawPredicate.indexOf('}'); + CompoundTag tag = null; + if (tagStartIndex != -1 && tagEndIndex != -1) { + try { + tag = SNBT.deserializeCompoundTag(rawPredicate.substring(tagStartIndex, tagEndIndex + 1)); + } catch (final Exception e) { + if (Via.getManager().isDebug()) { + Via.getPlatform().getLogger().log(Level.SEVERE, "Failed to parse block predicate tag: " + rawPredicate.substring(tagStartIndex, tagEndIndex + 1), e); + } + } + } + + return new BlockPredicate( + new HolderSet(new int[]{Protocol1_20_5To1_20_3.MAPPINGS.getNewBlockId(id)}), + propertyMatchers.isEmpty() ? null : propertyMatchers.toArray(EMPTY_PROPERTY_MATCHERS), + tag + ); + } + private void updateAttributes(final StructuredDataContainer data, final ListTag attributeModifiersTag, final boolean showInTooltip) { final AttributeModifier[] modifiers = attributeModifiersTag.stream().map(modifierTag -> { final String attributeName = modifierTag.getString("AttributeName"); @@ -676,7 +750,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter keys) { + this(keys.toArray(new String[0])); + } + + public KeyMappings(final ListTag keys) { + this(keys.getValue().stream().map(StringTag::getValue).toArray(String[]::new)); + } + public @Nullable String idToKey(final int id) { if (id < 0 || id >= keys.length) { return null; diff --git a/common/src/main/resources/assets/viaversion/data/items-blocks-1.20.3.nbt b/common/src/main/resources/assets/viaversion/data/items-blocks-1.20.3.nbt new file mode 100644 index 0000000000000000000000000000000000000000..4d60cfd514a4e4c5ee94b735e2ae50283c5a6d2d GIT binary patch literal 40707 zcmeHwX>%htl6FP7b&=vBiaNSIhwXV|zi({p?_m_ll2jJ2Wsxn*zy5vl0urc_++N!= zp0&S7%qLMO+(;xcvwB5wQJmC=YTcX{#mP6tS7p5~PMX7ZQx#{A`*H(bG2U%gb@Nm$ zXNH_D>+L>e8FF53mQ_Q%tMFKWE|yibYgQ$37u)%Kg*+oP2@gcm9fHY`)qqo{K(Ox@na7u&nooqQBUd`=**Pak|?s zf84H${(ieXP$N=`uhz$9Fqm}{T!d4 zm+Sd+uj?Xf|*09-i${rI_B73X_&0W(hxL3gfZ z+kI6`!f2lb?7ZHi3o~xOdW-FDSM8ZU^-l-%{BgTl=FbQ05~Y#RMsK){IV5<$T37p0 zv$=_hJbbY#cXh)rx{fI#*k9J=db^RLxI8>O+EfP&pjjy1l>+~BEcb^WIrCw;S=80$ zfYkz1rYX<@Xg!%L!+rTOf%ylByb!(e{UWS^wyOus-NBcOeg3)|BzlWtOq- zW3Cyew>TcGG?S2X#HKFOh+fD1B6tU+$hym4VO^z#5A7Bnu>{|~{X$Ff&71GF6x!aghSK|a z!@8H#sA)HBmY~(J8UKv7oz?cS=;@Y2zb|CFVOX~s{;k^#|JE&reqV;|g{iQ$5H$=G zZRt%h#b&;Ion`2y?d}a^wxX{aB@>srP3j`p93WEA#&6}m4v4G3B!ICdF`=tc1YO-C z=qh;=vP^0l;ZP;2c7&6fO+zNWidO-pO1*5i%OWP!S}__6Ce_N4YP~X4E0&>JGe&R2 z`$N?-Jk*WxyO7>0_w>4ZDzW7ThE`AU*cwS^o`>r8eJy*ga(!Q?zOPE(*Irsizb@>Rfrxey`RX;wF&~mi2uhj~TA>4~qy;l5US8sTNtcnY~?KOEk z0vC(da-(nE9rrs(_*{gG_=etmzkSg(y~ll3>2DTjIdN~kDi`*$B^~uv^>$O7?642M z7JbfYi?c+%L<_C>OwuoZJnl(II#!3`g2bW2aaHe%ORc(4eQ6XSdR!xAY=5X;aS$r& z=9MJD{YF~6n5^p6>umclgOD}*jF(j?z)Q{2h{ep2aXom# zFrsX<4Daiin2zU=};^yKKmk6txv?3PcE4WO}*Cx>CO3Oz|CuwrPhef?1D z_Q+X{-N{%|VeDgi%I~qf!68Fz&~kp46n1KHN&0$%ky_FK%F1?2W$VEEG#ih5$#}HG ziDzA6!P6SvIKaJa%4@sh@oNX#LQ)j$m(pO#ON+5JV^|6=4PjN#DZd747M$9jp$S?& z6mhML*kD;hkI<~mkTVYI6>2) zACpljovN4G9y+p>+SRL7xi1FRSfX+{Yj)+!rh*y^!?8J#LZp1)`E)3u`6$jG%KaKI zSr%sx$Ni>URK@A4TrTm%#1+-V%@56H`%-*WBUz7r>KWk_8ju4f{)fe?d@attKR#FU z?GMET?bHv|2B|M~0twZZy;@m1g_P1cq?Aq~p@w0Fg4rX zBHNBl$>Wp254sF=BQGsF0VW+Uop!$KCdv{%gWgWcMXI`9SV5X%9NuV!y|-*zoKZv$ zHFSB!T|22T;v@}^6w`f$YGNNm>o_#s?sSw-{HCYaz&1hj9?l>aqA7uDk|tb<@zrA? zYqp~e>ttQ6APjt|l2ukUM);<<)Rjw>t*(`A=8Z%17;?UTT|l~kR`Z;9`dzs! z2D+PS2i;gxr0{L%jTE1FQYY?pf~hw6q*)wS&sYNJ-|RD*5PK;9UX<8+p-VCv@KCL+F=iNOAioQ8TWW#_OOStskk77>rTn_r;UK#a$i0|4Ol%sVuiM)*&3sd^blTT zksFv&PF6^k)V1WM*i}*`bz|CQF5O^?UE}Inq-2!T4G61|Jz(3qWH-$=c0q&eWDnWq zu4Pzk*T@%pD{I0}=T*7D5K6XZR;AyzwwD>>x^44szjrdgBRaOF&&Ur!#xEf$(*=lR zn@9l6cM<^8sRY2#02Sfe5Xs|)=2J4fB%`cA(`+cn!oz$m$WG17zp)8SK5*R&)Ls?we*&;&F?aSc`+ny!6aYrl%U+F>1 zK{$-zxa9#kqQtyLeA03$H1ixD@iEM$yCR7OE$AS0Hx7Lc7|Vj~5jmbYWJtr!(PKHP zIpl0!?y+1!SmJsY7f(1WYU@iA>M>)W&8H-^Rnl}yU%6(pC$!0w>~nJ&!A)fZw}%p% zk%#)+Jj!Qb4^3mwaXDTyxm&Ow!&3-Kk+0J3K)OI@%Ev z?S$EdLo1Gxa-u)kRoe~Dp;**cst@QvEX4_)wF1}Dj5IDcD1s;;rrMe*dSi{6!uPIi zQ_fVII7M*^#=y)lZtx7_Mouw(R|jXpy^yA!>Nri&3HM2gT}#(wp!84*UQtJ-(6#he z3f_0LtH>N5yBb$mpE_Gucz=DXnKjq0wiec1yTO&wZgpj}8(0`xh_~2x?M{&AK6dql zzN_6G!np6~6XkStjBsvSy<>T?5-8~+;q>Bx6cz#TB&+h8$P^Jz(olAgEAJxP4&1`H zZJl@p?~`iHjxz%;vPn-xlmt_pud`AJk}`&(J!x{+-M-r48jmz9sibnv;%^4_G2+&x zE~zFUK;W^f$14?nJVdYcLIU(Ub5d`}x;%vL_=vM7bXA|XP^v)_36T_%Ae}$2bXGWM zV$;E4*I^zw@YYWNhfi$eJ8UdN-Xk*BJtCvnA^L_EK9*taQci3HH8gVx|;7ZpM2i-}#VRfdvShpkGYwNW+AFU1BtiQOHY;*6mJ=+|#4_59DoZGeo z=ay{a`u;b#x7^z8r|z}Q+nif&`8Iwh?cc$WUT8B|0B*x~(j#rgQ2GT2fW^Lo70^h# zBLHLTg@9bU#t+~~+dKdRZt6&K3rCWhE|8jK3J$m#Lzaf7SN=P4Ar-G@}@~M z+7q`~YZ^XH8r9Gn8Hd(-0ZyCP;A>hBT4~b;x3oR1vj>QqbOi4=oB9DV9TTu+!IjS8 zIEN^TPB2>(*wWBV&mO{Fg5T*45NSdahUD{5V>S2r2n>yAn$5WN!mIYXCi7$KHRIMB z#;wdzSE~)KZDJLms&N?>y_i+ei)fE!C90YLL2;hn`jK60OP#lk3 z(HMaBj;k*v8O9fIieb#3X`EEk&ZCELs7=eyNu?uNZ;%|a*9e%s>zqd{9VpH)=9b6$ z0C^^}r!>*x>n`QIG4ETdN8`l&Ohcf8lRo0Ks`kiju3^w8RNHBSmt6wnGLYROq=)Vy zCOGR$^zl(pepr6FWMjpNx{LggVP2)Su0k{a7+N)IE0pCd+(ciYI67C&QD>~NFgWBC zDn$2AAG>>pSgMNh8YI4axd}OiYPjS{zYyNi`$#73UZBJig{=<=3mPlA`iYH6%(n80 zQ{9F#rpbf4Nm6yRJ7wy|UT8~xprs>XG}jR^*l!l)!vhQ!Ayc!ZB)4Nh+6!q>xj~4u z2*S96CP5tAAnt(IyAl^q+kJ5gdqgPQah-d>51n{+`>JW^q=Q>QNPpjT)hkP*C+L@odI1}K&VGW`3G5->U{C1& zE_>ugFY+>YDmQu=dZVqD!`gZ|tgV>Acj4AAXM|SG7&qFw8GLIOf*B4QIyMT=V7bd# zZZS^T5P*M~$q^H32p|ltst{U1A+%adL_+`}Llq>c7MzK2=`SoTq!42DI%DEGm>BZp z6!0du%oKQ<8Das;%Y1Az5MZSNx#8azNFZS(+)ZJY#Tq>Uc|?SKG8J6vgu!0>js8Ozz> zW*oEX%;4OrGh_e#jgARrX<2n<84SaH{p^uem@9v2FVNWC+-oB37rtdzNPEUZ+BXim z4!z^S&_52m(jIcq#QMl#raf}dC60z{VGaQv_b#>#&6xbae}^z#s6W$gDd=ef^9Qnw zSVKh(tvK0Ckm2q)3fq=ZwG=ve|DBGu&R55c04SlN~azIbtH^Ij7a? z6+I?v&U{5_Gv;dOPWBsG=1%KvR5f=1PGs@QPlj29CP}8{n(T*5EkT=3d0;YHo$)iW<}7I zJitZffGalAr&n-7Ie|ohZvJWxgO(HOpBI>~z>lIhhvst6zu?%#{u;2$5$BKMQfwum z7=wvkaVpYgF*HvJ=;O-$5>lJ`V5sK=NlDkBw$9GTQP8PB@fJASfr-;F8xk_JHQ5I&10^%TG-%2%;PvNqL^gbpL=+ zhV{oAi@X%4WcLgq|Lj2=VPKtl+^8-K=AHm&O$DjHIANkVneTBZFNUyLWFt-;;1i>H za)@j=Hf#l54c-B}N<(0v1~bbstLN8P6=#|aeDR>CVZ>zwn;AnxR-=eXX3}Gt&xm`> zWg**scv_u_xD=tYtSZ)7?(wl&FeIs(2+fM3@ySGjthehS|Fdf%4O8%|u+9g$&ECB-I6%i6DFmOLr?K#z9D1^COJr4p?RB zSdHs#reH1q88=u$z$=zC&oeQF6zrUcIj+Cyl*|(qCdxI;r1Tk>Gmzf&0Y1bA4PZYA zzd$2#fG7KSe%cC|e~&nl^ghjNM2Al%y}>1$&KU(KUhLL0SVnDNlPgyP%UA3vdL2N&>Ee++2Z$ItwLd3RRX_cPDIG4I z_!q16`4_9|>6bnk=yO)r69-jbBNa~1N>2K9A*y)(De8Cnc}9vqVe>t){G^?BT|xdp zkC3e(8@fXzDW8##)C!)qp;q)e)QSjSD^94jqTf<0{>8Q8UtA0NZWW#8Kx*UE8a2 zQ!-^{8(rN6Egf%(ALdo{KwjWHSeQ72RTV}*ne^%qQu-3I_W=^h1FS=z3bho3vTP53 z5U7NHtat?X9-m4``3K;z%_K2wb9^M-R$n#)Sa9Ji09W$^IdotMVw6J&KHx$G(`Q%H zIsiwEVv5PV##low9eOD*vfUlS5%=J@#Vg^Hj&1#g<`ERGu){uIl*bhmZ5uX+)J$(5 z5pw_yNXdM2jWA&`#o5T4Z(^=&Wr)yVPJ#rPfpdxh8q1MnIfoXAe3(*IRe}s))aeq8 z77%w~1|4#|S-=SjG&eznue3j0N=#^Kl~9xgEMOyfh4z3F6U%8xakbp8c35N_G+u5u z&(K%;L}cm2?QJ>VSCC2JL=B&5h!6g8()EL*#-uTm3FFvFT*lXe(S{!rj;qfYbH0#+ zhwTq8!SG?fMQ4UOjAUR%V9Lu^^FPPhH^%T0XFd1Wq@(b-rouq*MZ{T?!XU)Vhfl`_ zt7Lfll*KWO=W6ZH5rz#0pi8DBk}ka>dJXpo@%V8d3_Y>a#6;sQ%#d-qjmQYr&P$*B z82V^B0LLU(@ci2xw!Td8K=REt2AS6NR+0H02RfJ6gsmPQlRlv+7Vs0orr>)DCR$lyS9UmKE{i=4Z99L$I5z|bYdWFrC_wI;J8tlUr&Q|W> z`+(&=I+DFPK7M6auMwKE1d{jixHTt7`KY5)bh?1hp@e0cFodp5x`^+)#Z#0hA4Ye{ z#_pLi~+I$K_`6YxO(#^yNp<<*dOiQ7bBJ$wyz30PdDEtT> zZwTGtlZXpkL_VrhTGjH}| zUY%v6FkVdbtm?6AI|3)59q2vy0U$((4R+|+p?rjWpLZXK8p+eO-g&90q~xW_ndOFD z#>~ojE@ZHBR@z%RD>N{fQ=SkjXXS}&rV%21N-}TdYWh=vD^ek2HRmJuM0ip2FjIMU_>2Q8gmBno^Bm|Sip-Iu zPdYM^imF~9f>N`X{pWun4nrid4#SBt(yVpG0ntH_`1ZTxA-ES7BbS04ZHtKq$Y=Yh zP7={HOY)^n3ZaGEW3$!;HC6PqW$c@i!K}>r;!ED|64kQkjLX5Er%1_nnYooU@(dgGjkxN>yE<~Gko{- zidBf*Ord?RunqH`%>caTvA45AncT0jB}blUq@lH^cj2Ii5p!nt%a`m#Y1E*Bw>XT4 zq2j9H`6qe-qi~Q~?`ZkQY#_hQK*B~Cb9K#H0Qh*|wEc>@&3P8X)ELSN#I&lO>d#Pa zt8IiF^B`<%TGgRkte}6FN{q0aP!)npuS5b+Ca*rCK4&8jF}hVi%(Sl_Yn)lg(aZdm zXf4uEa}JEwK@k+!=uSfi%q6lK{|g#l;=py~+4-c}q`%`Y#Z~#HBg51%{X6qQd^07~ zMX+sPDGQ;kFx*HUVI`C9NJ`m`q-5BUl=eH4Jz-{?lAZb8hyqKDHkHFWQks#6%`q((ZN%E-UM!2}U1FmD5N!&;H5S}~YQevi%LLfzN zaW-2aPD+kXALziw$0t|bYY_y}EbbX3c@U;nN%baA5Ysa3nv4+gw_&44fm`qt*V~%| zZ2pp5=|ltVUIwB;%ZCiSA@K#p&){njHu4NM6aXj%?UEa70xsaEnJtl^w=Fv*!EbU4 zB7xHYb%LqMQD^pldBY?|o>WVw?pHy#Oty|#iC6O?x3OZdZDYl7%f^b))W(XTZW}9R zy=7y?%r|VTm`8?_I6v@WjeX!kNa#y1AhppI4TZM-@ZE16+V+bhwmPd{Ty4-CfNb}# z4ZsS0Imcs#h57F+!s-d%MBvy8^KC{RV|Qb>UV6_I|k4b25#|gQ*5= zqzaXg+3zG$p!TU)3dMkUdT~v$_x^do{}4MNi$)kD-G!^{G#p@3{C`#eynnX3aZ>}q zj$m$IVmzkyS7Pbq?ZwPgm^nYgcr3b0SS&d)weE^2Hz1*pTT+b6bVrWJNbJZWFxK)q zL<{W@H8hLJB-oBQEbO)s8|zlzRac?|Ufa_?}8RzQ+>Ir?lr%UZ@}USHk;frhaXfS>JbXeD^2Wsf$~; zFP8Ha`akA0^<`g2h~9{@9+FqNXt%kE1+?ISa`pH{p(K5{*RHUVzaToJ563v-3j$YE z7oX8W>Pe)!pNtnN27A#P^UsJ!WDWZ3M?Cu7dXL#Cm%wblS%}QvZP?JM*u3IOm2cVf zrtBBT&I|!tHHhX7?nF!MCQ!1Pl_&+^ybiFMNfL;FxoSvCRL^lbLUHC!aKBM#&M%I& znV6rpqiDWqJvtP#^+BQQU;?B7#2CoP)lq+9r|%6gH2k`V3XSwMkE5xI>=6zI|4&6BM452yRKhy z>MM&Q^}*yzGr)}z$FRrAn-K|RZ`I_hPpUqGZ{h1=oC4<}3QKEAe0+P$mQ3I!emyR^ z`60E|7tkIRdnCbdrz!6z!~Zmy4}ovVTqJy}b4Nh@@FEu%-%3Pfv}LTY=1*5QvW(+G zMRkHTf`-J#C)>#MwIPonvZaHW!tW*MKxVFoiygH}WkHCqdET<&`;?pZxknO{J~NMG zJ}#MH@#oF|#!5tXY6BQd!efHIHw%PY3kL;uY;P!NHpG$rMG;uDa1*~Qh=LEt(Gd_M zO3tF2wy1a-$b=667KtN`0Q*Lm*!LsB=FZL`qQ{>hVks;xb%u|3*A5Z;XP`|9+A85J zf4_k?|0%J%lQ24Y@Zc=k#lIr*r;qo|wpu^yTNz2*&_8-8WWL~(6*>uv{0m|s|51#s zH3}>5!D@|dQCK||qOf|{8JpVz zHp2yz1e!ozOK6J}{4+K;6o%GBuu~Vk>vY~}%S`4M3moR^% z_}#6@*}qam?q3)e{GS)kR5|YAk_Vn-NG4Mn@jc^^ z>tW8hb}`UHa$GycF!KSuNn(`;_`2(b*In=O-zDw{UOu$5j$XUimt*Y`K-k4OeVb$k zM?8w|f>XII<7Y^bTk$wA2}&NKL}15VtUaZl>oWXcE$wRC~j z;w2v1*%!gDlMjUJ7n1YOgiq7YhKKJcYw$n)^thGEe2%o4m5fzp1(@{#5kE7M_`6>s z{(@-JUl{B-w4yGP*5|frT6MdF%!lf-PQ?bIMC!ID#YEUzf+A1if>z=w^4=;YyvmOV z1sT$hbcqVxLiPHX0{SHZ3Z;=b;U^y`X!+o)#}NhTlFWy2A&2)ReRKdW>+_`^U_EMQD1i6e_P@mG{{5f; zzufWvog8Rz6dfTCI;*4u`0dCCU-Bu0&YX2Bbmq)Oj33BLHC}IgS=nyiHFm-uEs8;3 zq8T8l0fJWQy^n|N-p6C%-p7Of-pAv1@8dDK_el^J*g?uNmeW4RS`ftF`y|iG7r0zX zyZ1>_xrh&1TeT!ryh0*UnHr$i1BoF@ZLH7aNG6R@3fq&!M-_Yf8q8e*SEmQpec5lJ{Rd8f{s z5kJg?WP9ZSDVI;f86tqShB%3d8-yM$R|{+o1%fF_Y-J8l3tP8f{`jRIrTJfc_1s+G zb!SR_)QfTM{Ei?UKh#V3=hG+I$orha;j$Mx*#Wta2s!jRK7u$B`qJA{m ztT$prFz>D463j%ndR}l4aZNtc6NhNVaH~be@vB9~vbM_LyoED%s|PcFy9cubwhluK z_%jqqI+A|7P$Ya86+Z^o5t1rIr?LwQ){eq28CKHc_}h-}4ZIQfl-D!}l6PAESR&kI zF&2Y-mXHML)5jqpmf;IK2)@a}AbwHk&K`4Q^@N+iBjP~U2$NGj;0rU3!pZ?H2Q)rp z#S@YY%pXvWcYXSYC6I35i?7y*!v&{u{emLS#amK$gBzgx7Gw()qI717Wnt>TS<;E( zRLuRI4?e#6Rl+U?zW|pf{!@^RcT4u)hV1-+VGeSRxP7r4RmOYkGn7usaROiqm4*l+ zM##Rnw`g+#l4x}Ja(bIXm1&Aey?HjRV(EkTZ9=rem literal 0 HcmV?d00001