From f0880a27a0f8b4a4b66a4220cf7aa127fe4202c1 Mon Sep 17 00:00:00 2001 From: NotMyFault Date: Sun, 17 Oct 2021 16:32:36 +0200 Subject: [PATCH] feat: Paperweight support (#1362) * Initial work towards paperweight * feat: Port Fawe classes to paperweight - Hide UnsafeUtilities in javadocs - Fix typo in Regenerator - Create fawe tmp directory for fawe regen * Update adapters * chore: Update MiniMessage * Address todos * Fix leftover Tuinity loggers --- .github/workflows/build.yml | 3 +- buildSrc/build.gradle.kts | 6 + buildSrc/src/main/kotlin/AdapterConfig.kt | 47 + buildSrc/src/main/kotlin/CommonJavaConfig.kt | 92 + buildSrc/src/main/kotlin/GradleExtras.kt | 4 +- buildSrc/src/main/kotlin/PlatformConfig.kt | 61 +- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 3 + .../adapters/adapter-1_17_1/build.gradle.kts | 21 + .../impl/v1_17_R1_2/PaperweightAdapter.java | 1007 ++++++ .../v1_17_R1_2/PaperweightDataConverters.java | 2795 +++++++++++++++++ .../v1_17_R1_2/PaperweightFakePlayer.java | 98 + .../PaperweightWorldNativeAccess.java | 179 ++ .../fawe/PaperweightBlockMaterial.java | 185 ++ .../fawe/PaperweightFaweAdapter.java | 652 ++++ .../PaperweightFaweWorldNativeAccess.java | 296 ++ .../v1_17_R1_2/fawe/PaperweightGetBlocks.java | 1053 +++++++ .../fawe/PaperweightGetBlocks_Copy.java | 251 ++ .../fawe/PaperweightMapChunkUtil.java | 31 + .../fawe/PaperweightPlatformAdapter.java | 439 +++ .../fawe/PaperweightStarlightRelighter.java | 238 ++ .../PaperweightStarlightRelighterFactory.java | 28 + .../fawe/nbt/PaperweightLazyCompoundTag.java | 159 + .../fawe/regen/PaperweightRegen.java | 715 +++++ .../adapters/adapter-legacy/build.gradle.kts | 7 + .../src/main/resources/worldedit-adapters.jar | Bin 831176 -> 617703 bytes worldedit-bukkit/build.gradle.kts | 26 +- .../bukkit/adapter/Regenerator.java | 4 +- .../worldedit/bukkit/adapter/Refraction.java | 46 + .../core/util/UnsafeUtility.java | 3 +- 30 files changed, 8383 insertions(+), 68 deletions(-) create mode 100644 buildSrc/src/main/kotlin/AdapterConfig.kt create mode 100644 buildSrc/src/main/kotlin/CommonJavaConfig.kt create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightDataConverters.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightFakePlayer.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightWorldNativeAccess.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightBlockMaterial.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweWorldNativeAccess.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks_Copy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightMapChunkUtil.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightPlatformAdapter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighter.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighterFactory.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/nbt/PaperweightLazyCompoundTag.java create mode 100644 worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/regen/PaperweightRegen.java create mode 100644 worldedit-bukkit/adapters/adapter-legacy/build.gradle.kts rename worldedit-bukkit/{ => adapters/adapter-legacy}/src/main/resources/worldedit-adapters.jar (70%) create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Refraction.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 041f8fa0d..30201b465 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,9 +23,10 @@ jobs: uses: "actions/setup-java@v2.3.1" with: distribution: "temurin" + cache: 'gradle' java-version: "17" - name: "Clean Build" - run: "./gradlew clean build" + run: "./gradlew clean build --no-daemon" - name: Archive Artifacts uses: actions/upload-artifact@v2 with: diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 3dd5403a2..b4f784954 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -6,7 +6,12 @@ plugins { } repositories { + mavenCentral() gradlePluginPortal() + maven { + name = "PaperMC" + url = uri("https://papermc.io/repo/repository/maven-public/") + } maven { name = "EngineHub" url = uri("https://maven.enginehub.org/repo/") @@ -23,4 +28,5 @@ dependencies { implementation(gradleApi()) implementation("org.ajoberstar.grgit:grgit-gradle:4.1.0") implementation("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0") + implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.1.11") } diff --git a/buildSrc/src/main/kotlin/AdapterConfig.kt b/buildSrc/src/main/kotlin/AdapterConfig.kt new file mode 100644 index 000000000..71cce4921 --- /dev/null +++ b/buildSrc/src/main/kotlin/AdapterConfig.kt @@ -0,0 +1,47 @@ +import io.papermc.paperweight.util.constants.REOBF_CONFIG +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.attributes.Attribute +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.named + +// TODO https://github.com/PaperMC/paperweight/pull/87 +interface Obfuscation : Named { + companion object { + val OBFUSCATION_ATTRIBUTE = Attribute.of( + "com.fastasyncworldedit.obfuscation", + Obfuscation::class.java + ) + const val NONE = "none" + const val REOBFUSCATED = "reobfuscated" + } +} + +// For specific version pinning, see +// https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ +fun Project.applyPaperweightAdapterConfiguration( + paperVersion: String +) { + applyCommonConfiguration() + apply(plugin = "java-library") + applyCommonJavaConfiguration( + sourcesJar = true, + banSlf4j = false, + ) + apply(plugin = "io.papermc.paperweight.userdev") + + dependencies { + paperDevBundle(paperVersion) + "implementation"(project(":worldedit-bukkit")) + } + + tasks.named("assemble") { + dependsOn("reobfJar") + } + + configurations[REOBF_CONFIG].attributes { + attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.REOBFUSCATED)) + } +} diff --git a/buildSrc/src/main/kotlin/CommonJavaConfig.kt b/buildSrc/src/main/kotlin/CommonJavaConfig.kt new file mode 100644 index 000000000..b0029429c --- /dev/null +++ b/buildSrc/src/main/kotlin/CommonJavaConfig.kt @@ -0,0 +1,92 @@ +import org.gradle.api.Project +import org.gradle.api.attributes.java.TargetJvmVersion +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.plugins.quality.CheckstyleExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.api.tasks.testing.Test +import org.gradle.external.javadoc.StandardJavadocDocletOptions +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.withType + +fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean = true) { + applyCommonConfiguration() + apply(plugin = "eclipse") + apply(plugin = "idea") + + tasks + .withType() + .matching { it.name == "compileJava" || it.name == "compileTestJava" } + .configureEach { + val disabledLint = listOf( + "processing", "path", "fallthrough", "serial" + ) + options.release.set(11) + options.compilerArgs.addAll(listOf("-Xlint:all") + disabledLint.map { "-Xlint:-$it" }) + options.isDeprecation = true + options.encoding = "UTF-8" + options.compilerArgs.add("-parameters") + } + + configurations.all { + attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 16) + } + + tasks.withType().configureEach { + useJUnitPlatform() + } + + dependencies { + "compileOnly"("com.google.code.findbugs:jsr305:3.0.2") + "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.8.1") + "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.8.1") + "testImplementation"("org.mockito:mockito-core:3.12.4") + "testImplementation"("org.mockito:mockito-junit-jupiter:3.12.4") + "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.8.1") + } + + // Java 8 turns on doclint which we fail + tasks.withType().configureEach { + (options as StandardJavadocDocletOptions).apply { + addStringOption("Xdoclint:none", "-quiet") + tags( + "apiNote:a:API Note:", + "implSpec:a:Implementation Requirements:", + "implNote:a:Implementation Note:" + ) + options.encoding = "UTF-8" + links( + "https://javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/", + "https://jd.adventure.kyori.net/api/4.9.1/", + "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/2.14.1/", + "https://javadoc.io/doc/com.google.guava/guava/21.0/", + "https://www.antlr.org/api/Java/", + "https://docs.enginehub.org/javadoc/org.enginehub.piston/core/0.5.7/", + "https://docs.enginehub.org/javadoc/org.enginehub.piston/default-impl/0.5.7/", + "https://papermc.io/javadocs/paper/1.17/", + "https://ci.athion.net/job/FastAsyncWorldEdit-1.17-Core-Javadocs/javadoc/" // needed for other module linking + ) + } + } + + configure { + disableAutoTargetJvm() + withJavadocJar() + if (sourcesJar) { + withSourcesJar() + } + } + + if (banSlf4j) { + configurations["compileClasspath"].apply { + resolutionStrategy.componentSelection { + withModule("org.slf4j:slf4j-api") { + reject("No SLF4J allowed on compile classpath") + } + } + } + } +} diff --git a/buildSrc/src/main/kotlin/GradleExtras.kt b/buildSrc/src/main/kotlin/GradleExtras.kt index e7d1e0ede..beedf1607 100644 --- a/buildSrc/src/main/kotlin/GradleExtras.kt +++ b/buildSrc/src/main/kotlin/GradleExtras.kt @@ -1,6 +1,6 @@ import org.gradle.api.Project import org.gradle.api.plugins.ExtraPropertiesExtension -import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.tasks.SourceSetContainer import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.the @@ -9,4 +9,4 @@ val Project.ext: ExtraPropertiesExtension get() = extensions.getByType() val Project.sourceSets: SourceSetContainer - get() = the().sourceSets + get() = the().sourceSets diff --git a/buildSrc/src/main/kotlin/PlatformConfig.kt b/buildSrc/src/main/kotlin/PlatformConfig.kt index b25193f65..ad49908ab 100644 --- a/buildSrc/src/main/kotlin/PlatformConfig.kt +++ b/buildSrc/src/main/kotlin/PlatformConfig.kt @@ -14,7 +14,6 @@ import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.getByName import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.register @@ -29,68 +28,16 @@ fun Project.applyPlatformAndCoreConfiguration() { apply(plugin = "maven-publish") apply(plugin = "com.github.johnrengelman.shadow") + applyCommonJavaConfiguration( + sourcesJar = name in setOf("worldedit-core", "worldedit-bukkit"), + ) + if (project.hasProperty("buildnumber")) { ext["internalVersion"] = "$version;${rootProject.ext["gitCommitHash"]}" } else { ext["internalVersion"] = "$version" } - tasks - .withType() - .matching { it.name == "compileJava" || it.name == "compileTestJava" } - .configureEach { - val disabledLint = listOf( - "processing", "path", "fallthrough", "serial" - ) - options.release.set(11) - options.compilerArgs.addAll(listOf("-Xlint:all") + disabledLint.map { "-Xlint:-$it" }) - options.isDeprecation = false - options.encoding = "UTF-8" - options.compilerArgs.add("-parameters") - } - - configurations.all { - attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 16) - } - - tasks.withType().configureEach { - useJUnitPlatform() - } - - dependencies { - "compileOnly"("com.google.code.findbugs:jsr305:3.0.2") - "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.8.1") - "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.8.1") - "testImplementation"("org.mockito:mockito-core:3.12.4") - "testImplementation"("org.mockito:mockito-junit-jupiter:3.12.4") - "testImplementation"("net.bytebuddy:byte-buddy:1.11.9") - "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.8.1") - } - - // Java 8 turns on doclint which we fail - tasks.withType().configureEach { - (options as StandardJavadocDocletOptions).apply { - addStringOption("Xdoclint:none", "-quiet") - tags( - "apiNote:a:API Note:", - "implSpec:a:Implementation Requirements:", - "implNote:a:Implementation Note:" - ) - options.encoding = "UTF-8" - links( - "https://javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/", - "https://jd.adventure.kyori.net/api/4.9.1/", - "https://javadoc.io/doc/org.apache.logging.log4j/log4j-api/2.14.1/", - "https://javadoc.io/doc/com.google.guava/guava/21.0/", - "https://www.antlr.org/api/Java/", - "https://docs.enginehub.org/javadoc/org.enginehub.piston/core/0.5.7/", - "https://docs.enginehub.org/javadoc/org.enginehub.piston/default-impl/0.5.7/", - "https://papermc.io/javadocs/paper/1.17/", - "https://ci.athion.net/job/FastAsyncWorldEdit-1.17-Core-Javadocs/javadoc/" // needed for other module linking - ) - } - } - configure { disableAutoTargetJvm() withJavadocJar() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43eeed0ec..f8e40def9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ jchronic = "0.2.4a" lz4-java = "1.8.0" lz4-stream = "1.0.0" ## Internal -adventure-text-minimessage = "4.1.0-SNAPSHOT" +adventure-text-minimessage = "4.2.0-SNAPSHOT" text-adapter = "3.0.6" text = "3.0.4" piston = "0.5.7" diff --git a/settings.gradle.kts b/settings.gradle.kts index 785a423dd..284f76861 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,9 @@ rootProject.name = "FastAsyncWorldEdit" include("worldedit-libs") +include("worldedit-bukkit:adapters:adapter-legacy") +include("worldedit-bukkit:adapters:adapter-1_17_1") + listOf("bukkit", "core", "cli").forEach { include("worldedit-libs:$it") include("worldedit-$it") diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts new file mode 100644 index 000000000..eb2486a16 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + java +} + +applyPaperweightAdapterConfiguration( + "1.17.1-R0.1-20211001.111503-102" +) + +repositories { + maven { + name = "PaperMC" + url = uri("https://papermc.io/repo/repository/maven-public/") + content { + includeModule("io.papermc", "paperlib") + } + } +} + +dependencies { + compileOnly(libs.paperlib) +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightAdapter.java new file mode 100644 index 000000000..9d2c53368 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightAdapter.java @@ -0,0 +1,1007 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.DoubleTag; +import com.sk89q.jnbt.EndTag; +import com.sk89q.jnbt.FloatTag; +import com.sk89q.jnbt.IntArrayTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.LongArrayTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.NBTConstants; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.world.DataFixer; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.item.ItemType; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; +import net.minecraft.resources.RegistryReadOps; +import net.minecraft.resources.RegistryWriteOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.StringRepresentable; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.Clearable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkBiomeContainer; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_17_R1.CraftServer; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.generator.ChunkGenerator; +import org.spigotmc.SpigotConfig; +import org.spigotmc.WatchdogThread; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class PaperweightAdapter implements BukkitImplAdapter { + + private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); + + private final Field serverWorldsField; + private final Method getChunkFutureMethod; + private final Field chunkProviderExecutorField; + private final Watchdog watchdog; + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public PaperweightAdapter() throws NoSuchFieldException, NoSuchMethodException { + // A simple test + CraftServer.class.cast(Bukkit.getServer()); + + int dataVersion = CraftMagicNumbers.INSTANCE.getDataVersion(); + if (dataVersion != 2730) { + throw new UnsupportedClassVersionError("Not 1.17.1!"); + } + + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + getChunkFutureMethod = ServerChunkCache.class.getDeclaredMethod("getChunkFutureMainThread", + int.class, int.class, ChunkStatus.class, boolean.class); + getChunkFutureMethod.setAccessible(true); + + chunkProviderExecutorField = ServerChunkCache.class.getDeclaredField( + Refraction.pickName("mainThreadProcessor", "h") + ); + chunkProviderExecutorField.setAccessible(true); + + new PaperweightDataConverters(CraftMagicNumbers.INSTANCE.getDataVersion(), this).build(ForkJoinPool.commonPool()); + + Watchdog watchdog; + try { + Class.forName("org.spigotmc.WatchdogThread"); + watchdog = new SpigotWatchdog(); + } catch (ClassNotFoundException | NoSuchFieldException e) { + try { + watchdog = new MojangWatchdog(((CraftServer) Bukkit.getServer()).getServer()); + } catch (NoSuchFieldException ex) { + watchdog = null; + } + } + this.watchdog = watchdog; + + try { + Class.forName("org.spigotmc.SpigotConfig"); + SpigotConfig.config.set("world-settings.worldeditregentempworld.verbose", false); + } catch (ClassNotFoundException ignored) { + } + } + + @Override + public DataFixer getDataFixer() { + return PaperweightDataConverters.INSTANCE; + } + + /** + * Read the given NBT data into the given tile entity. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + static void readTagIntoTileEntity(net.minecraft.nbt.CompoundTag tag, BlockEntity tileEntity) { + tileEntity.load(tag); + tileEntity.setChanged(); + } + + /** + * Write the tile entity's NBT data to the given tag. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + private static void readTileEntityIntoTag(BlockEntity tileEntity, net.minecraft.nbt.CompoundTag tag) { + tileEntity.save(tag); + } + + /** + * Get the ID string of the given entity. + * + * @param entity the entity + * @return the entity ID + */ + private static String getEntityId(Entity entity) { + return EntityType.getKey(entity.getType()).toString(); + } + + /** + * Create an entity using the given entity ID. + * + * @param id the entity ID + * @param world the world + * @return an entity or null + */ + @Nullable + private static Entity createEntityFromId(String id, net.minecraft.world.level.Level world) { + return EntityType.byString(id).map(t -> t.create(world)).orElse(null); + } + + /** + * Write the given NBT data into the given entity. + * + * @param entity the entity + * @param tag the tag + */ + private static void readTagIntoEntity(net.minecraft.nbt.CompoundTag tag, Entity entity) { + entity.load(tag); + } + + /** + * Write the entity's NBT data to the given tag. + * + * @param entity the entity + * @param tag the tag + */ + private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag tag) { + entity.save(tag); + } + + private static Block getBlockFromType(BlockType blockType) { + return Registry.BLOCK.get(ResourceLocation.tryParse(blockType.getId())); + } + + private static Item getItemFromType(ItemType itemType) { + return Registry.ITEM.get(ResourceLocation.tryParse(itemType.getId())); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockData data) { + net.minecraft.world.level.block.state.BlockState state = ((CraftBlockData) data).getState(); + int combinedId = Block.getId(state); + return combinedId == 0 && state.getBlock() != Blocks.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + Block mcBlock = getBlockFromType(state.getBlockType()); + net.minecraft.world.level.block.state.BlockState newState = mcBlock.defaultBlockState(); + Map, Object> states = state.getStates(); + newState = applyProperties(mcBlock.getStateDefinition(), newState, states); + final int combinedId = Block.getId(newState); + return combinedId == 0 && state.getBlockType() != BlockTypes.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + @Override + public BaseBlock getBlock(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + int internalId = Block.getId(blockData); + BlockState state = BlockStateIdAccess.getBlockStateById(internalId); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + + // Read the NBT data + BlockEntity te = chunk.getBlockEntity(blockPos); + if (te != null) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + readTileEntityIntoTag(te, tag); // Load data + return state.toBaseBlock((CompoundTag) toNative(tag)); + } + + return state.toBaseBlock(); + } + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new PaperweightWorldNativeAccess(this, + new WeakReference<>(((CraftWorld) world).getHandle())); + } + + private static net.minecraft.core.Direction adapt(Direction face) { + switch (face) { + case NORTH: + return net.minecraft.core.Direction.NORTH; + case SOUTH: + return net.minecraft.core.Direction.SOUTH; + case WEST: + return net.minecraft.core.Direction.WEST; + case EAST: + return net.minecraft.core.Direction.EAST; + case DOWN: + return net.minecraft.core.Direction.DOWN; + case UP: + default: + return net.minecraft.core.Direction.UP; + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private net.minecraft.world.level.block.state.BlockState applyProperties( + StateDefinition stateContainer, + net.minecraft.world.level.block.state.BlockState newState, + Map, Object> states + ) { + for (Map.Entry, Object> state : states.entrySet()) { + net.minecraft.world.level.block.state.properties.Property property = + stateContainer.getProperty(state.getKey().getName()); + Comparable value = (Comparable) state.getValue(); + // we may need to adapt this value, depending on the source prop + if (property instanceof DirectionProperty) { + Direction dir = (Direction) value; + value = adapt(dir); + } else if (property instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + String enumName = (String) value; + value = ((net.minecraft.world.level.block.state.properties.EnumProperty) property) + .getValue(enumName).orElseThrow(() -> + new IllegalStateException( + "Enum property " + property.getName() + " does not contain " + enumName + ) + ); + } + + newState = newState.setValue( + (net.minecraft.world.level.block.state.properties.Property) property, + (Comparable) value + ); + } + return newState; + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + readEntityIntoTag(mcEntity, tag); + return new BaseEntity(com.sk89q.worldedit.world.entity.EntityTypes.get(id), (CompoundTag) toNative(tag)); + } + + @Nullable + @Override + public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) { + checkNotNull(location); + checkNotNull(state); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + ServerLevel worldServer = craftWorld.getHandle(); + + Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle()); + + if (createdEntity != null) { + CompoundTag nativeTag = state.getNbtData(); + if (nativeTag != null) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + readTagIntoEntity(tag, createdEntity); + } + + createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + worldServer.addEntity(createdEntity, SpawnReason.CUSTOM); + return createdEntity.getBukkitEntity(); + } else { + return null; + } + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId()); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return TranslatableComponent.of(getItemFromType(itemType).getDescriptionId()); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return TranslatableComponent.of(CraftItemStack.asNMSCopy(BukkitAdapter.adapt(itemStack)).getDescriptionId()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Map> getProperties(BlockType blockType) { + Map> properties = Maps.newTreeMap(String::compareTo); + Block block = getBlockFromType(blockType); + StateDefinition blockStateList = + block.getStateDefinition(); + for (net.minecraft.world.level.block.state.properties.Property state : blockStateList.getProperties()) { + Property property; + if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) { + property = new BooleanProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues())); + } else if (state instanceof DirectionProperty) { + property = new DirectionalProperty(state.getName(), + (List) state.getPossibleValues().stream().map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase(Locale.ROOT))).collect(Collectors.toList())); + } else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + property = new EnumProperty(state.getName(), + (List) state.getPossibleValues().stream().map(e -> ((StringRepresentable) e).getSerializedName()).collect(Collectors.toList())); + } else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) { + property = new IntegerProperty(state.getName(), ImmutableList.copyOf(state.getPossibleValues())); + } else { + throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName()); + } + + properties.put(property.getName(), property); + } + return properties; + } + + @Override + public void sendFakeNBT(final Player player, final BlockVector3 pos, final CompoundBinaryTag nbtData) { + + } + + @Override + public void sendFakeOP(Player player) { + ((CraftPlayer) player).getHandle().networkManager.send(new ClientboundEntityEventPacket( + ((CraftPlayer) player).getHandle(), (byte) 28 + )); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack item) { + ItemStack stack = new ItemStack(Registry.ITEM.get(ResourceLocation.tryParse(item.getType().getId())), item.getAmount()); + stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(item.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbtData(((CompoundTag) toNative(nmsStack.getTag()))); + return weStack; + } + + private final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new)); + + @Override + public boolean simulateItemUse(org.bukkit.World world, BlockVector3 position, BaseItem item, Direction face) { + CraftWorld craftWorld = (CraftWorld) world; + ServerLevel worldServer = craftWorld.getHandle(); + ItemStack stack = CraftItemStack.asNMSCopy(BukkitAdapter.adapt(item instanceof BaseItemStack + ? ((BaseItemStack) item) : new BaseItemStack(item.getType(), item.getNbtData(), 1))); + stack.setTag((net.minecraft.nbt.CompoundTag) fromNative(item.getNbtData())); + + PaperweightFakePlayer fakePlayer; + try { + fakePlayer = fakePlayers.get(worldServer); + } catch (ExecutionException ignored) { + return false; + } + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); + fakePlayer.absMoveTo(position.getBlockX(), position.getBlockY(), position.getBlockZ(), + (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); + + final BlockPos blockPos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + final Vec3 blockVec = Vec3.atLowerCornerOf(blockPos); + final net.minecraft.core.Direction enumFacing = adapt(face); + BlockHitResult rayTrace = new BlockHitResult(blockVec, enumFacing, blockPos, false); + UseOnContext context = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTrace); + InteractionResult result = stack.placeItem(context, InteractionHand.MAIN_HAND); + if (result != InteractionResult.SUCCESS) { + if (worldServer.getBlockState(blockPos).use(worldServer, fakePlayer, InteractionHand.MAIN_HAND, rayTrace).consumesAction()) { + result = InteractionResult.SUCCESS; + } else { + result = stack.getItem().use(worldServer, fakePlayer, InteractionHand.MAIN_HAND).getResult(); + } + } + + return result == InteractionResult.SUCCESS; + } + + @Override + public boolean canPlaceAt(org.bukkit.World world, BlockVector3 position, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockData = Block.stateById(internalId); + return blockData.canSurvive(((CraftWorld) world).getHandle(), new BlockPos(position.getX(), position.getY(), position.getZ())); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) { + try { + doRegen(bukkitWorld, region, extent, options); + } catch (Exception e) { + throw new IllegalStateException("Regen failed.", e); + } + + return true; + } + + private void doRegen(org.bukkit.World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception { + Environment env = bukkitWorld.getEnvironment(); + ChunkGenerator gen = bukkitWorld.getGenerator(); + + Path tempDir = Files.createTempDirectory("FastAsyncWorldEditWorldGen"); + LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + try (LevelStorageSource.LevelStorageAccess session = levelStorage.c("worldeditregentempworld", worldDimKey)) { + ServerLevel originalWorld = ((CraftWorld) bukkitWorld).getHandle(); + PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer() + .getWorldData().overworldData(); + WorldGenSettings originalOpts = levelProperties.worldGenSettings(); + + long seed = options.getSeed().orElse(originalWorld.getSeed()); + WorldGenSettings newOpts = options.getSeed().isPresent() + ? replaceSeed(originalWorld, seed, originalOpts) + : originalOpts; + + LevelSettings newWorldSettings = new LevelSettings( + "worldeditregentempworld", + levelProperties.settings.gameType(), + levelProperties.settings.hardcore(), + levelProperties.settings.difficulty(), + levelProperties.settings.allowCommands(), + levelProperties.settings.gameRules(), + levelProperties.settings.getDataPackConfig() + ); + PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, Lifecycle.stable()); + + ServerLevel freshWorld = new ServerLevel( + originalWorld.getServer(), + originalWorld.getServer().executor, + session, newWorldData, + originalWorld.dimension(), + originalWorld.dimensionType(), + new NoOpWorldLoadListener(), + newOpts.dimensions().get(worldDimKey).generator(), + originalWorld.isDebug(), + seed, + ImmutableList.of(), + false, + env, gen, + bukkitWorld.getBiomeProvider() + ); + try { + regenForWorld(region, extent, freshWorld, options); + } finally { + freshWorld.getChunkSource().close(false); + } + } finally { + try { + @SuppressWarnings("unchecked") + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException ignored) { + } + SafeFiles.tryHardToDeleteDir(tempDir); + } + } + + private WorldGenSettings replaceSeed(ServerLevel originalWorld, long seed, WorldGenSettings originalOpts) { + RegistryWriteOps nbtReadRegOps = RegistryWriteOps.create( + NbtOps.INSTANCE, + originalWorld.getServer().registryAccess() + ); + RegistryReadOps nbtRegOps = RegistryReadOps.createAndLoad( + NbtOps.INSTANCE, + originalWorld.getServer().getResourceManager(), + originalWorld.getServer().registryAccess() + ); + Codec dimCodec = WorldGenSettings.CODEC; + return dimCodec + .encodeStart(nbtReadRegOps, originalOpts) + .flatMap(tag -> + dimCodec.parse( + recursivelySetSeed(new Dynamic<>(nbtRegOps, tag), seed, new HashSet<>()) + ) + ) + .get() + .map( + l -> l, + error -> { + throw new IllegalStateException("Unable to map GeneratorOptions: " + error.message()); + } + ); + } + + @SuppressWarnings("unchecked") + private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) { + if (!seen.add(dynamic)) { + return dynamic; + } + return dynamic.updateMapValues(pair -> { + if (pair.getFirst().asString("").equals("seed")) { + return pair.mapSecond(v -> v.createLong(seed)); + } + if (pair.getSecond().getValue() instanceof net.minecraft.nbt.CompoundTag) { + return pair.mapSecond(v -> recursivelySetSeed((Dynamic) v, seed, seen)); + } + return pair; + }); + } + + private BiomeType adapt(ServerLevel serverWorld, Biome origBiome) { + ResourceLocation key = serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(origBiome); + if (key == null) { + return null; + } + return BiomeTypes.get(key.toString()); + } + + @SuppressWarnings("unchecked") + private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, RegenOptions options) throws WorldEditException { + List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); + BlockableEventLoop executor; + try { + executor = (BlockableEventLoop) chunkProviderExecutorField.get(serverWorld.getChunkSource()); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Couldn't get executor for chunk loading.", e); + } + executor.managedBlock(() -> { + // bail out early if a future fails + if (chunkLoadings.stream().anyMatch(ftr -> + ftr.isDone() && Futures.getUnchecked(ftr) == null + )) { + return false; + } + return chunkLoadings.stream().allMatch(CompletableFuture::isDone); + }); + Map chunks = new HashMap<>(); + for (CompletableFuture future : chunkLoadings) { + @Nullable + ChunkAccess chunk = future.getNow(null); + checkState(chunk != null, "Failed to generate a chunk, regen failed."); + chunks.put(chunk.getPos(), chunk); + } + + for (BlockVector3 vec : region) { + BlockPos pos = new BlockPos(vec.getBlockX(), vec.getBlockY(), vec.getBlockZ()); + ChunkAccess chunk = chunks.get(new ChunkPos(pos)); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(pos); + int internalId = Block.getId(blockData); + BlockStateHolder state = BlockStateIdAccess.getBlockStateById(internalId); + Objects.requireNonNull(state); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + blockEntity.save(tag); + state = state.toBaseBlock(((CompoundTag) toNative(tag))); + } + extent.setBlock(vec, state.toBaseBlock()); + if (options.shouldRegenBiomes()) { + ChunkBiomeContainer biomeIndex = chunk.getBiomes(); + if (biomeIndex != null) { + Biome origBiome = biomeIndex.getNoiseBiome(vec.getBlockX(), vec.getBlockY(), vec.getBlockZ()); + BiomeType adaptedBiome = adapt(serverWorld, origBiome); + if (adaptedBiome != null) { + extent.setBiome(vec, adaptedBiome); + } + } + } + } + } + + @SuppressWarnings("unchecked") + private List> submitChunkLoadTasks(Region region, ServerLevel serverWorld) { + ServerChunkCache chunkManager = serverWorld.getChunkSource(); + List> chunkLoadings = new ArrayList<>(); + // Pre-gen all the chunks + for (BlockVector2 chunk : region.getChunks()) { + try { + //noinspection unchecked + chunkLoadings.add( + ((CompletableFuture>) + getChunkFutureMethod.invoke(chunkManager, chunk.getX(), chunk.getZ(), ChunkStatus.FEATURES, true)) + .thenApply(either -> either.left().orElse(null)) + ); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Couldn't load chunk for regen.", e); + } + } + return chunkLoadings; + } + + private ResourceKey getWorldDimKey(Environment env) { + switch (env) { + case NETHER: + return LevelStem.NETHER; + case THE_END: + return LevelStem.END; + case NORMAL: + default: + return LevelStem.OVERWORLD; + } + } + + private static final Set SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.NEIGHBORS, + SideEffect.LIGHTING, + SideEffect.VALIDATION, + SideEffect.ENTITY_AI, + SideEffect.EVENTS, + SideEffect.UPDATE + ); + + @Override + public Set getSupportedSideEffects() { + return SUPPORTED_SIDE_EFFECTS; + } + + @Override + public boolean clearContainerBlockContents(org.bukkit.World world, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + + BlockEntity entity = originalWorld.getBlockEntity(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + if (entity instanceof Clearable) { + ((Clearable) entity).clearContent(); + return true; + } + return false; + } + + // ------------------------------------------------------------------------ + // Code that is less likely to break + // ------------------------------------------------------------------------ + + /** + * Converts from a non-native NMS NBT structure to a native WorldEdit NBT + * structure. + * + * @param foreign non-native NMS NBT structure + * @return native WorldEdit NBT structure + */ + Tag toNative(net.minecraft.nbt.Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof net.minecraft.nbt.CompoundTag) { + Map values = new HashMap<>(); + Set foreignKeys = ((net.minecraft.nbt.CompoundTag) foreign).getAllKeys(); + + for (String str : foreignKeys) { + net.minecraft.nbt.Tag base = ((net.minecraft.nbt.CompoundTag) foreign).get(str); + values.put(str, toNative(base)); + } + return new CompoundTag(values); + } else if (foreign instanceof net.minecraft.nbt.ByteTag) { + return new ByteTag(((net.minecraft.nbt.ByteTag) foreign).getAsByte()); + } else if (foreign instanceof net.minecraft.nbt.ByteArrayTag) { + return new ByteArrayTag(((net.minecraft.nbt.ByteArrayTag) foreign).getAsByteArray()); + } else if (foreign instanceof net.minecraft.nbt.DoubleTag) { + return new DoubleTag(((net.minecraft.nbt.DoubleTag) foreign).getAsDouble()); + } else if (foreign instanceof net.minecraft.nbt.FloatTag) { + return new FloatTag(((net.minecraft.nbt.FloatTag) foreign).getAsFloat()); + } else if (foreign instanceof net.minecraft.nbt.IntTag) { + return new IntTag(((net.minecraft.nbt.IntTag) foreign).getAsInt()); + } else if (foreign instanceof net.minecraft.nbt.IntArrayTag) { + return new IntArrayTag(((net.minecraft.nbt.IntArrayTag) foreign).getAsIntArray()); + } else if (foreign instanceof net.minecraft.nbt.LongArrayTag) { + return new LongArrayTag(((net.minecraft.nbt.LongArrayTag) foreign).getAsLongArray()); + } else if (foreign instanceof net.minecraft.nbt.ListTag) { + try { + return toNativeList((net.minecraft.nbt.ListTag) foreign); + } catch (Throwable e) { + logger.log(Level.WARNING, "Failed to convert net.minecraft.nbt.ListTag", e); + return new ListTag(ByteTag.class, new ArrayList()); + } + } else if (foreign instanceof net.minecraft.nbt.LongTag) { + return new LongTag(((net.minecraft.nbt.LongTag) foreign).getAsLong()); + } else if (foreign instanceof net.minecraft.nbt.ShortTag) { + return new ShortTag(((net.minecraft.nbt.ShortTag) foreign).getAsShort()); + } else if (foreign instanceof net.minecraft.nbt.StringTag) { + return new StringTag(foreign.getAsString()); + } else if (foreign instanceof net.minecraft.nbt.EndTag) { + return new EndTag(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName()); + } + } + + /** + * Convert a foreign NBT list tag into a native WorldEdit one. + * + * @param foreign the foreign tag + * @return the converted tag + * @throws SecurityException on error + * @throws IllegalArgumentException on error + */ + private ListTag toNativeList(net.minecraft.nbt.ListTag foreign) throws SecurityException, IllegalArgumentException { + List values = new ArrayList<>(); + int type = foreign.getElementType(); + + for (net.minecraft.nbt.Tag tag : foreign) { + values.add(toNative(tag)); + } + + Class cls = NBTConstants.getClassFromType(type); + return new ListTag(cls, values); + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + public net.minecraft.nbt.Tag fromNative(Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundTag) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + for (Map.Entry entry : ((CompoundTag) foreign) + .getValue().entrySet()) { + tag.put(entry.getKey(), fromNative(entry.getValue())); + } + return tag; + } else if (foreign instanceof ByteTag) { + return net.minecraft.nbt.ByteTag.valueOf(((ByteTag) foreign).getValue()); + } else if (foreign instanceof ByteArrayTag) { + return new net.minecraft.nbt.ByteArrayTag(((ByteArrayTag) foreign).getValue()); + } else if (foreign instanceof DoubleTag) { + return net.minecraft.nbt.DoubleTag.valueOf(((DoubleTag) foreign).getValue()); + } else if (foreign instanceof FloatTag) { + return net.minecraft.nbt.FloatTag.valueOf(((FloatTag) foreign).getValue()); + } else if (foreign instanceof IntTag) { + return net.minecraft.nbt.IntTag.valueOf(((IntTag) foreign).getValue()); + } else if (foreign instanceof IntArrayTag) { + return new net.minecraft.nbt.IntArrayTag(((IntArrayTag) foreign).getValue()); + } else if (foreign instanceof LongArrayTag) { + return new net.minecraft.nbt.LongArrayTag(((LongArrayTag) foreign).getValue()); + } else if (foreign instanceof ListTag) { + net.minecraft.nbt.ListTag tag = new net.minecraft.nbt.ListTag(); + ListTag foreignList = (ListTag) foreign; + for (Tag t : foreignList.getValue()) { + tag.add(fromNative(t)); + } + return tag; + } else if (foreign instanceof LongTag) { + return net.minecraft.nbt.LongTag.valueOf(((LongTag) foreign).getValue()); + } else if (foreign instanceof ShortTag) { + return net.minecraft.nbt.ShortTag.valueOf(((ShortTag) foreign).getValue()); + } else if (foreign instanceof StringTag) { + return net.minecraft.nbt.StringTag.valueOf(((StringTag) foreign).getValue()); + } else if (foreign instanceof EndTag) { + return net.minecraft.nbt.EndTag.INSTANCE; + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName()); + } + } + + @Override + public boolean supportsWatchdog() { + return watchdog != null; + } + + @Override + public void tickWatchdog() { + watchdog.tick(); + } + + private class SpigotWatchdog implements Watchdog { + private final Field instanceField; + private final Field lastTickField; + + SpigotWatchdog() throws NoSuchFieldException { + Field instanceField = WatchdogThread.class.getDeclaredField("instance"); + instanceField.setAccessible(true); + this.instanceField = instanceField; + + Field lastTickField = WatchdogThread.class.getDeclaredField("lastTick"); + lastTickField.setAccessible(true); + this.lastTickField = lastTickField; + } + + @Override + public void tick() { + try { + WatchdogThread instance = (WatchdogThread) this.instanceField.get(null); + if ((long) lastTickField.get(instance) != 0) { + WatchdogThread.tick(); + } + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "Failed to tick watchdog", e); + } + } + } + + private static class MojangWatchdog implements Watchdog { + private final DedicatedServer server; + private final Field tickField; + + MojangWatchdog(DedicatedServer server) throws NoSuchFieldException { + this.server = server; + Field tickField = MinecraftServer.class.getDeclaredField( + Refraction.pickName("nextTickTime", "ao") + ); + tickField.setAccessible(true); + this.tickField = tickField; + } + + @Override + public void tick() { + try { + tickField.set(server, Util.getMillis()); + } catch (IllegalAccessException ignored) { + } + } + } + + private static class NoOpWorldLoadListener implements ChunkProgressListener { + @Override + public void updateSpawnPos(ChunkPos spawnPos) { + } + + @Override + public void onStatusChange(ChunkPos pos, @org.jetbrains.annotations.Nullable ChunkStatus status) { + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public void setChunkRadius(int radius) { + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightDataConverters.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightDataConverters.java new file mode 100644 index 000000000..e2b4fac88 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightDataConverters.java @@ -0,0 +1,2795 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.datafixers.DSL.TypeReference; +import com.mojang.datafixers.DataFixer; +import com.mojang.datafixers.DataFixerBuilder; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import com.sk89q.jnbt.CompoundTag; +import net.minecraft.core.Direction; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.StringUtil; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.item.DyeColor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * Handles converting all Pre 1.13.2 data using the Legacy DataFix System (ported to 1.13.2) + * + * We register a DFU Fixer per Legacy Data Version and apply the fixes using legacy strategy + * which is safer, faster and cleaner code. + * + * The pre DFU code did not fail when the Source version was unknown. + * + * This class also provides util methods for converting compounds to wrap the update call to + * receive the source version in the compound + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +class PaperweightDataConverters extends DataFixerBuilder implements com.sk89q.worldedit.world.DataFixer { + + @SuppressWarnings("unchecked") + @Override + public T fixUp(FixType type, T original, int srcVer) { + if (type == FixTypes.CHUNK) { + return (T) fixChunk((CompoundTag) original, srcVer); + } else if (type == FixTypes.BLOCK_ENTITY) { + return (T) fixBlockEntity((CompoundTag) original, srcVer); + } else if (type == FixTypes.ENTITY) { + return (T) fixEntity((CompoundTag) original, srcVer); + } else if (type == FixTypes.BLOCK_STATE) { + return (T) fixBlockState((String) original, srcVer); + } else if (type == FixTypes.ITEM_TYPE) { + return (T) fixItemType((String) original, srcVer); + } else if (type == FixTypes.BIOME) { + return (T) fixBiome((String) original, srcVer); + } + return original; + } + + private CompoundTag fixChunk(CompoundTag originalChunk, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative(originalChunk); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.CHUNK, tag, srcVer); + return (CompoundTag) adapter.toNative(fixed); + } + + private CompoundTag fixBlockEntity(CompoundTag origTileEnt, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative(origTileEnt); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.BLOCK_ENTITY, tag, srcVer); + return (CompoundTag) adapter.toNative(fixed); + } + + private CompoundTag fixEntity(CompoundTag origEnt, int srcVer) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative(origEnt); + net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.ENTITY, tag, srcVer); + return (CompoundTag) adapter.toNative(fixed); + } + + private String fixBlockState(String blockState, int srcVer) { + net.minecraft.nbt.CompoundTag stateNBT = stateToNBT(blockState); + Dynamic dynamic = new Dynamic<>(OPS_NBT, stateNBT); + net.minecraft.nbt.CompoundTag fixed = (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(References.BLOCK_STATE, dynamic, srcVer, DATA_VERSION).getValue(); + return nbtToState(fixed); + } + + private String nbtToState(net.minecraft.nbt.CompoundTag tagCompound) { + StringBuilder sb = new StringBuilder(); + sb.append(tagCompound.getString("Name")); + if (tagCompound.contains("Properties", 10)) { + sb.append('['); + net.minecraft.nbt.CompoundTag props = tagCompound.getCompound("Properties"); + sb.append(props.getAllKeys().stream().map(k -> k + "=" + props.getString(k).replace("\"", "")).collect(Collectors.joining(","))); + sb.append(']'); + } + return sb.toString(); + } + + private static net.minecraft.nbt.CompoundTag stateToNBT(String blockState) { + int propIdx = blockState.indexOf('['); + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + if (propIdx < 0) { + tag.putString("Name", blockState); + } else { + tag.putString("Name", blockState.substring(0, propIdx)); + net.minecraft.nbt.CompoundTag propTag = new net.minecraft.nbt.CompoundTag(); + String props = blockState.substring(propIdx + 1, blockState.length() - 1); + String[] propArr = props.split(","); + for (String pair : propArr) { + final String[] split = pair.split("="); + propTag.putString(split[0], split[1]); + } + tag.put("Properties", propTag); + } + return tag; + } + + private String fixBiome(String key, int srcVer) { + return fixName(key, srcVer, References.BIOME); + } + + private String fixItemType(String key, int srcVer) { + return fixName(key, srcVer, References.ITEM_NAME); + } + + private static String fixName(String key, int srcVer, TypeReference type) { + return INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, net.minecraft.nbt.StringTag.valueOf(key)), srcVer, DATA_VERSION) + .getValue().getAsString(); + } + + private final PaperweightAdapter adapter; + + private static final NbtOps OPS_NBT = NbtOps.INSTANCE; + private static final int LEGACY_VERSION = 1343; + private static int DATA_VERSION; + static PaperweightDataConverters INSTANCE; + + private final Map> converters = new EnumMap<>(LegacyType.class); + private final Map> inspectors = new EnumMap<>(LegacyType.class); + + // Set on build + private DataFixer fixer; + private static final Map DFU_TO_LEGACY = new HashMap<>(); + + public enum LegacyType { + LEVEL(References.LEVEL), + PLAYER(References.PLAYER), + CHUNK(References.CHUNK), + BLOCK_ENTITY(References.BLOCK_ENTITY), + ENTITY(References.ENTITY), + ITEM_INSTANCE(References.ITEM_STACK), + OPTIONS(References.OPTIONS), + STRUCTURE(References.STRUCTURE); + + private final TypeReference type; + + LegacyType(TypeReference type) { + this.type = type; + DFU_TO_LEGACY.put(type.typeName(), this); + } + + public TypeReference getDFUType() { + return type; + } + } + + PaperweightDataConverters(int dataVersion, PaperweightAdapter adapter) { + super(dataVersion); + DATA_VERSION = dataVersion; + INSTANCE = this; + this.adapter = adapter; + registerConverters(); + registerInspectors(); + } + + + // Called after fixers are built and ready for FIXING + @Override + public DataFixer build(final Executor executor) { + return this.fixer = new WrappedDataFixer(DataFixers.getDataFixer()); + } + + @SuppressWarnings("unchecked") + private class WrappedDataFixer implements DataFixer { + private final DataFixer realFixer; + + WrappedDataFixer(DataFixer realFixer) { + this.realFixer = realFixer; + } + + @Override + public Dynamic update(TypeReference type, Dynamic dynamic, int sourceVer, int targetVer) { + LegacyType legacyType = DFU_TO_LEGACY.get(type.typeName()); + if (sourceVer < LEGACY_VERSION && legacyType != null) { + net.minecraft.nbt.CompoundTag cmp = (net.minecraft.nbt.CompoundTag) dynamic.getValue(); + int desiredVersion = Math.min(targetVer, LEGACY_VERSION); + + cmp = convert(legacyType, cmp, sourceVer, desiredVersion); + sourceVer = desiredVersion; + dynamic = new Dynamic(OPS_NBT, cmp); + } + return realFixer.update(type, dynamic, sourceVer, targetVer); + } + + private net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int desiredVersion) { + List converters = PaperweightDataConverters.this.converters.get(type); + if (converters != null && !converters.isEmpty()) { + for (DataConverter converter : converters) { + int dataVersion = converter.getDataVersion(); + if (dataVersion > sourceVer && dataVersion <= desiredVersion) { + cmp = converter.convert(cmp); + } + } + } + + List inspectors = PaperweightDataConverters.this.inspectors.get(type); + if (inspectors != null && !inspectors.isEmpty()) { + for (DataInspector inspector : inspectors) { + cmp = inspector.inspect(cmp, sourceVer, desiredVersion); + } + } + + return cmp; + } + + @Override + public Schema getSchema(int i) { + return realFixer.getSchema(i); + } + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp) { + return convert(type.getDFUType(), cmp); + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) { + return convert(type.getDFUType(), cmp, sourceVer); + } + + public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + return convert(type.getDFUType(), cmp, sourceVer, targetVer); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp) { + int i = cmp.contains("DataVersion", 99) ? cmp.getInt("DataVersion") : -1; + return convert(type, cmp, i); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) { + return convert(type, cmp, sourceVer, DATA_VERSION); + } + + public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (sourceVer >= targetVer) { + return cmp; + } + return (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, cmp), sourceVer, targetVer).getValue(); + } + + + public interface DataInspector { + net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer); + } + + public interface DataConverter { + + int getDataVersion(); + + net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp); + } + + + private void registerInspector(LegacyType type, DataInspector inspector) { + this.inspectors.computeIfAbsent(type, k -> new ArrayList<>()).add(inspector); + } + + private void registerConverter(LegacyType type, DataConverter converter) { + int version = converter.getDataVersion(); + + List list = this.converters.computeIfAbsent(type, k -> new ArrayList<>()); + if (!list.isEmpty() && list.get(list.size() - 1).getDataVersion() > version) { + for (int j = 0; j < list.size(); ++j) { + if (list.get(j).getDataVersion() > version) { + list.add(j, converter); + break; + } + } + } else { + list.add(converter); + } + } + + private void registerInspectors() { + registerEntityItemList("EntityHorseDonkey", "SaddleItem", "Items"); + registerEntityItemList("EntityHorseMule", "Items"); + registerEntityItemList("EntityMinecartChest", "Items"); + registerEntityItemList("EntityMinecartHopper", "Items"); + registerEntityItemList("EntityVillager", "Inventory"); + registerEntityItemListEquipment("EntityArmorStand"); + registerEntityItemListEquipment("EntityBat"); + registerEntityItemListEquipment("EntityBlaze"); + registerEntityItemListEquipment("EntityCaveSpider"); + registerEntityItemListEquipment("EntityChicken"); + registerEntityItemListEquipment("EntityCow"); + registerEntityItemListEquipment("EntityCreeper"); + registerEntityItemListEquipment("EntityEnderDragon"); + registerEntityItemListEquipment("EntityEnderman"); + registerEntityItemListEquipment("EntityEndermite"); + registerEntityItemListEquipment("EntityEvoker"); + registerEntityItemListEquipment("EntityGhast"); + registerEntityItemListEquipment("EntityGiantZombie"); + registerEntityItemListEquipment("EntityGuardian"); + registerEntityItemListEquipment("EntityGuardianElder"); + registerEntityItemListEquipment("EntityHorse"); + registerEntityItemListEquipment("EntityHorseDonkey"); + registerEntityItemListEquipment("EntityHorseMule"); + registerEntityItemListEquipment("EntityHorseSkeleton"); + registerEntityItemListEquipment("EntityHorseZombie"); + registerEntityItemListEquipment("EntityIronGolem"); + registerEntityItemListEquipment("EntityMagmaCube"); + registerEntityItemListEquipment("EntityMushroomCow"); + registerEntityItemListEquipment("EntityOcelot"); + registerEntityItemListEquipment("EntityPig"); + registerEntityItemListEquipment("EntityPigZombie"); + registerEntityItemListEquipment("EntityRabbit"); + registerEntityItemListEquipment("EntitySheep"); + registerEntityItemListEquipment("EntityShulker"); + registerEntityItemListEquipment("EntitySilverfish"); + registerEntityItemListEquipment("EntitySkeleton"); + registerEntityItemListEquipment("EntitySkeletonStray"); + registerEntityItemListEquipment("EntitySkeletonWither"); + registerEntityItemListEquipment("EntitySlime"); + registerEntityItemListEquipment("EntitySnowman"); + registerEntityItemListEquipment("EntitySpider"); + registerEntityItemListEquipment("EntitySquid"); + registerEntityItemListEquipment("EntityVex"); + registerEntityItemListEquipment("EntityVillager"); + registerEntityItemListEquipment("EntityVindicator"); + registerEntityItemListEquipment("EntityWitch"); + registerEntityItemListEquipment("EntityWither"); + registerEntityItemListEquipment("EntityWolf"); + registerEntityItemListEquipment("EntityZombie"); + registerEntityItemListEquipment("EntityZombieHusk"); + registerEntityItemListEquipment("EntityZombieVillager"); + registerEntityItemSingle("EntityFireworks", "FireworksItem"); + registerEntityItemSingle("EntityHorse", "ArmorItem"); + registerEntityItemSingle("EntityHorse", "SaddleItem"); + registerEntityItemSingle("EntityHorseMule", "SaddleItem"); + registerEntityItemSingle("EntityHorseSkeleton", "SaddleItem"); + registerEntityItemSingle("EntityHorseZombie", "SaddleItem"); + registerEntityItemSingle("EntityItem", "Item"); + registerEntityItemSingle("EntityItemFrame", "Item"); + registerEntityItemSingle("EntityPotion", "Potion"); + + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItem("TileEntityRecordPlayer", "RecordItem")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityBrewingStand", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityChest", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDispenser", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDropper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityFurnace", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityHopper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityShulkerBox", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorMobSpawnerMobs()); + registerInspector(LegacyType.CHUNK, new DataInspectorChunks()); + registerInspector(LegacyType.ENTITY, new DataInspectorCommandBlock()); + registerInspector(LegacyType.ENTITY, new DataInspectorEntityPassengers()); + registerInspector(LegacyType.ENTITY, new DataInspectorMobSpawnerMinecart()); + registerInspector(LegacyType.ENTITY, new DataInspectorVillagers()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorBlockEntity()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorEntity()); + registerInspector(LegacyType.LEVEL, new DataInspectorLevelPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayerVehicle()); + registerInspector(LegacyType.STRUCTURE, new DataInspectorStructure()); + } + + private void registerConverters() { + registerConverter(LegacyType.ENTITY, new DataConverterEquipment()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterSignText()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterMaterialId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterSpawnEgg()); + registerConverter(LegacyType.ENTITY, new DataConverterMinecart()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterMobSpawner()); + registerConverter(LegacyType.ENTITY, new DataConverterUUID()); + registerConverter(LegacyType.ENTITY, new DataConverterHealth()); + registerConverter(LegacyType.ENTITY, new DataConverterSaddle()); + registerConverter(LegacyType.ENTITY, new DataConverterHanging()); + registerConverter(LegacyType.ENTITY, new DataConverterDropChances()); + registerConverter(LegacyType.ENTITY, new DataConverterRiding()); + registerConverter(LegacyType.ENTITY, new DataConverterArmorStand()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBook()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterCookedFish()); + registerConverter(LegacyType.ENTITY, new DataConverterZombie()); + registerConverter(LegacyType.OPTIONS, new DataConverterVBO()); + registerConverter(LegacyType.ENTITY, new DataConverterGuardian()); + registerConverter(LegacyType.ENTITY, new DataConverterSkeleton()); + registerConverter(LegacyType.ENTITY, new DataConverterZombieType()); + registerConverter(LegacyType.ENTITY, new DataConverterHorse()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterTileEntity()); + registerConverter(LegacyType.ENTITY, new DataConverterEntity()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBanner()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionWater()); + registerConverter(LegacyType.ENTITY, new DataConverterShulker()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterShulkerBoxItem()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterShulkerBoxBlock()); + registerConverter(LegacyType.OPTIONS, new DataConverterLang()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterTotem()); + registerConverter(LegacyType.CHUNK, new DataConverterBedBlock()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBedItem()); + } + + private void registerEntityItemList(String type, String... keys) { + registerInspector(LegacyType.ENTITY, new DataInspectorItemList(type, keys)); + } + + private void registerEntityItemSingle(String type, String key) { + registerInspector(LegacyType.ENTITY, new DataInspectorItem(type, key)); + } + + private void registerEntityItemListEquipment(String type) { + registerEntityItemList(type, "ArmorItems", "HandItems"); + } + + private static final Map OLD_ID_TO_KEY_MAP = new HashMap<>(); + + static { + final Map map = OLD_ID_TO_KEY_MAP; + map.put("EntityItem", new ResourceLocation("item")); + map.put("EntityExperienceOrb", new ResourceLocation("xp_orb")); + map.put("EntityAreaEffectCloud", new ResourceLocation("area_effect_cloud")); + map.put("EntityGuardianElder", new ResourceLocation("elder_guardian")); + map.put("EntitySkeletonWither", new ResourceLocation("wither_skeleton")); + map.put("EntitySkeletonStray", new ResourceLocation("stray")); + map.put("EntityEgg", new ResourceLocation("egg")); + map.put("EntityLeash", new ResourceLocation("leash_knot")); + map.put("EntityPainting", new ResourceLocation("painting")); + map.put("EntityTippedArrow", new ResourceLocation("arrow")); + map.put("EntitySnowball", new ResourceLocation("snowball")); + map.put("EntityLargeFireball", new ResourceLocation("fireball")); + map.put("EntitySmallFireball", new ResourceLocation("small_fireball")); + map.put("EntityEnderPearl", new ResourceLocation("ender_pearl")); + map.put("EntityEnderSignal", new ResourceLocation("eye_of_ender_signal")); + map.put("EntityPotion", new ResourceLocation("potion")); + map.put("EntityThrownExpBottle", new ResourceLocation("xp_bottle")); + map.put("EntityItemFrame", new ResourceLocation("item_frame")); + map.put("EntityWitherSkull", new ResourceLocation("wither_skull")); + map.put("EntityTNTPrimed", new ResourceLocation("tnt")); + map.put("EntityFallingBlock", new ResourceLocation("falling_block")); + map.put("EntityFireworks", new ResourceLocation("fireworks_rocket")); + map.put("EntityZombieHusk", new ResourceLocation("husk")); + map.put("EntitySpectralArrow", new ResourceLocation("spectral_arrow")); + map.put("EntityShulkerBullet", new ResourceLocation("shulker_bullet")); + map.put("EntityDragonFireball", new ResourceLocation("dragon_fireball")); + map.put("EntityZombieVillager", new ResourceLocation("zombie_villager")); + map.put("EntityHorseSkeleton", new ResourceLocation("skeleton_horse")); + map.put("EntityHorseZombie", new ResourceLocation("zombie_horse")); + map.put("EntityArmorStand", new ResourceLocation("armor_stand")); + map.put("EntityHorseDonkey", new ResourceLocation("donkey")); + map.put("EntityHorseMule", new ResourceLocation("mule")); + map.put("EntityEvokerFangs", new ResourceLocation("evocation_fangs")); + map.put("EntityEvoker", new ResourceLocation("evocation_illager")); + map.put("EntityVex", new ResourceLocation("vex")); + map.put("EntityVindicator", new ResourceLocation("vindication_illager")); + map.put("EntityIllagerIllusioner", new ResourceLocation("illusion_illager")); + map.put("EntityMinecartCommandBlock", new ResourceLocation("commandblock_minecart")); + map.put("EntityBoat", new ResourceLocation("boat")); + map.put("EntityMinecartRideable", new ResourceLocation("minecart")); + map.put("EntityMinecartChest", new ResourceLocation("chest_minecart")); + map.put("EntityMinecartFurnace", new ResourceLocation("furnace_minecart")); + map.put("EntityMinecartTNT", new ResourceLocation("tnt_minecart")); + map.put("EntityMinecartHopper", new ResourceLocation("hopper_minecart")); + map.put("EntityMinecartMobSpawner", new ResourceLocation("spawner_minecart")); + map.put("EntityCreeper", new ResourceLocation("creeper")); + map.put("EntitySkeleton", new ResourceLocation("skeleton")); + map.put("EntitySpider", new ResourceLocation("spider")); + map.put("EntityGiantZombie", new ResourceLocation("giant")); + map.put("EntityZombie", new ResourceLocation("zombie")); + map.put("EntitySlime", new ResourceLocation("slime")); + map.put("EntityGhast", new ResourceLocation("ghast")); + map.put("EntityPigZombie", new ResourceLocation("zombie_pigman")); + map.put("EntityEnderman", new ResourceLocation("enderman")); + map.put("EntityCaveSpider", new ResourceLocation("cave_spider")); + map.put("EntitySilverfish", new ResourceLocation("silverfish")); + map.put("EntityBlaze", new ResourceLocation("blaze")); + map.put("EntityMagmaCube", new ResourceLocation("magma_cube")); + map.put("EntityEnderDragon", new ResourceLocation("ender_dragon")); + map.put("EntityWither", new ResourceLocation("wither")); + map.put("EntityBat", new ResourceLocation("bat")); + map.put("EntityWitch", new ResourceLocation("witch")); + map.put("EntityEndermite", new ResourceLocation("endermite")); + map.put("EntityGuardian", new ResourceLocation("guardian")); + map.put("EntityShulker", new ResourceLocation("shulker")); + map.put("EntityPig", new ResourceLocation("pig")); + map.put("EntitySheep", new ResourceLocation("sheep")); + map.put("EntityCow", new ResourceLocation("cow")); + map.put("EntityChicken", new ResourceLocation("chicken")); + map.put("EntitySquid", new ResourceLocation("squid")); + map.put("EntityWolf", new ResourceLocation("wolf")); + map.put("EntityMushroomCow", new ResourceLocation("mooshroom")); + map.put("EntitySnowman", new ResourceLocation("snowman")); + map.put("EntityOcelot", new ResourceLocation("ocelot")); + map.put("EntityIronGolem", new ResourceLocation("villager_golem")); + map.put("EntityHorse", new ResourceLocation("horse")); + map.put("EntityRabbit", new ResourceLocation("rabbit")); + map.put("EntityPolarBear", new ResourceLocation("polar_bear")); + map.put("EntityLlama", new ResourceLocation("llama")); + map.put("EntityLlamaSpit", new ResourceLocation("llama_spit")); + map.put("EntityParrot", new ResourceLocation("parrot")); + map.put("EntityVillager", new ResourceLocation("villager")); + map.put("EntityEnderCrystal", new ResourceLocation("ender_crystal")); + map.put("TileEntityFurnace", new ResourceLocation("furnace")); + map.put("TileEntityChest", new ResourceLocation("chest")); + map.put("TileEntityEnderChest", new ResourceLocation("ender_chest")); + map.put("TileEntityRecordPlayer", new ResourceLocation("jukebox")); + map.put("TileEntityDispenser", new ResourceLocation("dispenser")); + map.put("TileEntityDropper", new ResourceLocation("dropper")); + map.put("TileEntitySign", new ResourceLocation("sign")); + map.put("TileEntityMobSpawner", new ResourceLocation("mob_spawner")); + map.put("TileEntityNote", new ResourceLocation("noteblock")); + map.put("TileEntityPiston", new ResourceLocation("piston")); + map.put("TileEntityBrewingStand", new ResourceLocation("brewing_stand")); + map.put("TileEntityEnchantTable", new ResourceLocation("enchanting_table")); + map.put("TileEntityEnderPortal", new ResourceLocation("end_portal")); + map.put("TileEntityBeacon", new ResourceLocation("beacon")); + map.put("TileEntitySkull", new ResourceLocation("skull")); + map.put("TileEntityLightDetector", new ResourceLocation("daylight_detector")); + map.put("TileEntityHopper", new ResourceLocation("hopper")); + map.put("TileEntityComparator", new ResourceLocation("comparator")); + map.put("TileEntityFlowerPot", new ResourceLocation("flower_pot")); + map.put("TileEntityBanner", new ResourceLocation("banner")); + map.put("TileEntityStructure", new ResourceLocation("structure_block")); + map.put("TileEntityEndGateway", new ResourceLocation("end_gateway")); + map.put("TileEntityCommand", new ResourceLocation("command_block")); + map.put("TileEntityShulkerBox", new ResourceLocation("shulker_box")); + map.put("TileEntityBed", new ResourceLocation("bed")); + } + + private static ResourceLocation getKey(String type) { + final ResourceLocation key = OLD_ID_TO_KEY_MAP.get(type); + if (key == null) { + throw new IllegalArgumentException("Unknown mapping for " + type); + } + return key; + } + + private static void convertCompound(LegacyType type, net.minecraft.nbt.CompoundTag cmp, String key, int sourceVer, int targetVer) { + cmp.put(key, convert(type, cmp.getCompound(key), sourceVer, targetVer)); + } + + private static void convertItem(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + if (nbttagcompound.contains(key, 10)) { + convertCompound(LegacyType.ITEM_INSTANCE, nbttagcompound, key, sourceVer, targetVer); + } + } + + private static void convertItems(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + if (nbttagcompound.contains(key, 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound.getList(key, 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ITEM_INSTANCE, nbttaglist.getCompound(j), sourceVer, targetVer)); + } + } + + } + + private static class DataConverterEquipment implements DataConverter { + + DataConverterEquipment() { + } + + public int getDataVersion() { + return 100; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("Equipment", 10); + net.minecraft.nbt.ListTag nbttaglist1; + + if (!nbttaglist.isEmpty() && !cmp.contains("HandItems", 10)) { + nbttaglist1 = new net.minecraft.nbt.ListTag(); + nbttaglist1.add(nbttaglist.get(0)); + nbttaglist1.add(new net.minecraft.nbt.CompoundTag()); + cmp.put("HandItems", nbttaglist1); + } + + if (nbttaglist.size() > 1 && !cmp.contains("ArmorItem", 10)) { + nbttaglist1 = new net.minecraft.nbt.ListTag(); + nbttaglist1.add(nbttaglist.get(1)); + nbttaglist1.add(nbttaglist.get(2)); + nbttaglist1.add(nbttaglist.get(3)); + nbttaglist1.add(nbttaglist.get(4)); + cmp.put("ArmorItems", nbttaglist1); + } + + cmp.remove("Equipment"); + if (cmp.contains("DropChances", 9)) { + nbttaglist1 = cmp.getList("DropChances", 5); + net.minecraft.nbt.ListTag nbttaglist2; + + if (!cmp.contains("HandDropChances", 10)) { + nbttaglist2 = new net.minecraft.nbt.ListTag(); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(0))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(0.0F)); + cmp.put("HandDropChances", nbttaglist2); + } + + if (!cmp.contains("ArmorDropChances", 10)) { + nbttaglist2 = new net.minecraft.nbt.ListTag(); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(1))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(2))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(3))); + nbttaglist2.add(net.minecraft.nbt.FloatTag.valueOf(nbttaglist1.getFloat(4))); + cmp.put("ArmorDropChances", nbttaglist2); + } + + cmp.remove("DropChances"); + } + + return cmp; + } + } + + private static class DataInspectorBlockEntity implements DataInspector { + + private static final Map b = Maps.newHashMap(); + private static final Map c = Maps.newHashMap(); + + DataInspectorBlockEntity() { + } + + @Nullable + private static String convertEntityId(int i, String s) { + String key = new ResourceLocation(s).toString(); + if (i < 515 && DataInspectorBlockEntity.b.containsKey(key)) { + return DataInspectorBlockEntity.b.get(key); + } else { + return DataInspectorBlockEntity.c.get(key); + } + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (!cmp.contains("tag", 10)) { + return cmp; + } else { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + String s = cmp.getString("id"); + String s1 = convertEntityId(sourceVer, s); + boolean flag; + + if (s1 == null) { + // CraftBukkit - Remove unnecessary warning (occurs when deserializing a Shulker Box item) + // DataInspectorBlockEntity.a.warn("Unable to resolve BlockEntity for ItemInstance: {}", s); + flag = false; + } else { + flag = !nbttagcompound2.contains("id"); + nbttagcompound2.putString("id", s1); + } + + convert(LegacyType.BLOCK_ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + } + + return cmp; + } + } + + static { + Map map = DataInspectorBlockEntity.b; + + map.put("minecraft:furnace", "Furnace"); + map.put("minecraft:lit_furnace", "Furnace"); + map.put("minecraft:chest", "Chest"); + map.put("minecraft:trapped_chest", "Chest"); + map.put("minecraft:ender_chest", "EnderChest"); + map.put("minecraft:jukebox", "RecordPlayer"); + map.put("minecraft:dispenser", "Trap"); + map.put("minecraft:dropper", "Dropper"); + map.put("minecraft:sign", "Sign"); + map.put("minecraft:mob_spawner", "MobSpawner"); + map.put("minecraft:noteblock", "Music"); + map.put("minecraft:brewing_stand", "Cauldron"); + map.put("minecraft:enhanting_table", "EnchantTable"); + map.put("minecraft:command_block", "CommandBlock"); + map.put("minecraft:beacon", "Beacon"); + map.put("minecraft:skull", "Skull"); + map.put("minecraft:daylight_detector", "DLDetector"); + map.put("minecraft:hopper", "Hopper"); + map.put("minecraft:banner", "Banner"); + map.put("minecraft:flower_pot", "FlowerPot"); + map.put("minecraft:repeating_command_block", "CommandBlock"); + map.put("minecraft:chain_command_block", "CommandBlock"); + map.put("minecraft:standing_sign", "Sign"); + map.put("minecraft:wall_sign", "Sign"); + map.put("minecraft:piston_head", "Piston"); + map.put("minecraft:daylight_detector_inverted", "DLDetector"); + map.put("minecraft:unpowered_comparator", "Comparator"); + map.put("minecraft:powered_comparator", "Comparator"); + map.put("minecraft:wall_banner", "Banner"); + map.put("minecraft:standing_banner", "Banner"); + map.put("minecraft:structure_block", "Structure"); + map.put("minecraft:end_portal", "Airportal"); + map.put("minecraft:end_gateway", "EndGateway"); + map.put("minecraft:shield", "Shield"); + map = DataInspectorBlockEntity.c; + map.put("minecraft:furnace", "minecraft:furnace"); + map.put("minecraft:lit_furnace", "minecraft:furnace"); + map.put("minecraft:chest", "minecraft:chest"); + map.put("minecraft:trapped_chest", "minecraft:chest"); + map.put("minecraft:ender_chest", "minecraft:enderchest"); + map.put("minecraft:jukebox", "minecraft:jukebox"); + map.put("minecraft:dispenser", "minecraft:dispenser"); + map.put("minecraft:dropper", "minecraft:dropper"); + map.put("minecraft:sign", "minecraft:sign"); + map.put("minecraft:mob_spawner", "minecraft:mob_spawner"); + map.put("minecraft:noteblock", "minecraft:noteblock"); + map.put("minecraft:brewing_stand", "minecraft:brewing_stand"); + map.put("minecraft:enhanting_table", "minecraft:enchanting_table"); + map.put("minecraft:command_block", "minecraft:command_block"); + map.put("minecraft:beacon", "minecraft:beacon"); + map.put("minecraft:skull", "minecraft:skull"); + map.put("minecraft:daylight_detector", "minecraft:daylight_detector"); + map.put("minecraft:hopper", "minecraft:hopper"); + map.put("minecraft:banner", "minecraft:banner"); + map.put("minecraft:flower_pot", "minecraft:flower_pot"); + map.put("minecraft:repeating_command_block", "minecraft:command_block"); + map.put("minecraft:chain_command_block", "minecraft:command_block"); + map.put("minecraft:shulker_box", "minecraft:shulker_box"); + map.put("minecraft:white_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:green_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:red_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:black_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:bed", "minecraft:bed"); + map.put("minecraft:standing_sign", "minecraft:sign"); + map.put("minecraft:wall_sign", "minecraft:sign"); + map.put("minecraft:piston_head", "minecraft:piston"); + map.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); + map.put("minecraft:unpowered_comparator", "minecraft:comparator"); + map.put("minecraft:powered_comparator", "minecraft:comparator"); + map.put("minecraft:wall_banner", "minecraft:banner"); + map.put("minecraft:standing_banner", "minecraft:banner"); + map.put("minecraft:structure_block", "minecraft:structure_block"); + map.put("minecraft:end_portal", "minecraft:end_portal"); + map.put("minecraft:end_gateway", "minecraft:end_gateway"); + map.put("minecraft:shield", "minecraft:shield"); + } + } + + private static class DataInspectorEntity implements DataInspector { + + private static final Logger a = LogManager.getLogger(PaperweightDataConverters.class); + + DataInspectorEntity() { + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("EntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag"); + String s = cmp.getString("id"); + String s1; + + if ("minecraft:armor_stand".equals(s)) { + s1 = sourceVer < 515 ? "ArmorStand" : "minecraft:armor_stand"; + } else { + if (!"minecraft:spawn_egg".equals(s)) { + return cmp; + } + + s1 = nbttagcompound2.getString("id"); + } + + boolean flag; + + flag = !nbttagcompound2.contains("id", 8); + nbttagcompound2.putString("id", s1); + + convert(LegacyType.ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + } + + return cmp; + } + } + + + private abstract static class DataInspectorTagged implements DataInspector { + + private final ResourceLocation key; + + DataInspectorTagged(String type) { + this.key = getKey(type); + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (this.key.equals(new ResourceLocation(cmp.getString("id")))) { + cmp = this.inspectChecked(cmp, sourceVer, targetVer); + } + + return cmp; + } + + abstract net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer); + } + + private static class DataInspectorItemList extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItemList(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String s : this.keys) { + PaperweightDataConverters.convertItems(nbttagcompound, s, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataInspectorItem extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItem(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String key : this.keys) { + PaperweightDataConverters.convertItem(nbttagcompound, key, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataConverterMaterialId implements DataConverter { + + private static final String[] materials = new String[2268]; + + DataConverterMaterialId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("id", 99)) { + short short0 = cmp.getShort("id"); + + if (short0 > 0 && short0 < materials.length && materials[short0] != null) { + cmp.putString("id", materials[short0]); + } + } + + return cmp; + } + + static { + materials[1] = "minecraft:stone"; + materials[2] = "minecraft:grass"; + materials[3] = "minecraft:dirt"; + materials[4] = "minecraft:cobblestone"; + materials[5] = "minecraft:planks"; + materials[6] = "minecraft:sapling"; + materials[7] = "minecraft:bedrock"; + materials[8] = "minecraft:flowing_water"; + materials[9] = "minecraft:water"; + materials[10] = "minecraft:flowing_lava"; + materials[11] = "minecraft:lava"; + materials[12] = "minecraft:sand"; + materials[13] = "minecraft:gravel"; + materials[14] = "minecraft:gold_ore"; + materials[15] = "minecraft:iron_ore"; + materials[16] = "minecraft:coal_ore"; + materials[17] = "minecraft:log"; + materials[18] = "minecraft:leaves"; + materials[19] = "minecraft:sponge"; + materials[20] = "minecraft:glass"; + materials[21] = "minecraft:lapis_ore"; + materials[22] = "minecraft:lapis_block"; + materials[23] = "minecraft:dispenser"; + materials[24] = "minecraft:sandstone"; + materials[25] = "minecraft:noteblock"; + materials[27] = "minecraft:golden_rail"; + materials[28] = "minecraft:detector_rail"; + materials[29] = "minecraft:sticky_piston"; + materials[30] = "minecraft:web"; + materials[31] = "minecraft:tallgrass"; + materials[32] = "minecraft:deadbush"; + materials[33] = "minecraft:piston"; + materials[35] = "minecraft:wool"; + materials[37] = "minecraft:yellow_flower"; + materials[38] = "minecraft:red_flower"; + materials[39] = "minecraft:brown_mushroom"; + materials[40] = "minecraft:red_mushroom"; + materials[41] = "minecraft:gold_block"; + materials[42] = "minecraft:iron_block"; + materials[43] = "minecraft:double_stone_slab"; + materials[44] = "minecraft:stone_slab"; + materials[45] = "minecraft:brick_block"; + materials[46] = "minecraft:tnt"; + materials[47] = "minecraft:bookshelf"; + materials[48] = "minecraft:mossy_cobblestone"; + materials[49] = "minecraft:obsidian"; + materials[50] = "minecraft:torch"; + materials[51] = "minecraft:fire"; + materials[52] = "minecraft:mob_spawner"; + materials[53] = "minecraft:oak_stairs"; + materials[54] = "minecraft:chest"; + materials[56] = "minecraft:diamond_ore"; + materials[57] = "minecraft:diamond_block"; + materials[58] = "minecraft:crafting_table"; + materials[60] = "minecraft:farmland"; + materials[61] = "minecraft:furnace"; + materials[62] = "minecraft:lit_furnace"; + materials[65] = "minecraft:ladder"; + materials[66] = "minecraft:rail"; + materials[67] = "minecraft:stone_stairs"; + materials[69] = "minecraft:lever"; + materials[70] = "minecraft:stone_pressure_plate"; + materials[72] = "minecraft:wooden_pressure_plate"; + materials[73] = "minecraft:redstone_ore"; + materials[76] = "minecraft:redstone_torch"; + materials[77] = "minecraft:stone_button"; + materials[78] = "minecraft:snow_layer"; + materials[79] = "minecraft:ice"; + materials[80] = "minecraft:snow"; + materials[81] = "minecraft:cactus"; + materials[82] = "minecraft:clay"; + materials[84] = "minecraft:jukebox"; + materials[85] = "minecraft:fence"; + materials[86] = "minecraft:pumpkin"; + materials[87] = "minecraft:netherrack"; + materials[88] = "minecraft:soul_sand"; + materials[89] = "minecraft:glowstone"; + materials[90] = "minecraft:portal"; + materials[91] = "minecraft:lit_pumpkin"; + materials[95] = "minecraft:stained_glass"; + materials[96] = "minecraft:trapdoor"; + materials[97] = "minecraft:monster_egg"; + materials[98] = "minecraft:stonebrick"; + materials[99] = "minecraft:brown_mushroom_block"; + materials[100] = "minecraft:red_mushroom_block"; + materials[101] = "minecraft:iron_bars"; + materials[102] = "minecraft:glass_pane"; + materials[103] = "minecraft:melon_block"; + materials[106] = "minecraft:vine"; + materials[107] = "minecraft:fence_gate"; + materials[108] = "minecraft:brick_stairs"; + materials[109] = "minecraft:stone_brick_stairs"; + materials[110] = "minecraft:mycelium"; + materials[111] = "minecraft:waterlily"; + materials[112] = "minecraft:nether_brick"; + materials[113] = "minecraft:nether_brick_fence"; + materials[114] = "minecraft:nether_brick_stairs"; + materials[116] = "minecraft:enchanting_table"; + materials[119] = "minecraft:end_portal"; + materials[120] = "minecraft:end_portal_frame"; + materials[121] = "minecraft:end_stone"; + materials[122] = "minecraft:dragon_egg"; + materials[123] = "minecraft:redstone_lamp"; + materials[125] = "minecraft:double_wooden_slab"; + materials[126] = "minecraft:wooden_slab"; + materials[127] = "minecraft:cocoa"; + materials[128] = "minecraft:sandstone_stairs"; + materials[129] = "minecraft:emerald_ore"; + materials[130] = "minecraft:ender_chest"; + materials[131] = "minecraft:tripwire_hook"; + materials[133] = "minecraft:emerald_block"; + materials[134] = "minecraft:spruce_stairs"; + materials[135] = "minecraft:birch_stairs"; + materials[136] = "minecraft:jungle_stairs"; + materials[137] = "minecraft:command_block"; + materials[138] = "minecraft:beacon"; + materials[139] = "minecraft:cobblestone_wall"; + materials[141] = "minecraft:carrots"; + materials[142] = "minecraft:potatoes"; + materials[143] = "minecraft:wooden_button"; + materials[145] = "minecraft:anvil"; + materials[146] = "minecraft:trapped_chest"; + materials[147] = "minecraft:light_weighted_pressure_plate"; + materials[148] = "minecraft:heavy_weighted_pressure_plate"; + materials[151] = "minecraft:daylight_detector"; + materials[152] = "minecraft:redstone_block"; + materials[153] = "minecraft:quartz_ore"; + materials[154] = "minecraft:hopper"; + materials[155] = "minecraft:quartz_block"; + materials[156] = "minecraft:quartz_stairs"; + materials[157] = "minecraft:activator_rail"; + materials[158] = "minecraft:dropper"; + materials[159] = "minecraft:stained_hardened_clay"; + materials[160] = "minecraft:stained_glass_pane"; + materials[161] = "minecraft:leaves2"; + materials[162] = "minecraft:log2"; + materials[163] = "minecraft:acacia_stairs"; + materials[164] = "minecraft:dark_oak_stairs"; + materials[170] = "minecraft:hay_block"; + materials[171] = "minecraft:carpet"; + materials[172] = "minecraft:hardened_clay"; + materials[173] = "minecraft:coal_block"; + materials[174] = "minecraft:packed_ice"; + materials[175] = "minecraft:double_plant"; + materials[256] = "minecraft:iron_shovel"; + materials[257] = "minecraft:iron_pickaxe"; + materials[258] = "minecraft:iron_axe"; + materials[259] = "minecraft:flint_and_steel"; + materials[260] = "minecraft:apple"; + materials[261] = "minecraft:bow"; + materials[262] = "minecraft:arrow"; + materials[263] = "minecraft:coal"; + materials[264] = "minecraft:diamond"; + materials[265] = "minecraft:iron_ingot"; + materials[266] = "minecraft:gold_ingot"; + materials[267] = "minecraft:iron_sword"; + materials[268] = "minecraft:wooden_sword"; + materials[269] = "minecraft:wooden_shovel"; + materials[270] = "minecraft:wooden_pickaxe"; + materials[271] = "minecraft:wooden_axe"; + materials[272] = "minecraft:stone_sword"; + materials[273] = "minecraft:stone_shovel"; + materials[274] = "minecraft:stone_pickaxe"; + materials[275] = "minecraft:stone_axe"; + materials[276] = "minecraft:diamond_sword"; + materials[277] = "minecraft:diamond_shovel"; + materials[278] = "minecraft:diamond_pickaxe"; + materials[279] = "minecraft:diamond_axe"; + materials[280] = "minecraft:stick"; + materials[281] = "minecraft:bowl"; + materials[282] = "minecraft:mushroom_stew"; + materials[283] = "minecraft:golden_sword"; + materials[284] = "minecraft:golden_shovel"; + materials[285] = "minecraft:golden_pickaxe"; + materials[286] = "minecraft:golden_axe"; + materials[287] = "minecraft:string"; + materials[288] = "minecraft:feather"; + materials[289] = "minecraft:gunpowder"; + materials[290] = "minecraft:wooden_hoe"; + materials[291] = "minecraft:stone_hoe"; + materials[292] = "minecraft:iron_hoe"; + materials[293] = "minecraft:diamond_hoe"; + materials[294] = "minecraft:golden_hoe"; + materials[295] = "minecraft:wheat_seeds"; + materials[296] = "minecraft:wheat"; + materials[297] = "minecraft:bread"; + materials[298] = "minecraft:leather_helmet"; + materials[299] = "minecraft:leather_chestplate"; + materials[300] = "minecraft:leather_leggings"; + materials[301] = "minecraft:leather_boots"; + materials[302] = "minecraft:chainmail_helmet"; + materials[303] = "minecraft:chainmail_chestplate"; + materials[304] = "minecraft:chainmail_leggings"; + materials[305] = "minecraft:chainmail_boots"; + materials[306] = "minecraft:iron_helmet"; + materials[307] = "minecraft:iron_chestplate"; + materials[308] = "minecraft:iron_leggings"; + materials[309] = "minecraft:iron_boots"; + materials[310] = "minecraft:diamond_helmet"; + materials[311] = "minecraft:diamond_chestplate"; + materials[312] = "minecraft:diamond_leggings"; + materials[313] = "minecraft:diamond_boots"; + materials[314] = "minecraft:golden_helmet"; + materials[315] = "minecraft:golden_chestplate"; + materials[316] = "minecraft:golden_leggings"; + materials[317] = "minecraft:golden_boots"; + materials[318] = "minecraft:flint"; + materials[319] = "minecraft:porkchop"; + materials[320] = "minecraft:cooked_porkchop"; + materials[321] = "minecraft:painting"; + materials[322] = "minecraft:golden_apple"; + materials[323] = "minecraft:sign"; + materials[324] = "minecraft:wooden_door"; + materials[325] = "minecraft:bucket"; + materials[326] = "minecraft:water_bucket"; + materials[327] = "minecraft:lava_bucket"; + materials[328] = "minecraft:minecart"; + materials[329] = "minecraft:saddle"; + materials[330] = "minecraft:iron_door"; + materials[331] = "minecraft:redstone"; + materials[332] = "minecraft:snowball"; + materials[333] = "minecraft:boat"; + materials[334] = "minecraft:leather"; + materials[335] = "minecraft:milk_bucket"; + materials[336] = "minecraft:brick"; + materials[337] = "minecraft:clay_ball"; + materials[338] = "minecraft:reeds"; + materials[339] = "minecraft:paper"; + materials[340] = "minecraft:book"; + materials[341] = "minecraft:slime_ball"; + materials[342] = "minecraft:chest_minecart"; + materials[343] = "minecraft:furnace_minecart"; + materials[344] = "minecraft:egg"; + materials[345] = "minecraft:compass"; + materials[346] = "minecraft:fishing_rod"; + materials[347] = "minecraft:clock"; + materials[348] = "minecraft:glowstone_dust"; + materials[349] = "minecraft:fish"; + materials[350] = "minecraft:cooked_fish"; // Paper - cooked_fished -> cooked_fish + materials[351] = "minecraft:dye"; + materials[352] = "minecraft:bone"; + materials[353] = "minecraft:sugar"; + materials[354] = "minecraft:cake"; + materials[355] = "minecraft:bed"; + materials[356] = "minecraft:repeater"; + materials[357] = "minecraft:cookie"; + materials[358] = "minecraft:filled_map"; + materials[359] = "minecraft:shears"; + materials[360] = "minecraft:melon"; + materials[361] = "minecraft:pumpkin_seeds"; + materials[362] = "minecraft:melon_seeds"; + materials[363] = "minecraft:beef"; + materials[364] = "minecraft:cooked_beef"; + materials[365] = "minecraft:chicken"; + materials[366] = "minecraft:cooked_chicken"; + materials[367] = "minecraft:rotten_flesh"; + materials[368] = "minecraft:ender_pearl"; + materials[369] = "minecraft:blaze_rod"; + materials[370] = "minecraft:ghast_tear"; + materials[371] = "minecraft:gold_nugget"; + materials[372] = "minecraft:nether_wart"; + materials[373] = "minecraft:potion"; + materials[374] = "minecraft:glass_bottle"; + materials[375] = "minecraft:spider_eye"; + materials[376] = "minecraft:fermented_spider_eye"; + materials[377] = "minecraft:blaze_powder"; + materials[378] = "minecraft:magma_cream"; + materials[379] = "minecraft:brewing_stand"; + materials[380] = "minecraft:cauldron"; + materials[381] = "minecraft:ender_eye"; + materials[382] = "minecraft:speckled_melon"; + materials[383] = "minecraft:spawn_egg"; + materials[384] = "minecraft:experience_bottle"; + materials[385] = "minecraft:fire_charge"; + materials[386] = "minecraft:writable_book"; + materials[387] = "minecraft:written_book"; + materials[388] = "minecraft:emerald"; + materials[389] = "minecraft:item_frame"; + materials[390] = "minecraft:flower_pot"; + materials[391] = "minecraft:carrot"; + materials[392] = "minecraft:potato"; + materials[393] = "minecraft:baked_potato"; + materials[394] = "minecraft:poisonous_potato"; + materials[395] = "minecraft:map"; + materials[396] = "minecraft:golden_carrot"; + materials[397] = "minecraft:skull"; + materials[398] = "minecraft:carrot_on_a_stick"; + materials[399] = "minecraft:nether_star"; + materials[400] = "minecraft:pumpkin_pie"; + materials[401] = "minecraft:fireworks"; + materials[402] = "minecraft:firework_charge"; + materials[403] = "minecraft:enchanted_book"; + materials[404] = "minecraft:comparator"; + materials[405] = "minecraft:netherbrick"; + materials[406] = "minecraft:quartz"; + materials[407] = "minecraft:tnt_minecart"; + materials[408] = "minecraft:hopper_minecart"; + materials[417] = "minecraft:iron_horse_armor"; + materials[418] = "minecraft:golden_horse_armor"; + materials[419] = "minecraft:diamond_horse_armor"; + materials[420] = "minecraft:lead"; + materials[421] = "minecraft:name_tag"; + materials[422] = "minecraft:command_block_minecart"; + materials[2256] = "minecraft:record_13"; + materials[2257] = "minecraft:record_cat"; + materials[2258] = "minecraft:record_blocks"; + materials[2259] = "minecraft:record_chirp"; + materials[2260] = "minecraft:record_far"; + materials[2261] = "minecraft:record_mall"; + materials[2262] = "minecraft:record_mellohi"; + materials[2263] = "minecraft:record_stal"; + materials[2264] = "minecraft:record_strad"; + materials[2265] = "minecraft:record_ward"; + materials[2266] = "minecraft:record_11"; + materials[2267] = "minecraft:record_wait"; + // Paper start + materials[409] = "minecraft:prismarine_shard"; + materials[410] = "minecraft:prismarine_crystals"; + materials[411] = "minecraft:rabbit"; + materials[412] = "minecraft:cooked_rabbit"; + materials[413] = "minecraft:rabbit_stew"; + materials[414] = "minecraft:rabbit_foot"; + materials[415] = "minecraft:rabbit_hide"; + materials[416] = "minecraft:armor_stand"; + materials[423] = "minecraft:mutton"; + materials[424] = "minecraft:cooked_mutton"; + materials[425] = "minecraft:banner"; + materials[426] = "minecraft:end_crystal"; + materials[427] = "minecraft:spruce_door"; + materials[428] = "minecraft:birch_door"; + materials[429] = "minecraft:jungle_door"; + materials[430] = "minecraft:acacia_door"; + materials[431] = "minecraft:dark_oak_door"; + materials[432] = "minecraft:chorus_fruit"; + materials[433] = "minecraft:chorus_fruit_popped"; + materials[434] = "minecraft:beetroot"; + materials[435] = "minecraft:beetroot_seeds"; + materials[436] = "minecraft:beetroot_soup"; + materials[437] = "minecraft:dragon_breath"; + materials[438] = "minecraft:splash_potion"; + materials[439] = "minecraft:spectral_arrow"; + materials[440] = "minecraft:tipped_arrow"; + materials[441] = "minecraft:lingering_potion"; + materials[442] = "minecraft:shield"; + materials[443] = "minecraft:elytra"; + materials[444] = "minecraft:spruce_boat"; + materials[445] = "minecraft:birch_boat"; + materials[446] = "minecraft:jungle_boat"; + materials[447] = "minecraft:acacia_boat"; + materials[448] = "minecraft:dark_oak_boat"; + materials[449] = "minecraft:totem_of_undying"; + materials[450] = "minecraft:shulker_shell"; + materials[452] = "minecraft:iron_nugget"; + materials[453] = "minecraft:knowledge_book"; + // Paper end + } + } + + private static class DataConverterArmorStand implements DataConverter { + + DataConverterArmorStand() { + } + + public int getDataVersion() { + return 147; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("ArmorStand".equals(cmp.getString("id")) && cmp.getBoolean("Silent") && !cmp.getBoolean("Marker")) { + cmp.remove("Silent"); + } + + return cmp; + } + } + + private static class DataConverterBanner implements DataConverter { + + DataConverterBanner() { + } + + public int getDataVersion() { + return 804; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:banner".equals(cmp.getString("id")) && cmp.contains("tag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + + if (nbttagcompound2.contains("Base", 99)) { + cmp.putShort("Damage", (short) (nbttagcompound2.getShort("Base") & 15)); + if (nbttagcompound1.contains("display", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound1.getCompound("display"); + + if (nbttagcompound3.contains("Lore", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound3.getList("Lore", 8); + + if (nbttaglist.size() == 1 && "(+NBT)".equals(nbttaglist.getString(0))) { + return cmp; + } + } + } + + nbttagcompound2.remove("Base"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + } + } + } + + return cmp; + } + } + + private static class DataConverterPotionId implements DataConverter { + + private static final String[] potions = new String[128]; + + DataConverterPotionId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:potion".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + short short0 = cmp.getShort("Damage"); + + if (!nbttagcompound1.contains("Potion", 8)) { + String s = DataConverterPotionId.potions[short0 & 127]; + + nbttagcompound1.putString("Potion", s == null ? "minecraft:water" : s); + cmp.put("tag", nbttagcompound1); + if ((short0 & 16384) == 16384) { + cmp.putString("id", "minecraft:splash_potion"); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + DataConverterPotionId.potions[0] = "minecraft:water"; + DataConverterPotionId.potions[1] = "minecraft:regeneration"; + DataConverterPotionId.potions[2] = "minecraft:swiftness"; + DataConverterPotionId.potions[3] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[4] = "minecraft:poison"; + DataConverterPotionId.potions[5] = "minecraft:healing"; + DataConverterPotionId.potions[6] = "minecraft:night_vision"; + DataConverterPotionId.potions[7] = null; + DataConverterPotionId.potions[8] = "minecraft:weakness"; + DataConverterPotionId.potions[9] = "minecraft:strength"; + DataConverterPotionId.potions[10] = "minecraft:slowness"; + DataConverterPotionId.potions[11] = "minecraft:leaping"; + DataConverterPotionId.potions[12] = "minecraft:harming"; + DataConverterPotionId.potions[13] = "minecraft:water_breathing"; + DataConverterPotionId.potions[14] = "minecraft:invisibility"; + DataConverterPotionId.potions[15] = null; + DataConverterPotionId.potions[16] = "minecraft:awkward"; + DataConverterPotionId.potions[17] = "minecraft:regeneration"; + DataConverterPotionId.potions[18] = "minecraft:swiftness"; + DataConverterPotionId.potions[19] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[20] = "minecraft:poison"; + DataConverterPotionId.potions[21] = "minecraft:healing"; + DataConverterPotionId.potions[22] = "minecraft:night_vision"; + DataConverterPotionId.potions[23] = null; + DataConverterPotionId.potions[24] = "minecraft:weakness"; + DataConverterPotionId.potions[25] = "minecraft:strength"; + DataConverterPotionId.potions[26] = "minecraft:slowness"; + DataConverterPotionId.potions[27] = "minecraft:leaping"; + DataConverterPotionId.potions[28] = "minecraft:harming"; + DataConverterPotionId.potions[29] = "minecraft:water_breathing"; + DataConverterPotionId.potions[30] = "minecraft:invisibility"; + DataConverterPotionId.potions[31] = null; + DataConverterPotionId.potions[32] = "minecraft:thick"; + DataConverterPotionId.potions[33] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[34] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[35] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[36] = "minecraft:strong_poison"; + DataConverterPotionId.potions[37] = "minecraft:strong_healing"; + DataConverterPotionId.potions[38] = "minecraft:night_vision"; + DataConverterPotionId.potions[39] = null; + DataConverterPotionId.potions[40] = "minecraft:weakness"; + DataConverterPotionId.potions[41] = "minecraft:strong_strength"; + DataConverterPotionId.potions[42] = "minecraft:slowness"; + DataConverterPotionId.potions[43] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[44] = "minecraft:strong_harming"; + DataConverterPotionId.potions[45] = "minecraft:water_breathing"; + DataConverterPotionId.potions[46] = "minecraft:invisibility"; + DataConverterPotionId.potions[47] = null; + DataConverterPotionId.potions[48] = null; + DataConverterPotionId.potions[49] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[50] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[51] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[52] = "minecraft:strong_poison"; + DataConverterPotionId.potions[53] = "minecraft:strong_healing"; + DataConverterPotionId.potions[54] = "minecraft:night_vision"; + DataConverterPotionId.potions[55] = null; + DataConverterPotionId.potions[56] = "minecraft:weakness"; + DataConverterPotionId.potions[57] = "minecraft:strong_strength"; + DataConverterPotionId.potions[58] = "minecraft:slowness"; + DataConverterPotionId.potions[59] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[60] = "minecraft:strong_harming"; + DataConverterPotionId.potions[61] = "minecraft:water_breathing"; + DataConverterPotionId.potions[62] = "minecraft:invisibility"; + DataConverterPotionId.potions[63] = null; + DataConverterPotionId.potions[64] = "minecraft:mundane"; + DataConverterPotionId.potions[65] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[66] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[67] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[68] = "minecraft:long_poison"; + DataConverterPotionId.potions[69] = "minecraft:healing"; + DataConverterPotionId.potions[70] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[71] = null; + DataConverterPotionId.potions[72] = "minecraft:long_weakness"; + DataConverterPotionId.potions[73] = "minecraft:long_strength"; + DataConverterPotionId.potions[74] = "minecraft:long_slowness"; + DataConverterPotionId.potions[75] = "minecraft:long_leaping"; + DataConverterPotionId.potions[76] = "minecraft:harming"; + DataConverterPotionId.potions[77] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[78] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[79] = null; + DataConverterPotionId.potions[80] = "minecraft:awkward"; + DataConverterPotionId.potions[81] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[82] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[83] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[84] = "minecraft:long_poison"; + DataConverterPotionId.potions[85] = "minecraft:healing"; + DataConverterPotionId.potions[86] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[87] = null; + DataConverterPotionId.potions[88] = "minecraft:long_weakness"; + DataConverterPotionId.potions[89] = "minecraft:long_strength"; + DataConverterPotionId.potions[90] = "minecraft:long_slowness"; + DataConverterPotionId.potions[91] = "minecraft:long_leaping"; + DataConverterPotionId.potions[92] = "minecraft:harming"; + DataConverterPotionId.potions[93] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[94] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[95] = null; + DataConverterPotionId.potions[96] = "minecraft:thick"; + DataConverterPotionId.potions[97] = "minecraft:regeneration"; + DataConverterPotionId.potions[98] = "minecraft:swiftness"; + DataConverterPotionId.potions[99] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[100] = "minecraft:poison"; + DataConverterPotionId.potions[101] = "minecraft:strong_healing"; + DataConverterPotionId.potions[102] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[103] = null; + DataConverterPotionId.potions[104] = "minecraft:long_weakness"; + DataConverterPotionId.potions[105] = "minecraft:strength"; + DataConverterPotionId.potions[106] = "minecraft:long_slowness"; + DataConverterPotionId.potions[107] = "minecraft:leaping"; + DataConverterPotionId.potions[108] = "minecraft:strong_harming"; + DataConverterPotionId.potions[109] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[110] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[111] = null; + DataConverterPotionId.potions[112] = null; + DataConverterPotionId.potions[113] = "minecraft:regeneration"; + DataConverterPotionId.potions[114] = "minecraft:swiftness"; + DataConverterPotionId.potions[115] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[116] = "minecraft:poison"; + DataConverterPotionId.potions[117] = "minecraft:strong_healing"; + DataConverterPotionId.potions[118] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[119] = null; + DataConverterPotionId.potions[120] = "minecraft:long_weakness"; + DataConverterPotionId.potions[121] = "minecraft:strength"; + DataConverterPotionId.potions[122] = "minecraft:long_slowness"; + DataConverterPotionId.potions[123] = "minecraft:leaping"; + DataConverterPotionId.potions[124] = "minecraft:strong_harming"; + DataConverterPotionId.potions[125] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[126] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[127] = null; + } + } + + private static class DataConverterSpawnEgg implements DataConverter { + + private static final String[] eggs = new String[256]; + + DataConverterSpawnEgg() { + } + + public int getDataVersion() { + return 105; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:spawn_egg".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag"); + short short0 = cmp.getShort("Damage"); + + if (!nbttagcompound2.contains("id", 8)) { + String s = DataConverterSpawnEgg.eggs[short0 & 255]; + + if (s != null) { + nbttagcompound2.putString("id", s); + nbttagcompound1.put("EntityTag", nbttagcompound2); + cmp.put("tag", nbttagcompound1); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + + DataConverterSpawnEgg.eggs[1] = "Item"; + DataConverterSpawnEgg.eggs[2] = "XPOrb"; + DataConverterSpawnEgg.eggs[7] = "ThrownEgg"; + DataConverterSpawnEgg.eggs[8] = "LeashKnot"; + DataConverterSpawnEgg.eggs[9] = "Painting"; + DataConverterSpawnEgg.eggs[10] = "Arrow"; + DataConverterSpawnEgg.eggs[11] = "Snowball"; + DataConverterSpawnEgg.eggs[12] = "Fireball"; + DataConverterSpawnEgg.eggs[13] = "SmallFireball"; + DataConverterSpawnEgg.eggs[14] = "ThrownEnderpearl"; + DataConverterSpawnEgg.eggs[15] = "EyeOfEnderSignal"; + DataConverterSpawnEgg.eggs[16] = "ThrownPotion"; + DataConverterSpawnEgg.eggs[17] = "ThrownExpBottle"; + DataConverterSpawnEgg.eggs[18] = "ItemFrame"; + DataConverterSpawnEgg.eggs[19] = "WitherSkull"; + DataConverterSpawnEgg.eggs[20] = "PrimedTnt"; + DataConverterSpawnEgg.eggs[21] = "FallingSand"; + DataConverterSpawnEgg.eggs[22] = "FireworksRocketEntity"; + DataConverterSpawnEgg.eggs[23] = "TippedArrow"; + DataConverterSpawnEgg.eggs[24] = "SpectralArrow"; + DataConverterSpawnEgg.eggs[25] = "ShulkerBullet"; + DataConverterSpawnEgg.eggs[26] = "DragonFireball"; + DataConverterSpawnEgg.eggs[30] = "ArmorStand"; + DataConverterSpawnEgg.eggs[41] = "Boat"; + DataConverterSpawnEgg.eggs[42] = "MinecartRideable"; + DataConverterSpawnEgg.eggs[43] = "MinecartChest"; + DataConverterSpawnEgg.eggs[44] = "MinecartFurnace"; + DataConverterSpawnEgg.eggs[45] = "MinecartTNT"; + DataConverterSpawnEgg.eggs[46] = "MinecartHopper"; + DataConverterSpawnEgg.eggs[47] = "MinecartSpawner"; + DataConverterSpawnEgg.eggs[40] = "MinecartCommandBlock"; + DataConverterSpawnEgg.eggs[48] = "Mob"; + DataConverterSpawnEgg.eggs[49] = "Monster"; + DataConverterSpawnEgg.eggs[50] = "Creeper"; + DataConverterSpawnEgg.eggs[51] = "Skeleton"; + DataConverterSpawnEgg.eggs[52] = "Spider"; + DataConverterSpawnEgg.eggs[53] = "Giant"; + DataConverterSpawnEgg.eggs[54] = "Zombie"; + DataConverterSpawnEgg.eggs[55] = "Slime"; + DataConverterSpawnEgg.eggs[56] = "Ghast"; + DataConverterSpawnEgg.eggs[57] = "PigZombie"; + DataConverterSpawnEgg.eggs[58] = "Enderman"; + DataConverterSpawnEgg.eggs[59] = "CaveSpider"; + DataConverterSpawnEgg.eggs[60] = "Silverfish"; + DataConverterSpawnEgg.eggs[61] = "Blaze"; + DataConverterSpawnEgg.eggs[62] = "LavaSlime"; + DataConverterSpawnEgg.eggs[63] = "EnderDragon"; + DataConverterSpawnEgg.eggs[64] = "WitherBoss"; + DataConverterSpawnEgg.eggs[65] = "Bat"; + DataConverterSpawnEgg.eggs[66] = "Witch"; + DataConverterSpawnEgg.eggs[67] = "Endermite"; + DataConverterSpawnEgg.eggs[68] = "Guardian"; + DataConverterSpawnEgg.eggs[69] = "Shulker"; + DataConverterSpawnEgg.eggs[90] = "Pig"; + DataConverterSpawnEgg.eggs[91] = "Sheep"; + DataConverterSpawnEgg.eggs[92] = "Cow"; + DataConverterSpawnEgg.eggs[93] = "Chicken"; + DataConverterSpawnEgg.eggs[94] = "Squid"; + DataConverterSpawnEgg.eggs[95] = "Wolf"; + DataConverterSpawnEgg.eggs[96] = "MushroomCow"; + DataConverterSpawnEgg.eggs[97] = "SnowMan"; + DataConverterSpawnEgg.eggs[98] = "Ozelot"; + DataConverterSpawnEgg.eggs[99] = "VillagerGolem"; + DataConverterSpawnEgg.eggs[100] = "EntityHorse"; + DataConverterSpawnEgg.eggs[101] = "Rabbit"; + DataConverterSpawnEgg.eggs[120] = "Villager"; + DataConverterSpawnEgg.eggs[200] = "EnderCrystal"; + } + } + + private static class DataConverterMinecart implements DataConverter { + + private static final List a = Lists.newArrayList("MinecartRideable", "MinecartChest", "MinecartFurnace", "MinecartTNT", "MinecartSpawner", "MinecartHopper", "MinecartCommandBlock"); + + DataConverterMinecart() { + } + + public int getDataVersion() { + return 106; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Minecart".equals(cmp.getString("id"))) { + String s = "MinecartRideable"; + int i = cmp.getInt("Type"); + + if (i > 0 && i < DataConverterMinecart.a.size()) { + s = DataConverterMinecart.a.get(i); + } + + cmp.putString("id", s); + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterMobSpawner implements DataConverter { + + DataConverterMobSpawner() { + } + + public int getDataVersion() { + return 107; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (!"MobSpawner".equals(cmp.getString("id"))) { + return cmp; + } else { + if (cmp.contains("EntityId", 8)) { + String s = cmp.getString("EntityId"); + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("SpawnData"); + + nbttagcompound1.putString("id", s.isEmpty() ? "Pig" : s); + cmp.put("SpawnData", nbttagcompound1); + cmp.remove("EntityId"); + } + + if (cmp.contains("SpawnPotentials", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("SpawnPotentials", 10); + + for (int i = 0; i < nbttaglist.size(); ++i) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompound(i); + + if (nbttagcompound2.contains("Type", 8)) { + net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound2.getCompound("Properties"); + + nbttagcompound3.putString("id", nbttagcompound2.getString("Type")); + nbttagcompound2.put("Entity", nbttagcompound3); + nbttagcompound2.remove("Type"); + nbttagcompound2.remove("Properties"); + } + } + } + + return cmp; + } + } + } + + private static class DataConverterUUID implements DataConverter { + + DataConverterUUID() { + } + + public int getDataVersion() { + return 108; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("UUID", 8)) { + cmp.putUUID("UUID", UUID.fromString(cmp.getString("UUID"))); + } + + return cmp; + } + } + + private static class DataConverterHealth implements DataConverter { + + private static final Set a = Sets.newHashSet("ArmorStand", "Bat", "Blaze", "CaveSpider", "Chicken", "Cow", "Creeper", "EnderDragon", "Enderman", "Endermite", "EntityHorse", "Ghast", "Giant", "Guardian", "LavaSlime", "MushroomCow", "Ozelot", "Pig", "PigZombie", "Rabbit", "Sheep", "Shulker", "Silverfish", "Skeleton", "Slime", "SnowMan", "Spider", "Squid", "Villager", "VillagerGolem", "Witch", "WitherBoss", "Wolf", "Zombie"); + + DataConverterHealth() { + } + + public int getDataVersion() { + return 109; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (DataConverterHealth.a.contains(cmp.getString("id"))) { + float f; + + if (cmp.contains("HealF", 99)) { + f = cmp.getFloat("HealF"); + cmp.remove("HealF"); + } else { + if (!cmp.contains("Health", 99)) { + return cmp; + } + + f = cmp.getFloat("Health"); + } + + cmp.putFloat("Health", f); + } + + return cmp; + } + } + + private static class DataConverterSaddle implements DataConverter { + + DataConverterSaddle() { + } + + public int getDataVersion() { + return 110; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id")) && !cmp.contains("SaddleItem", 10) && cmp.getBoolean("Saddle")) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound1.putString("id", "minecraft:saddle"); + nbttagcompound1.putByte("Count", (byte) 1); + nbttagcompound1.putShort("Damage", (short) 0); + cmp.put("SaddleItem", nbttagcompound1); + cmp.remove("Saddle"); + } + + return cmp; + } + } + + private static class DataConverterHanging implements DataConverter { + + DataConverterHanging() { + } + + public int getDataVersion() { + return 111; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + boolean flag = "Painting".equals(s); + boolean flag1 = "ItemFrame".equals(s); + + if ((flag || flag1) && !cmp.contains("Facing", 99)) { + Direction enumdirection; + + if (cmp.contains("Direction", 99)) { + enumdirection = Direction.from2DDataValue(cmp.getByte("Direction")); + cmp.putInt("TileX", cmp.getInt("TileX") + enumdirection.getStepX()); + cmp.putInt("TileY", cmp.getInt("TileY") + enumdirection.getStepY()); + cmp.putInt("TileZ", cmp.getInt("TileZ") + enumdirection.getStepZ()); + cmp.remove("Direction"); + if (flag1 && cmp.contains("ItemRotation", 99)) { + cmp.putByte("ItemRotation", (byte) (cmp.getByte("ItemRotation") * 2)); + } + } else { + enumdirection = Direction.from2DDataValue(cmp.getByte("Dir")); + cmp.remove("Dir"); + } + + cmp.putByte("Facing", (byte) enumdirection.get2DDataValue()); + } + + return cmp; + } + } + + private static class DataConverterDropChances implements DataConverter { + + DataConverterDropChances() { + } + + public int getDataVersion() { + return 113; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + net.minecraft.nbt.ListTag nbttaglist; + + if (cmp.contains("HandDropChances", 9)) { + nbttaglist = cmp.getList("HandDropChances", 5); + if (nbttaglist.size() == 2 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F) { + cmp.remove("HandDropChances"); + } + } + + if (cmp.contains("ArmorDropChances", 9)) { + nbttaglist = cmp.getList("ArmorDropChances", 5); + if (nbttaglist.size() == 4 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F && nbttaglist.getFloat(2) == 0.0F && nbttaglist.getFloat(3) == 0.0F) { + cmp.remove("ArmorDropChances"); + } + } + + return cmp; + } + } + + private static class DataConverterRiding implements DataConverter { + + DataConverterRiding() { + } + + public int getDataVersion() { + return 135; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + while (cmp.contains("Riding", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = this.b(cmp); + + this.convert(cmp, nbttagcompound1); + cmp = nbttagcompound1; + } + + return cmp; + } + + protected void convert(net.minecraft.nbt.CompoundTag nbttagcompound, net.minecraft.nbt.CompoundTag nbttagcompound1) { + net.minecraft.nbt.ListTag nbttaglist = new net.minecraft.nbt.ListTag(); + + nbttaglist.add(nbttagcompound); + nbttagcompound1.put("Passengers", nbttaglist); + } + + protected net.minecraft.nbt.CompoundTag b(net.minecraft.nbt.CompoundTag nbttagcompound) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Riding"); + + nbttagcompound.remove("Riding"); + return nbttagcompound1; + } + } + + private static class DataConverterBook implements DataConverter { + + DataConverterBook() { + } + + public int getDataVersion() { + return 165; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:written_book".equals(cmp.getString("id"))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("pages", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("pages", 8); + + for (int i = 0; i < nbttaglist.size(); ++i) { + String s = nbttaglist.getString(i); + Component object = null; + + if (!"null".equals(s) && !StringUtil.isNullOrEmpty(s)) { + if ((s.charAt(0) != 34 || s.charAt(s.length() - 1) != 34) && (s.charAt(0) != 123 || s.charAt(s.length() - 1) != 125)) { + object = new TextComponent(s); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s, Component.class, true); + if (object == null) { + object = new TextComponent(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = Component.Serializer.fromJson(s); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = Component.Serializer.fromJsonLenient(s); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = new TextComponent(s); + } + } + } else { + object = new TextComponent(""); + } + + nbttaglist.set(i, net.minecraft.nbt.StringTag.valueOf(Component.Serializer.toJson(object))); + } + + nbttagcompound1.put("pages", nbttaglist); + } + } + + return cmp; + } + } + + private static class DataConverterCookedFish implements DataConverter { + + private static final ResourceLocation a = new ResourceLocation("cooked_fished"); + + DataConverterCookedFish() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("id", 8) && DataConverterCookedFish.a.equals(new ResourceLocation(cmp.getString("id")))) { + cmp.putString("id", "minecraft:cooked_fish"); + } + + return cmp; + } + } + + private static class DataConverterZombie implements DataConverter { + + private static final Random a = new Random(); + + DataConverterZombie() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id")) && cmp.getBoolean("IsVillager")) { + if (!cmp.contains("ZombieType", 99)) { + int i = -1; + + if (cmp.contains("VillagerProfession", 99)) { + try { + i = this.convert(cmp.getInt("VillagerProfession")); + } catch (RuntimeException runtimeexception) { + ; + } + } + + if (i == -1) { + i = this.convert(DataConverterZombie.a.nextInt(6)); + } + + cmp.putInt("ZombieType", i); + } + + cmp.remove("IsVillager"); + } + + return cmp; + } + + private int convert(int i) { + return i >= 0 && i < 6 ? i : -1; + } + } + + private static class DataConverterVBO implements DataConverter { + + DataConverterVBO() { + } + + public int getDataVersion() { + return 505; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.putString("useVbo", "true"); + return cmp; + } + } + + private static class DataConverterGuardian implements DataConverter { + + DataConverterGuardian() { + } + + public int getDataVersion() { + return 700; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Guardian".equals(cmp.getString("id"))) { + if (cmp.getBoolean("Elder")) { + cmp.putString("id", "ElderGuardian"); + } + + cmp.remove("Elder"); + } + + return cmp; + } + } + + private static class DataConverterSkeleton implements DataConverter { + + DataConverterSkeleton() { + } + + public int getDataVersion() { + return 701; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + + if ("Skeleton".equals(s)) { + int i = cmp.getInt("SkeletonType"); + + if (i == 1) { + cmp.putString("id", "WitherSkeleton"); + } else if (i == 2) { + cmp.putString("id", "Stray"); + } + + cmp.remove("SkeletonType"); + } + + return cmp; + } + } + + private static class DataConverterZombieType implements DataConverter { + + DataConverterZombieType() { + } + + public int getDataVersion() { + return 702; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id"))) { + int i = cmp.getInt("ZombieType"); + + switch (i) { + case 0: + default: + break; + + case 1: + case 2: + case 3: + case 4: + case 5: + cmp.putString("id", "ZombieVillager"); + cmp.putInt("Profession", i - 1); + break; + + case 6: + cmp.putString("id", "Husk"); + } + + cmp.remove("ZombieType"); + } + + return cmp; + } + } + + private static class DataConverterHorse implements DataConverter { + + DataConverterHorse() { + } + + public int getDataVersion() { + return 703; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id"))) { + int i = cmp.getInt("Type"); + + switch (i) { + case 0: + default: + cmp.putString("id", "Horse"); + break; + + case 1: + cmp.putString("id", "Donkey"); + break; + + case 2: + cmp.putString("id", "Mule"); + break; + + case 3: + cmp.putString("id", "ZombieHorse"); + break; + + case 4: + cmp.putString("id", "SkeletonHorse"); + } + + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterTileEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterTileEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterTileEntity.a.get(cmp.getString("id")); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterTileEntity.a.put("Airportal", "minecraft:end_portal"); + DataConverterTileEntity.a.put("Banner", "minecraft:banner"); + DataConverterTileEntity.a.put("Beacon", "minecraft:beacon"); + DataConverterTileEntity.a.put("Cauldron", "minecraft:brewing_stand"); + DataConverterTileEntity.a.put("Chest", "minecraft:chest"); + DataConverterTileEntity.a.put("Comparator", "minecraft:comparator"); + DataConverterTileEntity.a.put("Control", "minecraft:command_block"); + DataConverterTileEntity.a.put("DLDetector", "minecraft:daylight_detector"); + DataConverterTileEntity.a.put("Dropper", "minecraft:dropper"); + DataConverterTileEntity.a.put("EnchantTable", "minecraft:enchanting_table"); + DataConverterTileEntity.a.put("EndGateway", "minecraft:end_gateway"); + DataConverterTileEntity.a.put("EnderChest", "minecraft:ender_chest"); + DataConverterTileEntity.a.put("FlowerPot", "minecraft:flower_pot"); + DataConverterTileEntity.a.put("Furnace", "minecraft:furnace"); + DataConverterTileEntity.a.put("Hopper", "minecraft:hopper"); + DataConverterTileEntity.a.put("MobSpawner", "minecraft:mob_spawner"); + DataConverterTileEntity.a.put("Music", "minecraft:noteblock"); + DataConverterTileEntity.a.put("Piston", "minecraft:piston"); + DataConverterTileEntity.a.put("RecordPlayer", "minecraft:jukebox"); + DataConverterTileEntity.a.put("Sign", "minecraft:sign"); + DataConverterTileEntity.a.put("Skull", "minecraft:skull"); + DataConverterTileEntity.a.put("Structure", "minecraft:structure_block"); + DataConverterTileEntity.a.put("Trap", "minecraft:dispenser"); + } + } + + private static class DataConverterEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterEntity.a.get(cmp.getString("id")); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterEntity.a.put("AreaEffectCloud", "minecraft:area_effect_cloud"); + DataConverterEntity.a.put("ArmorStand", "minecraft:armor_stand"); + DataConverterEntity.a.put("Arrow", "minecraft:arrow"); + DataConverterEntity.a.put("Bat", "minecraft:bat"); + DataConverterEntity.a.put("Blaze", "minecraft:blaze"); + DataConverterEntity.a.put("Boat", "minecraft:boat"); + DataConverterEntity.a.put("CaveSpider", "minecraft:cave_spider"); + DataConverterEntity.a.put("Chicken", "minecraft:chicken"); + DataConverterEntity.a.put("Cow", "minecraft:cow"); + DataConverterEntity.a.put("Creeper", "minecraft:creeper"); + DataConverterEntity.a.put("Donkey", "minecraft:donkey"); + DataConverterEntity.a.put("DragonFireball", "minecraft:dragon_fireball"); + DataConverterEntity.a.put("ElderGuardian", "minecraft:elder_guardian"); + DataConverterEntity.a.put("EnderCrystal", "minecraft:ender_crystal"); + DataConverterEntity.a.put("EnderDragon", "minecraft:ender_dragon"); + DataConverterEntity.a.put("Enderman", "minecraft:enderman"); + DataConverterEntity.a.put("Endermite", "minecraft:endermite"); + DataConverterEntity.a.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); + DataConverterEntity.a.put("FallingSand", "minecraft:falling_block"); + DataConverterEntity.a.put("Fireball", "minecraft:fireball"); + DataConverterEntity.a.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); + DataConverterEntity.a.put("Ghast", "minecraft:ghast"); + DataConverterEntity.a.put("Giant", "minecraft:giant"); + DataConverterEntity.a.put("Guardian", "minecraft:guardian"); + DataConverterEntity.a.put("Horse", "minecraft:horse"); + DataConverterEntity.a.put("Husk", "minecraft:husk"); + DataConverterEntity.a.put("Item", "minecraft:item"); + DataConverterEntity.a.put("ItemFrame", "minecraft:item_frame"); + DataConverterEntity.a.put("LavaSlime", "minecraft:magma_cube"); + DataConverterEntity.a.put("LeashKnot", "minecraft:leash_knot"); + DataConverterEntity.a.put("MinecartChest", "minecraft:chest_minecart"); + DataConverterEntity.a.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); + DataConverterEntity.a.put("MinecartFurnace", "minecraft:furnace_minecart"); + DataConverterEntity.a.put("MinecartHopper", "minecraft:hopper_minecart"); + DataConverterEntity.a.put("MinecartRideable", "minecraft:minecart"); + DataConverterEntity.a.put("MinecartSpawner", "minecraft:spawner_minecart"); + DataConverterEntity.a.put("MinecartTNT", "minecraft:tnt_minecart"); + DataConverterEntity.a.put("Mule", "minecraft:mule"); + DataConverterEntity.a.put("MushroomCow", "minecraft:mooshroom"); + DataConverterEntity.a.put("Ozelot", "minecraft:ocelot"); + DataConverterEntity.a.put("Painting", "minecraft:painting"); + DataConverterEntity.a.put("Pig", "minecraft:pig"); + DataConverterEntity.a.put("PigZombie", "minecraft:zombie_pigman"); + DataConverterEntity.a.put("PolarBear", "minecraft:polar_bear"); + DataConverterEntity.a.put("PrimedTnt", "minecraft:tnt"); + DataConverterEntity.a.put("Rabbit", "minecraft:rabbit"); + DataConverterEntity.a.put("Sheep", "minecraft:sheep"); + DataConverterEntity.a.put("Shulker", "minecraft:shulker"); + DataConverterEntity.a.put("ShulkerBullet", "minecraft:shulker_bullet"); + DataConverterEntity.a.put("Silverfish", "minecraft:silverfish"); + DataConverterEntity.a.put("Skeleton", "minecraft:skeleton"); + DataConverterEntity.a.put("SkeletonHorse", "minecraft:skeleton_horse"); + DataConverterEntity.a.put("Slime", "minecraft:slime"); + DataConverterEntity.a.put("SmallFireball", "minecraft:small_fireball"); + DataConverterEntity.a.put("SnowMan", "minecraft:snowman"); + DataConverterEntity.a.put("Snowball", "minecraft:snowball"); + DataConverterEntity.a.put("SpectralArrow", "minecraft:spectral_arrow"); + DataConverterEntity.a.put("Spider", "minecraft:spider"); + DataConverterEntity.a.put("Squid", "minecraft:squid"); + DataConverterEntity.a.put("Stray", "minecraft:stray"); + DataConverterEntity.a.put("ThrownEgg", "minecraft:egg"); + DataConverterEntity.a.put("ThrownEnderpearl", "minecraft:ender_pearl"); + DataConverterEntity.a.put("ThrownExpBottle", "minecraft:xp_bottle"); + DataConverterEntity.a.put("ThrownPotion", "minecraft:potion"); + DataConverterEntity.a.put("Villager", "minecraft:villager"); + DataConverterEntity.a.put("VillagerGolem", "minecraft:villager_golem"); + DataConverterEntity.a.put("Witch", "minecraft:witch"); + DataConverterEntity.a.put("WitherBoss", "minecraft:wither"); + DataConverterEntity.a.put("WitherSkeleton", "minecraft:wither_skeleton"); + DataConverterEntity.a.put("WitherSkull", "minecraft:wither_skull"); + DataConverterEntity.a.put("Wolf", "minecraft:wolf"); + DataConverterEntity.a.put("XPOrb", "minecraft:xp_orb"); + DataConverterEntity.a.put("Zombie", "minecraft:zombie"); + DataConverterEntity.a.put("ZombieHorse", "minecraft:zombie_horse"); + DataConverterEntity.a.put("ZombieVillager", "minecraft:zombie_villager"); + } + } + + private static class DataConverterPotionWater implements DataConverter { + + DataConverterPotionWater() { + } + + public int getDataVersion() { + return 806; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id"); + + if ("minecraft:potion".equals(s) || "minecraft:splash_potion".equals(s) || "minecraft:lingering_potion".equals(s) || "minecraft:tipped_arrow".equals(s)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (!nbttagcompound1.contains("Potion", 8)) { + nbttagcompound1.putString("Potion", "minecraft:water"); + } + + if (!cmp.contains("tag", 10)) { + cmp.put("tag", nbttagcompound1); + } + } + + return cmp; + } + } + + private static class DataConverterShulker implements DataConverter { + + DataConverterShulker() { + } + + public int getDataVersion() { + return 808; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id")) && !cmp.contains("Color", 99)) { + cmp.putByte("Color", (byte) 10); + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxItem implements DataConverter { + + public static final String[] a = new String[] { "minecraft:white_shulker_box", "minecraft:orange_shulker_box", "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", "minecraft:silver_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", "minecraft:red_shulker_box", "minecraft:black_shulker_box" }; + + DataConverterShulkerBoxItem() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker_box".equals(cmp.getString("id")) && cmp.contains("tag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag"); + + if (nbttagcompound1.contains("BlockEntityTag", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag"); + + if (nbttagcompound2.getList("Items", 10).isEmpty()) { + nbttagcompound2.remove("Items"); + } + + int i = nbttagcompound2.getInt("Color"); + + nbttagcompound2.remove("Color"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + + cmp.putString("id", DataConverterShulkerBoxItem.a[i % 16]); + } + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxBlock implements DataConverter { + + DataConverterShulkerBoxBlock() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id"))) { + cmp.remove("Color"); + } + + return cmp; + } + } + + private static class DataConverterLang implements DataConverter { + + DataConverterLang() { + } + + public int getDataVersion() { + return 816; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.contains("lang", 8)) { + cmp.putString("lang", cmp.getString("lang").toLowerCase(Locale.ROOT)); + } + + return cmp; + } + } + + private static class DataConverterTotem implements DataConverter { + + DataConverterTotem() { + } + + public int getDataVersion() { + return 820; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:totem".equals(cmp.getString("id"))) { + cmp.putString("id", "minecraft:totem_of_undying"); + } + + return cmp; + } + } + + private static class DataConverterBedBlock implements DataConverter { + + private static final Logger a = LogManager.getLogger(PaperweightDataConverters.class); + + DataConverterBedBlock() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + try { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Level"); + int i = nbttagcompound1.getInt("xPos"); + int j = nbttagcompound1.getInt("zPos"); + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("TileEntities", 10); + net.minecraft.nbt.ListTag nbttaglist1 = nbttagcompound1.getList("Sections", 10); + + for (int k = 0; k < nbttaglist1.size(); ++k) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist1.getCompound(k); + byte b0 = nbttagcompound2.getByte("Y"); + byte[] abyte = nbttagcompound2.getByteArray("Blocks"); + + for (int l = 0; l < abyte.length; ++l) { + if (416 == (abyte[l] & 255) << 4) { + int i1 = l & 15; + int j1 = l >> 8 & 15; + int k1 = l >> 4 & 15; + net.minecraft.nbt.CompoundTag nbttagcompound3 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound3.putString("id", "bed"); + nbttagcompound3.putInt("x", i1 + (i << 4)); + nbttagcompound3.putInt("y", j1 + (b0 << 4)); + nbttagcompound3.putInt("z", k1 + (j << 4)); + nbttaglist.add(nbttagcompound3); + } + } + } + } catch (Exception exception) { + DataConverterBedBlock.a.warn("Unable to datafix Bed blocks, level format may be missing tags."); + } + + return cmp; + } + } + + private static class DataConverterBedItem implements DataConverter { + + DataConverterBedItem() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:bed".equals(cmp.getString("id")) && cmp.getShort("Damage") == 0) { + cmp.putShort("Damage", (short) DyeColor.RED.getId()); + } + + return cmp; + } + } + + private static class DataConverterSignText implements DataConverter { + + public static final Gson a = new GsonBuilder().registerTypeAdapter(Component.class, new JsonDeserializer() { + MutableComponent a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + if (jsonelement.isJsonPrimitive()) { + return new TextComponent(jsonelement.getAsString()); + } else if (jsonelement.isJsonArray()) { + JsonArray jsonarray = jsonelement.getAsJsonArray(); + MutableComponent ichatbasecomponent = null; + Iterator iterator = jsonarray.iterator(); + + while (iterator.hasNext()) { + JsonElement jsonelement1 = (JsonElement) iterator.next(); + MutableComponent ichatbasecomponent1 = this.a(jsonelement1, jsonelement1.getClass(), jsondeserializationcontext); + + if (ichatbasecomponent == null) { + ichatbasecomponent = ichatbasecomponent1; + } else { + ichatbasecomponent.append(ichatbasecomponent1); + } + } + + return ichatbasecomponent; + } else { + throw new JsonParseException("Don't know how to turn " + jsonelement + " into a Component"); + } + } + + public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + return this.a(jsonelement, type, jsondeserializationcontext); + } + }).create(); + + DataConverterSignText() { + } + + public int getDataVersion() { + return 101; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Sign".equals(cmp.getString("id"))) { + this.convert(cmp, "Text1"); + this.convert(cmp, "Text2"); + this.convert(cmp, "Text3"); + this.convert(cmp, "Text4"); + } + + return cmp; + } + + private void convert(net.minecraft.nbt.CompoundTag nbttagcompound, String s) { + String s1 = nbttagcompound.getString(s); + Component object = null; + + if (!"null".equals(s1) && !StringUtil.isNullOrEmpty(s1)) { + if ((s1.charAt(0) != 34 || s1.charAt(s1.length() - 1) != 34) && (s1.charAt(0) != 123 || s1.charAt(s1.length() - 1) != 125)) { + object = new TextComponent(s1); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s1, Component.class, true); + if (object == null) { + object = new TextComponent(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = Component.Serializer.fromJson(s1); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = Component.Serializer.fromJsonLenient(s1); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = new TextComponent(s1); + } + } + } else { + object = new TextComponent(""); + } + + nbttagcompound.putString(s, Component.Serializer.toJson(object)); + } + } + + private static class DataInspectorPlayerVehicle implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("RootVehicle", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("RootVehicle"); + + if (nbttagcompound1.contains("Entity", 10)) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + } + + return cmp; + } + } + + private static class DataInspectorLevelPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Player", 10)) { + convertCompound(LegacyType.PLAYER, cmp, "Player", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorStructure implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + net.minecraft.nbt.ListTag nbttaglist; + int j; + net.minecraft.nbt.CompoundTag nbttagcompound1; + + if (cmp.contains("entities", 9)) { + nbttaglist = cmp.getList("entities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.contains("nbt", 10)) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + } + + if (cmp.contains("blocks", 9)) { + nbttaglist = cmp.getList("blocks", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.contains("nbt", 10)) { + convertCompound(LegacyType.BLOCK_ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + } + + return cmp; + } + } + + private static class DataInspectorChunks implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Level", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Level"); + net.minecraft.nbt.ListTag nbttaglist; + int j; + + if (nbttagcompound1.contains("Entities", 9)) { + nbttaglist = nbttagcompound1.getList("Entities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + } + + if (nbttagcompound1.contains("TileEntities", 9)) { + nbttaglist = nbttagcompound1.getList("TileEntities", 10); + + for (j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.BLOCK_ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + } + } + + return cmp; + } + } + + private static class DataInspectorEntityPassengers implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.contains("Passengers", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("Passengers", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, nbttaglist.getCompound(j), sourceVer, targetVer)); + } + } + + return cmp; + } + } + + private static class DataInspectorPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + convertItems(cmp, "Inventory", sourceVer, targetVer); + convertItems(cmp, "EnderItems", sourceVer, targetVer); + if (cmp.contains("ShoulderEntityLeft", 10)) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityLeft", sourceVer, targetVer); + } + + if (cmp.contains("ShoulderEntityRight", 10)) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityRight", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorVillagers implements DataInspector { + ResourceLocation entityVillager = getKey("EntityVillager"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (entityVillager.equals(new ResourceLocation(cmp.getString("id"))) && cmp.contains("Offers", 10)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Offers"); + + if (nbttagcompound1.contains("Recipes", 9)) { + net.minecraft.nbt.ListTag nbttaglist = nbttagcompound1.getList("Recipes", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompound(j); + + convertItem(nbttagcompound2, "buy", sourceVer, targetVer); + convertItem(nbttagcompound2, "buyB", sourceVer, targetVer); + convertItem(nbttagcompound2, "sell", sourceVer, targetVer); + nbttaglist.set(j, nbttagcompound2); + } + } + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMinecart implements DataInspector { + ResourceLocation entityMinecartMobSpawner = getKey("EntityMinecartMobSpawner"); + ResourceLocation tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + String s = cmp.getString("id"); + if (entityMinecartMobSpawner.equals(new ResourceLocation(s))) { + cmp.putString("id", tileEntityMobSpawner.toString()); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", s); + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMobs implements DataInspector { + ResourceLocation tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (tileEntityMobSpawner.equals(new ResourceLocation(cmp.getString("id")))) { + if (cmp.contains("SpawnPotentials", 9)) { + net.minecraft.nbt.ListTag nbttaglist = cmp.getList("SpawnPotentials", 10); + + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); + + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + } + + convertCompound(LegacyType.ENTITY, cmp, "SpawnData", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorCommandBlock implements DataInspector { + ResourceLocation tileEntityCommand = getKey("TileEntityCommand"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (tileEntityCommand.equals(new ResourceLocation(cmp.getString("id")))) { + cmp.putString("id", "Control"); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", "MinecartCommandBlock"); + } + + return cmp; + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightFakePlayer.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightFakePlayer.java new file mode 100644 index 000000000..3402def33 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightFakePlayer.java @@ -0,0 +1,98 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2; + +import com.mojang.authlib.GameProfile; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ServerboundClientInformationPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stat; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +import java.util.OptionalInt; +import java.util.UUID; + +class PaperweightFakePlayer extends ServerPlayer { + private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]"); + private static final Vec3 ORIGIN = new Vec3(0.0D, 0.0D, 0.0D); + + PaperweightFakePlayer(ServerLevel world) { + super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE); + } + + @Override + public Vec3 position() { + return ORIGIN; + } + + @Override + public void tick() { + } + + @Override + public void die(DamageSource damagesource) { + } + + @Override + public Entity changeDimension(ServerLevel worldserver, TeleportCause cause) { + return this; + } + + @Override + public OptionalInt openMenu(MenuProvider factory) { + return OptionalInt.empty(); + } + + @Override + public void updateOptions(ServerboundClientInformationPacket packet) { + } + + @Override + public void displayClientMessage(Component message, boolean actionBar) { + } + + @Override + public void sendMessage(Component message, ChatType type, UUID sender) { + } + + @Override + public void awardStat(Stat stat, int amount) { + } + + @Override + public void awardStat(Stat stat) { + } + + @Override + public boolean isInvulnerableTo(DamageSource damageSource) { + return true; + } + + @Override + public void openTextEdit(SignBlockEntity sign) { + } +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightWorldNativeAccess.java new file mode 100644 index 000000000..c9b310881 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/PaperweightWorldNativeAccess.java @@ -0,0 +1,179 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import javax.annotation.Nullable; + +public class PaperweightWorldNativeAccess implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final PaperweightAdapter adapter; + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + this.adapter = adapter; + this.world = world; + } + + private ServerLevel getWorld() { + return Objects.requireNonNull(world.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getWorld().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) { + int stateId = BlockStateIdAccess.getBlockStateId(state); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) { + return chunk.getBlockState(position); + } + + @Nullable + @Override + public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) { + return chunk.setType(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE)); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) { + return Block.updateFromNeighbourShapes(block, getWorld(), position); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos position) { + getWorld().getChunkSource().getLightEngine().checkBlock(position); + } + + @Override + public boolean updateTileEntity(final BlockPos position, final CompoundBinaryTag tag) { + return false; + } + + @Override + public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk chunk) { + return chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING); + } + + @Override + public void markBlockChanged(LevelChunk chunk, BlockPos position) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().getChunkSource().blockChanged(position); + } + } + + @Override + public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + ServerLevel world = getWorld(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + world.updateNeighborsAt(pos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + Block block = oldState.getBlock(); + fireNeighborChanged(pos, world, block, pos.west()); + fireNeighborChanged(pos, world, block, pos.east()); + fireNeighborChanged(pos, world, block, pos.below()); + fireNeighborChanged(pos, world, block, pos.above()); + fireNeighborChanged(pos, world, block, pos.north()); + fireNeighborChanged(pos, world, block, pos.south()); + } + if (newState.hasAnalogOutputSignal()) { + world.updateNeighbourForOutputSignal(pos, newState.getBlock()); + } + } + + private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { + world.getBlockState(neighborPos).neighborChanged(world, neighborPos, block, pos, false); + } + + @Override + public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) { + ServerLevel world = getWorld(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = world.getWorld(); + BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); + world.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + getWorld().onBlockStateChange(pos, oldState, newState); + } + + @Override + public void flush() { + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightBlockMaterial.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightBlockMaterial.java new file mode 100644 index 000000000..b81512b7c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightBlockMaterial.java @@ -0,0 +1,185 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.PushReaction; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; + +public class PaperweightBlockMaterial implements BlockMaterial { + + private final Block block; + private final BlockState blockState; + private final Material material; + private final boolean isTranslucent; + private final CraftBlockData craftBlockData; + private final org.bukkit.Material craftMaterial; + private final int opacity; + private final CompoundTag tile; + + public PaperweightBlockMaterial(Block block) { + this(block, block.defaultBlockState()); + } + + public PaperweightBlockMaterial(Block block, BlockState blockState) { + this.block = block; + this.blockState = blockState; + this.material = blockState.getMaterial(); + this.craftBlockData = CraftBlockData.fromData(blockState); + this.craftMaterial = craftBlockData.getMaterial(); + BlockBehaviour.Properties blockInfo = ReflectionUtil.getField(BlockBehaviour.class, block, "properties"); + this.isTranslucent = !(boolean) ReflectionUtil.getField(BlockBehaviour.Properties.class, blockInfo, "canOcclude"); + opacity = blockState.getLightBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); + BlockEntity tileEntity = !(block instanceof EntityBlock) ? null : ((EntityBlock) block).newBlockEntity( + BlockPos.ZERO, + blockState + ); + tile = tileEntity == null + ? null + : new PaperweightLazyCompoundTag(Suppliers.memoize(() -> tileEntity.save(new net.minecraft.nbt.CompoundTag()))); + } + + public Block getBlock() { + return block; + } + + public BlockState getState() { + return blockState; + } + + public CraftBlockData getCraftBlockData() { + return craftBlockData; + } + + public Material getMaterial() { + return material; + } + + @Override + public boolean isAir() { + return blockState.isAir(); + } + + @Override + public boolean isFullCube() { + return craftMaterial.isOccluding(); + } + + @Override + public boolean isOpaque() { + return material.isSolidBlocking(); + } + + @Override + public boolean isPowerSource() { + return blockState.isSignalSource(); + } + + @Override + public boolean isLiquid() { + return material.isLiquid(); + } + + @Override + public boolean isSolid() { + return material.isSolid(); + } + + @Override + public float getHardness() { + return craftBlockData.getState().destroySpeed; + } + + @Override + public float getResistance() { + return block.getExplosionResistance(); + } + + @Override + public float getSlipperiness() { + return block.getFriction(); + } + + @Override + public int getLightValue() { + return blockState.getLightEmission(); + } + + @Override + public int getLightOpacity() { + return opacity; + } + + @Override + public boolean isFragileWhenPushed() { + return material.getPushReaction() == PushReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return material.getPushReaction() == PushReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return block.isRandomlyTicking(blockState); + } + + @Override + public boolean isMovementBlocker() { + return material.isSolid(); + } + + @Override + public boolean isBurnable() { + return material.isFlammable(); + } + + @Override + public boolean isToolRequired() { + // Removed in 1.16.1, this is not present in higher versions + return false; + } + + @Override + public boolean isReplacedDuringPlacement() { + return material.isReplaceable(); + } + + @Override + public boolean isTranslucent() { + return isTranslucent; + } + + @Override + public boolean hasContainer() { + return block instanceof EntityBlock; + } + + @Override + public boolean isTile() { + return block instanceof EntityBlock; + } + + @Override + public CompoundTag getDefaultTile() { + return tile; + } + + @Override + public int getMapColor() { + // rgb field + return material.getColor().col; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweAdapter.java new file mode 100644 index 000000000..0487cdebc --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweAdapter.java @@ -0,0 +1,652 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; +import com.fastasyncworldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.regen.PaperweightRegen; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.nbt.IntTag; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.TreeType; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_17_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_17_R1.CraftServer; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_17_R1.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_17_R1.util.CraftNamespacedKey; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.OptionalInt; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements + IDelegateBukkitImplAdapter { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final BukkitImplAdapter parent; + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + private final PaperweightMapChunkUtil mapUtil = new PaperweightMapChunkUtil(); + private char[] ibdToStateOrdinal = null; + private int[] ordinalToIbdID = null; + private boolean initialised = false; + private Map>> allBlockProperties = null; + + public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException { + this.parent = new PaperweightFaweAdapter(); + } + + @Nullable + private static String getEntityId(Entity entity) { + ResourceLocation resourceLocation = net.minecraft.world.entity.EntityType.getKey(entity.getType()); + return resourceLocation == null ? null : resourceLocation.toString(); + } + + private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag compoundTag) { + entity.save(compoundTag); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + private synchronized boolean init() { + if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) { + return false; + } + ibdToStateOrdinal = new char[BlockTypesCache.states.length]; // size + ordinalToIbdID = new int[ibdToStateOrdinal.length]; // size + for (int i = 0; i < ibdToStateOrdinal.length; i++) { + BlockState blockState = BlockTypesCache.states[i]; + PaperweightBlockMaterial material = (PaperweightBlockMaterial) blockState.getMaterial(); + int id = Block.BLOCK_STATE_REGISTRY.getId(material.getState()); + char ordinal = blockState.getOrdinalChar(); + ibdToStateOrdinal[id] = ordinal; + ordinalToIbdID[ordinal] = id; + } + Map>> properties = new HashMap<>(); + try { + for (Field field : BlockStateProperties.class.getDeclaredFields()) { + Object obj = field.get(null); + if (!(obj instanceof net.minecraft.world.level.block.state.properties.Property)) { + continue; + } + net.minecraft.world.level.block.state.properties.Property state = (net.minecraft.world.level.block.state.properties.Property) obj; + Property property; + if (state instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) { + property = new BooleanProperty( + state.getName(), + (List) ImmutableList.copyOf(state.getPossibleValues()) + ); + } else if (state instanceof DirectionProperty) { + property = new DirectionalProperty( + state.getName(), + state + .getPossibleValues() + .stream() + .map(e -> Direction.valueOf(((StringRepresentable) e).getSerializedName().toUpperCase())) + .collect(Collectors.toList()) + ); + } else if (state instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + property = new EnumProperty( + state.getName(), + state + .getPossibleValues() + .stream() + .map(e -> ((StringRepresentable) e).getSerializedName()) + .collect(Collectors.toList()) + ); + } else if (state instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) { + property = new IntegerProperty( + state.getName(), + (List) ImmutableList.copyOf(state.getPossibleValues()) + ); + } else { + throw new IllegalArgumentException("FastAsyncWorldEdit needs an update to support " + state + .getClass() + .getSimpleName()); + } + properties.compute(property.getName().toLowerCase(Locale.ROOT), (k, v) -> { + if (v == null) { + v = new ArrayList<>(Collections.singletonList(property)); + } else { + v.add(property); + } + return v; + }); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } finally { + allBlockProperties = ImmutableMap.copyOf(properties); + } + initialised = true; + return true; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new PaperweightBlockMaterial(block); + } + + @Override + public synchronized BlockMaterial getMaterial(BlockState state) { + net.minecraft.world.level.block.state.BlockState blockState = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new PaperweightBlockMaterial(blockState.getBlock(), blockState); + } + + public Block getBlock(BlockType blockType) { + return Registry.BLOCK.get(new ResourceLocation(blockType.getNamespace(), blockType.getResource())); + } + + @SuppressWarnings("deprecation") + @Override + public BaseBlock getBlock(Location location) { + Preconditions.checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel serverLevel = craftWorld.getHandle(); + LevelChunk levelChunk = serverLevel.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + org.bukkit.block.Block bukkitBlock = location.getBlock(); + BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + if (state.getBlockType().getMaterial().hasContainer()) { + + // Read the NBT data + BlockEntity blockEntity = levelChunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + blockEntity.save(tag); // readTileEntityIntoTag - load data + return state.toBaseBlock((CompoundTag) toNative(tag)); + } + } + + return state.toBaseBlock(); + } + + @Override + public Set getSupportedSideEffects() { + return SideEffectSet.defaults().getSideEffectsToApply(); + } + + public boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, BlockStateHolder state, boolean update) { + CraftChunk craftChunk = (CraftChunk) chunk; + LevelChunk levelChunk = craftChunk.getHandle(); + Level level = levelChunk.getLevel(); + + BlockPos blockPos = new BlockPos(x, y, z); + net.minecraft.world.level.block.state.BlockState blockState = ((PaperweightBlockMaterial) state.getMaterial()).getState(); + LevelChunkSection[] levelChunkSections = levelChunk.getSections(); + int y4 = y >> 4; + LevelChunkSection section = levelChunkSections[y4]; + + net.minecraft.world.level.block.state.BlockState existing; + if (section == null) { + existing = ((PaperweightBlockMaterial) BlockTypes.AIR.getDefaultState().getMaterial()).getState(); + } else { + existing = section.getBlockState(x & 15, y & 15, z & 15); + } + + levelChunk.removeBlockEntity(blockPos); // Force delete the old tile entity + + CompoundTag compoundTag = state instanceof BaseBlock ? state.getNbtData() : null; + if (compoundTag != null || existing instanceof TileEntityBlock) { + level.setBlock(blockPos, blockState, 0); + // remove tile + if (compoundTag != null) { + // We will assume that the tile entity was created for us, + // though we do not do this on the Forge version + BlockEntity blockEntity = level.getBlockEntity(blockPos); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(compoundTag); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + blockEntity.load(tag); // readTagIntoTileEntity - load data + } + } + } else { + if (existing == blockState) { + return true; + } + if (section == null) { + if (blockState.isAir()) { + return true; + } + levelChunkSections[y4] = section = new LevelChunkSection(y4 << 4); + } + levelChunk.setBlockState(blockPos, blockState, false); + } + if (update) { + level.getMinecraftWorld().sendBlockUpdated(blockPos, existing, blockState, 0); + } + return true; + } + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new PaperweightFaweWorldNativeAccess( + this, + new WeakReference<>(((CraftWorld) world).getHandle()) + ); + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + Preconditions.checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + if (id != null) { + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + readEntityIntoTag(mcEntity, minecraftTag); + //add Id for AbstractChangeSet to work + final CompoundTag tag = (CompoundTag) toNative(minecraftTag); + final Map tags = new HashMap<>(tag.getValue()); + tags.put("Id", new StringTag(id)); + return new CompoundTag(tags); + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + net.minecraft.world.level.block.state.BlockState mcState = material.getCraftBlockData().getState(); + return OptionalInt.of(Block.BLOCK_STATE_REGISTRY.getId(mcState)); + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + net.minecraft.world.level.block.state.BlockState ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) { + return BlockTypesCache.states[adaptToChar(blockState)]; + } + + public char adaptToChar(net.minecraft.world.level.block.state.BlockState blockState) { + int id = Block.BLOCK_STATE_REGISTRY.getId(blockState); + if (initialised) { + return ibdToStateOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + try { + init(); + return ibdToStateOrdinal[id]; + } catch (ArrayIndexOutOfBoundsException e1) { + LOGGER.error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!", + blockState.getBlock(), Block.BLOCK_STATE_REGISTRY.getId(blockState), ibdToStateOrdinal.length, e1 + ); + return 0; + } + } + } + + public char ibdIDToOrdinal(int id) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal[id]; + } + init(); + return ibdToStateOrdinal[id]; + } + } + + @Override + public char[] getIbdToStateOrdinal() { + if (initialised) { + return ibdToStateOrdinal; + } + synchronized (this) { + if (initialised) { + return ibdToStateOrdinal; + } + init(); + return ibdToStateOrdinal; + } + } + + public int ordinalToIbdID(char ordinal) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + init(); + return ordinalToIbdID[ordinal]; + } + } + + @Override + public int[] getOrdinalToIbdID() { + if (initialised) { + return ordinalToIbdID; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID; + } + init(); + return ordinalToIbdID; + } + } + + @Override + public > BlockData adapt(B state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + return material.getCraftBlockData(); + } + + @Override + public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chunkPacket) { + ServerLevel nmsWorld = ((CraftWorld) world).getHandle(); + ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ()); + if (map != null && map.wasAccessibleSinceLastSave()) { + boolean flag = false; + // PlayerChunk.d players = map.players; + Stream stream = /*players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag) + */ Stream.empty(); + + ServerPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle(); + stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer) + .forEach(entityPlayer -> { + synchronized (chunkPacket) { + ClientboundLevelChunkPacket nmsPacket = (ClientboundLevelChunkPacket) chunkPacket.getNativePacket(); + if (nmsPacket == null) { + nmsPacket = mapUtil.create(this, chunkPacket); + chunkPacket.setNativePacket(nmsPacket); + } + try { + FaweCache.IMP.CHUNK_FLAG.get().set(true); + entityPlayer.connection.send(nmsPacket); + } finally { + FaweCache.IMP.CHUNK_FLAG.get().set(false); + } + } + }); + } + } + + @Override + public Map> getProperties(BlockType blockType) { + return getParent().getProperties(blockType); + } + + @Override + public boolean canPlaceAt(org.bukkit.World world, BlockVector3 blockVector3, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId); + return blockState1.hasPostProcess( + ((CraftWorld) world).getHandle(), + new BlockPos(blockVector3.getX(), blockVector3.getY(), blockVector3.getZ()) + ); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { + ItemStack stack = new ItemStack(Registry.ITEM.get(ResourceLocation.tryParse(baseItemStack.getType().getId())), baseItemStack.getAmount()); + stack.setTag(((net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public boolean generateTree( + TreeGenerator.TreeType treeType, EditSession editSession, BlockVector3 blockVector3, + org.bukkit.World bukkitWorld + ) { + TreeType bukkitType = BukkitWorld.toBukkitTreeType(treeType); + if (bukkitType == TreeType.CHORUS_PLANT) { + blockVector3 = blockVector3.add(0, 1, 0); // bukkit skips the feature gen which does this offset normally, so we have to add it back + } + ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle(); + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + boolean grownTree = bukkitWorld.generateTree(BukkitAdapter.adapt(bukkitWorld, blockVector3), bukkitType); + serverLevel.captureBlockStates = false; + serverLevel.captureTreeGeneration = false; + if (!grownTree) { + serverLevel.capturedBlockStates.clear(); + return false; + } else { + for (CraftBlockState craftBlockState : serverLevel.capturedBlockStates.values()) { + if (craftBlockState == null || craftBlockState.getType() == Material.AIR) { + continue; + } + editSession.setBlock(craftBlockState.getX(), craftBlockState.getY(), craftBlockState.getZ(), + BukkitAdapter.adapt(((org.bukkit.block.BlockState) craftBlockState).getBlockData()) + ); + } + + serverLevel.capturedBlockStates.clear(); + return true; + } + } + + @Override + public List getEntities(org.bukkit.World world) { + // Quickly add each entity to a list copy. + List mcEntities = new ArrayList<>(); + ((CraftWorld) world).getHandle().entityManager.getEntityGetter().getAll().forEach(mcEntities::add); + + List list = new ArrayList<>(); + mcEntities.forEach((mcEnt) -> { + org.bukkit.entity.Entity bukkitEntity = mcEnt.getBukkitEntity(); + if (bukkitEntity.isValid()) { + list.add(bukkitEntity); + } + + }); + return list; + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbtData(((CompoundTag) toNative(nmsStack.getTag()))); + return weStack; + } + + @Override + public Tag toNative(net.minecraft.nbt.Tag foreign) { + return parent.toNative(foreign); + } + + @Override + public net.minecraft.nbt.Tag fromNative(Tag foreign) { + if (foreign instanceof PaperweightLazyCompoundTag) { + return ((PaperweightLazyCompoundTag) foreign).get(); + } + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new PaperweightRegen(bukkitWorld, region, target, options).regenerate(); + } + + @Override + public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + return new PaperweightGetBlocks(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biomeType) { + Biome biomeBase = CraftBlock.biomeToBiomeBase( + MinecraftServer.getServer().registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY), + BukkitAdapter.adapt(biomeType) + ); + return MinecraftServer.getServer().registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getId(biomeBase); + } + + @Override + public Iterable getRegisteredBiomes() { + WritableRegistry biomeRegistry = ((CraftServer) Bukkit.getServer()) + .getServer() + .registryAccess() + .ownedRegistryOrThrow( + Registry.BIOME_REGISTRY); + return biomeRegistry.stream() + .map(biomeRegistry::getKey) + .map(CraftNamespacedKey::fromMinecraft) + .collect(Collectors.toList()); + } + + @Override + public RelighterFactory getRelighterFactory() { + try { + Class.forName("ca.spottedleaf.starlight.light.StarLightEngine"); + if (PaperweightStarlightRelighter.isUsable()) { + return new PaperweightStarlightRelighterFactory(); + } + } catch (ThreadDeath td) { + throw td; + } catch (Throwable ignored) { + + } + return new NMSRelighterFactory(); + } + + @Override + public Map>> getAllProperties() { + if (initialised) { + return allBlockProperties; + } + synchronized (this) { + if (initialised) { + return allBlockProperties; + } + init(); + return allBlockProperties; + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweWorldNativeAccess.java new file mode 100644 index 000000000..017bb5622 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightFaweWorldNativeAccess.java @@ -0,0 +1,296 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.TaskManager; +import com.fastasyncworldedit.core.util.task.RunnableVal; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess { + + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + private static final Direction[] NEIGHBOUR_ORDER = { + Direction.EAST, + Direction.WEST, + Direction.DOWN, + Direction.UP, + Direction.NORTH, + Direction.SOUTH + }; + private final PaperweightFaweAdapter paperweightFaweAdapter; + private final WeakReference level; + private final AtomicInteger lastTick; + private final Set cachedChanges = new HashSet<>(); + private final Set cachedChunksToSend = new HashSet<>(); + private SideEffectSet sideEffectSet; + + public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference level) { + this.paperweightFaweAdapter = paperweightFaweAdapter; + this.level = level; + // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. + // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. + this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + } + + private Level getLevel() { + return Objects.requireNonNull(level.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getLevel().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState blockState) { + int stateId = paperweightFaweAdapter.ordinalToIbdID(blockState.getOrdinalChar()); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(blockState)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk levelChunk, BlockPos blockPos) { + return levelChunk.getBlockState(blockPos); + } + + @Nullable + @Override + public synchronized net.minecraft.world.level.block.state.BlockState setBlockState( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + int currentTick = MinecraftServer.currentTick; + if (Fawe.isMainThread()) { + return levelChunk.setBlockState(blockPos, blockState, + this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) + ); + } + // Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( ) + cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState)); + cachedChunksToSend.add(new IntPair(levelChunk.bukkitChunk.getX(), levelChunk.bukkitChunk.getZ())); + boolean nextTick = lastTick.get() > currentTick; + if (nextTick || cachedChanges.size() >= 1024) { + if (nextTick) { + lastTick.set(currentTick); + } + flushAsync(nextTick); + } + return blockState; + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition( + net.minecraft.world.level.block.state.BlockState blockState, + BlockPos blockPos + ) { + return Block.updateFromNeighbourShapes(blockState, getLevel(), blockPos); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos blockPos) { + getLevel().getChunkSource().getLightEngine().checkBlock(blockPos); + } + + @Override + public boolean updateTileEntity(BlockPos blockPos, CompoundBinaryTag tag) { + // We will assume that the tile entity was created for us, + // though we do not do this on the other versions + BlockEntity blockEntity = getLevel().getBlockEntity(blockPos); + if (blockEntity == null) { + return false; + } + net.minecraft.nbt.Tag nativeTag = paperweightFaweAdapter.fromNativeBinary(tag); + blockEntity.load((CompoundTag) nativeTag); + return true; + } + + @Override + public void notifyBlockUpdate( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + getLevel().sendBlockUpdated(blockPos, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk levelChunk) { + return levelChunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING); + } + + @Override + public void markBlockChanged(LevelChunk levelChunk, BlockPos blockPos) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + ((ServerChunkCache) getLevel().getChunkSource()).blockChanged(blockPos); + } + } + + @Override + public void notifyNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + Level level = getLevel(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + level.blockUpdated(blockPos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + // Un-nest neighbour updating + for (Direction direction : NEIGHBOUR_ORDER) { + BlockPos shifted = blockPos.relative(direction); + level.getBlockState(shifted).neighborChanged(level, shifted, oldState.getBlock(), blockPos, false); + } + } + if (newState.hasAnalogOutputSignal()) { + level.updateNeighbourForOutputSignal(blockPos, newState.getBlock()); + } + } + + @Override + public void updateNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState, + int recursionLimit + ) { + Level level = getLevel(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = level.getWorld(); + if (craftWorld != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent( + craftWorld.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()), + CraftBlockData.fromData(newState) + ); + level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + } + newState.triggerEvent(level, blockPos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + getLevel().onBlockStateChange(blockPos, oldState, newState); + } + + private synchronized void flushAsync(final boolean sendChunks) { + final Set changes = Set.copyOf(cachedChanges); + cachedChanges.clear(); + final Set toSend; + if (sendChunks) { + toSend = Set.copyOf(cachedChunksToSend); + cachedChunksToSend.clear(); + } else { + toSend = Collections.emptySet(); + } + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + changes.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE) + )); + if (!sendChunks) { + return; + } + for (IntPair chunk : toSend) { + PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x, chunk.z, false); + } + } + }; + TaskManager.IMP.async(() -> TaskManager.IMP.sync(runnableVal)); + } + + @Override + public synchronized void flush() { + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE) + )); + for (IntPair chunk : cachedChunksToSend) { + PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x, chunk.z, false); + } + } + }; + if (Fawe.isMainThread()) { + runnableVal.run(); + } else { + TaskManager.IMP.sync(runnableVal); + } + cachedChanges.clear(); + cachedChunksToSend.clear(); + } + + private static final class CachedChange { + + private final LevelChunk levelChunk; + private final BlockPos blockPos; + private final net.minecraft.world.level.block.state.BlockState blockState; + + private CachedChange( + LevelChunk levelChunk, + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + this.levelChunk = levelChunk; + this.blockPos = blockPos; + this.blockState = blockState; + } + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks.java new file mode 100644 index 000000000..3147e659c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks.java @@ -0,0 +1,1053 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.implementation.QueueHandler; +import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; +import com.fastasyncworldedit.core.util.collection.AdaptedMap; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockTypes; +import io.papermc.lib.PaperLib; +import io.papermc.paper.event.block.BeaconDeactivatedEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.IntTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.BitStorage; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BeaconBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkBiomeContainer; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.HashMapPalette; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.LinearPalette; +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.apache.logging.log4j.Logger; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + private static final Function nmsTile2We = + tileEntity -> new PaperweightLazyCompoundTag(Suppliers.memoize( + () -> tileEntity.save(new net.minecraft.nbt.CompoundTag()))); + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); + private final ServerLevel serverLevel; + private final int chunkX; + private final int chunkZ; + private final int minHeight; + private final int maxHeight; + private final int minSectionPosition; + private final int maxSectionPosition; + private LevelChunkSection[] sections; + private LevelChunk levelChunk; + private DataLayer[] blockLight; + private DataLayer[] skyLight; + private boolean createCopy = false; + private PaperweightGetBlocks_Copy copy = null; + private boolean forceLoadSections = true; + private boolean lightUpdate = false; + + public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { + this(((CraftWorld) world).getHandle(), chunkX, chunkZ); + } + + public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { + super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); + this.serverLevel = serverLevel; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.minHeight = serverLevel.getMinBuildHeight(); + this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. + this.minSectionPosition = minHeight >> 4; + this.maxSectionPosition = maxHeight >> 4; + this.skyLight = new DataLayer[getSectionCount()]; + this.blockLight = new DataLayer[getSectionCount()]; + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } + + @Override + public boolean isCreateCopy() { + return createCopy; + } + + @Override + public void setCreateCopy(boolean createCopy) { + this.createCopy = createCopy; + } + + @Override + public IChunkGet getCopy() { + return copy; + } + + @Override + public void setLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256); + bitArray.fromRaw(data); + Heightmap.Types nativeType = Heightmap.Types.valueOf(type.name()); + Heightmap heightMap = getChunk().heightmaps.get(nativeType); + heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + ChunkBiomeContainer index = getChunk().getBiomes(); + Biome biomes = null; + if (y == -1) { + for (y = serverLevel.getMinBuildHeight(); y < serverLevel.getMaxBuildHeight(); y += 4) { + biomes = index.getNoiseBiome(x >> 2, y >> 2, z >> 2); + if (biomes != null) { + break; + } + } + } else { + biomes = index.getNoiseBiome(x >> 2, y >> 2, z >> 2); + } + return biomes != null ? PaperweightPlatformAdapter.adapt(biomes, serverLevel) : null; + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.BLOCK).getDataLayerData( + sectionPos); + if (dataLayer != null) { + lightUpdate = true; + synchronized (dataLayer) { + byte[] bytes = PaperLib.isPaper() ? dataLayer.getIfSet() : dataLayer.getData(); + if (!PaperLib.isPaper() || bytes != DataLayer.EMPTY_NIBBLE) { + Arrays.fill(bytes, (byte) 0); + } + } + } + if (sky) { + SectionPos sectionPos1 = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer1 = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.SKY) + .getDataLayerData(sectionPos1); + if (dataLayer1 != null) { + lightUpdate = true; + synchronized (dataLayer1) { + byte[] bytes = PaperLib.isPaper() ? dataLayer1.getIfSet() : dataLayer1.getData(); + if (!PaperLib.isPaper() || bytes != DataLayer.EMPTY_NIBBLE) { + Arrays.fill(bytes, (byte) 0); + } + } + } + } + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return new PaperweightLazyCompoundTag(Suppliers.memoize(() -> blockEntity.save(new net.minecraft.nbt.CompoundTag()))); + } + + @Override + public Map getTiles() { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); + } + + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (skyLight[alayer] == null) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = + serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.SKY).getDataLayerData(sectionPos); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + LightLayer.BLOCK, + sectionPos, + dataLayer, + true + ); + } + skyLight[alayer] = dataLayer; + } + return skyLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int getEmittedLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (blockLight[alayer] == null) { + serverLevel.getRawBrightness(new BlockPos(1, 1, 1), 5); + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.BLOCK) + .getDataLayerData(sectionPos); + // If the server hasn't generated the section's DataLayer yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData(LightLayer.BLOCK, sectionPos, + dataLayer, true + ); + } + blockLight[alayer] = dataLayer; + } + return blockLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int[] getHeightMap(HeightMapType type) { + long[] longArray = getChunk().heightmaps.get(Heightmap.Types.valueOf(type.name())).getRawData(); + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256, longArray); + return bitArray.toRaw(new int[256]); + } + + @Override + public CompoundTag getEntity(UUID uuid) { + Entity entity = serverLevel.getEntity(uuid); + if (entity != null) { + org.bukkit.entity.Entity bukkitEnt = entity.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + for (List entry : /*getChunk().getEntitySlices()*/ new List[0]) { + if (entry != null) { + for (Entity ent : entry) { + if (uuid.equals(ent.getUUID())) { + org.bukkit.entity.Entity bukkitEnt = ent.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + } + } + } + return null; + } + + @Override + public Set getEntities() { + List[] slices = /*getChunk().getEntitySlices()*/ new List[0]; + int size = 0; + for (List slice : slices) { + if (slice != null) { + size += slice.size(); + } + } + if (slices.length == 0) { + return Collections.emptySet(); + } + int finalSize = size; + return new AbstractSet() { + @Override + public int size() { + return finalSize; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof CompoundTag)) { + return false; + } + CompoundTag getTag = (CompoundTag) get; + Map value = getTag.getValue(); + CompoundTag getParts = (CompoundTag) value.get("UUID"); + UUID getUUID = new UUID(getParts.getLong("Most"), getParts.getLong("Least")); + for (List slice : slices) { + if (slice != null) { + for (Entity entity : slice) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + } + } + return false; + } + + @NotNull + @Override + public Iterator iterator() { + Iterable result = Iterables.transform( + Iterables.concat(slices), + new com.google.common.base.Function() { + @Nullable + @Override + public CompoundTag apply(@Nullable Entity input) { + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + return (CompoundTag) adapter.toNative(input.saveWithoutId(tag)); + } + } + ); + return result.iterator(); + } + }; + } + + private void removeEntity(Entity entity) { + entity.discard(); + } + + public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); + } + + @Override + public synchronized > T call(IChunkSet set, Runnable finalizer) { + forceLoadSections = false; + copy = createCopy ? new PaperweightGetBlocks_Copy(serverLevel) : null; + try { + ServerLevel nmsWorld = serverLevel; + LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); + boolean fastmode = set.isFastMode() && Settings.IMP.QUEUE.NO_TICK_FASTMODE; + + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } + + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != 0) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); + } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); + } + } + } + } + + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + if (!set.hasSection(layerNo)) { + continue; + } + int layer = layerNo - getMinSectionPosition(); + + bitMask |= 1 << layer; + + char[] tmp = set.load(layerNo); + char[] setArr = new char[4096]; + System.arraycopy(tmp, 0, setArr, 0, 4096); + + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[layer]) { + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(layer, copyArr); + } + + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[layer]; + if (existingSection == null) { + newSection = PaperweightPlatformAdapter.newChunkSection(layerNo, setArr, fastmode, adapter); + if (PaperweightPlatformAdapter.setSectionAtomic(levelChunkSections, null, newSection, layer)) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, layer); + continue; + } else { + existingSection = levelChunkSections[layer]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + + layer); + continue; + } + } + } + PaperweightPlatformAdapter.fieldTickingBlockCount.set(existingSection, (short) 0); + + //ensure that the server doesn't try to tick the chunksection while we're editing it. + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + + synchronized (lock) { + // lock.acquire(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[layer]) { + this.sections[layer] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(layer, new char[4096], true), loadPrivately(layerNo))) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } + newSection = + PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + fastmode, + adapter + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + levelChunkSections, + existingSection, + newSection, + layer + )) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + + layer); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, layer); + } + } + } + } + + // Biomes + BiomeType[] biomes = set.getBiomes(); + if (biomes != null) { + // set biomes + ChunkBiomeContainer currentBiomes = nmsChunk.getBiomes(); + if (createCopy) { + copy.storeBiomes(currentBiomes); + } + for (int y = 0, i = 0; y < 64; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, i++) { + final BiomeType biome = biomes[i]; + if (biome != null) { + Biome nmsBiome = + nmsWorld.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).get( + ResourceLocation.tryParse(biome.getId())); + if (nmsBiome == null) { + throw new NullPointerException("BiomeBase null for BiomeType " + biome.getId()); + } + currentBiomes.setBiome(x, y, z, nmsBiome); + } + } + } + } + } + + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet( + set.getLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; + + syncTasks = new Runnable[4]; + + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); + } + }; + } + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } + + syncTasks[2] = () -> { + final List[] entities = /*nmsChunk.e()*/ new List[0]; + + for (final Collection ents : entities) { + if (!ents.isEmpty()) { + final Iterator iter = ents.iterator(); + while (iter.hasNext()) { + final Entity entity = iter.next(); + if (entityRemoves.contains(entity.getUUID())) { + if (createCopy) { + copy.storeEntity(entity); + } + iter.remove(); + removeEntity(entity); + } + } + } + } + }; + } + + Set entities = set.getEntities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; + } + + syncTasks[1] = () -> { + for (final CompoundTag nativeTag : entities) { + final Map entityTagMap = nativeTag.getValue(); + final StringTag idTag = (StringTag) entityTagMap.get("Id"); + final ListTag posTag = (ListTag) entityTagMap.get("Pos"); + final ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.getDouble(0); + final double y = posTag.getDouble(1); + final double z = posTag.getDouble(2); + final float yaw = rotTag.getFloat(0); + final float pitch = rotTag.getFloat(1); + final String id = idTag.getValue(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + if (entity != null) { + final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative( + nativeTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + nmsWorld.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + } + }; + + } + + // set tiles + Map tiles = set.getTiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; + } + + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final CompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.getX() + bx; + final int y = blockHash.getY(); + final int z = blockHash.getZ() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); + } + if (tileEntity != null) { + final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNative( + nativeTag); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.load(tag); + } + } + } + }; + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markUnsaved(); + // send to player + if (Settings.IMP.LIGHTING.MODE == 0 || !Settings.IMP.LIGHTING.DELAY_PACKET_SENDING) { + this.send(finalMask, finalLightUpdate); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.get().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } + } + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + return null; + } else { + return queueHandler.async(callback, null); + } + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + }; + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback == null) { + if (finalizer != null) { + finalizer.run(); + } + } else { + callback.run(); + } + } + } + return null; + } catch (Throwable e) { + e.printStackTrace(); + return null; + } finally { + forceLoadSections = true; + } + } + + private void updateGet( + LevelChunk nmsChunk, + LevelChunkSection[] chunkSections, + LevelChunkSection section, + char[] arr, + int layer + ) { + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + this.reset(); + } + if (this.sections == null) { + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + } + if (this.sections[layer] != section) { + // Not sure why it's funky, but it's what I did in commit fda7d00747abe97d7891b80ed8bb88d97e1c70d1 and I don't want to touch it >dords + this.sections[layer] = new LevelChunkSection[]{section}.clone()[0]; + } + } finally { + sectionLock.writeLock().unlock(); + } + this.blocks[layer] = arr; + } + + private char[] loadPrivately(int layer) { + layer -= getMinSectionPosition(); + if (super.sections[layer] != null) { + synchronized (super.sectionLocks[layer]) { + if (super.sections[layer].isFull() && super.blocks[layer] != null) { + char[] blocks = new char[4096]; + System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096); + return blocks; + } + } + } + return PaperweightGetBlocks.this.update(layer, null, true); + } + + @Override + public synchronized void send(int mask, boolean lighting) { + PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); + } + + /** + * Update a given (nullable) data array to the current data stored in the server's chunk, associated with this + * {@link PaperweightPlatformAdapter} instance. Not synchronised to the {@link PaperweightPlatformAdapter} instance as synchronisation + * is handled where necessary in the method, and should otherwise be handled correctly by this method's caller. + * + * @param layer layer index (0 may denote a negative layer in the world, e.g. at y=-32) + * @param data array to be updated/filled with data or null + * @param aggressive if the cached section array should be re-acquired. + * @return the given array to be filled with data, or a new array if null is given. + */ + @Override + public char[] update(int layer, char[] data, boolean aggressive) { + LevelChunkSection section = getSections(aggressive)[layer]; + // Section is null, return empty array + if (section == null) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + return data; + } + if (data != null && data.length != 4096) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + } + if (data == null || data == FaweCache.IMP.EMPTY_CHAR_4096) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + } + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(section); + synchronized (lock) { + // Efficiently convert ChunkSection to raw data + try { + lock.acquire(); + + final PalettedContainer blocks = section.getStates(); + final BitStorage bits = (BitStorage) PaperweightPlatformAdapter.fieldStorage.get(blocks); + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get(blocks); + + final int bitsPerEntry = (int) PaperweightPlatformAdapter.fieldBitsPerEntry.get(bits); + final long[] blockStates = bits.getRaw(); + + new BitArrayUnstretched(bitsPerEntry, 4096, blockStates).toRaw(data); + + int num_palette; + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + num_palette = palette.getSize(); + } else { + // The section's palette is the global block palette. + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char ordinal = adapter.ibdIDToOrdinal(paletteVal); + // Don't read "empty". + data[i] = ordinal == 0 ? 1 : ordinal; + } + return data; + } + + char[] paletteToOrdinal = FaweCache.IMP.PALETTE_TO_BLOCK_CHAR.get(); + try { + if (num_palette != 1) { + for (int i = 0; i < num_palette; i++) { + char ordinal = ordinal(palette.valueFor(i), adapter); + paletteToOrdinal[i] = ordinal; + } + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char val = paletteToOrdinal[paletteVal]; + if (val == Character.MAX_VALUE) { + val = ordinal(palette.valueFor(i), adapter); + paletteToOrdinal[i] = val; + } + // Don't read "empty". + if (val == 0) { + val = 1; + } + data[i] = val; + } + } else { + char ordinal = ordinal(palette.valueFor(0), adapter); + // Don't read "empty". + if (ordinal == 0) { + ordinal = 1; + } + Arrays.fill(data, ordinal); + } + } finally { + for (int i = 0; i < num_palette; i++) { + paletteToOrdinal[i] = Character.MAX_VALUE; + } + } + return data; + } catch (IllegalAccessException | InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + lock.release(); + } + } + } + + private char ordinal(net.minecraft.world.level.block.state.BlockState ibd, PaperweightFaweAdapter adapter) { + if (ibd == null) { + return BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + return adapter.adaptToChar(ibd); + } + } + + public LevelChunkSection[] getSections(boolean force) { + force &= forceLoadSections; + sectionLock.readLock().lock(); + LevelChunkSection[] tmp = sections; + sectionLock.readLock().unlock(); + if (tmp == null || force) { + try { + sectionLock.writeLock().lock(); + tmp = sections; + if (tmp == null || force) { + LevelChunkSection[] chunkSections = getChunk().getSections(); + tmp = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, tmp, 0, chunkSections.length); + sections = tmp; + } + } finally { + sectionLock.writeLock().unlock(); + } + } + return tmp; + } + + public LevelChunk getChunk() { + LevelChunk levelChunk = this.levelChunk; + if (levelChunk == null) { + synchronized (this) { + levelChunk = this.levelChunk; + if (levelChunk == null) { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + } + } + } + return levelChunk; + } + + private void fillLightNibble(char[][] light, LightLayer lightLayer, int minSectionPosition, int maxSectionPosition) { + for (int Y = 0; Y <= maxSectionPosition - minSectionPosition; Y++) { + if (light[Y] == null) { + continue; + } + SectionPos sectionPos = SectionPos.of(levelChunk.getPos(), Y + minSectionPosition); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(lightLayer).getDataLayerData( + sectionPos); + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + Arrays.fill(LAYER_COUNT, lightLayer == LightLayer.SKY ? (byte) 15 : (byte) 0); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + lightLayer, + sectionPos, + dataLayer, + true + ); + } + synchronized (dataLayer) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + int i = y << 8 | z << 4 | x; + if (light[Y][i] < 16) { + dataLayer.set(x, y, z, light[Y][i]); + } + } + } + } + } + } + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return getSections(false)[layer] != null; + } + + @Override + public synchronized boolean trim(boolean aggressive) { + skyLight = new DataLayer[getSectionCount()]; + blockLight = new DataLayer[getSectionCount()]; + if (aggressive) { + sectionLock.writeLock().lock(); + sections = null; + levelChunk = null; + sectionLock.writeLock().unlock(); + return super.trim(true); + } else if (sections == null) { + // don't bother trimming if there are no sections stored. + return true; + } else { + for (int i = getMinSectionPosition(); i <= getMaxSectionPosition(); i++) { + int layer = i - getMinSectionPosition(); + if (!hasSection(i) || !super.sections[layer].isFull()) { + continue; + } + LevelChunkSection existing = getSections(true)[layer]; + try { + final PalettedContainer blocksExisting = existing.getStates(); + + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get( + blocksExisting); + int paletteSize; + + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + paletteSize = palette.getSize(); + } else { + super.trim(false, i); + continue; + } + if (paletteSize == 1) { + //If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks. + continue; + } + super.trim(false, i); + } catch (IllegalAccessException ignored) { + super.trim(false, i); + } + } + return true; + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks_Copy.java new file mode 100644 index 000000000..5a1f87bd6 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightGetBlocks_Copy.java @@ -0,0 +1,251 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.nbt.PaperweightLazyCompoundTag; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkBiomeContainer; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Future; + +public class PaperweightGetBlocks_Copy implements IChunkGet { + + private final Map tiles = new HashMap<>(); + private final Set entities = new HashSet<>(); + private final char[][] blocks; + private final int minHeight; + private final int maxHeight; + private final ServerLevel serverLevel; + private ChunkBiomeContainer chunkBiomeContainer; + + protected PaperweightGetBlocks_Copy(ServerLevel world) { + this.serverLevel = world; + this.minHeight = world.getMinBuildHeight(); + this.maxHeight = world.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. + this.blocks = new char[getSectionCount()][]; + } + + protected void storeTile(BlockEntity blockEntity) { + tiles.put( + BlockVector3.at( + blockEntity.getBlockPos().getX(), + blockEntity.getBlockPos().getY(), + blockEntity.getBlockPos().getZ() + ), + new PaperweightLazyCompoundTag(Suppliers.memoize(() -> blockEntity.save(new net.minecraft.nbt.CompoundTag()))) + ); + } + + @Override + public Map getTiles() { + return tiles; + } + + @Override + @Nullable + public CompoundTag getTile(int x, int y, int z) { + return tiles.get(BlockVector3.at(x, y, z)); + } + + protected void storeEntity(Entity entity) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + net.minecraft.nbt.CompoundTag compoundTag = new net.minecraft.nbt.CompoundTag(); + entities.add((CompoundTag) adapter.toNative(entity.save(compoundTag))); + } + + @Override + public Set getEntities() { + return this.entities; + } + + @Override + public CompoundTag getEntity(UUID uuid) { + for (CompoundTag tag : entities) { + UUID tagUUID; + if (tag.containsKey("UUID")) { + int[] arr = tag.getIntArray("UUID"); + tagUUID = new UUID((long) arr[0] << 32 | (arr[1] & 0xFFFFFFFFL), (long) arr[2] << 32 | (arr[3] & 0xFFFFFFFFL)); + } else if (tag.containsKey("UUIDMost")) { + tagUUID = new UUID(tag.getLong("UUIDMost"), tag.getLong("UUIDLeast")); + } else if (tag.containsKey("PersistentIDMSB")) { + tagUUID = new UUID(tag.getLong("PersistentIDMSB"), tag.getLong("PersistentIDLSB")); + } else { + return null; + } + if (uuid.equals(tagUUID)) { + return tag; + } + } + return null; + } + + @Override + public boolean isCreateCopy() { + return false; + } + + @Override + public void setCreateCopy(boolean createCopy) { + } + + @Override + public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + + @Override + public int getMaxSectionPosition() { + return maxHeight >> 4; + } + + @Override + public int getMinSectionPosition() { + return minHeight >> 4; + } + + protected void storeBiomes(ChunkBiomeContainer chunkBiomeContainer) { + // The to do one line below is pre-paperweight and needs to be revised + // TODO revisit last parameter, BiomeStorage[] *would* be more efficient + this.chunkBiomeContainer = new ChunkBiomeContainer(chunkBiomeContainer.biomeRegistry, serverLevel, + chunkBiomeContainer.writeBiomes() + ); + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + Biome biome = null; + if (y == -1) { + for (y = serverLevel.getMinBuildHeight(); y <= serverLevel.getMaxBuildHeight(); y += 4) { + biome = this.chunkBiomeContainer.getNoiseBiome(x >> 2, y >> 2, z >> 2); + if (biome != null) { + break; + } + } + } else { + biome = this.chunkBiomeContainer.getNoiseBiome(x >> 2, y >> 2, z >> 2); + } + return biome != null ? PaperweightPlatformAdapter.adapt(biome, serverLevel) : null; + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + } + + @Override + public boolean trim(boolean aggressive, int layer) { + return false; + } + + @Override + public IBlocks reset() { + return null; + } + + @Override + public int getSectionCount() { + return serverLevel.getSectionsCount(); + } + + protected void storeSection(int layer, char[] data) { + blocks[layer] = data; + } + + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + BlockState state = BlockTypesCache.states[get(x, y, z)]; + return state.toBaseBlock(this, x, y, z); + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer] != null; + } + + @Override + public char[] load(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer]; + } + + @Override + public char[] loadIfPresent(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer]; + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public int getSkyLight(int x, int y, int z) { + return 0; + } + + @Override + public int getEmittedLight(int x, int y, int z) { + return 0; + } + + @Override + public int[] getHeightMap(HeightMapType type) { + return new int[0]; + } + + @Override + public > T call(IChunkSet set, Runnable finalize) { + return null; + } + + public char get(int x, int y, int z) { + final int layer = (y >> 4) - getMinSectionPosition(); + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer][index]; + } + + + @Override + public boolean trim(boolean aggressive) { + return false; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightMapChunkUtil.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightMapChunkUtil.java new file mode 100644 index 000000000..be0fbd418 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightMapChunkUtil.java @@ -0,0 +1,31 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.bukkit.adapter.MapChunkUtil; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; + +public class PaperweightMapChunkUtil extends MapChunkUtil { + + public PaperweightMapChunkUtil() throws NoSuchFieldException { + fieldX = ClientboundLevelChunkPacket.class.getDeclaredField("TWO_MEGABYTES"); + fieldZ = ClientboundLevelChunkPacket.class.getDeclaredField("x"); + fieldBitMask = ClientboundLevelChunkPacket.class.getDeclaredField("z"); + fieldHeightMap = ClientboundLevelChunkPacket.class.getDeclaredField("availableSections"); + fieldChunkData = ClientboundLevelChunkPacket.class.getDeclaredField("biomes"); + fieldBlockEntities = ClientboundLevelChunkPacket.class.getDeclaredField("buffer"); + fieldFull = ClientboundLevelChunkPacket.class.getDeclaredField("blockEntitiesTags"); + fieldX.setAccessible(true); + fieldZ.setAccessible(true); + fieldBitMask.setAccessible(true); + fieldHeightMap.setAccessible(true); + fieldChunkData.setAccessible(true); + fieldBlockEntities.setAccessible(true); + fieldFull.setAccessible(true); + } + + @Override + public ClientboundLevelChunkPacket createPacket() { + // TODO ??? return new ClientboundLevelChunkPacket(); + throw new UnsupportedOperationException(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightPlatformAdapter.java new file mode 100644 index 000000000..ab3fa79a7 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightPlatformAdapter.java @@ -0,0 +1,439 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.bukkit.adapter.NMSAdapter; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.TaskManager; +import com.fastasyncworldedit.core.util.UnsafeUtility; +import com.mojang.datafixers.util.Either; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.BitStorage; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkBiomeContainer; +import net.minecraft.world.level.chunk.HashMapPalette; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.LinearPalette; +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.gameevent.GameEventDispatcher; +import net.minecraft.world.level.gameevent.GameEventListener; +import org.bukkit.craftbukkit.v1_17_R1.CraftChunk; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Semaphore; +import java.util.function.Function; +import java.util.stream.Stream; + +public final class PaperweightPlatformAdapter extends NMSAdapter { + + public static final Field fieldStorage; + public static final Field fieldPalette; + public static final Field fieldBits; + + public static final Field fieldBitsPerEntry; + + public static final Field fieldTickingFluidContent; + public static final Field fieldTickingBlockCount; + public static final Field fieldNonEmptyBlockCount; + + private static final Field fieldBiomes; + + private static final MethodHandle methodGetVisibleChunk; + + private static final int CHUNKSECTION_BASE; + private static final int CHUNKSECTION_SHIFT; + + private static final Field fieldLock; + private static final long fieldLockOffset; + + private static final Field fieldGameEventDispatcherSections; + private static final MethodHandle methodremoveTickingBlockEntity; + + private static final Field fieldRemove; + + static { + try { + fieldBits = PalettedContainer.class.getDeclaredField("bits"); + fieldBits.setAccessible(true); + fieldStorage = PalettedContainer.class.getDeclaredField("storage"); + fieldStorage.setAccessible(true); + fieldPalette = PalettedContainer.class.getDeclaredField("palette"); + fieldPalette.setAccessible(true); + + fieldBitsPerEntry = BitStorage.class.getDeclaredField("bits"); + fieldBitsPerEntry.setAccessible(true); + + fieldTickingFluidContent = LevelChunkSection.class.getDeclaredField("tickingFluidCount"); + fieldTickingFluidContent.setAccessible(true); + fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField("tickingBlockCount"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount = LevelChunkSection.class.getDeclaredField("nonEmptyBlockCount"); + fieldNonEmptyBlockCount.setAccessible(true); + + fieldBiomes = ChunkBiomeContainer.class.getDeclaredField("biomes"); + fieldBiomes.setAccessible(true); + + Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod("getVisibleChunkIfPresent", long.class); + getVisibleChunkIfPresent.setAccessible(true); + methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent); + + Unsafe unsafe = UnsafeUtility.getUNSAFE(); + fieldLock = PalettedContainer.class.getDeclaredField("lock"); + fieldLockOffset = unsafe.objectFieldOffset(fieldLock); + + fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField("gameEventDispatcherSections"); + fieldGameEventDispatcherSections.setAccessible(true); + Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod("removeBlockEntityTicker", BlockPos.class); + removeBlockEntityTicker.setAccessible(true); + methodremoveTickingBlockEntity = MethodHandles.lookup().unreflect(removeBlockEntityTicker); + + fieldRemove = BlockEntity.class.getDeclaredField("remove"); + fieldRemove.setAccessible(true); + + CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); + int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); + if ((scale & (scale - 1)) != 0) { + throw new Error("data type scale not a power of two"); + } + CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } catch (RuntimeException e) { + throw e; + } catch (Throwable rethrow) { + rethrow.printStackTrace(); + throw new RuntimeException(rethrow); + } + } + + static boolean setSectionAtomic( + LevelChunkSection[] sections, + LevelChunkSection expected, + LevelChunkSection value, + int layer + ) { + long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; + if (layer >= 0 && layer < sections.length) { + return UnsafeUtility.getUNSAFE().compareAndSwapObject(sections, offset, expected, value); + } + return false; + } + + static DelegateSemaphore applyLock(LevelChunkSection section) { + //todo there has to be a better way to do this. Maybe using a() in DataPaletteBlock which acquires the lock in NMS? + try { + synchronized (section) { + Unsafe unsafe = UnsafeUtility.getUNSAFE(); + PalettedContainer blocks = section.getStates(); + Semaphore currentLock = (Semaphore) unsafe.getObject(blocks, fieldLockOffset); + if (currentLock instanceof DelegateSemaphore) { + return (DelegateSemaphore) currentLock; + } + DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); + unsafe.putObject(blocks, fieldLockOffset, newLock); + return newLock; + } + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + if (!PaperLib.isPaper()) { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + } else { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + return nmsChunk; + } + nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + return nmsChunk; + } + // Avoid "async" methods from the main thread. + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); + try { + CraftChunk chunk = (CraftChunk) future.get(); + return chunk.getHandle(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + return TaskManager.IMP.sync(() -> serverLevel.getChunk(chunkX, chunkZ)); + } + + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { + ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; + try { + return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.asLong(chunkX, chunkZ)); + } catch (Throwable thr) { + throw new RuntimeException(thr); + } + } + + public static void sendChunk(ServerLevel nmsWorld, int chunkX, int chunkZ, boolean lighting) { + ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ); + if (chunkHolder == null) { + return; + } + ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); + // UNLOADED_CHUNK + Optional optional = ((Either) chunkHolder + .getTickingChunkFuture() + .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); + if (PaperLib.isPaper()) { + // getChunkAtIfLoadedImmediately is paper only + optional = optional.or(() -> Optional.ofNullable(nmsWorld + .getChunkSource() + .getChunkAtIfLoadedImmediately(chunkX, chunkZ))); + } + if (optional.isEmpty()) { + return; + } + LevelChunk levelChunk = optional.get(); + TaskManager.IMP.task(() -> { + ClientboundLevelChunkPacket chunkPacket = new ClientboundLevelChunkPacket(levelChunk); + nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(chunkPacket)); + if (lighting) { + //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + boolean trustEdges = true; + ClientboundLightUpdatePacket packet = + new ClientboundLightUpdatePacket(coordIntPair, nmsWorld.getChunkSource().getLightEngine(), null, null, + trustEdges + ); + nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + } + }); + } + + private static Stream nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { + return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false); + } + + /* + NMS conversion + */ + public static LevelChunkSection newChunkSection( + final int layer, final char[] blocks, boolean fastmode, + CachedBukkitAdapter adapter + ) { + return newChunkSection(layer, null, blocks, fastmode, adapter); + } + + public static LevelChunkSection newChunkSection( + final int layer, final Function get, char[] set, + boolean fastmode, CachedBukkitAdapter adapter + ) { + if (set == null) { + return newChunkSection(layer); + } + final int[] blockToPalette = FaweCache.IMP.BLOCK_TO_PALETTE.get(); + final int[] paletteToBlock = FaweCache.IMP.PALETTE_TO_BLOCK.get(); + final long[] blockStates = FaweCache.IMP.BLOCK_STATES.get(); + final int[] blocksCopy = FaweCache.IMP.SECTION_BLOCKS.get(); + try { + int[] num_palette_buffer = new int[1]; + Map ticking_blocks = new HashMap<>(); + int air; + if (get == null) { + air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, + set, ticking_blocks, fastmode, adapter + ); + } else { + air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, + num_palette_buffer, get, set, ticking_blocks, fastmode, adapter + ); + } + int num_palette = num_palette_buffer[0]; + // BlockStates + int bitsPerEntry = MathMan.log2nlz(num_palette - 1); + if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) { + bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry + } else { + bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries + } + if (bitsPerEntry > 8) { + bitsPerEntry = MathMan.log2nlz(Block.BLOCK_STATE_REGISTRY.size() - 1); + } + + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) { + blockStates[i] = 0; + } + } else { + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, 4096, blockStates); + bitArray.fromRaw(blocksCopy); + } + + LevelChunkSection levelChunkSection = newChunkSection(layer); + // set palette & data bits + final PalettedContainer dataPaletteBlocks = + levelChunkSection.getStates(); + // private DataPalette h; + // protected DataBits a; + final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd); + final BitStorage nmsBits = new BitStorage(bitsPerEntry, 4096, bits); + final Palette blockStatePalettedContainer; + if (bitsPerEntry <= 4) { + blockStatePalettedContainer = new LinearPalette<>(Block.BLOCK_STATE_REGISTRY, bitsPerEntry, dataPaletteBlocks, + NbtUtils::readBlockState + ); + } else if (bitsPerEntry < 9) { + blockStatePalettedContainer = new HashMapPalette<>( + Block.BLOCK_STATE_REGISTRY, + bitsPerEntry, + dataPaletteBlocks, + NbtUtils::readBlockState, + NbtUtils::writeBlockState + ); + } else { + blockStatePalettedContainer = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE; + } + + // set palette if required + if (bitsPerEntry < 9) { + for (int i = 0; i < num_palette; i++) { + final int ordinal = paletteToBlock[i]; + blockToPalette[ordinal] = Integer.MAX_VALUE; + final BlockState state = BlockTypesCache.states[ordinal]; + final net.minecraft.world.level.block.state.BlockState blockState = ((PaperweightBlockMaterial) state.getMaterial()).getState(); + blockStatePalettedContainer.idFor(blockState); + } + } + try { + fieldStorage.set(dataPaletteBlocks, nmsBits); + fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer); + fieldBits.set(dataPaletteBlocks, bitsPerEntry); + setCount(ticking_blocks.size(), 4096 - air, levelChunkSection); + if (!fastmode) { + ticking_blocks.forEach((pos, ordinal) -> levelChunkSection + .setBlockState(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), + Block.stateById(ordinal) + )); + } + } catch (final IllegalAccessException e) { + throw new RuntimeException(e); + } + + return levelChunkSection; + } catch (final Throwable e) { + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + throw e; + } + } + + private static LevelChunkSection newChunkSection(int layer) { + return new LevelChunkSection(layer); + } + + public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws + IllegalAccessException { + fieldTickingFluidContent.setShort(section, (short) 0); // TODO FIXME + fieldTickingBlockCount.setShort(section, (short) tickingBlockCount); + fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount); + } + + public static Biome[] getBiomeArray(ChunkBiomeContainer chunkBiomeContainer) { + try { + return (Biome[]) fieldBiomes.get(chunkBiomeContainer); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } + } + + public static BiomeType adapt(Biome biome, LevelAccessor levelAccessor) { + ResourceLocation resourceLocation = levelAccessor.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getKey( + biome); + if (resourceLocation == null) { + return levelAccessor.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY).getId(biome) == -1 + ? BiomeTypes.OCEAN + : null; + } + return BiomeTypes.get(resourceLocation.toString().toLowerCase(Locale.ROOT)); + } + + static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { + try { + // Do the method ourselves to avoid trying to reflect generic method parameters + if (levelChunk.loaded || levelChunk.level.isClientSide()) { + BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + if (blockEntity != null) { + if (!levelChunk.level.isClientSide) { + Block block = beacon.getBlockState().getBlock(); + if (block instanceof EntityBlock) { + GameEventListener gameEventListener = ((EntityBlock) block).getListener(levelChunk.level, beacon); + if (gameEventListener != null) { + int i = SectionPos.blockToSectionCoord(beacon.getBlockPos().getY()); + GameEventDispatcher gameEventDispatcher = levelChunk.getEventDispatcher(i); + gameEventDispatcher.unregister(gameEventListener); + if (gameEventDispatcher.isEmpty()) { + try { + ((Int2ObjectMap) fieldGameEventDispatcherSections.get(levelChunk)) + .remove(i); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + } + fieldRemove.set(beacon, true); + } + } + methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighter.java new file mode 100644 index 000000000..a6a1f06bd --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighter.java @@ -0,0 +1,238 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.TaskManager; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.server.MCUtil; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.apache.logging.log4j.Logger; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.IntConsumer; + +public class PaperweightStarlightRelighter implements Relighter { + + public static final MethodHandle RELIGHT; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 + private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting + + private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); + private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); + + static { + MethodHandle tmp = null; + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + tmp = lookup.findVirtual( + ThreadedLevelLightEngine.class, + "relight", + MethodType.methodType( + int.class, // return type + // params + Set.class, + Consumer.class, + IntConsumer.class + ) + ); + } catch (NoSuchMethodException | IllegalAccessException e) { + LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e); + } + RELIGHT = tmp; + } + + private final ServerLevel serverLevel; + private final ReentrantLock lock = new ReentrantLock(); + private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); + private final ReentrantLock areaLock = new ReentrantLock(); + private final NMSRelighter delegate; + + public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { + this.serverLevel = serverLevel; + this.delegate = new NMSRelighter(queue); + } + + public static boolean isUsable() { + return RELIGHT != null; + } + + @Override + public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { + areaLock.lock(); + try { + long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2); + // TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH? + LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2)); + chunks.add(ChunkPos.asLong(cx, cz)); + } finally { + areaLock.unlock(); + } + return true; + } + + @Override + public void addLightUpdate(int x, int y, int z) { + delegate.addLightUpdate(x, y, z); + } + + /* + * This method is called "recursively", iterating and removing elements + * from the regions linked map. This way, chunks are loaded in batches to avoid + * OOMEs. + */ + @Override + public void fixLightingSafe(boolean sky) { + this.areaLock.lock(); + try { + if (regions.isEmpty()) { + return; + } + LongSet first = regions.removeFirst(); + fixLighting(first, () -> fixLightingSafe(true)); + } finally { + this.areaLock.unlock(); + } + } + + /* + * Processes a set of chunks and runs an action afterwards. + * The action is run async, the chunks are partly processed on the main thread + * (as required by the server). + */ + private void fixLighting(LongSet chunks, Runnable andThen) { + // convert from long keys to ChunkPos + Set coords = new HashSet<>(); + LongIterator iterator = chunks.iterator(); + while (iterator.hasNext()) { + coords.add(new ChunkPos(iterator.nextLong())); + } + TaskManager.IMP.task(() -> { + // trigger chunk load and apply ticket on main thread + List> futures = new ArrayList<>(); + for (ChunkPos pos : coords) { + futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + pos, + LIGHT_LEVEL, + Unit.INSTANCE + )) + ); + } + // collect futures and trigger relight once all chunks are loaded + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v -> + invokeRelight( + coords, + c -> { + }, // no callback for single chunks required + i -> { + if (i != coords.size()) { + LOGGER.warn("Processed {} chunks instead of {}", i, coords.size()); + } + // post process chunks on main thread + TaskManager.IMP.task(() -> postProcessChunks(coords)); + // call callback on our own threads + TaskManager.IMP.async(andThen); + } + ) + ); + }); + } + + private void invokeRelight( + Set coords, + Consumer chunkCallback, + IntConsumer processCallback + ) { + try { + int unused = (int) RELIGHT.invokeExact( + serverLevel.getChunkSource().getLightEngine(), + coords, + chunkCallback, // callback per chunk + processCallback // callback for all chunks + ); + } catch (Throwable throwable) { + LOGGER.error("Error occurred on relighting", throwable); + } + } + + /* + * Allow the server to unload the chunks again. + * Also, if chunk packets are sent delayed, we need to do that here + */ + private void postProcessChunks(Set coords) { + boolean delay = Settings.IMP.LIGHTING.DELAY_PACKET_SENDING; + for (ChunkPos pos : coords) { + int x = pos.x; + int z = pos.z; + if (delay) { // we still need to send the block changes of that chunk + PaperweightPlatformAdapter.sendChunk(serverLevel, x, z, false); + } + serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE); + } + } + + @Override + public void clear() { + + } + + @Override + public void removeLighting() { + this.delegate.removeLighting(); + } + + @Override + public void fixBlockLighting() { + fixLightingSafe(true); + } + + @Override + public void fixSkyLighting() { + fixLightingSafe(true); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ReentrantLock getLock() { + return this.lock; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public void close() throws Exception { + fixLightingSafe(true); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighterFactory.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighterFactory.java new file mode 100644 index 000000000..c87213e8d --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/PaperweightStarlightRelighterFactory.java @@ -0,0 +1,28 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe; + +import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.sk89q.worldedit.world.World; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; + +import javax.annotation.Nonnull; + +public class PaperweightStarlightRelighterFactory implements RelighterFactory { + + @Override + public @Nonnull + Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue) { + org.bukkit.World w = Bukkit.getWorld(world.getName()); + if (w == null) { + return NullRelighter.INSTANCE; + } + return new PaperweightStarlightRelighter(((CraftWorld) w).getHandle(), queue); + } + +} + diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/nbt/PaperweightLazyCompoundTag.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/nbt/PaperweightLazyCompoundTag.java new file mode 100644 index 000000000..9e20f9591 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/nbt/PaperweightLazyCompoundTag.java @@ -0,0 +1,159 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.nbt; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.LazyCompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import net.minecraft.nbt.NumericTag; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class PaperweightLazyCompoundTag extends LazyCompoundTag { + + private final Supplier compoundTagSupplier; + private CompoundTag compoundTag; + + public PaperweightLazyCompoundTag(Supplier compoundTagSupplier) { + super(new HashMap<>()); + this.compoundTagSupplier = compoundTagSupplier; + } + + public PaperweightLazyCompoundTag(net.minecraft.nbt.CompoundTag compoundTag) { + this(() -> compoundTag); + } + + public net.minecraft.nbt.CompoundTag get() { + return compoundTagSupplier.get(); + } + + @Override + public Map getValue() { + if (compoundTag == null) { + compoundTag = (CompoundTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(compoundTagSupplier.get()); + } + return compoundTag.getValue(); + } + + @Override + public CompoundBinaryTag asBinaryTag() { + getValue(); + return compoundTag.asBinaryTag(); + } + + public boolean containsKey(String key) { + return compoundTagSupplier.get().contains(key); + } + + public byte[] getByteArray(String key) { + return compoundTagSupplier.get().getByteArray(key); + } + + public byte getByte(String key) { + return compoundTagSupplier.get().getByte(key); + } + + public double getDouble(String key) { + return compoundTagSupplier.get().getDouble(key); + } + + public double asDouble(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag) { + return ((NumericTag) tag).getAsDouble(); + } + return 0; + } + + public float getFloat(String key) { + return compoundTagSupplier.get().getFloat(key); + } + + public int[] getIntArray(String key) { + return compoundTagSupplier.get().getIntArray(key); + } + + public int getInt(String key) { + return compoundTagSupplier.get().getInt(key); + } + + public int asInt(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag) { + return ((NumericTag) tag).getAsInt(); + } + return 0; + } + + public List getList(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof net.minecraft.nbt.ListTag) { + ArrayList list = new ArrayList<>(); + net.minecraft.nbt.ListTag nbtList = (net.minecraft.nbt.ListTag) tag; + for (net.minecraft.nbt.Tag elem : nbtList) { + if (elem instanceof net.minecraft.nbt.CompoundTag) { + list.add(new PaperweightLazyCompoundTag((net.minecraft.nbt.CompoundTag) elem)); + } else { + list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem)); + } + } + return list; + } + return Collections.emptyList(); + } + + public ListTag getListTag(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof net.minecraft.nbt.ListTag) { + return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag); + } + return new ListTag(StringTag.class, Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + ListTag listTag = getListTag(key); + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } + + public long[] getLongArray(String key) { + return compoundTagSupplier.get().getLongArray(key); + } + + public long getLong(String key) { + return compoundTagSupplier.get().getLong(key); + } + + public long asLong(String key) { + net.minecraft.nbt.Tag tag = compoundTagSupplier.get().get(key); + if (tag instanceof NumericTag) { + return ((NumericTag) tag).getAsLong(); + } + return 0; + } + + public short getShort(String key) { + return compoundTagSupplier.get().getShort(key); + } + + public String getString(String key) { + return compoundTagSupplier.get().getString(key); + } + + @Override + public String toString() { + return compoundTagSupplier.get().toString(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/regen/PaperweightRegen.java new file mode 100644 index 000000000..698d8bd0a --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_17_R1_2/fawe/regen/PaperweightRegen.java @@ -0,0 +1,715 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.regen; + +import com.fastasyncworldedit.bukkit.adapter.Regenerator; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.queue.IChunkCache; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_17_R1_2.fawe.PaperweightGetBlocks; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import io.papermc.lib.PaperLib; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.data.worldgen.biome.Biomes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.RegistryReadOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.LinearCongruentialGenerator; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.OverworldBiomeSource; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.SimpleRandomSource; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; +import net.minecraft.world.level.levelgen.synth.ImprovedNoise; +import net.minecraft.world.level.newbiome.area.Area; +import net.minecraft.world.level.newbiome.area.AreaFactory; +import net.minecraft.world.level.newbiome.context.BigContext; +import net.minecraft.world.level.newbiome.layer.Layer; +import net.minecraft.world.level.newbiome.layer.Layers; +import net.minecraft.world.level.newbiome.layer.traits.PixelTransformer; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.CraftServer; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.generator.CustomChunkGenerator; +import org.bukkit.generator.BlockPopulator; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.LongFunction; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class PaperweightRegen extends Regenerator { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingFlatField; + private static final Field generatorSettingBaseSupplierField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps + chunkStati.put( + ChunkStatus.STRUCTURE_REFERENCES, + Concurrency.FULL + ); // structure refs: radius 8, but only writes to current chunk + chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0 + chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8 + chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE + chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results + chunkStati.put( + ChunkStatus.LIQUID_CARVERS, + Concurrency.NONE + ); // liquid carvers: radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps + chunkStati.put( + ChunkStatus.LIGHT, + Concurrency.FULL + ); // light: radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField; + Field tmpFlatBedrockField; + try { //only present on paper + tmpPaperConfigField = Level.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField("settings"); + generatorSettingBaseSupplierField.setAccessible(true); + + generatorSettingFlatField = FlatLevelSource.class.getDeclaredField("settings"); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = ServerLevel.class.getDeclaredField("chunkSource"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private ServerLevel originalServerWorld; + private ServerChunkCache originalChunkProvider; + private ServerLevel freshWorld; + private ServerChunkCache freshChunkProvider; + private LevelStorageSource.LevelStorageAccess session; + private StructureManager structureManager; + private ThreadedLevelLightEngine threadedLevelLightEngine; + private ChunkGenerator chunkGenerator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalServerWorld.getChunkSource(); + if (!(originalChunkProvider instanceof ServerChunkCache)) { + return false; + } + + //flat bedrock? (only on paper) + if (worldPaperConfigField != null) { + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalServerWorld)); + } catch (Exception ignored) { + } + } + + seed = options.getSeed().orElse(originalServerWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment environment = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator(); + LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir); + ResourceKey levelStemResourceKey = getWorldDimKey(environment); + session = levelStorageSource.c("worldeditregentempworld", levelStemResourceKey); + PrimaryLevelData originalWorldData = originalServerWorld.serverLevelData; + + MinecraftServer server = originalServerWorld.getCraftServer().getServer(); + PrimaryLevelData levelProperties = (PrimaryLevelData) server.getWorldData(); + RegistryReadOps nbtRegOps = RegistryReadOps.createAndLoad( + NbtOps.INSTANCE, server.resources.getResourceManager(), + RegistryAccess.builtin() + ); + WorldGenSettings newOpts = WorldGenSettings.CODEC + .encodeStart(nbtRegOps, levelProperties.worldGenSettings()) + .flatMap(tag -> WorldGenSettings.CODEC.parse(this.recursivelySetSeed( + new Dynamic<>(nbtRegOps, tag), + seed, + new HashSet<>() + ))) + .result() + .orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions")); + LevelSettings newWorldSettings = new LevelSettings( + "worldeditregentempworld", + originalWorldData.settings.gameType(), + originalWorldData.settings.hardcore(), + originalWorldData.settings.difficulty(), + originalWorldData.settings.allowCommands(), + originalWorldData.settings.gameRules(), + originalWorldData.settings.getDataPackConfig() + ); + PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, Lifecycle.stable()); + + //init world + freshWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new ServerLevel( + server, + server.executor, + session, + newWorldData, + originalServerWorld.dimension(), + originalServerWorld.dimensionType(), + new RegenNoOpWorldLoadListener(), + // placeholder. Required for new ChunkProviderServer, but we create and then set it later + newOpts.dimensions().get(levelStemResourceKey).generator(), + originalServerWorld.isDebug(), + seed, + ImmutableList.of(), + false, + environment, + generator, + originalBukkitWorld.getBiomeProvider() + ) { + private final Biome singleBiome = options.hasBiomeType() ? BuiltinRegistries.BIOME.get(ResourceLocation.tryParse( + options + .getBiomeType() + .getId())) : null; + + @Override + public void tick(BooleanSupplier shouldKeepTicking) { //no ticking + } + + @Override + public Biome getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) { + if (options.hasBiomeType()) { + return singleBiome; + } + return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(biomeX, biomeY, biomeZ); + } + }).get(); + freshWorld.noSave = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name + if (worldPaperConfigField != null) { + worldPaperConfigField.set(freshWorld, originalServerWorld.paperConfig); + } + + //generator + if (originalChunkProvider.getGenerator() instanceof FlatLevelSource) { + FlatLevelGeneratorSettings generatorSettingFlat = (FlatLevelGeneratorSettings) generatorSettingFlatField.get( + originalChunkProvider.getGenerator()); + chunkGenerator = new FlatLevelSource(generatorSettingFlat); + } else if (originalChunkProvider.getGenerator() instanceof NoiseBasedChunkGenerator) { + Supplier generatorSettingBaseSupplier = (Supplier) generatorSettingBaseSupplierField + .get(originalChunkProvider.getGenerator()); + BiomeSource biomeSource = originalChunkProvider.getGenerator().getBiomeSource(); + if (biomeSource instanceof OverworldBiomeSource) { + biomeSource = fastOverworldBiomeSource(biomeSource); + } + chunkGenerator = new NoiseBasedChunkGenerator(biomeSource, seed, generatorSettingBaseSupplier); + } else if (originalChunkProvider.getGenerator() instanceof CustomChunkGenerator) { + chunkGenerator = (ChunkGenerator) delegateField.get(originalChunkProvider.getGenerator()); + } else { + LOGGER.error("Unsupported generator type {}", originalChunkProvider.getGenerator().getClass().getName()); + return false; + } + if (generator != null) { + chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator); + generateConcurrent = generator.isParallelCapable(); + } + + freshChunkProvider = new ServerChunkCache( + freshWorld, + session, + server.getFixerUpper(), + server.getStructureManager(), + server.executor, + chunkGenerator, + freshWorld.spigotConfig.viewDistance, + server.forceSynchronousWrites(), + new RegenNoOpWorldLoadListener(), + (chunkCoordIntPair, state) -> { + }, + () -> server.overworld().getDataStorage() + ) { + // redirect to our protoChunks list + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshWorld, freshChunkProvider); + + //let's start then + structureManager = server.getStructureManager(); + threadedLevelLightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception ignored) { + } + + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception ignored) { + } + + //remove world from server + try { + Fawe.get().getQueueHandler().sync(this::removeWorldFromWorldsMap); + } catch (Exception ignored) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception ignored) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return PaperLib.isPaper() + ? new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld, freshWorld) // paper + : new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld); // spigot + } + + @Override + protected LevelChunk createChunk(ProtoChunk protoChunk) { + return new LevelChunk( + freshWorld, + protoChunk, + null // we don't want to add entities + ); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalServerWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) { + blockPopulator.populate(freshWorld.getWorld(), random, levelChunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) { + @Override + public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + switch (env) { + case NETHER: + return LevelStem.NETHER; + case THE_END: + return LevelStem.END; + case NORMAL: + default: + return LevelStem.OVERWORLD; + } + } + + private Dynamic recursivelySetSeed( + Dynamic dynamic, + long seed, + Set> dynamicSet + ) { + return !dynamicSet.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> { + if (pair.getFirst().asString("").equals("seed")) { + return pair.mapSecond((v) -> v.createLong(seed)); + } else { + return ((Dynamic) pair.getSecond()).getValue() instanceof CompoundTag + ? pair.mapSecond((v) -> this.recursivelySetSeed((Dynamic) v, seed, dynamicSet)) + : pair; + + } + }); + } + + private BiomeSource fastOverworldBiomeSource(BiomeSource biomeSource) throws Exception { + Field legacyBiomeInitLayerField = OverworldBiomeSource.class.getDeclaredField("legacyBiomeInitLayer"); + legacyBiomeInitLayerField.setAccessible(true); + Field largeBiomesField = OverworldBiomeSource.class.getDeclaredField("largeBiomes"); + largeBiomesField.setAccessible(true); + Field biomeRegistryField = OverworldBiomeSource.class.getDeclaredField("biomes"); + biomeRegistryField.setAccessible(true); + Field areaLazyField = Layer.class.getDeclaredField("area"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = Layers.class.getDeclaredMethod( + "getDefaultLayer", long.class, boolean.class, int.class, int.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(biomeSource); + boolean largebiomes = largeBiomesField.getBoolean(biomeSource); + Registry biomeRegistryMojang = (Registry) biomeRegistryField.get(biomeSource); + Registry biomeRegistry; + if (options.hasBiomeType()) { + Biome biome = BuiltinRegistries.BIOME.get(ResourceLocation.tryParse(options.getBiomeType().getId())); + biomeRegistry = new MappedRegistry<>( + ResourceKey.createRegistryKey(new ResourceLocation("fawe_biomes")), + Lifecycle.experimental() + ); + ((MappedRegistry) biomeRegistry).registerMapping(0, BuiltinRegistries.BIOME.getResourceKey(biome).get(), biome, + Lifecycle.experimental() + ); + } else { + biomeRegistry = biomeRegistryMojang; + } + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke( + null, + legacyBiomeInitLayer, + largebiomes ? 6 : 4, + 4, + (LongFunction) (salt -> new FastWorldGenContextArea(seed, salt)) + ); + biomeSource = new FastOverworldBiomeSource(biomeRegistry, new FastGenLayer(factory)); + + return biomeSource; + } + + private static class FastOverworldBiomeSource extends BiomeSource { + + private final Registry biomeRegistry; + private final boolean isSingleRegistry; + private final FastGenLayer fastGenLayer; + + public FastOverworldBiomeSource( + Registry biomeRegistry, + FastGenLayer genLayer + ) { + super(biomeRegistry.stream().collect(Collectors.toList())); + this.biomeRegistry = biomeRegistry; + this.isSingleRegistry = biomeRegistry.entrySet().size() == 1; + this.fastGenLayer = genLayer; + } + + @Override + protected Codec codec() { + return OverworldBiomeSource.CODEC; + } + + @Override + public BiomeSource withSeed(final long seed) { + return null; + } + + @Override + public Biome getNoiseBiome(int biomeX, int biomeY, int biomeZ) { + if (this.isSingleRegistry) { + return this.biomeRegistry.byId(0); + } + return this.fastGenLayer.get(this.biomeRegistry, biomeX, biomeZ); + } + + } + + private static class FastWorldGenContextArea implements BigContext { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final ImprovedNoise improvedNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.improvedNoise = new ImprovedNoise(new SimpleRandomSource(seed)); + } + + private static long mix(long seed, long salt) { + long l = LinearCongruentialGenerator.next(salt, salt); + l = LinearCongruentialGenerator.next(l, salt); + l = LinearCongruentialGenerator.next(l, salt); + long m = LinearCongruentialGenerator.next(seed, l); + m = LinearCongruentialGenerator.next(m, l); + m = LinearCongruentialGenerator.next(m, l); + return m; + } + + @Override + public FastAreaLazy createResult(PixelTransformer pixelTransformer) { + return new FastAreaLazy(sharedAreaMap, pixelTransformer); + } + + @Override + public void initRandom(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.next(l, x); + l = LinearCongruentialGenerator.next(l, z); + l = LinearCongruentialGenerator.next(l, x); + l = LinearCongruentialGenerator.next(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int nextRandom(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.next(e, this.magicrandom)); + return mod; + } + + @Override + public ImprovedNoise getBiomeNoise() { + return this.improvedNoise; + } + + } + + private static class FastGenLayer extends Layer { + + private final FastAreaLazy fastAreaLazy; + + public FastGenLayer(AreaFactory factory) { + super(() -> null); + this.fastAreaLazy = factory.make(); + } + + @Override + public Biome get(Registry registry, int x, int z) { + ResourceKey key = Biomes.byId(this.fastAreaLazy.get(x, z)); + if (key == null) { + return registry.get(Biomes.byId(0)); + } + Biome biome = registry.get(key); + if (biome == null) { + return registry.get(Biomes.byId(0)); + } + return biome; + } + + } + + private static class FastAreaLazy implements Area { + + private final PixelTransformer transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a synchronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, PixelTransformer transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int get(int x, int z) { + long zx = ChunkPos.asLong(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + + } + + private static class RegenNoOpWorldLoadListener implements ChunkProgressListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void updateSpawnPos(ChunkPos spawnPos) { + } + + @Override + public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) { + } + + @Override + public void start() { + + } + + @Override + public void stop() { + } + + // TODO Paper only(?) @Override + public void setChunkRadius(int radius) { + } + + } + + private class FastProtoChunk extends ProtoChunk { + + // avoid warning on paper + public FastProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world, ServerLevel serverLevel) { + super(pos, upgradeData, world, serverLevel); + } + + // compatibility with spigot + public FastProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor) { + super(pos, upgradeData, levelHeightAccessor); + } + + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + + // no one will ever see the entities! + @Override + public List getEntities() { + return Collections.emptyList(); + } + + } + + protected class ChunkStatusWrap extends ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeighborChunkRadius() { + return chunkStatus.getRange(); + } + + @Override + public String name() { + return chunkStatus.getName(); + } + + @Override + public CompletableFuture processChunk(Long xz, List accessibleChunks) { + return chunkStatus.generate( + Runnable::run, // TODO revisit, we might profit from this somehow? + freshWorld, + chunkGenerator, + structureManager, + threadedLevelLightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks + ); + } + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-legacy/build.gradle.kts b/worldedit-bukkit/adapters/adapter-legacy/build.gradle.kts new file mode 100644 index 000000000..28fef6986 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-legacy/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + base +} + +artifacts { + add("default", file("./src/main/resources/worldedit-adapters.jar")) +} diff --git a/worldedit-bukkit/src/main/resources/worldedit-adapters.jar b/worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar similarity index 70% rename from worldedit-bukkit/src/main/resources/worldedit-adapters.jar rename to worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar index 69042e235577334a9a7bbb278f989d84cd3e4593..3d5390bd5bcf3e51bcbab5f98f169435027b8fa8 100644 GIT binary patch delta 6601 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 delta 206913 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 z zaRdhBUc(hA zz4-g25f=VCa@#MyyLHSj!U5=Ky*%Rd6A@TY^<*$gm&9<$Jzj3SRfyETGf-?Rx_#C9 z`(!OX`)fR_!!b|7PYWc?1DGLgQFjZtc4QPf~TJRfa!(ZD=et|DM^ipNJRDr^(JaQe53O=N=wJU!jBmU##| zrOJRu`@SSxSqTvVO}n|=f>+3=WsuRI#BhR83CSJ{`lBX-gx%ZrKrb!IT76@k=uF$) zkX+)q*6muwRswDvI5XJNOH6KS{j0(D*>Zg`G9)z2XxF&tHnW>}Kxna7wkSfEh=J=k zRe@}($&si+bw*GM}_}8!RV6gTF-G>v*mm zf?rR>bQNOk#`_5IW5+CXj^;qklCx0*TEoIhp)Q>4Ty?(dpPschQb!FToXY9(b}xy8 z%CY;c?pe+?QJo8O#MZ&nlg9F;O>$B0tXgYNPX6r9>r*`Mr` zhP{i%`bEswOn<1kW9?#?%=_aQO?f)pTPf=|63<0aX@x_*0&8p<7!g2yxa~5pq?nZY zEx9ulj+z=_)yh;UzTa)Fiu2?0*+Z~jf9N8>+(J^j~I za6AmMGE*8f6Pigi_*R6>(DSbb;}{@dC*82+zuL%M`AlACPvNCU%Zj_s8<(;oh)$+Q zV|Ct71uUD>a%<7%8`FTiR^1*K`t-CIb-aRp6$o-ZuzvM(_O7VkC?_1AC%U5w32QT?W-+$rx;x19jI|vDc}gvGvpkA zywI!}9R3jaYBza!|M0%PSWurT0(vJ6Bw5NHg*Ig1fGw-&+R-E@%BZ zdw751P(K}j2kV{B4hf z53p;^S$0SqU3vxbQr6w2;ugrt0KF=#cIR8~VtyTm`&IE{li)toCayumZr=vl6VT+R80xMohKD1E{j2#g1LR|ugI4Ho02#)LLnv_GV( zTy@y!=4B*>j=f~zDyr#GZY{z=2ReXUxgNm={Oir!^xaKGaIG9W70nyiJ=J3 zp#uYydlp{w&|)E9#r|sEL^v9EM0O9VieplFV8?A64W_h0_z!3$9cH}iz-?lwh6w^m z(Z?7X%(X$=_p_miTt9-VK0%3??|UII<$Z^heON7*m;ebGHNuCTB z90;cYt=d0UIk?E1+8v8xfwSEF9mldK)snG%WTOH^cM{RRD0Y<^H-8vGc^v&Mde^Sj zgE@DA9S>I}Dd9Nc>xsOyPMSk_mS^y{pPZ@;#8`hIEWSniZnQ3n71}~$C&eOvgL-v& zp|P!IAQ84^WD;uNXu}=*p1qMrp)CLpo{=wzAb*5P-HB0+V(sM%z^B<^WFs~S7x)7` zyhS(-igXr-?}o()AZ))RX1g*v?0qoO7N7^*SvvEj*qx;mWf2dcmW?QvmvbJJ!yF$j z7>_=JP(0BY^}6K!@?*(_oy@FQa*2q=IrfW_1~}R)q7!%B1fk`}dI~p8A;9p3>Fj@9 z-+xb=e7XK18HoQ3CH=`Y9}W$v>R!FZhdw+BRF;^(jC7V$)b~oPV%OmEy;Je+jfYPv z1dDcD4RXh=96klgYqT96h5t-U!Aod~vhH@X^7@Fxkh);6*M0X!s7=3nIuvg_!|&4V zNgmCZ+2o}6WS3B=O}TqCv~{G_*QG0<9R4Ex;%YOnZwVSogi2_YZjulUk7>L1Z%>CC9_AL4U}gZlxO;e+xz0z`?41D1ZjbW&?d9K(`$n8 zr~tWvdASJHO}Xh0^``xE1Mv|aQWPC@DFOM;v}pqS((&aT`_cp|hylcKZS!R#4$3L{ zo$}*bMa{Jh1qf~vwRc$=(tET+^6eD&=_Yri0I$(yv)&YriPi@x)iliB8=HX*^{Z-l z)Dq$x8)PFpB5fg@LOrtbkdtgNDo>dy4uK0swhMFFmbM|}LlJC35sKhU@BxS&+P@xt zebD$3U8j$BX&7C_YR`v&Fsi(<{}SLuRcp?9rO1OeWgc_s&;pn;W5^$$^&t^NnX;*J zNzA+B_b1qUv(bmP z8|=Nc>cboKh49j%QwFM2haluZj`A|dj!1+|X%M~?|1nWB0ZoCqO-%riG|9Y8j>DyK zJ|aQ>V9R0_XNk3@pLy1(O=~4`wdCxvt%l(jptXaf0YaXn{ zk%1DdH{TY40R8@)_n$uu?c`jISJb5@|L&&$rK>aaF?_GP}N2KC^?>MOrv2ZxLHj+4Tl zI9dp(0h&0ZTC8Wmj4o`E(AyKBpCBlBBhHfVCZDr?dmh#&oF71Mus?~S zi2@Pm5vn4|#@N~@y~4Hew6#|0E@VOd7Lmqm%yR~o2-F$|AM*){_CIN!sPRFt-=eX6 zV$Eig^MA83+wpB@m+M)nFf!rA6lXUQDs@rr03LWPp0qk(9X~@JM~okz!*h8 zZLb1O{<06DBnK)F-pHz>!VrS`CBGAX8t=O!p3PLh42ibOwa^tfHmlH=mYHy2^sW9O z+#Y@{?6>uEB;rsC2Y*^mq84o?fMWx?$oO#Zl4DX~rg|Lo{M$KOFHJgKK-%axuiNKR z#ga=J0kWWZ4i z`LRJC`|Djd+o@v?OaJc~RJXSh|{CtIZIME;(J>I{s2m|F2D<_}L{-pRv0__IilqKuUypvG(;AK3K zWirI0OKWvXe9|D@!_5=4)t-FZOWN%QMhi~&dP_)ce-IMp1|Z*_-_9iD6Y3ikrb(w_ z#Fc6HkB5CvuFeMuOsU23pwb(Urp@ z!00AXgtwsoYoh{b`A0W2zB zOq=UejwU~AFz}Q@lmno)o-?(7;>3OzP5O(BSnv!@&!LOPp!rk!)0|JtA>Al#R8MDW zAxF7uILR_>an8)6=)W$@|MPgW#%!06e>~n`_>`ILPyd?bWEf?^QGh;BL}KV~xD1P= z5uaeOOF|-7aPO)sDx{J;|G}gD#$YMyP%BPMV_yB6$PznW9?NJFw5hB1jys+1 zs@R^cc-R!HyV?L@z|g6=gzR$}h(O?^8Yb3TJkS1tT$oIjK#*HeI*&qP-=J=e}%^;)nQC6!_ zqm~?oVo~tx?WkWdRe_F>SzH1LhH%~+4L#qi~sAk!Uo^ERp8 z#VDI{V-kKzjNmH%#0EUqGAa@9Jlx?8tmi8cQFkIG1SJWrSFn_?EYRC?!3MSEu8&kE z;-U2UUQ&R|yJF_*{j)|(Tf*dfL6gv&9ZF{v*ss9D*s=X)EfoY6f_gxZ=#!JyTlOh( z6$j2;b2@?8=Mu4(`@@~uiC-xpdOPNkTu!U(Esf&_(<#ftu|QuNwI-o80T%&0x8$=y zTV^&Uy8H02-qJt4gN&VdCuELswefq=M-rG?<&!=Rc7Bg1Zq!_};>0pQsgW;crFQ2( z`}{kbpa*Vq$b6t6q0DO;&whL>n=WiJ4k+k6xsFnFk4{c|P92;{!Q5nj~RiAji;h#1@TK05!#z{*&X8Qpic57 zy#456CGh4W5$*_&QzlsN-65uyiON}$xDpyyh@ok6+zhC0RY*TmYskBy*-4OfJ%nr6PXn}_rnc!7=`5$#;j z@m)E4j5FhE)BDn41@vEiDF0iVGYL=4o`7seMUnqM5%3>D8#4_CCk2oNj|g1-H>6{g zx_yzQ0ah1Dg{VuN;aAA8bOaVXc>otZswBqG=8R=7yEytkL6qM^TjCXZCr%8Y6%#S?9k(_iO8)_dUjic!j?_ocVk}nB>Y8nZ@A5 zln%@@jsNRd@h2THdlj!C%WK3azn{k$tXrL_lr`ssG z0b0MN4hix#;1RJN$O4#K*WF z{O%mT3>tQuAGk{>Z5pZT4rXhXCCCgd=$GxMuS(EJx@-(wI;Au+z_tigMbz1QpOV!ST3l!cBc2~tz=_- z1y|0}qkTqG(3MO_ia}f)so4(%4-ipew-72n*qch3Q6`SI9GfEtSYh#pm__RE1;s2~ zAU|=RaMqH+0E9+TjlvAJv%xjr!*Q7ZY?mrrLM24JU=%F?zbRaD?32;@afN?`=l;lk zNmTmsw~MV)Qg!>zRwFw+p~yMUH{7_TC9ow3{)(@7vn8A&(`fKFJx`E@v<--O5!cP_ zam0{bP+B@x=ZpCkY9_6k(4?znuR8v*su*hKA01RlY%o2$?ep(I;ypCH$P<8g>`u4dUs8P;Y~ zE%E1k+CtLLmjtNA%~0Wj4^NPHliC{AS;1bkaX90iLDP~JDd8XZ<^INXIz}P8A72}B znus@MFfS6WSpUT<;Q0MZ|nkb6`QrT0ksq`&9dDk@l{s7^HO5qWXii@{Ek$xNcrv^(40aQ&jhIqW|x4WVS zFKU-spexhtn(?UVn4&L(d0Etb5&!TWObNqA@2oA{t;y$>9?u|X>K8h zz6N%S*n0r221&HdM%xldd1kgHuW37ZDd6v{8-);a!zR7)@1OD$Y?4#v9WW%T4+L5l zESSwOWLrYIiWp9ocy|URFA~pTU2rx<8hO}EiE9Wc;pEm))Jg9C@EKoCOXJ-?^us&? z90)fpIl1r0YMY)XtWRwI)<3f?2p@>)izmCYNeR2kg?c8wGUt(yrwy-*Z+<^%!?w^; zqiz;?43CNq)l>yb@a?IVIKdp&Q!8VcMfaEarbjTvNKroi*L4;yM}`e4q^tOfkfK`j zA4FovZ;%=?HOQ;~(^!B6lbIqZvEbk&07N1NP;lajS`xB^1vHk`Dz1 zCyGiDOsJduz%Vbr!f>Q@E#DIh*o5rThLJ!b9YA0n=1;&8_gPyH1M+}oUcPKVaUNki zfj1MyZJjj|c+;cEezzz)YiQS#-8_ZyE<)32;>_v3PP^TkxA`k3;CU1_x+}=atIy^XpWr4#&avSSgyAdA z1*(hVw-K@syX$zQ{G~lRMz<}|z<^UfdQ93i$Li>2RO4&YsY<*-(Z5Qxt3(bhUg^v0 z%d71>7LNqiM$tQ)R!Rv7W{efN@Df&;xwBO%H%WtyZ#cMA7kP%)@T=8J*XxDaW}rG?F>6e8`HMNemDbrfM)ir@XV%P*e^acAxlM^X z8oEiQiK~_2cZH_STk2o^=Zew`S54=l04|p#HheI8`K*fb1UfXV$uB|c2Jd)zV?g6X zMnI>YEQZr5?f?c#AOmsSN?y>x@z`}d@rahEd1v%d$GOMk4ZJ}H?qQ+FE<2Y+2;;sj zdPQ;*(`KQZVNogiQ4YSm)@nQ!==aINA0+P&1g_qwLtUc@* zKi!Y23_6G;=7wt#pwhfq_rx*z>+etEt0)TXzXf9`atoI}lM+^5JyFpm6}VIQ zG>*V2dgtEOG)^fRn@`Zq7mw&oJ_qH!^f~ihR5<|{Nbj{oZ4IUH7);bvU?3<;{0u7K z;ysG17SZR^niUAgZKP-Z`o@dz3UW4TaqFg z5rAR=t=daa9=+ZOz8h=Azmpd};F6q9axYl7pYF-%pp!IeUo{-)8lYi)TFXYH$DK<# zlCQWuEqb~NT%z|fR_2>RFYlPeQUon9&%5l}fi#+4GS8jb!JWySR50`3?U?4I*aKC8 zD3`GP5Zvh(9ZZ@}Z2$|s4rIdO95j?Ls&FKf60Fo-9B|fqktKl({2|n>Zqo^pwTFQ+ z9~6+*uA1U>c$J}5{ia>$_c=E&NHa+;k{fh_2_kWV)3m{REjCJ}$5t*{w?nM^J()ME zvFPDh(hsB67b>fM*0v+v=zC@1Fo6!d&q6~N?-MFa`m>b>0?2we*0eBYHhF*28@Pzb zeDuH!5(Q=wSSep6yQEsC3Xd;NPMjk|mxlicNTN>`v_dM|z9bpSx-(syR?-TVd zEUw7Rdd*OuPr?OI;bIOm3nv1?)qdVaI$ZFKuW2sH%rW(;D+i1QbJ+mOO;`xI!F@rd9o5|wC&nZ6XF&utn z*a)VeMev=2ar}qe*k{=9s|p#b1LWj5$pl_21avX#3aRpW@y{ zZb5rF`O|Eql%|3`(3PJH^0`;O=AxLDp2L|mY1|9jAh*aMr%9!-RnOu$ro{%p^=yZI zB)8r~HWikw3aEIhn~$CSjun~ClkYShg)NKa)eD6A-$f|>N>vvGDMGCOxk87auFqc( zAqThWBlgoGI6~M0PRGTNrj|%1FA>@r%eYa)4~_z|eJ)f%(v_59$`9u(qzxxIk;N3i zdx0nBu@!|5(7u6r!1NE$+#GK(nI@0@@$=mDY{g~oVT~0b=N{@;z8Mu1I515LLOLX2 zJ!y5aw0kXkW{PTKp1dkr{^-hY<{5ck1Ps&vJ%8Tj(Xx}owt$UUwAF87>eP5?R9nkc zjaDwYf}ag2AX&4I#1lUPkv&||iqn4c>A7!N-W6S5O^zdL7(uyJjWU`E%<3nY0^9=S z^BE+o_XgtsoP_POgJ%l1scrdEYaA(mCZw9R+&1G7OsyH~{=q)*?!rSnyn7Mx1Y!c2 zR&zhCRxS%rtJ0j@f<-47$VxOTS!M{Y#5IVo;5BW&_9PpnmST?2<<8^Wpr;!QE6j<- zcpM8}Y5FE|?N%wTkj}luY?(h@=gsdr{kn*`G_UdeWdJ`Q+@ZgSYbgD5bdT&n^5~^2 zEC91ZoG!k(vw#V_lC$#hCs=eR9M~3CDxYLBRL~U5Pr9DGa=fzbS6Ng9KfgA&>)}^) zI$GV(Rvc_Zu!vQ=0+`h=t`H^9kHC^Eef-!ao|cDYn(R&|+OB|HaZ14&fZs5xp7>dE z(ywvDYyi10#wcV8LHpcQ71R~Y&ygI5a8Mr`+Spz+#EBG|GN)qDI@$ZW&$R z&Hug;;eVpJ)96-{YmB|bN}kFSz(?Qn-=_XgmY6;NkUY!a{aK&8(I0OE(;qKQWgEN0tOa@Gy{T-Yvvx;v zbe$&WhQngZBz!ra_Y(f=1$=vYgX!kTE+r6A5Y-2hV*$#j{CnIo1Qh-1{izj(Vm<9Y zC;R@v7}+ZqC!ick9&7IbYUP<|2NWB_763S-W4)K1gAcD^$^_iXQod<2bm+J)XD>Su z0hTDx=OjI@bEj@$#reNyf|ldDcDYcBRt&K|>8r9;Z8B7`|H`n}L)TT#Avr$RU$+kf z(cF5M%u;Tkp2=<=e_gJ>o!7t0Kh8wVkq~A6Ho`jJblq6=S)8jCxOm#9U`xpAq18;t z6D5D6rxMqX3P*^Fooc4tTIv8|uc!NRy7qihRBW5cTiqjEtUj!XtH6F@$d{m^>I@xo zmU>LQC9%+{SG0jA{^-2Q*&AseR4iy-ZEWtDmr}d8S9Pj^DqVhs$>I ztz+F%Q^!cb}y z&arwO!;P-a&opH8MrW1GqN2H)cZtEu2zfjnVF(LStl!=th~M z7(d;j#FMc-5t{ZYDgwc`Au)xKl5bK-d1uDjtJ1oD#E)6$S*OpaWzU`%eZ%CDAI-7U zt|j_vFuY8xpM;{~eS`ZDr!}D1WUp{0W3*^Xr?{>fv`dguu!jB<<^l1hGw5F^m6?@nl3IOzEzIM+a`YU_%sjJ8YjJ?;>tI`5(x0$kq{=eGIW0m8gCFhH5kv z0AHkx5uzAck4<}+Y2jYUKA+f^6Z82mNHuX`{ zAwi|W`1Xl0Hg6r}BOy??$d2%IjE)^N4=7aVfSjf*BS>;r;!3ifU)AWKu4D42o6=74CpvL=lLue}#O)JIu^fo_%(uAKO!zvNtLIg| zdX?$oRtBygJ_%J1#ZuQj&tQ!Q`2Hs6xZnZb_=|0&_6YEgj04#|lDK**>X|8wlFP3f zteu9tKV(`9(_C{lHynP(KHm})7kgaJ9pHo^w;^5QWJi!IF>ArzT!bQr|08EYN=EQ7 zQRv*-TjRZPQ)@2IM?Cwp%8V`Lq_M}wuT`JkiMXIp^$5EqWeglV_>h>>H{r7e z-JPLV5-VN~>kJS|3ceJah~CUaxCQrHmMLawr)}k!%S;9LCy%UJ(|MRXM6NP)K-xa~ zo}yOeMkP znre>@Yq-5cYCxj_e!si%rJU=Hrj`r;BS-Z3@lTCTb3$M_t}r#_XukAE+$Bl?4FzAL zWybD%bzz*r-pqCXfraTq*xJNTvZs{jeu}4QJLx55E6u2rAK@qBHMYZ^!8PvKDvECP;8ZgjGgf#|sEy3D`!)JZ`?&bS{@C=mf8X z>b%pJp{c;N-bDv?NrreOTN8KexqDCG-Odh`ewXN$YbJF)x)qS=$}e*x@I4y#9fs~f z-Ryx=e*;K4@E+kfdhchqIZ1ye{IuwedGP^ury<6nhH!RR4px z%ZNk?nPFF*mnCrC=74qPMQFwLP8q=%@kRrAJw|sp=y4l z1JFCLe34%e*`m%ap7rgV3d-!~+#A}!7}|3{)YaCW4Ml23QCLEycvT4{eN5N%`KwActd zO0UGrJ{;u5?x8n~!_d57fXt5k4CU7BahxY;s;0WnlL=D|&S|{$7VW0)KjFp0_)a-xYCuy@!bwpCw36N( ze=vym)G+ZJ7*hKIB{2GicnbPltgpbn^83`~&Amn_o zeU%@p49;}aS8*ha1Q-X_{Jm7z`k|PUrfOkP!?Y;`r*-iK+#kouS`Ml^-Hj-4*MHJLV`Axg6%aDted=q7dYAT(nBATKru7(OM!nH1NEE z5ynE7&QH#vvN{oQ4QLK?2*7kM@Qm@w0vdO|;aPk0if%ez3tQRn(J5M%;XCxNut=^Q z$4I%cBxyOc>T^6#%kBz&=hLcV6Sgz61M? z@Mb~|rLt*}!%ygl8BW`+exo8`E6s54FZWcG!cJ1Aams*Qm5xU8%oA<>dp&YH-jcwEFvVjBXi)pYP7sWfCF`J#fp; zt+1<6?C~hbM;-Z3{s1)=A|GqWA~!#L1&iXyvV`JlfHCnO_yhE49>_Ln;i^sGX%dmH z1hF0na35fAM7719f=^Nz{p8$9wU#J{BJJX(A)&XLV(G8Z)@g}3tx>Bz`}VBcJhzrI z!g9_@x}YU|MyEiEAOx}Z|1#gQ)0yuKKtmLM8~FdN@T=+|;iRl01|`d4i?#~ zSzZCW(-w#Ic=zK&Ir4)q)NMnH0c9U-elE(N^jVo1LxZ>9x=6W^(dF(u0gi@&# z@pbn_l^>gj|2T;TRTaQh0wRln>Kq-Ni>>?pC4a`ZZb+*=RCVGVKN`z^li@)fpA)wl_ zg-3~}*lUn8b9<~zk#F%Sz;|d|p7#P*Dv_eCc3UGXrQM8CUUS-6&q*IBRsw=RXPrWf zFA5zcoTNP&>)M*KDwAo%wN412xBpTeh*pEXkh~HYX@CB1Ej3j~K5D~kajiz1G5p0` zTQR3#J$QXwlw_W*cjBu@p(>h`okSyS&E%?xoEN4uq-d5v!qzQ^#D$v;-+qw0fM;AKt4*X*B$A{ z1fxLFdK(Pz)u*$M+*>=6H)g4jgx?A4WGTUtt#oH~HFNCz|Q{~v!_Y`jn3Jj;P&2xF*C{W0~#d1i&73Ci9&pK!aUmviOO0_8NiGFyG z-(Dv25sAo@YcGQe_+Qm7@o9LX1~Nm*!KXON{D+PUDRv6T4E65?adPq-A6ld&qY)e75yc4H^&^#%^&>WZ<4~$0C9wu*zc(Ja}B4%0TVRh8+pb=SC~PpaZDkQcVz)s z%cO(#3|hlL#sR?Ql@s*RfMnHWDy>t+njf=gMk8jBl_Mk#M|8)MXL%|BM_)X6BrSca z7p##$+LXXd7p)}_B+ewhdetpdLtiTKYBpFf`Wrt5d73bz18p#)#z@;I)I}sJ(09?O zQi47LaIrl%cS%>>lai0a;?MF@A~j$|!?7Dy$FQE-Li(gFcSDK()h}~WJB_du5u*DfoVcRu#;jGHeU3e%>G(qx@+W~-F)R6{a%fBj*e zw^|*h58Cp6ZKEC9ctB&xK#E+l9RDS5!av@n%*s*bv)BW6v8<4o*_iNA4BCCopq9Sd z*u#T|zOrBOhny7ZJ$S~?0D1_~b%#CAmZGTRCY~kUfq<$d^7F$gj$-Fc6_2v;H5cl= z6}T#yb}SUm!7e#}vrB7)lGKwUnkL>fctGZxzo1-jZ>?C&HP_Ir$isi-KYwH*Jr%-x6An0-{0>SR6mYu z_?}`XVrSyS(v6MeZBf9uhSS;=4zhaeg$|#y3+y?mBNB*T4=fK-vYivDfZ}M;E<~;* z`~jV9NKfBUvaCw$PGNN|O-rCoj7bQ~Z^@|bP24f$iW z4hP+O%of^#C*EiH%1=qV!5V|JR9ThV@lv63AMs=7*-E!5K?ERWXA2q7cvTdujT8dw zp(iPrS{(^c(6=4W9HEj7d}5c&E*vrfu-XvJUt+Ov{#x>1M%3fA|H4K37_c0RDouE6 zUm*!$rArTWJ1&vTYh%eJ`Z?&q7b1VJZ(KZXytnx7lES=*bEkw}bCV@E`)kdz<$IQ~ zexNmUwH5tQYA-G*WV+vCa7pOa_?Qg;Z^Mr1^P5)x6y~&v6!r@P@Q=2KUpuZ;W%{JC zgx?f?W#g4iWoR`>2jo-2!po6rxR57w1yi2)y~3WHTi?7i)6Ow{wQfBZYDgDWwrII( zB$wfc#o9U90mVZcR*CwYG2tixn`1@TPd>WKG#qdK(34@pZh zwJy?UW`Q_1=Z}V*lPXi#x^a2Bmj!wU#rs3KClPFkGgxo;Ze)T)26AtS0sRl$-UEbt z2dd`<=$*UNol5bdiI|E8k7y?u3or1mQWlQH^I`KoXg3ayz#w|C;^;kEeT+;++*xE< zCQt05_?`=sB-6UM`uuVFyhHECydD8-z)&2qH@=DTMH3#9I&*DBQYH9N%! z2W|*lJg~R}9}zr9g?me=f&IeKaSMg-S%RA?JpG4Nr5ER-o4^~wm*1@N zjkqrV36LsTP5i(aTS~TcpE>Q~HSRLLd@kJO3sV#91_vd>5y-%j7h~+0a_&<3XDt1$ zqmI!vCC_1u4b>w{RE}C6SqyPRMdJo2ZthvAr#M!|T76>ez|^jas|lWEIQ7m#Gu&m? z`uCeax3c?rWCfomEz`L;Q-|UAU?Tn}KgE{6y-;AAd`ErXkw(<|WJNrMO&dipPTSbG z_H^ct6?VrxlRUshdLCja92gFGO?{~-<7WreF>kV~;%Q1~CZCLXn|!%f;Ji?(3w{vw zHZu4`QeT|F7?D5|PRmKL-wl_St)I<}WA0ar?&T$q@sQ3xPau*?YUweCWQ}*7M}MIa zBKF!ZIzo_7NTOg&O9UjB&9>JtgEv#XDywgh_S+|kXGL7dP9%(oq|I4CtlG!cjv9I2 z4dNtHMsEpCh@uw1T#p7OQl!?G2O8&!6h%U!S|(B%QR{kfQ&4qI{aXMDw)VHin(jBHJP^Hh2)^T!z-vvx|0 zSAID}*&J_lJ1C@FAQe@DpcX53Mj>JwWmnzFeQluTJAgvoGOCb%=(}i$ z71E~hw+FiasqB9hc1@T17&2szNJsqF(*rr+!?=aqylU$Gckj4!b@>zfLtafx zMh;@kE9A0fVIC`@DUODbb(DdCh;DmV%{tvZ<|Rc1^WMU{u6h_{W-23kiCGaZKR^ex zbX?GRGGyw|F;FPgz*=!bNFSdq_c9&EGheU%y#9rXDj38R(-xPIM#%?|EN>OF*>2+e zEP4SparuD`8g{kcBtaJ&Dl8xZ@n!RY=9kq>^W}Vc1GH#t$b(dAW!hqvEDvgS@!tYn z+AlVu)EPZWb+y%t2hoQ`uo{3FG;K`{A55xFRuWl6*Y*bY1|RmW2KVSmbQ0e*TSwSF zr+OVsM4am?IoZ%}%dFGuL~|L`?h;qs3HyrKzzKX>)x}~ zcvI0@6l%j~{wJih6zDa79NRu4aJ#bK@D>n(u>lkn-A-7_HV1Q)LF#}`1iP#b2Qy18 zElH;;{qn>Lr2q;gi8*VkW+G=V#H|B~>S0g`g!z=Vli1J6|HLlBOXHvsa{8*+2+ZbJ z7bj*Hl;@Y%)9X#V@|TfD1_PelHdT$}^(xwNhbCq>GH)h8b)JR;gaiOO`uI?%pWJcy zp^vE%#=)X`$;Fy+0m7DU9bqoPlV^k@1-8P=6G>FfU!^1#`NqGePOyi60-bFq5>jU0 zW&;e>bqNhspr($A2n6?1e@i%q0~M}k4Q>xC+(wPFTiA~u)9=i~jZH3cnyoQRb%o9t zcS!BN5Q77qHI|y3FL6n<;__UC4l7=#f1;=2W(ywS1#+Y6fKGtV7(-*N&H)d$e<#uw zZ9cKCYQ{Aq$(jup*SPdF&iJB7JbZ**GipNFjtNZknj(?n z+sUEi3+)EZblWoH6Y(fIJ^li21!Wsq=1Vn?ze1z2j$Yq9TUlb2fNB*Ko$idoU!Jxj zH)Dok95>$Sy#Q~hPY=y9m*;H#zXt&CP&#i^a}UZb{XkG3Fkl4O_Z$rIucK_~7i-V4 z{rGrGwC9BBV3ehuk>>#8p!VyN*joN zHpD448)9_KVR-`>UHo^?qBk?dK9R4-Bj_HJ0(Rn^?EPj`>b1go7&`n;=ICaCU=FB zN&%M&5ua8%DSg)2m~NznS7%L^i6r?(3L-f@#$d7Kv`)a#2@5>I9-?tLqBNqC`9Q>> zHE4;t5IMb#Cn3XVhdUfiYG>E>3%9D=K608^9KK;nSjFn!JYMQMj+yLFg>*pqv`kfk zk)o-NtK!}-hDf_5$Z5XgMz+5tcug@2*n|=oMim%GV6YrE$#w3=qs=X+Kq6=TRTiXu z#vhS3b9d>7O$2?ELs5^paH;Udx1!B22~Br?t4owQf484^g{HhA}M&(uPx)Y|xnceT^6#SdgCPFJE$nmfZlu{1}`E zReVaoxDHnnUU#(x}9UM#NuOb0^LuZ~S#QOC3haaE9TiK{#;KB*v62S*X z8{*;l&M0zmuBzjhhSXO;3}MPM0z%T<`%|H^}jR!dU*BLp&9-$F@gQ@EsiSy*If4d)~7|f+M`8uF?E4_r^$Q7bHM;3AG=7uthHSb zn%GJ!e+VTyulmK^o+WO_KR^E3O&Bhd5o%uq>ih67tfF) zQ>!$1JTG*+3Md|qTj!G}o0}OIz0S^@0#PUm2&^V^ae|l|i2GDf*b4~kjbl`!FlS@~YgkNuK7$`{a=g~w z3sYZSE{GqaONO44LQ_3IRb6VaLp0sU`5KhLE42P_1{bUu)$%}I!LH<4@Sx#xc?BK zDEc4?V0td@~b)%ih0vqi*NH*kAHREwfTQP&H8ulq0dDU zG=jJ?Lk_B*x(}HE?w15!PHQ2%h0WTn85|(9t}f$38zE0!h(e2UedC(%S%W>+h+Gq` z-aWOnZi1a5-=^Be*Sv1n5c5(tN->ddybY^N~p*y~~A~_#JeucUd{t;nEah3Tni(Nvt*)@=^ zZ-|SK7~hvs|B`y3nh#JP+hbBT(u`1DqTSjn{mk9mBJu!X}+T#*Fsa&~mJf z7;A|!-Y0P#FjWjc4E&NGb6lJ)e+VGW=n8FMiX(>kv*63-w9Ms2^CVn_Y*Q$=qr zYd*0m4ScMy<%%sx0$Md#dP{Z*U&g?{CUaslP?8c;HB@O5J%-a2)?{{Aq=fPvDpfVw z{)T>`#6;Meut~!j!=}L`fTuLM1lr@MxF{?}<5sD-ygXKB77F(@|Fd72oEOf=OmL8G ze5&!{M;N}sEjE)xO)!5fGjL(rnMaVu9i#!+Mx>a|y-v?6so@!;P%jTlew>M*3S*Qh zO-Sc1%xMfcVaHXDXmZ>O%iH(_%F{HppM)Q95-ix#$;?eHtL@#RA5zaY z^Hrs|$n5UwpCBuZ{*5cfRYb{rZNKviT9l6U>Py$xP09U4=Yv!YW8g+%sb2Vqqa&Qt zuhK=>B~FIATr;mvK9@6v*+mj?Fp-zdAI6e#YI|rV5y1~f12Tn2LO3630JnS7Z7uHf z`FSSp3cT1$+91B$T3@-5D4Q9o8v_geKDQB!Z?92$-bw1C++|D#g3A?jsUm4}%;1@6 zT$Q>~$LKw%BCkH|AYge;cDrzsRt`Wzk?|Xs`co-^s-YfdUlx_sw_=W3LiqU`=-j;8 z7K~mdd{ax!6anmT8T_)+pUb*75ofnovJNY$gVyeo)K4as`KFJfXS(24y(<$Mu*dkf zY(qZBj-*ecj%K$*1`QMux`aI6i@P|iW&8|mFN;Kf)zbVLZv>*1AHQ>QkfpQr0HA^T zJd(dDVrM-|!00Fr2OJ-#n4gW-^1UjgTt2U!j9DcYO)He?@nhoXn_R48h6Eqcuo`}| z6~&)paV~r@a3a+^yxc1wE+IyniR2Yj(W3J&o;W9?d!40JZimZ9OOQaVJQg3DSH*rp zT2mQu`}JFn2?3}y`R-f=WIp3=(? z)Uo=O3}d{XXe-??Bvt+G`l~6m5XV}$w61?|5bwmwroAK9y`|txRqzYT@Ag{JN6Z)u zy~8J5f#~-iLjL#4VE|B-v7d{)X^;z^0gEpy43@t={v6PgfcSyMcC!-kf~Vnf^NRwCI>i9~E- zdN0;(!M;air`d>?3Tj}075c(Ag0B}J3=rx==?IQZoLOIR8U6vmD+gT@ zm--i(d~9en`GJl6UVuv)pOpg~sHY z$f3F9!PJ9!`OuWquwlU%GKla=d}*%n6bWf;oSSNwT@I=m9m*Z(6Cd)cj;q=#+S`t^ z+m1=ADyrI#4bQxP4V-sYWoKs_L${2d?Z5BCsB5lQc~>9JR%lhXl}Dy@d0r8I;FAM> z*@S^g$1j8;9TX=Zi*i>yi>N}ASfXo&#|mhg@aDwJ5sb;d{;WHRjoh5h9^9dmCqvIp zESjvLY$il}Jv({_yam zkbHvZKq<9$D6i_Qrmv8LNauS$VgxTx*)H2#8o{RFN`6X~Zr3(Yl+S4^gzbs?s4)&7mpKb0JEY=wloY75c|47^+OELb+Eg&b|CQ{gh4Bx2fX9vLzIC%zlZ{-kmCy zUO+#zUHDL3a_`Pk1PCPJW8MI)5Lm*tzWsy4m#JyofhMgsv?P);w4e(A5p{rkXJAYs z{ts+(IlbfF8e(%Hz=DVkC3^Qth}o(AB{u%(C>(M#cQQc8hOjr~iI@?0ClN{$XG6kJ zRx~Kd3>8SXMuJ9Dr)9ns?qkZYfgNt@rY(`Ki67~n&?|0{sn-Hr_@S#UmRTBt zIAHl*@9U-}?AG3B__JsKQ~k^ja~0*=MyW47f4HA(5ji_+GTTOy814JT0vxSO$uT3O zX#Xx_Ai0JFD)@vz(TQN+-p98+S(psZ+*kcHFp~Aitc#6O4o>KBtb?k^CEU#InxR~r zB@p4G%!sOQUj8H-MMT~We3}VlD=4%8p5`63YFf#T*i5;rYY8a9T(O?v1|A+0z~$`*S)aXc12!8P`diTI921oUt#~ zD6O=c8-|hN+?NT!O~9?f|AMlKBMqbTp~_06I`owxn1J~%+ZcWd?06h$^+GhK!A94H zwRN2%u7To-|FTF+MDc`c)*=j3kDlaI=g6$It(zG=ktM%2i!k^Cu<+m0QqO+n6vjB? z&-|&<9kC+gD^t0^mG!;b%khh%H%Y()r3*NoXSPw2}%oW5$tcBz(qLh z&Xc$;Ul%oR8{yB~({i&nFzVSrhsU*@fKSJ6lZ9U;&POeo%m0y9nnsX!Zf)9J3QO}i z$bvVfS?sF8p3R7HC5)|sal(2EmIy%oH5iKoCz1|2PC~E%=p8*d%;DM8J!V^Me<-f; zlWBGt&_Z&S`<9nX=L0Z)(yEKJ_IAfVUR*_-PxiIr?b{vSmBYUt%D3g*Fl>o|q?PB{ zIJpd0>t;?P*E5l_ZHRh?Wr{AJ6vFzxz3cMpfHWJYenGi{y)Srq z!FO$jSfW1A3Wlm=S5p1bwz(pa{>@}XX8?%3yI4`%nVWLMy8acsSZ%`B1GAD0veP#aizh6_HMp z6q#OOLtIXr-lDY5yMbG!4lGxfdf5LGkt*$rmx$G3?Eg{dlKZ5Cx7OTLVPv4(Z74J`$Mb)t1tMK`?Nb%G1M zVttWBUs;87 zW-`5)8Sk{5X5Xf=tnjBwW6qnx?QmBTKiOLkkslfFrh{$pd$i-&`wAl21pOO~uT{73 z=C?xe@B_oCh56DiBC@qyUo2ibV@z}EzUn$Z0!M%R^=i8A)2SrNm*6AoqFxTQ$`$!( z`;Fftvgwps(L(xDHVgeSJ$Y-#w;?LYm;kSXqI8u$;{2v$<@18e!>&C;x${2XD|`;) zXSgVIH@?ghK_d7&(*ergLai%B(g(bbAJ7Uhi4V?rye!gPMb<(TeQ6WuO|XIn_fj;@ zsDMWo{S~!S4xc zh(|V0#u;UT?PZ52jS8KpDRvK$t==PTv0{#}6N<}igiUvlF16UcP+^Vcb%ai)-tFY| zaHecuND^!_%b9tq>O_~X9gWx|m_e&Oi*8)8=*wM2QR$9h+3&*pr!wHUB0TY2h1 zY;9)3Fwj-qx-cn5S-YF=ZuiuqM@_BFkpG}%k`A6K0te;PSM8Rl;xG*gcTyC`Pdo%I zABn9C;dJ#l@py-JmxiGQp)xvJrhwhpixicwOZ>5SKg#>gsnotNiA^|S&f6sgjCmse zE|1c3bED(YK7TCbk>7Ok|5%trr<+tYK4mmM+5c0$8mqpOXNq0IUENC!We7g?IzLXy zB`A7_=;=t7+&E#Cm}Cgciw|!<0H3>tcphVNM^8)gM-F352lWWw=v7_Z<27*E4*$JR z$XI=VG?)@j7iIkevpfH&r`UiSV$c*BQ`D4p#Gy?7VRbeasAp3|z_g@pS&Ab%HY<-O z3qqE%-w0-`LHzPXhb#p z>>Da>Zd55ts>i6cEtc+O+W@4(IpLz>H$1^8kE)u!oYII+wbngtT{%oCM^sJ2M`@5Q z+elL4WNu*;S(uGR#Kn_S(X68QwhTWnC6Km-SJH&S56v`sOoE3Z79e9{-cUg!LDS67 zgm(jLQb}RqlrT*p`-{Q0@f0t#bk>i5ZEvUH$CDDLEp5x5M#Y(!#QLNuWQ&CV%o#S><}@LiAfDjR z_ebKzC+VOB^oI}tHV{aw8Q@qNdGugg4jp&EUr~|cQl6zzlUyLJ*O&qQs&_aA$PjylBfx~1|rc}Rd8J2Jx;+@TF(cz3-5ZZEad zA|b_~ZRM=!RI-RPGd_WB{^>VN?l!HOZT%mv z&M7$4sLR5!?WAMdR>!u@j-7OjFSc#lw#|-h+xDdXnX0Los#DKUxn6 z9NvgXkZp)202GBa`ags|2y420AbscWmn?ji!9B6wVMqN#<75!|L*j51#2_(R4avmT z1|<=2{5(kPmsmm5(#QQ1 zI{P8EsjR%Hp}LO_dEBYUcBI8p30ET@si-r8y{B-LuCC6~Qf8T^E;H)RlYl$YfF+GA z_hA=n2Vfl=fy>}$Qf#t{CqmeFIb7zT)z(|B_0mjIml~Z!zra9~0+mN3cSMhUEAh3*KO+A+s8nGw9jj$K8 z#im6dt%UzPQ_D#mqCGn3gdYbth8STA7~g;egvmOodUnXGFb2GGnZ+`{sZQo{KX>g+ zGVs^MYAzg%>pJ7p>g(}MQ(!RNlC?1h{0j&&CCogz3DY9)p*=oW)P~K`7(YlMp`CWP z1dVg?S*OEE{eo^w&&Y(la8h#tBNj{`e?*_$AIyv}*DPJM?3W_%p+1H=^XMR!GsW-& zWYU<6vPxy|QOR_&9Y5BP?wOu@X|2DD#A-GkcS0`Atx&8jkan#hW${XSrYx|jFqBn2 zhga)#n#+04Xw(f-^lxNdWv+J~NE79RG6%R_RW*NB4^kLS8rb}M*KrPYUQ&NxjDUxF zHl%*Ib8%FTH~cin zBIPqDc8}KV2ol3Q;T>{XbI3DNSMdNzJ1`G6Vu?RYBg|L3ofY$WtNG{)Fg8bY@ z_5@nwt&iOo89daq0Zpi9=!k~X4i-*J*T@5m^?{bujqIS@<4F155Os88B8 zm#?7L%Y6cavSz1imUi-Vlo#s^+|f7ds0 zRb#i%`I|-WGe$wG@}OSE7bo#oSDFxaX~-^AY(ui^P@5^{`$*hzs>DAi|Al|4nO^3;!d z;sL_m6r8PPMI)~Px{#ed%XS_@abJs$cF}GP34}unU*mr5z$K17@^meHbHnhG-txmR zA!}AlOz9Y6g_->x5N3+9Ah@7eWr?Z%OIY@D56|Q>RwYGyJ@4LPVc6LQmx+B@EMN%| zX37QzSKeJYS2+je%&+OlLzjk3va*R|e(q<74z8&s9Zf_K@%s3FV4##m$c0<=4^00} zWR~snguQNt*cpaO=wjKFt_!;^Pygy&c_;)WUlz`~(NMG)04rWDQO(OVHwc_5KukS+ zm^R)a_E|*duNePV|p>-fUWEwN{bPK8NBJgcu39>KYiXTchCz4&EkQbn7nqLi>~&>W&Kli(_duwph>#YhS)@ND=DjDkuS?sIL+1Unt?r(wBdKKs;t|xC) zfv3s?%1E`jv=cA%g%pX(FOwwqc&vFZ+qHx~fU-qKlJfz6Jr4f>al^jXR<7t}E_u|c zlf`<4YMcRNR565!DVh|*Hvt+-S(@=82tU6K}411kgJ)Y&67Nb+m)nt|kR z!1O%nq&NpC)46ZpQ}83WE%oO_Bi{b&?g_V*XqnZ)$;(j_)z&R;W=; zct+?<)|c*mC2d8pj_v+rc9=pJAQ|!wIa&04RYr?z(@E-gSt{t>si(qE#Da~`nSGdL zzXRE#{=V{k*a7_LxOv5x{;D$M^3&g@RHli+*-S-YJ%d&zBv^+v2$Osgv=ZP2KstAg zBnkJ#t=PDfbTL-K*xls1Y=eMsF5YhJCjmJ{!pQ2r3zOsg*hOP2zeU~7nOgQUs!RjH zZlBzlV#S=^X&&SON`n&`Ec=~#67>16v8nI#7;c1}3(59#VPq4Y9_;> z*BQ%f?k78M)~@Xu*mtY}sC`6MfU)1_EMAZO?)1>tyxqa5l#Wu9Vzna3VXoVOL7_SnMsm=KLVJD%hObfK z3|H9w;tXJAT0xCSiyJ&Be>ANX_s*&73Ha)is&^uZ+(P8S8OvKZ9ctH+hZfmt<0LaX zQ2u!iov#&+rC7QljHVs}DCH*rfGhH+tR->z0SnQEIUKB(WujDvd8BkFs||{)O_eVN z!Uh;QyT}YF)PhgON0YTyDk#`*643O+Ag0r;N`sYd)KPUi?CV#eX8wXV%UMI2z+q{+ zszrAnR>2Q7p zEUc;WuhPojj{6b;wEr+(HiKsZ!*a;W7eej!!MtFCN`b(vFTlGgZj}^zX>Vfrj2$ zaQ`aJP-Gc)xScaJ3BwSs-vz8pyE322p8~oN!`GO%CkRR4F85W8O~Vch2fHX&&7fFr z6X94mIhC3L&SaOlS#*(+_YP^L+)KfXl#wEv+0#1t4T=G z{9B%TFK*XIfZ+q<-nrG|s4V#r;#RrO(S(a*<3kVu%rquB*rT|*lKQ($fx?L9a(Cl+ zjTalPYFd63lZ+4_gg{LIf5hN>St}q$o%hhaQg0cDJa3QzcY^G>9IaEpe8Ys!>9oMH zC$!%*1=>7|uTZz_3=FL9D}TuXlKLC$UI*M6l@H_Ry}3S!RCupxY_6i)pVQDR-y+Z6 zb(K^=4=PNy@7L8}E;5gS`6&XHqShB!B>iPj-Gg1fv5Xe+XLN|y+b6l30V0zRjM#t^ zBStqk>dv|X!t?-|8*sEQW>Kz=F`96h=G0E=A7J}Q%kbP{Zv@v;n7uKP247;#WCYGS zOewQVKPp)s!?itR4#A$gev(q0`4+)cq*f}(bFwG1->z?(O8=MA&-`Lo2zc1g86I(a{$0rdycQLmK({BM8453GK` z4XTFxt?Ug8CxKqeL2YC^GaSTZ3cjTS62t`WDH)es(4kVwD`^qe!=!jBDfh@Y$oR3L zq(lHCVYk_sPx8S>t>9A$iqIQ01L!MPq!5G5D@wG#7ek`M#+&w066gs77tRe+q8FUk z`>{->$+-~6fu^olQ z08nv|u~5`Q4BUBocA~^JQ%*kua~VyaBwDH{eyltMxw;Wk#F{c*!XJ@x>F-Rn=dPC5 zLYptFLaqt+tSNGTXkf|3O!d+sYe~dpy2Kx`Fsf^BNnYiugZ{wP=*3PK4W9=n*Fq2M zuql6BK*Xg|P!YfMmreV#-7=b7ZjG=C5@PKqn`|shRjdiez6PhZzKE}?=Cw(# ziiLI=1l#O~Hj>Our}%c<0Oc)!@)}XWRzN%JkFIPT;;W8e+??#&;-F30a&(%yr*g{B2yb7KG=Y?HfNbG_FeeO*z zo}<|H@XfNH=NMOdq-=Ea0sh>P#**kYN27=5)xglS+(d+k^N%3}uurN(e!z)&?&6$f zLGBV|xQB}Lk+hUnb~aXaV%p-dXET`b84v^{PgK@6#K*B z>l#CiH{2v`@TtUCtw}`uNCKoMAYN#l&sy-VsG3xszeeT`qxhM@!5N|6+Yb0N@Ja9K zjsobC&4=Re@H6QF*MftdjRWtRmX7#A4Pma`L!RyBLjx>@?3*kr%Cw$_?|6*G8VFAc zRh%b5SvqX-6A3i`9%C0jP0Hg3(r^%Rr4Ry%Y5kc7uPI?-h78q$%;xWj3PFr1OG0EE z%6K*Kjo6Hu=R;TJNKAR6QZFq7f%lxS_JZ(Upk>V|N5&lh3mgW};tl4`i8di6*M2@l zhtvHnVvqgzF(@~>m^XY7dd|Q#OgBFRbdsH8xQ{vTk95Blo!)DLCUR_xCQoiEtR2dm zJ;B1j0z<55Kj9mL)XkD#>)OfsiptpC75v-&&(Wg@Ru&!PvSvv{aN>xRH0>0^p zJF0wLUM@eC^dad}^{@c6bR4-C^AxV{)LZ4S7PK5N9`mf8B#)3w%~DFuQbyhIRyuwF zT0Z{k2DXEm=k>}SCZ;g#al5G*K3jy{Em+0HoySX&R=imDR@u}|DuS^^QSh;4LR2!T_}yT5*R$ABXmT|uJ`mf~u7j2AZ1;r0%g!YS=ekC%5*i|wggaAbe%h}J)#76M^t zNFlmuVULUv{oDY9K1fOjqA8GWz{9-%@ZSo6CPea}`%wGIJ&7bi^$uzx`h12mCUHLN zRuok{0n<5twnMIku9nZ=D?QcWjh>g>*l3u1c`)<3cGMM4x0}wT&-KQH#KRgRpMro; zF;RT1z$RbyLPLSG_AlZ;3+l}vbRLJkaf)2}1h*Ns5QM*>h>0Vk*bh24m+&-IP;}Eogy8J5CY68bHMcTbp1Fz>MsLnjE5BFyu&W4bHsA8l?25 zt`2f?=wf+0>O+3Qm6&o!2V~EG+#l3Ava@Z&W*j&qUo(QJO>jBV-^xVu2dNd&C<4;68qD&IgY&4ev|jG}wP(&mV0>uUw#WtZetO49T%KFS`H1)X~`3E2Z=*6dLv z{(xAq_&rJ*6u@ULMPyKNN`oJl?(m}P4u%+Lypwsr9j=ZfG`gLkObii68n?EIpB zDTe7T@;_Fs)dOcJ-|3)s(*y@>tM^q_K)Nn)dxnS}k#`dVVF=oACZsd(Snn?W(dB3LpoZaZH`|o#nc?sx;U=3LGptNs^ zTk#lzJ#SImz!-w`I^h7>!xKL~^x&P5>it%3=$+B(19tW-oe|5ukZyDzs4cx_Zg`e% zd~Y`pxS`q(SX-e5x8%4n9Q&l5;4!`04&+N^3g-~_}li1YK}H%wk=&okHet$6xFcrO5M{^*_oiks<`2!dUD z#n6r+QZFUiK|bP8`7qg+##Z1Qax~{lS}DMki_(aqWpzS%?gDQTF|}%<){cUvMA?3pSp;R%isYhF`9DdzB9*QL z%UqR$1TDcba*C#SB?_*x1ggc0g+nx{CeAX#A{h#^Uq#%NiBOlwhEk%LW{Of(4oXZ? zWSJ_6MN3>|kBMt1jn^ttSy#O4B4sRiS7wT5xD?LxMgIVr z!4MzN@I^UuKh8$*c{%ep?peYAez98n2nrviuv0lAKQ`}R}$_EviT9Y6jZ1~Hr?Irh8P?3UpL=8LT;{H44q z-0a@Q|KB_1W4moG89uYI$>F$*NxSH`FYrs=1f64tu4uD7hk}~a(+=1+xhabV^tdZt zF;?fYN^#|%Hk%!mS1*y~V`I!5LxlgFrw-UYz%$gEmt`4Iva*JjsVCAN`4izF9q+rD z<0}W;@U)KH0aC~W(vXR742P=C+Wd;V$+t&LYt-9Yw2W&b4cVWWLoc6Yp+ihu``4$+ zL(Fl@4zH#rBv33JHL2l_T!{@$(yJ@<^=vdmpg$F_R;NH}*4<>hy7)P)!rjuN zfW88hH}v(P0iIlkm=(DsPp=R+V(b9v<^i$@x2rACR;w=ucK0 zPufAB|Drzi46obkep6n=*j`M-yhn840qkfl?5@1N6;^(4zHTY4h+lc-P+n}?UN{AL zpZ2phGRiv5D>KjV+RBVc-M3F1(Ny+EJu0VeN5QLT#AR&7ps}w+ZI;*O*tj{lWWSzBg%0k99k zI~|TzT4A!qhYp(hd(5L|IMP0r28VI|D=f;TAZVmMntqv02ixFIlyOH04M z?FDmCn(c*2P*X=;0~&^-@wTc)3E)?Ul%cNe1w@eO<_bbkm4h)NCGFKPzOqKluMP>r z^Nkg7nDw3E#LERaXXEiNi;|8y8yKE}C@SsBti$0e!@N-d3PICrc}Dw-YIRVcHGMf>Otu;cNHU0!#@Oop65*aI5-JTF-K_;3FRKK|B0CnTM- zG>8=Uqk+ThB>Nx=5P-VJzu*d9?H$YM&)U^{IGufW`0B?_P;K6verNa3-M3ERo#4*X zTDEJqRwg_2jYQ0VjQgMQ)?IUdl{?~qjr(-prTgz7p1Z9Op8Ia!j?N(a7FXVl{Vd@1 z&L;bs7VxY4>Y(>7%mL6X?H$2u7q`)O5Z()KQ2bjTpqMide87B2uS*x2%SpqogEuyB zUW;H&H?qrF$^iXH2cN|CFth^LPA6F-(k08e%^~qbTvc2I72H4^r#Ko^3>GCs*lm8# z%+MbuF&VUxQ7wmn6=L=Xnjw=tBL_cDb!(K8@W*`dK@pWe(F~|?-?OLBN<123;C|qO(ye7IpD1v?KPx1jL>O0)AU2?mp__SfO{-Tq2$eE$M(u zWrR^?iT1DrkA6Oz9RG`nmt399Xp^J29zSovr?RcDQCnR9d@k1U%>BGYmLo_|#@*d5 zAIvlmfy3hj3)Lr4kQwkc$$5$ysQ$}Y$}!OP*bhMf`e{EO;BB)Xf^Ku4rC(MR|pu>S5zN(!jH%1Mek&^GxGLG<5_Q`CXCr;aJA z_GunI99eGoTz%Z(Z<7A{xYONiI{37Kzr=I&bRxVkv-fo3zx1>AbOOJa0$6%BV7xw4 z^|zqBlz#~3m6f8u1?P30tiJ`~MesuyFPkL&EeNlUMExxUFYq4%dub--&7mCQ&vQh-QQ*6`aM;Q8s{J;8L+;o}Y74V;sNkkwZ;e*K{#qXZ+8VY^d?MtEl}CxN zLpVSt?WVO_E7G_7EBgp0g$o!G9-x@T_<$b7p}t%}FuXr-+6+XO9;Ww@%MmGo7J_8b zs$DyguHEkZ9`vNdjeH(la#&G>@_FN|cMH`|sV-v^oFX!Oz_xqPJnU%9;j~(}!8tIN z1{qWAB#%*ly6{&@f83-Vmc_#(-H?$kZsN(w7M)G+kzJOgX6mkfl=CUr5D+c`#)J#K zZ;m!HGh*)^a#KP#ePh|}8XSNcY~uEer8oGD;pkaqlO}sH?B*s)Uh_K~l~a-xqyDXu zpMm5PP5)4_!g@)^Mii~Oh;pvRfjZKUQf~iOFzWOx#&UVKp(QN(bS<6jS@MXzKq8G^ z)GnH?iFT3*mKpZHtI2xE003N9R7a!%)bT(o(vYZ*5ofuttVPEOSFq}zlRU@ta`%1{ zsG*SMV&YI^&d4}QF^3?cr{6#B+k8Xzlf-6@T>C4By|ur)*%xKB!cqBJ$L-B!P2AE; zc_VhJ_xcoWncCc$ofQx zxI-4|e#y*&f?JH=kojOEtfL!}*-Y#oWGY-Y%Qtq5T$XF2^t)Jw$;ryoNi5sI6jYDd z8#1`5V69RIX=2a>TPYlxwhCuStG@QuTgONvstlJTt*hhM;Q}o0Rp%3n=Z&&z3X-cV=gq(tTps%bVDo3U&GPn~f4=%2dR1DQwX6hK&o zDfL)gO0I?i-Scyn0^NDDHN1RcHEVD!=4??&r$8a%V%vUE-GVQC2t8`SEvMLu{Y)TO zBY0X_4}he&Ua(v>SBIF~lN^F)(9sG@#K5hcmmK)QfU}EWp|H0*prBpgVvu#krjqM( zi~*4ZA}cKNI2gDZnosuN1ldl_0FHuQGqh<XDwaE!ba3_P$@ z0>p0t+z7?E3#OqX;m_!Q0*BE&HEeA7*4BhAiXizamzI=7QAD&OnirSW-fN$|x1V)y zzqe%AGsX@MQnndb?zi5*u06lsFIlF4zj69D(c*|gOQDN!0>%~o)lM#Kh^TQiHC8>f zhY?M%GeZmiT8I=Gubl%8!??}yd$y?d+AM6R9sqZK96ODTu4vcF@A9>b&Id_?$;VaQ zf%`SD#=Z-u(`p+7xy0O_A#n%^agt}ckXf+`&6mM~(sdSlbh{#kVx!CE!hUJ3)zJ`^ z_d&e5uqlmI4XDaUj!B&1^;~=8Q=AlT^2ZVM;uoH>bykqYDql@miCe2G(hZ(#f6M;K zhz5xc;!MktW1x{mmk4~`FEfZ*?^_?PYp;I zJY0p~x23eJBc;BXX@ug2_rTxfX)@M)f=IPp4KD&iHrL2zW2;%MiBDD-=+SZMKP z(L=6Vi-0JRk|isJ+LL5=aPN54(UG;kQ=^QH%VZ-)<%}DIcL-!klj-8x_o#r zyKz)2%nUe?>GjwC;bpX0{mSE^6~_$Dg;#=;PMEJsz&^Ajdc+B~Q4Uh@L@qF>hL6+U zre}4<8m66W{+ANp?;ul8N;A!gz(bqTZ}{Lqw^|$F3r2O4FTXzUt|9h(YX8=XeF4nxzO$ z3XX67xH&BIEWuws$K(+k$AUbgX;LKIh`VdaHVeCm$K|=$V>w>5x-Ly;CWztep zx+nW@PnoUOEep{MtoVW+_dd#R<q9a@SEP@8?tCN=+oQMSOcJzOckO^U zMde^}nEni|E#h|WI4zTR17 zzp`+zQSR?WgPWbp_SxAkwVHR{@E!hvIy!d{-t$L4?}NfEHnx*oKe$?2u1V9Mn=Wx! zv)`{GY&y_6p-F{Z1hlI6c@rOcg&G1LGYc6j`TQ<_Pu?QeaY(}^c_r=>@((LWj_JxH z#x8~}>Ue}z1dZeGagtt?oXDk^xUut1GSLp__ZK&xrah43?z#;=N6OKem0H;RJyMJw zxp5#~k%!FE4QZC0YSIcOgBjj`DfBnSl@I+7EECf1|5Zic4bASU@%{xVJ zNy`h|VB~3DXh)83;DqNZl`)2z3J{cZTEHKFJB&bEaQ_MOH!BlIm>9>$);=e>u6>T~ z@mJ8zK20j<4xwz(!p_&pDqLK@UrjW1h2>!$j91fQ^H7PG-2O5cyj-S=X(s>iP+gFr zr)dkt2r-<3<8$${5)sO`jr#&j=LlyJAr?oHz8oG9p?hWQHjbe-qzrznn|;DnXJl7* z6{2?n>FBr;^L0j!HFp{|9x10!4)Qpj;m^K>Sm^>zc;C|Hvgk}I(^ zt*jKReQj@;!da*8qXJk8BRbIN9Z!-r;HC1syL@dNc+>BcVld#7gsIp*=Zoy#2Fd?6sr z7)j_dDJb@@UJ~7xNAnBJen2ODb9@rqRGF;Sk^n!ZV^kX4N*71Tl+E0_qowud;Hugp zWBXrqtLnaQY>cI#jT#Bfk%Cgffj#*bCwLme(k!IRJczwZ^0s6^=Y|Cu404ISWcl{U zQ3|%W2OSy5@*D8CZ_zhPL8>ND0oJV3$eef)r z_9l|{JJb^1K=9@5N-%m|)LnrySjMw(*hYxNDdXCHR7mY5QoES_B4f3U8%T$F{ANgc zuxEpp8BQf+q<$cm;QmL9{WQR~o#t3xt9*aiNTmU_}9+*<Qv{{2*!xY0w$19V4t6`H@PR0N zD=lVkj0A9}{7N2s>7ft3h927HU;S!UpDj5e3-F)07!O0dw_7zU2->91#nx+FEenjx^KN- zVAt@E+c}sIJxOX2ZQ$8o+n!L*8|Nh=MCFYV3-F@(7%%zR2}aUK`qY#kMlfa1^5l<4 zr-gv8dP3(Yh9p=~&v6ezx2^T?!P|a!09#rQAJ*VDa{0yX*bO4&t!wZeK)09U?Im6U zsjUY}Q%T^BK!{U+|7|7$&8Ki@%`0! zchzydFDH*^Fdn5MlYqrU=&)u|s z7U!$#($#f)S@Y&oWVx^?wJWbrmr0ZU6a+lTkXN-94oPYrER31VfH9|*leS7M$Iml7 zMZ!#MZ=&|bugl0cy%~URH5$zO4`pC59xhhuEZ4S|H5ae0uhf=V6uG*ypn4ukp~3^w zNmIt~m%a%mj5=)f31;JHDSpkVQO+o#|B^+mnZcE@K-3U&2>0jmq2@Ug+)mG-^ zT6z)QIVdwAifc{oT=}qI&YZ z8Vc;!)+go-DX;n6Y~8-tQ9sc@MTO7M5L0<= zE4CrqaE;4(9d!jHMn?LbQc4G;0pVqXTD0B!2}+l(fBxjinaGTk*$%}vE7St2IB?km z6y+0EFV9!u)J|DrEN)js{-$9w@^~wf{>FwbWi+3CI&QA=wbL}TFmg~9BPi{PEgqmY{$#Zw&Y@Axg0irIMMG25g!Qm*QxIh%k$nS2hKB}#O_pJPr z&dzCJW5;-Q&2H7wlExRGL|+cbWjSEabx=-Vk`z!c&5>2*R{fHQ2+1&dR5?HEN0ug{?0sq&I?RQ4B<#fgRZWY>Y_PU@;OS)9lylUca{{9MR1a-rgcZYH)EX}#gjJ(~xn z({g^>1Tdddv35Aiz zK!Y4{Ri)tWsvBq_^^_U&vS|7Yjm4Y*o)7|`D_Sy;(6S^_5;LTl(2s!eB=^A?3dpj< zGvB+uh6WKZ)_V`pB@klZ-`~a9?Fyd~F?+G8F+>61iu|V1MwGS%73fdh8HaHS3(+N_ z4@PB(Lqwn)t_+`e_r%QFBFUGy=e?M_d}3uE zE$Fm9)BCYRlxOZAFmWk%IGhe&(B@}|T!YRACz%hR9yzJDwni)8jxF_3)_SdjV7^_I zdt>(&FS(Y=dFESrF9=eEG5iB%v8iK}3-S#b;3H8(vR7GcRjJuk*5)vuUf#ZTWLjvk zDE^#mlPA#Izo?AL=q)8xiGK-+Ps*dmx zPye%@1NX-A8AR0q&nYU)lVZ$W<5sZzr7s)5BdLFv2R226`I({fa4n-9v2TSd+xnfN z_(Jh1{WA@GAn=Yua4gauZl-fD@Ronwi0$;MKv#Z-2G?MQZ_EA-nv zAObHl&#AbN+f0xVLN;duQO32!uH(|7OU~sMEDJuM!QE&Lt5UW26+h|17+Is2YoHOJ zzuUER@i5T&Smv;0l0^S+S10Sq7);%T;|RAoNH{prUhyq6{0=tjkbWZ?>`x7e0( zfM_hlgiKHTQgta58v^szLBe8920+Mf;2x%ateN|lTgWwlG}}@_lJnwR8#A$(i`ryl zh{c`VAC(_?1G0?0+P_0!TPHyNAYlfujEvx1^JtS0xIrn5Nx!R^^fS*rpoW zcLQycm)$)a-DCjvKwIIqPt9nsPo2dlGaoW1I+Z|%L5SgS>y=$~FO<6Bpki{yIEnd> zBubv`g;fuX*W3QhJJdcxS?mG2$mteDkJ#0R8zgF1kDe@{Yo7{p(e_$UqyiUUuf-~* z^|$)ZT_k8@ql5dwjHF=1}APw;inbYED9U>-dgkwVx0XOEw2SF12%Y)GxQ9 z^-xxsLie>K@n~F!JE`D{8fG`PWxwFE@k)#b(kk1lb0(7J>8Zea5m~6q&M0T&^bOJx z;lDVoipOjaIRjS6$uZv43!r`gwu=Ppr->caB|`BWyyiAD;LvFh$@CFjWjP3giDOMRlwW-hCW=yg)lVhjJr1`B zkwJyfmDCAD;D8QDChI$Wqjop!5IDE!2t&S>H;^t<md^e{eS8LPu@25$>R&0 z!w2NR`9h-3WD7NvIFNc5yi$cO#Bm%0K1)Ef;k8PAgx5`<-)l$;%?3K0qx2cQHLw`0J{{p`tZjI(5^2RQ2{Di zSDX*zuTs&);;FpaaWV05fdueV+kor1SaKSPsjp`GL24;*GyyKa0e`@Cig0l$5qw^* z6m>|nd+!b;By#yvNW1JIH<=JYf^$WQvrlT7moYb8Vgs+GZ4(1A{2W{3ACTYUF{dA` zeK`jdVt?Gn=G9XLQHj(kt>Z9Exu{V;cRgx&LpiR5EImkO6}=o7@tx&($>0V|?AYDA?l~p+ z4rGZ5JLlP$wnA7kYE()E$AOSxLcQ~q;sA0^EBE5I=U)$YMV#gkzViBNCRBnrCRzx7 zeW3b)+W8y+^o;4C+RVAWCwFotj1qTa7QjAC8P^W%ILHR|aX!d3uQ) zRrhvqUv%H}HW$`yw!V+z@4QoAzM2*3Y7+H7HhJ1fsa$23zRX6qisQrIeLKI)l?+!d z5NyEnCtjx^zD1Gq1GP=@M>K}y^1h*(Md~PLMaziB3l;v!nlM%62V|~*qzCqwkm~kw%7grPY`;3XsZq zkc7fvgjK?eLUobRrBJ62#7nd2m+-+?m?~=`SkmMOxZ&+mR}W92L-O;QW67Sg*}9wQ z&mYR`)WZv%4QT!$nUR2CsNU6@M{XqhIVG1e4Kn+w@Mte7YrR)G0fC|L8M_$nb5mk{ zW)j9Q55XKz8j;Nh??%J~V3ge__R)$0Dp4w&6+=Ws7e*3>;!b)uie+@-=P~@STu)nn z6uGXp(beY`Az-bga+$`VP5EQo2`V|30a-w<3pAvX##QYdVImr=q0UNhTB+5)wt?l+ z`+(a}Dy8q@$C?NVk7Z3+MW-Rs+|KrR#}*!CFmXYZ6GENQy19qM9J=Z0W2=t=LZq)2 z5@wnb(sOCNWo5YH|M;I2^P+>d1bW8>l{QDD+^@yc} zEDAe;nkKDbU;0fubS=|Tm^t7ye9l(d3O~Wc~PG*LpnRGBz482i#lWOKCQI@rQ$X@pX zm30_wXNIDJTO~Jm4<_^IN#~Kp@88(!^)xvm*^}a3T5$H*9eBFQ*5|m+$-*j-!dHSCI;A<83={CdV8#> zJN@7}b!sTE*eM>+#e;@;NPG-Y!I);j)At5#RGBJ^N8jX8(5TE34};m=QZ_zeh(`wt zbMlg=i>8Rj4e?3w1XvJK3ZkSget&s$MyiD^xhz?gC7uN2?OB|y3*iMPL-Y?$@dn$??M*VN)y3W+`d1qchr(GRhA3a& zyO>pNojjLyi47Uy%;(v28Z72x# zcDI`0#*RAp7JQbcPLK^pO{m?J*(fN`phh%XW++dk-7UVbUmXQoosQ^cKPJ_r%&W(B z5xNpqMpo-Ax>iL)-TpSFJBr82kN?Zj-jWTfq^RDo>nwA74_-`p|8RVem@-rW8Bg0u zWrl2#$yQfo5$o6#cB)gaI)8O)=^uAN_ZaUI1HsH*`=)p9h|A&l*lJ(3UO4`X*76|H zy4_@LYSz(iDkESIn%k_wr+ax4hoyrg;=Jh4{@Pww0r>)9qf+SSJF-z^Q4S6YexClZhzp$r)757_gafn z3&6;$Z&+%Zbq6<7EqPwN0kvE@Mj%U*k+e`b6M(ho=CHMW`<8E3TjHjaQwDT4RpyU_ z?1*XrCmviw?0b^0{EhVzB#_H(8+j8m-C66%;2(H|68Gu~QpJu;XQb#(C&9@=AW7(K z@^=P(7E2%eFXxuS@qZjO8&H^rioO5UTW9jBQw>-k7wcBuRrkezT~zrY(16$~S;%U& zuYWKtN_#11w7VkG0QSTWZFLabuLLC{VjUcpkLL&-Ncmga(|d+dj30uWe2h}>vgmq+HG;EVY?!8pX@9Az(0Bf~wb0~3QKGDXwIb>f{n$VgcE!J~cc-Mq_ zunFII9MUoQBBs?t4<(kSl!Ou0z~s~R_<^{Dc4|6uEKM4vP&Mc zo;bF1WL=wwjD5^IfYGzg4ysLGn~ZEnl;_5Bv^C~ZGk;Tl;@GA=>7eKIV?Jw=^_2L4 zfPMVKwD=g$jUZjEmPt()*(M(}LXBVyOw&G0J>?jvho~qT&oX4zq&T5#=s)e4cEL0? z;OlN}_sto0&wkEnOkLy54v*cIOKxjDvRZJ`7=b`KQow=rvbU26vFDu1bk+a!k1|>$ z=fk~`Xn$>crz)5A*wx%KKU{rVX1?l&oi@1Pc#Y=_Bo*mSVcPN`OYmckqa~5j9$yR@ zoeJB9Or0O*6lh$ij$D3$D$R}~hfbL^720rfGO>EMR>$d^`?zp z4~3$ScJM3Xa_f4{jG;dY8uBx0>4*{COXCA+Zhun#WVv6?a;K`k&6kkm&rU+2DAEF7 zkA+8MQl_@irJb)`plcTz+9vHH#ONa`wRMUHnBnX2Np+VuPr8%eUMn7Bm3d^%aM(J4DN8(O{AkVAWNC=-1F z*?-qq^^}KM>iu^6d5`_PUp=SEd!zCxf7z>^lgLod z3D#2{c&PU|G*`XnTkrCsh7KpYY;;+ZABAYQU3-+^(Nc+M@luAlV2e#*Yix7bniAQcvsKL0398larg$&Rw} z7)^dgdjz?&<l1)p*H^etVk!e&W zz9qg5n2TtV_>TB4_Fqrg;(OxziiR7+4?sf_KeSnD&3qj^krX_Qv?EwLL#HBL{79!M zy724hpd$b*tX^WBPCFurjemFbLQe4$@rF(CdzDxb-8?{NJwS6n?%L@yv>2^!nD;Ve z%-cOc=jGT;fYT3PunympbT5o@kzn`CKJU(ukKEvUNQJdsjjQocvRFNSWbn$NM zxwxNtG5P>qN))3j4$+R?*rtzq=;8$7TOj^K&jHW9luMt*TMQyNfPd}mhuR#Zljw6$ z)z4%6^R$=_Q5C%iCVYWb(3e2Wm#LY)42Z8%7kw2_U!w?pjkeP_6=T+dExD90eky(j zc63pu___E6XcB?$NG4)#Ym#wp6qS3rCOpd2>RFULjd_u>z>NPm?Bb{0l!t3Dq61(!M!hJ_pCu*>l|_96YyT8_@-v!9zaTfg3BtdHZ*M~^eg{4O zJ*}cYLL~k~CVw4ChybunP@p40fexDjvh_z65P>YZQ2a^!88mZKz4(jxEB0DW72Sd#HV9p^%eKp+5mlhcRzh=FMU5o zKRQHjRAi{PpV@Dj>g|{6?N^eWc>A^eEz9vaSB3K+{cim}`a^}Q>=kmmGu)Z(EVr}V z?J9CR27l;}ize)%sng=!W%2&ME}GzWYV#Kvg@&Vjfc~*4yPw|OOGh3cCwd;HiG|tv z0Beh~chQ8xY=>$VX6Ix*PC5PTjPWFURsk0jX5SDR;K_?7=JT||iPr}Qc>1DA0P+aU z&=%zs<`hnXlXUBaInFiL=J2i~f7wOT4)V8N-39Kc`+2>fbFVGLX1?6d7rJxZ`7v(o=gnvps8*Ldw=jEvgLrYf z_A`Q3U|%?)-JLVQ+ln5bT(`@ebAVyP_w$v4cG0AYLS>Z-rB!n0?5=RSGl0VYU*~qd zK!U$0oWkI1s7fHhNf`QH(h^8xJ~_C6oPRu-rtlP+%TwtTc2gBkhsBvejeHz6@l5jb z33LUYMAvc&-NvQ#5SP&|o=2Zy55@RodI?1O2A@hl=0)@iUQEB|CG=N3-{nen@lwv^ zvv>+u@o~J2i@BDU@CrVYS8@w``2t?Wm+)G?5^vY@xqK&|$M^Aiewa7#F5bv{`G0(V z9{pcLRQPrF@ejF`f5mP5Z*J%J*c46?N0`X%3po zH&La6QYa?ErhHG!fRSF#-_tT-4S$`W+H0E5Z$VS9;E&nsUd#7sS!fxc-|dH4jvMQ=6zw!XB=qhb&A*5lv9e z(eh0kJrTv+0m~xP*LRd%l`Uym9B`SrS}t(TK-8#dd1z&V|9}6~^0Ar@ zDfqQkfW9ncVG&hXl+p_0l#(rLx7uWK*%-eLj1_k3tA)uF7)NQ7pP~Zi5!yr(Sl6cN z+B7_->-2{D|F7%@M~Dt%h4-n5*`?=yK(mW{fTs8Jt>s#b@95{d%BS6>8uyk@myN*`L&X!&^DMp$G`$@8 z%@qp#EDMBoob5ZNYcuU7rz_bJ+AQg(iJ|jO?RagrjeQ4MekHG;cYl_~_@P7m@!bg5 z`4Qf&X1l|FtcA~xJL!=~* z&MMo_pJ3|eT`_(Zi$7Nn2R3t9=YH`BIs5r@yI_=dm&f=E{ru$^e|>QASx|Mt@A`GT?GM_6wIFcjkUE ziIL{b>=(IZk};y7Fk`=%3`enxTot-o2b21RyVNltW=J0)jxSRX#T@vfdSs7__khlq z(rt7n=ugOnb|<3J3lVz~-wlG@L$i^BoCw=?GT%>2cqg634?uq(q-K7IF5-{TX8t%` z%n#G0{0X{&AAg0d`XnGeMjz$J={|mn9_450SxC!n{xt33&(LRik0Sp0us$D^LA)k2 z%8O{RvW^+FnLOIb+5$*MBdydHYNvpCOK6jJss`+k%Uz`{(iQ{rJ><}qz=lcbjA^H% zPyUIiRKT(vODHq<2wg$C&Tq@;9e=|(XcerB3D8%*Mt^2qTH+(oyX2~e8ar}avO;1@ zV<&KuV&X0>i;0u)UpfBsl*h!v0dZPPoPO@0@=cam0Gn{19W;?UVRt%U_{}6)b6e@6 zY-wj|XDM}XL%q)?fkmsdYJ1VwK@yo}dk=~;)|c)RXH_8K9}v}U#{sb%PF#2e744*Q z$D)jbqJLpMP@>wHSi@YHu}?H3tyY6G4~TQ^!Fv+QFk4CNpP{*bq3QH@no0je-@mAs z{*C19U0O*WD1=u6f!TDTRs;K=fiJn*GObpnqFxn4lWNJBoK;JPw?eh7NZbX*TCT0I zldCLQOIWFkGqBB-n%CZDm2F*|WqZYj(wNvZNPkP0`tTkm9!`p_iSdAk0DQZKZB+WBb1bS$+d?emjZN*@<)| zJCUwrTj>fGX^iLh$b+p#bUP&<>KyDO*aE31k_kNlQjh0=+M0OvN!+O_D8%RI3xB@o zihqf~bKHd((Ge42)wNZ1U5HRJ(X~x=ZAMI~x_G`lO7!)MKG~I5lrJt*gKmW%iuYWu zdhUWpiuYWpdhUg5vS42w@7f6uq6WR3FRpQXUUaY5`o(okF>%Z4QuN<3AU+C~-Txfj zI6ygd3aO7P3?3=#7mvimW6#m-ydC*smw)Qah1pZk(9>l>%jY8!T79_}-hq)T4?imr|H^P?T<`Ub=_2(UY_tHo1@X;mbj~j9#J3>05Lq{e-SU z&U`gAb_dks8qT2WIG3(R6n-PmrkfCH-^>f?7Cr-(cm>@B3~%SvbO$iJlN;zGycU*u zBi)^-dOb-GYv*Wd@ph1IRrN0h?0<8#CiDqFE7F=}Z4fX7Z1p7Fb@(gU=?% z5iI7=#ht3abtlX6mW(nG5>-^gp2C4@=c>r)JZ(Lxd|umtKA+aAnRcr~^Q+bttqcFj zR!|FRy>@G>c8O|Ts$HgCsajWS*J#((;o#HO-R&xIjP)h>@3K9SS2nYb0 zh&WSxP6_l>0{{Rxm*8LlM}Ie8F(ff0F)m|lVRLh>Rc&t@MG$`0dAZoVQa3eU-~R+~2lvxRVa&t0j|ogNthP;$N85Mr{m7q~Fn{RCjpSFlisF$T6$+k=QI|Fom>-jE__B z8M0eqNAN)CF8`*rCEJ!28wB%!+Rdi`?!EvhRO(RGI&GPHwIZN zeOGO+_f^-}=1yYmVWD}8igXck9xnNq$7P1g2lJoU5oM^^=NK zTs&DNgDbe|;R7Gn@ZpG+@+rmSfQs~pV2D`1vIBnjWY?tix20wF7USY)_xXyGMZ zQpivTF8R2Qj~FIIzaLOeN{781leweb{$rH9a)wJzp=(Sxket|_(Y!@Cc`ep$tBhv& zIya*3T->BeR!oOdbf!+Q8*#5C!wu02NHSeFZ4qpWP`U4+aNM;A!`f&75Clq}(*2kQ z!Ijs@9!&`=x_=A{C!Z;X^ABU%)svelQUt!FwXwowkOG*kyGitvi+v$)#6jSKq@4=~ zcYZ5E(UTO?0Tb79^~2MQs(VDlkX~0^Eo>Z8{_IhO`U&-3evQ2R-=SI|2$^*ghizF? zu7MWV~}cx>>IEfI{* zhcIprLrUkDUf|*$^3RTq$ag%_LvOFLOku7h+rPk<2`!;abet~13Tlbu99D@=6L%Ja zUur=90Z>Z=1PTBE2nYb0h&WRUgP-{z1^@uf440u~0Y-l}Uoj*xE@NzAb91d$&36-3 z6#u<8$>d|8rESm@6p(^x3yeXa&{PCUft0jVLaP*{Op<9j?PMmMdD8+a3L@YKD-R1; zx?#gY&yhmq9FA^W_z(E6=tA+nNlKfvii_r)E4_z}a( zO*vzUu|(HcqAkG?;1jyj_7*uzuY$*<$TBD+gfPRJ8P#YT*G<*P=vOobmJ+M7j5S!x z5EiSBq31M*Vcog(q&lN2F4qkutvfs!MkCfqXp(=?j24FI;_~6F!)-OkGa9G#dvsIh z2N~+)?IRM_Gqf(2F==MG(rXnAJlpq$E1YB`>4U#^nS#;|tLgYX~3 zc8Y&AU^!eQ7njkF4hD(brY*BTbuV?D;kIrTlI{IL>|pSxHC1feid_OEg*5U0{`Qdw z64))_Z5ey8w<@3jmMYh&lPb4thV@mIrz-kng!r~!!T}lGNHR1nH=z_KRA*4T#69Fk z#g2Dm9K^d+jp?ad;wwUMm|=^laiyr6TF!q~$9b7-ie_@1Usn1&5ym0($~cNXh6rIX zZ0gglmO4tgRAnp>ObP^!%Q%4)(LtMbRl}*lZJ{NjVVp!-!hnoHoMPB2s8X=3f}tt& zShP$MhC#OFLbHb8W*}#oIh88vjGtTLbityk(=U_~-eWkvDrdAU5ylWQqQ_?#`s07A zaZcM3$@UR}#)ypf@c~1Kj+E()TXvD*WPGKH(#yfhs*a`{bIlyrEpufYA{Z59Jh#B2 zs#S*3RjDSKS~fn~M~H~MR-R%sq={;SZZWIbXUb8V(pFA2MpRoD|0_zL7=+ZbRf3ls zI^NWy<3Yrfbf>Ra;+N_85uLcIn-qV`=el~Hp|O_Hg2&!DAd046dRWNZ@6FL_PaX`S z)@A2_+|!w&dC0Ia{qNIK_?;Q>Lley@u*InuE=B9ciT7 zRefWlxUm2v>2Bglx@6IcpT5mF4~7rv=>*yPNVarzJ_765_y|(R&PRxJJVwJ0q_4vT z`VEjZ*lCQ>^Lo;eJuX58yeGf6&X7f};#KkfQ>>03Aw8^dGwivM%LacHHw<|sE1P#z zHrLhH?i^jRv+l)C4XN@@cAxClknV2hFNofUzy4w8&xj7}n8PcBJDWDl;Q1r37@t?nK{QaZ zW02TIgjkl2Y!1yVkIhU&7aK>073lpnfmx>GDx1V@Hbtx`;ts8S%Ss*y=c&6_35szP z=y#SnIsqLKWaxe~i78qo7+&<~E?xBu0%tTt!(d-{S2)o5C}b|hJ_^5Ntl%omGHXvyH&zvM*aB<8zdwobjANf-5=zV ziK`n05gj&23KF#nz@MQ1DTdAx;Z8rnaJq9IXBoa16!ySs67*y<@UdAmkZfkxme8=Z zQerbCT){^a#i>s)q^$Xvo<8x|auuIec=Q>`YxE6*wZQx5uh?A2=bpTQFT7^m#BF@- zX>Q^hFPAzms`w0*#XnF>0|W{H00;;Gnus`4bF@!LBtrlIDV>)Q^8_=O|1}2`e_t^! zV{Bn_bIrX8d{jm9I9}D=FK;q=xfx)%QB+6*;W9u12nkm(1SA0rL2;4{Fp^|KGU3wo zSkYy@@K*3X`eSysPtWgwJUUh7*Ha_Xx`Egdy(>HJZddEB@t zSX)`)1_Wh+0Wk=%%l)BQzUqofKY;1J!}hWQ5^EyNgGD{;-8^NPt8Wk}ODu6og~DHxwb6mOdv? zom(HMteE8wEME~qkVA*%GrS%aco>-O<69fm#|^!pw+UWec^`!7yz*tfx{$AKU3K~4 z#g_+b{h6!k{q=smaDSD*e>&s~1%lO?WqQ$>c`JOi5fUI6L+z#XgZ?HAuwWnzq7CVU zta>TasbOAa{qjIHLL8x*UrmTqm(yO3puOz==p3H-C<~5;AqYJPkiA9ug!xo^`w+%= z4zYQg+l4sV`4AKxxe_u0R)4PLJSD*>W z41`gq`&Roh>qCJ`+P~`Z`r2CBwM=`z=KHB$>#GjU_xmaq)doWT0%~_dI*ede$ke>E zkWn0E!Dtvm8&c;l=N;#Mw3h{)0*`{3!$6L=APdGJxXWw(zK}mJShEfR7xSwL7Gy&X z9pQ4_IQz)f-~QoSf65c+i6>bwnI}$O7OX8Ne0+3X^fK!B?Gy{9^4qvdJ_`$KDhM}j z&$D0}OeZ$h7$9`}0Q2CP7R-WtI{)P>>Z@0tOzX_&<`XR_INYq4UTDEwZi*$y_HpFa zlPs8TxBAu|ZY{Q80k@`5UuOyPf^`8d`Sl_TPPSkB*0y@hf5L6C1*I$i66r8Fuk(WS z)gcp3K}c`8IK5_PZ5r3i!a!MBrGIK|t#2LAwbX*sz=ziVjpmFx7T6M@UxZc#_QwXCnSpbALHBrXe7 zR_dv3><~r{e=RM|>n8oCzS-he)xW-4Y+z^CX3+nhRCDzmiszXH~ zU-`=7T3@-J7g}$@YF;J!OjwIBwlm3Fc7kV)rX-E zvB8_GeU!mu_LLR+@gU5Q~g!DS{~ zZow6Be;w!p2-l(!))>+Cq$OU&16oLbJz zlJg7pq;xj3=O(zrf;-_ZA_VK{K(NZ6UtQr}f6FJa>&?;qi$cL#-*VpMyWt)a?zP}P zxW7}fv>j}-g>K=Q^XX)FGI<&Iv&ZN9>Uib{;SVNkwcsJxhA{OPXfVgRh!~HC?uc++ zXB*KPk50PI^jG_9eY99-!`e4~>=995ozt@k9QM%Zb>E@rUZ2uHOXs|8Riy@gQR+2Y$y(Pq!ugPOzk@M9J{4to%inq|Sf z+TiLy1>0YvJNFt|^p1EBPr#EV?6cr0c$#$Kkqt*kAS|>JnqE!DtG`PeqWY4yAzRB| zK{yWl8!2vh1zt7bH49#cKOyuA1T$-VHU8SFazd*ta~{_V z0%Urafja%5ZRW*pSORZa@D{vn3zht3MJ&--#OCt5cP;oczcYPxx$8oHvO|}^fBP1E z0DnQSrq7vIT(YzH95`G4MXFLB39++?YYMbCo3;qFL*`l~OQ0bpu9SVfVCSTMpbz4B}l(yQ! z%k*n#nTG+B`uc`+{1HjZ>atK~e}QlPy1Zaj4H>W%#lGc^%uE4*?^vn+6JcSeQnLeW zY>~J;7+hZIrxjLJv3E;iHM6L`rlvBW5Bk60-zNNE!H@70k-3Zhv^i##zbY75PiK1C z&@RDu2Z68QAn)7HH2csf!(v%|wPy7wV0uTJEHXmSw)-PX4fqMOk6ytMlYh( z_iqo^8BT@W0ZgNuj!IAz-Uv_hLyv4N_p z`jAiC_va05wdzh0>-43)e_cn85L=_z+d?lpWFGt0rj5(C1pCrHV?S-k)46uz032xI zAQG84xQ%@8N@ODZrrYwpJA(9xID| z$44{Af(7~0I*@9P>~Q3U8^_^z6DL@hjX4N|yBuL3N89deZpotK*wvM%14Q3^M&_P` zlTAESP34vym0f!h92Fe_MDW7Le!Ftx+UKXs*IC$GsWQVz$#+heYX*91{!42s^yZ;6GAv6LNY zYH009JH8#W)hQO9ic9Uw&;`|jRrUV-X#~Cn5}Hrl=)*D-f6FbbK!1zH@M~gx|EhXl zB}r;edyeH|UhQ%VSFl7IY?w@YqN~~-a?%IfHsWhmGXjkpr8vmD9c0)$Fu?lPW z5U)a5JoMMN2F%GXEoix|tQLu`bry#79cM+rPtF^zwr~xS&uRIq>*{OuH6a_tZLG@9 zF7CT|lVIX{e}q}T)*YuC*WsB=v)>@3cN<$4jxJmshv(qACjQpK^O$SXyT1FkeSXaf z?m*oP>N_V`;g7}h@d6WnXW>TtJ$Wyk!6^?`FAFTMueE~*1c_A@@uke4GY?^Grz*Am zu{k{bMHXI+myn=8X~FabWI1R50zgCbSOT{RFEjCSe+#eRtKsp#6c8Efg>#n{=jWZc zbjH-8;yH7tO*io>Izxv!a+a@d1>ecwm3R$r;z_- z8*rY9H@3JyqIMH+ZoZCe1~FthkBP&Z@Ky_NW3j_d{E7Z`%jdj!8xi2fk$C%lHcMzKwUZ7kNx~ zyvT(t(O$FgbtH)tTSuBx!(apHm$-E71mT+&zJ+hwODc-^EThOF?;d5GG1_i_*TO$X zf9&=)p%y*>?_2l*k}t0+NsH)6=&;f}H-3bFHSuE$Kf#C_Z{KRKs|)$7Xstfx2|EUs z){c_SZ>K4rBXqs+Zx()zebc&^VLT=!IQh zwA-iif4ZE~Jk3fjY_bFha`a;g=FXfsoowI1+Dq`&@P=hp2A3~q&yvcqr)L(>e-rIn z3ZRgdP{KgyRpndh&kI(U`$CHXp%ql)0Dg#=v6rm_+C+2WgehF6h_!@U#32mqVm3NY z{@PkLr#OTDPYelt4XkmWZoIzr+DUPzl z(Skhpq1_Gn6TWK>*N<-DG_)D1a|GhVvEn#W47J2Cw#x^0(O*l~@|DD~erF^Xf^5yg;s^hly=xK0#VqF5~8+!|K6&45OAeZX4;K6TERSY(Nl zMF|4x@Nv85TK;_haxyw=*X7mIgjG4QqEsv~#VM9JRV+p5*VzyRf8m=;MqDen#P7lS z+HyZj0J}zx1xZAoC^JR51p!gfez30?H=fD<2v zb$efk)j4QW;T9`Jr75Z`SS{E9?g+Oz&25An`JLTJSZIGNVO%3tnWEN$>qK3f3EN+$ zp0Hcc+e;o0^+cF%e@B;1u?AtlVJL<5m6h{?foeKN)7O^!YxsbqfD-H27FgdZg&{(U zw)rArwcr~J5$A@x#aWg(Tb!fCadR|YdyhvD1Se5O2lTg=IFBP1e6<`&Jg76p`5aZt zXX*DlOKcRsCx5)Ixd*Ae;-I$5P8`~)^zLq^Ob(~uFg6!if8t`{kj>d^@T4V19jdHSN+$?S(Bc-;!`ta^W-JBJ)9hbw{+-8Z}`EDX9a+5w^ z`+jwNMm3btzzq`x%`F5U%*2pwsP3 zxQ@{NgC({y>$;qn9pUT}+ZgAE5w7~xapp^=HV5p;vEFWpFgwO>tr6)2)RFxVD;gQ^ zoe0>ZQT%wZs$RNjute z5*vJWW;!`9Q@dpzw#u}Q20@mHTRdfnr^SBGob_=Az>Pyo+go6_+x|O*IQ{tY(D|7wYk#V33*=dZ4CysZ&O5G2u| z;E%*-7JMT9ro*v$dE~T6kj?af4_}&u#V((|%FLakVrKfyVeW2Fw7B7h(Eb*iG$?*^z zMi|?TltzwN&hu6JLm|H%kLN3mgO>Q2uQX!yl?I8|I=AQv*O(Fz`nH6qIh}!UFB&U_ zeJ+K{Pf94L^-;x8EHOyMtbTTrDrQ{$>4wgqDc;-zl zm|k2weQELBrMU%j^G?*Wa?S7`a|xdk!2+R3pIcgB?CTDUyS*0r_uoW~RC z+T3Xoo5C83A8v%BeEflzEn%CZ~5d z8pi$y>z=9k#Ea?O3%=R+5^|=Te`U&iOU{-jcJ4X19BCUm)BMYP^_9(e9vywR)p6WU z&aq^noNH&rIwz6$PA6C9@o_!LlJjK|nf00*ii7ss<9`9G4a7qf5)`x1yf6w&YPNd;`HLBMbitXQ9qV9XIk<%@+`Im$r{m- zHX?^9&!H37akh^sf7?M8k^PYkGsi;OhG6X&w>%f`;6Dxg=Uz@6|I&?*%8i!%J)2sw zfjZmMt>A>RmzXy%w&W%9Qu2cMJDfsV<7$0_(K`ExvyNJu0^DAhe_O2YFid$_n=D@E zJq>MMjZ0pEfNS0IDtWajuVIe8wv9b-cvwEiJ_ZeKld0082Oa-Sa zf^}yra>*MA*L5y=6BX;3inmz!fxNYyuyEUj@(rgvTi(v0e00e>$!g25Fl7VD%vOZ8 zk))w6c@Ig9dBHkUf8K}CvsE{~(9oS8pi?kEXdA*QB!1MUI^#^oMqQ^`@oDT&;Vwf$@}lf7+D$EqQ>yMM`Pgh0L>H z?exkzKjXq*7szK3W^^WNwZ-49#K~CsynMlwFIw^?N#577?Hx*ezespwM#U1ng>#qY z&7D`Wbb4WNKBvp)7B8KVKfPcYDTSog*H)%{wPn0rlY_bZbxZzmi&kOsyX7(jB%TU z-}W%(yU$|z4ec6aviSeblK*5-PKu1(izUmyEctKwgMF|xH`+1Cu2?o!{v`in%7d2t zSsrSW$6LpTzb>4iVOM#{HY}Z%>|}fi7G~#m zw$Rq!qjl#4lOx3=$E}2lBkNjOq!wG`J4acbtX!yHf~69*Qs>Jwn+$Rjj39neDV9oA zJ=o6hu{v)f{n}GlU3W<#hWL&Q3)PG1+#4aIf8#W{Hl_(Jt&i$!s(zO0uLd|dmmSJZ zTN(zJ&moVH+;T>NH;A=Tb6UmGPX2l4nI7?KkR@iR!3YC~b{@h_fdn|1ZXy5rh#=(HTQSGCw#2Cg zs;Oo*Co$MqjqEaAw^b$sZA8HSX1g%JTPZd_U3s_%5-7t?->&>gFgXVNu54 zQxfaewam&aT1h)Lq&wd=5pk>amrHuc@FV)niP@3AOyx8$9-Y_<3$plZ*rr<0f5IZQ z-F}n5|1_PDA6Xx*H=&K}AYKYZW!HAbOC5>gELAE;>QKWUu! z+o9&q<68Mv?Q6r96HLxA)hdMKHXq{9%ypFk{%xliUDoO723adb`g7T=)&sgXnXO@3 z1mq`n#T|SG95>;#&NsYu)?Oz;<_>P}SU2hOEo=Bk6k59u?VKfImddsKe}~zoa(zS@ zw*0z-C3~osmOs$aSOh;a^LHC(#-KG2s`dlG2I^Ki+JA zNTX)zLs%HJ>F?zF7-e?Z>3ww@?Sla_0USa%=g?W2mu&xrKTu|?zRbtINssn+t(o~a zZGjGft`{8sO(=r5szvzue}w-sT8@xlGdm3~raAe`ymq7a99UlA;~zvD*hQq91J2Ik zGmaz=kw4^#BuH^+_b^JLj5)82m@eKQo!HazM{SC(&hB zXd1Jud0WgfJ5z*p?U5Y3=ItU;L7P$8-ii3%si~#yPxmV&jPVT9f4e37y2f;J=PWa^ zQ&GhLO4`%MztSQHw-(c_6c6_qeuSdD_3;7lo^Jv&=u8ltO6a==@ z1AO1rztz|l$p);0eZ+|(aVpAhfR?+#kcQ7kSa{a4`#62bU zul?CTIk_p$*%I4YOR#f@raSfm|9;5wYT|az{f^qG_M_}te|bbk##j2P$|`(ASYZ#D zfN=f)AG6w`tT}xK-I2xAwOYY=gel$q#H)j>#f7avjzgH(`YYihNZ?$^rkHPeTjR5x zJ4*J4?fVKI(61}^gqeWTYnpSjx|X~Mi#yh)|Gneaf^J!d)ANr!S$84dh_wl->0b=c zfj8fB?J*jGe+o^iKCLl}1SZ)|Bimp1>fO4R6PM&ZwZV|?gVSig*71**@Xu@*HGxn$ z(N_f8`Bl=60)hl?Tx-SHHm=)Fh?+^*{?$y|NhYSXA^7pFK4;g-+I+7W(7!;^qrG3! zptpV<r5l@NPlRoqhBfEMJMG3gQ2=mt*>T| zKeQrPe^HkhW2Cr^RHKJ!cr2r*(Tk+(;S(09UL9QN&n(cCq7lAjK3*Q1IaSe5&qs&t zok8{lwmPaCQVR#GX<~v-&4@(-p;r6ol(PO z9D^{bXl0?LN=skz%3xUjNu3aBDd)_24v+8id+crteLb0BYEpa ze_5)@fJD<6!%t%^^>6h9J!SFJI7@x1KBK1z{FH5}_tg9JG?AYsS?XQ&XM%D9KTWaJ zo9eA3Be!+ju&qhuGDzQf7F4gS4z8&lWDBT4WUdhE1Z@UGn`s#_hNLI{jLVp98K&V% zGF;R!hZ_nlLm5VrVUTjpudepj=2iOYf9lwcP|o*#cFM9*jF9`^C(Dzq>$qs9v8d(0 zThK*X1s(sW#0t|`jBrf1xYsn6v`9;BuPcATuRFtFa6vCXbu{#VKKyk*0QBKs=%cn{ zs4mp8^qds+d>o|kuhLUJg@5{8m(yUnE@wf$E(@G;u2ar;$^}k&vMvRGT&>Fte=(d( z{>g7$p5~NUFiw}*kfY1V&hu1V4hJHV8cLWAqo>87=>Gu{($e<=hC%l0zXQyNG|r5z zo5lfDLp%gkI@Qwb-YP@!+*E^UY6PIJ@j8UA@k%(4J`OB<9_-`Hb75L~7~;+K^fy&i znCo{y&$LG98-~Gq;h61kT-t6(e=peq!?(l8G`Stdrr~xNpQg6MM1DLWEqy!WruEwn zGt&BRhuIp~c9@f9gWC@C($XG*Ip+H9P{g%)Tw9oy7KW0?V99nk^>DT9@EQvtmHyTe zg-fWm7IX!vt7KbgY9s2+=z0VGgXOeOUubVJ-Doe@AcD!&z_! z{2tDXqSJ*DI$fxD8+EOjP8T-QNoCU9_I_kiAMRxk_A~B}pctbY&(@$VyMqH7)yQ@S z&u%^VymkkVfyz8`~W`TCQ*gPo@R&l48>O(x$a$pG&}iOErX^cCQFoVl=bT z#A=Kh+YTb*x3)s$iuMrUe=j_p!8g)W29*QS_d`GC8)BJl;P2Nz3@aPJovpko3|yUq zbEd(wsAF$z+qSW>ZQI_BH(zXXW81cE+uYdr;*)#tse9^os@|IS516W%?&FG$UdVC3#NpyhJ9zTE-4LZ4?nvM>L++OX7q9gAa&UjFJ4iqUB5jl zZBhGP=e`nyhVR*L|FD%wFiAvl>uvAf!!h0s^wA)*8`bz#&*%fe_*kChltxC{ArnX7UaBA=sW7j4(v{CQJfQFQ5x?I%X3vzAUAv!L#r4 zlb2AUEZhb)E4$kX(3oDSK#U+MoRFShJU_2|)a*@t+;n_C?DoQ2v(@LSgXCMe)wi(t z%kqZg^VzlZfeoYI$-BAVt{|s1xNw~;PBy_-MW`de3oq)fynFc7clh;l_?4NR`SM9r z4%~waW@+|8!P^n7D@xsgjDHpc?j?8iJzk;&aaVdj`cJPAASVk!aR7N$k>A;GaFvZY zKHGKAQ-z610%e1Uq}k9wWYe$REpULu*E11Qy{=YlN{P*%?OJA$K3NgovRXR$Z%&_W z|5h;!fjK#)Jj)Pke!rb(iab1uq!D)2vz-qAW($fN`g#R`9l`rFEX4ThChG>jjKSpn zgIPZZriOM5z?p3ql*%QFPZY|9+dFi`x5lDiI?3sAtq}G&JSqX4wY{+QYqd+1sc9?O zu_z5sXWR(OW)i#i{Nde??I>r~|2M&|!LuLRvL8Ep_K3Yb6IS2rr>Y{hY!R2+jHp;@ zU17+Cb>2}Se`_b2f&WUG55e|wQJ3WXm=lP{51@N3AT$n!5Xuqxty{Wahm^PHN8~O6 zH{_?B%)dXp&qTdxH(0H>g5-SjhW z+Q$n4Xrp9LuN_nyT99kHA;xL2*;6!wNZ19!PKq)&96Srf1&K6CWnJJWyutFN1gLtH zxD8lR{7FQeF*&lV7N{}=bO>gg@$KF-zZ2aLgEBjvI{@1UE8O~8eueD45!~bqyyxUo zY->8=BZNT0kC=}L#Gllg z)lCMo4X23lm}59Yw+S~oVA-=7Y2;6?ZHEfw4p|;+9M{rF`j~vw9v3yi@m4ouInOx* zut*U3nNNq`Pz$26*vE)P{UMjD@dS~e=-5@(Y@Rrmks-I(`5`OQSxvA?wPD9HiFd%E zj{Uab>M=!X$+|t}`#^MUf*so~R4-a!t?rlbq$~oKP~4BrjVaF!YHEzzz9-gbB#1Lp zVg$CBaQ+LbSgiF;Y&~Nsy(BgmGF{XZunY0GGzB#@eO=gUdxwtn&0E|IP~-Hp;wQ^K zvheMl&4QY8sYD8+tcTF2Y>oSxIX3kpN;@0J%5#acFsI#F3mG?0A3y&qJUJypv#+O# z9jm>_viH#wb@NHxpUQ#OUCGvO8NUs2As%!sAM}j6_a@By6b!o>3nK`ExhKpGz<>@U ziVeW!$DJNA-Bsad{Q0g*HkgA<^7&4^zk@^anT7KIf2bs%O;zClU&%ox)uWhEAcM$m zAp1HC4?bGq+7-+}Ee{GGg(-9~7GlYG!4-21x1BsCGNB*NVbPD!JZ{o)3nBvT0jrU- z6VZ{HpD)%wMEfqa2moQGORS+dn7r-wWR416*pf42t4P^V%oZ4kj)UQR&a^+VEgh0$ z_cHtUVl8RX%smid+kasGDA}rCnn5rD$&5?~bu&Skh-cB`!!c%xnQ$xe4i8TWx^0}A z&8W~W{AE&*$Ho5P9O(Z;-u=MX-}L$!J8Y;9Id()8&Z18k9|>^a3?|oarxn0c3r6)5 znX1q627)_uTi>rO9k;v@tKMNRpG~*^<>6BsX})&p_6PErDR`F zuBN_jC7`RWDujY!+TN32P~h&3Jcu9UPZ`4p;aprU@{aTRmwRMzzmiDgy;lSOYcY(> zPTJcFF=I%KJ`RvLuZ!x^Yy5Ng*;Iy6l-oX`cqH^R*3mc~e|6pQ&Y_Z4`aGu_JTTO{ zaEc;R+A=^R>SdP5azNRG)J*9QJ7zz3COnS^>YS~wSoA}vnEH98PfCLe_?=poj`%sd zF>UoMzfd^;WgKT!Dz}M2tQju%$2mcs!lyGDxd_6oAR0gv4I-B*zV9gPWr-NLf$$GO zF5eRLvW73}2>bO0Y3o!4Zo_EL#MYg*T>~InZn-zLv~S%*>AXSdO!`%k588OyaXXI634wIkThU+|ryw7- z+^xHdQLP_Zi&j&zAV#{#BhS}XthgZW^UH0YK`nHqe#;;Mp(U|!GU8xeUmFwa*-5l> zg!DE$?n-(!{24xgU}3hAVGHeF9sez#b49-EvlKuhpURmniMclm;eGCc+@2conNq+% z)-l9XD_gh2*>McfCjqI2%bMy6t#&lyP(8Y}C-0QK=8iJ>c8$5)DsjYQBsIgp-fBEi zn~^iXG^cQBA8*ZJnO)mCo11p0ji86zjYgow&^L8KGpY&@H82CNba#X zssOkdmURp$43&6izC?MSG>v8)Q}CJ3_S8zJVvI60qgu;K?2?|(z&}Me5J=(TiTb)} zw?^-h7X*>zcix6TqU%1uz1_GVufgk*CaggDYQpglYU7xv`Kk&`U7c5PuI!%ZG?#%6m{fNO_Az8NEklwgfKlrx+B&MZ!L{;|&!u zAW!^ep|DBeC%{W6n3H8F9g^~ z_KOy+G=$O!D+-i7JZ(0RAmSlX6kVeS56(Jow|+PgG<;sJX|VTb8M7E#3{6x*DEiDN zZ4i^EaZGKDx=+a5kt>_$aZM-YTK5nYBO*lxZw&6&az{6-0r7@TjZrq~e##wz@2)Xu zq@G}P4C?q1>_-V0Oe7wLKa+c@eiz}n@DiLv!C+Usxya{t&bbB`b|PU0Twz5gY{dzB zJsdk-K^9t%@q2^V9Rpm!W{z=*2(o}1+_2#tnN|wVM8MMkGH={I z;?>3o9stC|G;>-4_qluF>JQiB@-M#OjEje>3U53G$K+#q!fx43OLstAI~u|B0JCER zSqjwI)%YpnLMKf&gd*t=l0E%HXM50Ct}77oIa&o`!oiD0DDLfpbp*!P3jZ`Lh1_V$ z*;r<_1c*g>ZNhz0MWk3NQ$Xq}Xk6A=^bS%1?of5@YU0)ap5nV0e)8UBo$N)yia?h@ zr@+9zup5)(h2WpEv&*KX44H+5zJ&BT)z04Dt!*{^0>R0kuJ5he4qU5y3R|rT9vG;F zYe@d6Y_{;eOm{~jk6bgU@CaZZMKj4^w45j)r(yPtk~=_k#0 z=E;`Lw9#{5Pv~E@vs*DQxW!Ioj$6+OByPGsP!Z~>c)MIUavip9_gV3XmDO#Fgx}Q& z@Wi2co*27nLk-zgXNIZ?Y4afP#LC!X-vlc;cr-iQ2GPBdXa#o+c)PJ!jcMt1=)`zB zu)mS)MBDD`*af%q12Ek<08y^}MLn3(J%lI^z*q5z2D@7^V*5gw!CUrjTM0%?$&QIP zEIOPp1tTJFSm*~v=3>3;aC68n-_Wxo6%QmfV>S00JlF(7-S_SxP9bqWNCo|w<`!%p zzG@D1fmH7ZJH3GYjGw!@{lV8^Vm_J=y+V<{d<>)dV+YCK0b+6kGlO>T9o#rQ$b#=e zb@)B#$v`BO{+bg#UwO+jJZDH>S&1e0!=X=N!V#h&G_~%ZN9VvqAodrT;7H*94zNLP zn36{+7oRa`vQP3z4TbDJmiT&CFi;H+E5(u5{#XTIwbr%6T5JJ&?uQCMx|hA&ZZMPpK|UV-MMlc zW28*mq?8tYKn+EJ2_2_gEJ{f*z6{JHN{4Q^oJ~CCwF6_qiVF>Jrja3FYFT_@k^c!5EL z{dV%frY1=BX~NGW$Zmj1gj1uup{v#{dnCD!#+fIE#7)@kw`P;2BbTy`*=kUwX&E=< z2CY{Om?j^fLA(ltUvexR{$kwaIivp;vpZsyShyMiUwNMtqV7qvHTFEYR^`z7Ut;ACD9!W?^uLn z=gbD)8b9)tQy7u>mTNTpINMZ(=~63hfDXC3tO^*}AH zG?@&lgPP7vhP4^}WS1yxC~rb@yEvv{8<)*~)8NcV4^y*7ag|LFFLA^2pSg6mv0}#i z`#?s4$0|dQl+!J#FE&HPsAyK$mR|at952 z;~LPOGb36tC>23#(cXOlzuC1hsj^_ExCCGiMSHD@ps z7_a`G=ZO))w8Y_dmSk}O6EPMOvI>VLGd5oTL zuA?!a@DRudGm_4F{;5hh@})Y&4~U^L$p7Mq|H_`fbY}M(IZof_^SampWW)9)QT~My zXov65ULr(7-Vx(pxU6&XAb*7AjK}*8A)9@LaGH>EsPTm$nQ=yfcJ-OoNKQze=Yp(P ziq$=do=4R83ahtx4SjVs2ND4Q1jL8tPO5Eq^ctZ#hBd%P!ng94S=#NRdo3hV5@v0R z@t<5_qu_1i^xEZr(+(0kFWMe*4n74D0syBwZ7#;$$LD^!8@%AB?)}Qvrx$Q${GWe4 z!Z-a9#+rL3Juz&l`vPxjn@5_QN>Zpc!&cqnUi)@ybGXeF_l9uZ7* zJfT>;txj_ILo_i23Iz^$SA_vwy^!sV*ZUU2tX1c>QpC<1sy^_4hTNIO-q9o?uJ=4{ z9sg4M`kn4ea!bEMr-bF?#gLD+p&ElHYbtwIp*uw;DU)RCl5M|K>1gFScn1X)&Nf{Z zf)rKZGZpzS#=`Sn(OoTT!w!Fn`&j|kTU3UL9 zmPvJ)QVV4B>`?oP+j&~3R?qvO4&@U(yovgw%l34-YcVWXv!1w&wf43yW91o649Pwv z_cLq)EP)9U#e`s@r7Gbm6PsMRy_e3^IOx|mWEa|VJ|K-1G{P(aKsihtz=^3cl+h?< z;9wm@U*wh~%EX0oDDt0&2hp0&;__MPuc##p+4#K_+dd!f3G=Yc(q|AN9#85kRj zw7j`m1ZC)IprU9##mM{T&+?)F-e$e6xhQL=@?C zk@B>E?LeFXWFKO7|4!yw(Rx1!;TkEq)lbUY7P9xq%`vG0i?}=$CER|RJ;Lg~$WYUd zT&M6*7`TRJHil+$e{d!XaG%YaB57NVwc_m%w>?97f~)n9-GFQVGtFX!r}&J_$zqjl zDRGJl&Mrfz_Hu$f^o{gw|3FS%6#V<$ztj&gN|sFs0IR#xs^5wUBy^$9YBZkJEFN%X z1rM3nV@|i5rEwvjj4Rj`yIVbG!*s#VB&8A#o(xfEg`Q4G+*RShsZLSdbx4DbcERPR zuT2vQB-rWE8YXf8 zp^}N_?sv-G)3bXLD{c(gpGn#xs5R&cE72HnNWaXRZpJzCbMz+u;hN?m@r$Tfl{8O$ zLHt={Y|5y-?TH)8kZuy=1|e%~!iznS5&8kAQZP#h$TX2>!Uj)o zK!;89lY$aa!LK@auap%DLvB-VtHs6_>jtNmjVuv5^=W%#uUBLfdwS3;mr<4F;8hRH zG%3E2AX4DhvkaL5bp(buyW-YS|D-=scxuP^{9qO~{YkS7Oe&1urXL*Cy4 z)GryZ{TtUvyN z?E6}ztwM-48ZT&THj_g3IFkBwENI_(n{^b((j(3{f5?Tv{eFQBAt*$7|i`>!u!6IA)9IMAt>X`p@4`GjQuxvh+vWUyPk$eRW+o3E8I*O{LZxtdFp#Mh@13cwU>>t^;dA^!F%A+XMo702gq zt1DfS->J=&$>jR(aM!hbVzdlcp7Nd$EPy!Pv2u^vKL}N+|K%(A9mmIqdN{obmDkam zoHWXnK`_+8mR_)B&_KTN_?`DZ4pLHVLBX4+Y#bb~BaHc@RGTXY+E!6b>v_jeeJ9_M zxA$76FUcdeUM2z#ikS-l%#=u=Z`Wxo-sXe*zYwig@7R^18o`6IhvDlv8$Lq*`JvJ^s3GKN5)|7LUI^)1 zU=Xsv5$IEZ22oV)9FUuZpb8rfEkfGXuWILR&}GtJFV(uO1}|+<7VOhE+h;2Rkjh7V zP20ibW3;4?A<+ide$9-b?f+%E>xsk}Q*Ta`^XBj-RoR$+%gWs~&a>P&R-Bsy@J8%- zi(_ukjUzD&dlVCEf5{%wAV$vQb1aAiAZ;k(Q?@0q$rPUEqSeo={S+=LBXGI>Xl}7C zeZx7I{`j?v$aqq+!pu`;sFmH0qfv*nl=Fug@lZq?SNjT}{n4b`OWE0{(Xpp>$+C?- zf*I>^Y z>LS#cE6c~-{jH&;$O-0t!LNI~Cl@m$CV3^*4q%F>0J3Ta)Xdg-V;qmJ7+3p%r;Kvt z>2*We9&P|?4Z7j~jXm%Fd;#UD00cw5u<+9h1?%6jdC2)9z8zxK8v4RAjn-HX19~g& z=sYrve29CNI)+}g=E&2 zD!VE}EIaM#Cxtj}^Kgc^@xz2~1>LODF85rx3Qz+ip%rlR9-2ho@LP>2s$tVjsE^KZ zG`95MeP1Wn!!yXcJ5#IBPnlQZcs27%g7m(sfdv-&iDurizQV7EgnE)xyFmQE;Y+hI@8Ta!?)P41~ktpCnmsoWkLMCjF}iLzuXK%}N1a zOeFtk#`5+3TBzUW_Mgib@;@|z&KL(w;_t+3fxTm!cI8bGB@C?fa`owlTl=nbQ!SK;^> z$fn(ev^3Z_*p0=|GcuC}{8rfy^*?fNLec2uRj&#`w)nrb5K_5m>+z@OS+D@#v(qEL z*#l~(NL%jJ4q!me)}s-reDWP!l6I+hF>^^zG%tu?Y zp6pxsHOzJS!);X)z?_wx2~6DY5onb?=Ak7)x|J9FuylfB)i=_7EV8~7o& z1NKm4#U^FMj;2?#$?mIbPImxC6F#rb4bRS0*8nOzEXFU?&|`Q<2joRhm&x82 z*g!Z(83`@H#H=&*jk@~ zjCHE7%LlK5&J?knopcy0}KXn`0c0YFF&Wx}Xh z3$Y{uLx}=RiA+JHGJ%t|HaGaS%0xV9>pS+K?~SA3t^nAoEZAp3m>Ho1(j5x96x!+T1b46*op z=4!$mDEFkew6laY=&QOb{|JZ}nF_XC6e>L?B|5h741SH^e8*SGiiLv+~eie{UgiKO`=!PUErCx2gCeIeB8a<$2YCAbi zn*Yh}cIMqb9dgSgH5b*DjkB>B)ioQwv$g8crLKf6%I)mQfn7pb`c}^JQ zYd`5D(QhT$qTeWGjS3^Q@tl-m87<>$tZw_4@j{Xkg!2IF;LVC{XE8RC;_$d=XQ>LC z%)$N~<6Igc3SOchmPRxofAfEO2}AQ{egzpdza9Eq7ankq-reNK>P`_!h_XbX-`=8_ z1v}(FSE)1H=#=L-=Svq6j}R+>;)l8f8uJ3$FA=b_l4b)+@!*L=Vt;8V#U~#GnFocN zv?90l2q*ziU2)vVoLHX_XbGF|mkohtDW9`(qwt|6SOMyB&9a{PLbPE}jnr^TE)YtSC?u{f9ix9kCj8?sd!<#gwqplNl&d){aXtCzN)hphla;NjR#od_;lz4I_W}R61v}s0o#v_TaSt z4vLrlT5Fn%U%!oC2TC!Fls$4%;;C{3@&EJWoid$ap5UG5~}p>CGjGh#)m_6?6hqRbi2 z2@pv6d_)*0E@U1uRd5y_Ajh`z=dLqRW?n!eA5G&-v)LPf87uya=m6hdzt z^`UIp#Zr=*mNi6M;$83N#0&z-`$j4s(2alpqqjU>FE^5pVK)YBJroQkSY$;!Qj?*d zIxjFs+o2+?)An+fmi9_mkR6dj@E)||MBz9KBrJwZ8ObOb7#SD5LJHvWGZ^)|lq|*4 z+Nn52M(Eb1)tXvc(cgtRxZ&JBHKGuFH=&Ug6gN6Z!8E83ajKz3q$wRo{R~(@#At!# zMAgl3GKTNO#b^RxW^)=}zIwJ8hzIs+PjMT&mBfd+lg~S=io_`~&yq0Xsk0k%@p&nw zXN%)tpWGa{vD3AR4jF7pTV+)CPEFOBEvYB`Fb=-^rrva$liwl*YC}>+$er#!Qs{Mmg8_2i>gM zjf#MFN~v{GP&Sf{E?HCYwx_MuSx)azP3)j3we3F%%$)NKI}}mwRX07TT$q7RZQnc{ zVU|FTDra`eR_YM;80B>i<5Cb?Ad}b}&sX9%x48wt;CWjT1Bj1D95A8^XibcMp8t(g zHy6eCg05snchiY8q11C_);_(>M8NQJZ+WgA$GQBF>OGKz({q`pW!xp{Pw^qK3$-uy z@K9ZcTz+

-^_pHC0XLDXd|HstV`(b7%l4-Jg9#J!wBE_sh1J0XQj|^7d18b>|G0oyG>xeU z#=ozvF#8OjBe9k@k@*t*BUQXk7h3X;D8AwHWPa_aG^$Q-7#LW$(#uJ0zbW_0?xox+ zVRDW&XqfqaGnixUiC@K2wbtX%B$CC$hHOOMtW{1;J9QxMCq$$MvB5t=FX*rr?XI)} zq$dqi$O=w@TD4nJgAdFdB6xF=&NvIS7EZz5e3A>rK3TVgg8e>;iJ60`&hkTat<$r( z9i+V}s2Mp}!$7dT?F;g>$R6R56ZD?tMkiv_IaM0OLVqRs8fkJIgCD*H6xZbA9oVC;MNC%9F*y+Oy0-wNUyyS28 zrW=WaAn&y8%TA{l0n3$qXic_Cq0zlxQHLK8Z?<&NOHDkyE(Z#*)Fw?%+tBb-VjLjd za6ke|_hWBHf_A~ojv$DkQAIeH^28<9)`K?%(Ny)L~ zadSN^ds^1=T(CYAT>z@~Gx~GStDHBQtu5Tp(HOakddLXyNKQF71nVwWAU3PpEdKcXW=J%7x!SO;6>RgX543@A1Yt(V#i2 zV@$R^eo%F;LN|4r`iiy0$Y4SeS!;>F_o#kCdna<2O(#{$Jd`mSd@ulznb(>k4j#ZN z&?%F_B&)23=N{WZbu7%4IuWaClT>BlRJt9uJZIj@uD2+)urxD(`g1I>nyTF29ZM6+ z$VKBlEf0Ye>-SJFFgld-sFNEaW%Nl!GHzRKhaK2&gb)jyg`kajLWtfSES z;oqOkp8=C+W6Zk!Yke2s??jP0If^}!l@(MTeiZ1!8a(ojvO@UC z1_(JvpUMi&&JLJq% z_t^uRxu*`J&xJ6f)cV)MqQ6w=0>is&sy;z^6<7|?IHn8Emrzm%u~KzB%@q6 zY={#sRq-bXWSCYv=d~wWBBRQbrh%49(l*$BBEi3uKw#?XWwh6U+?9WRqhx zNnLO+=uSx))@%R_cIfK7!*REQLBwP)~X(PX&o}SoYay~>b=bbNKqjo|v9XbUu%oIZ)O)DAMMRWxaL#Bj^Z~ZQ&owGZqG@$E zPUA94a;XERB=vdB5mtVYNTDzh<)b!Jl9P$K{t_L|iO*V*T&`Qi=0LxlCmma+im`CP zw>|b1{S2UK?RWL>N}!?wr6&%1AuvmYYIrt;gM~btX@KQ<1Mdp2J=%3rL!#nJ zj(m9jpsEe)b4@{>mC!(sy-NjlK_hjn?wzRpg&qd#XaptWkIDjweBW z$i7eTw|=QCDO+VQ)&EkqKI*5QqoX!4CdAoTwi><0BaXJp#?!b>{g3-QZA9OY}7HL{BS zf)ik&7pkiqyD{r0aUq5m0Av(R(w4gwcuxdc4APO1TVj3Ue3?W(AxF~Wx~+602sLc6 z1!A5%w>(<)M2N09ch+2C`~uZYU@2e_L;Kh;vpzdJU_hHb?l zcih$hxhxcbf}dG4LC7L6h`=D^RQ&p27eIq}f-(b0M$}Qv*KXI>AYe@lN{fj}Xa$Zm zSv^DR%9dNxPZMQ~vdu7|vZY*7N?b5A`|2xii4I z(5rsJhJD^11Ht#u=a6D-|3`lSAz_7?#Jn}c9lh0FgZ38DWs<;ziqPU8fwS~CV5$&q zSJET0jb^o9Vn04TkFL~@pB^JV)yLDWl~Z@`;C?L*n2AnXzes}SI}E}IPr7o0omcUR zn@!9gZRvR{h?@S3E9r9+LGYbj=eq#U(0%SvT+{z=?Zsp4cnmJb{old)O}ON?_TK#( zU+~j32LHx>R~Xrkq8VARWH9CKcYurNX$f!zYIRQOH2`lH680ud8-z%|yy9khS2HK1 zW^aDz8@f2VO2*+ZEb}SgFV8*JhkfjGvbV||jPkILk~xgliZqi?G_&X1>Bj~{nBGU7 zmWIzdoLmowqNNntum&O25uxQfG`4(nwtU1-Ww&jDB{B-FH2bvU8F5av24Li%RvrJw zNW#0J^SBEEHmI20AL9{2&b)(s5=G35Qs|LZ`jxkAY3+KlOoX;ighFy+<#VXnm~JyZ zdtMjO58hYvlS}gFH#I6S7d)z2B*KiHOpB6U!O3%do2M-kDt@^2Bdd=Fk*v3JuN|_E zYN#a3KRKM<@tZM8)n`$% z^j>QW`u-K)4ry<^Qf$5{#XNU_*#w&I{HT8n_gh!svpd_dh!(fy21qh&mj_tR(TBOl zc2tb(*Z~Mz?$08VyUS>I7AbcY5q!Ibboc%c1Y;?`X!$?y>pC6_eS^U;$gB4!u(Wg5 zE_VD)K0lCq{`_@)p;6l>+r$tWzdSL`BvH)6m~kR{3tBXarN}7g<0%2nbj9uE=>YX` z3UMd^xkr7`3+TtA1}K*nueTJN_5A!@`5XLFkDfUvhl)9nQ#W5fGm(@TuWXF6IVUH( z6LK#$Ex-zCD_h@yx|mv;ZKT|G-A{mdT@-SkvFptwRV z#{vQ8CyTWhJV4X@4>wpX)y27aeLPY`e(#eA>VsTz%y(?ridI}1yOcGgdt&R$&^3yJLuI+WMH`r{G z)e~0Sxdpo?Qp2yMKi}wi5x;Ckc$Z>+m)oBM>y#y8QtLEl1v>A>Y~1@(%Ga8;s;RoK z>opac=x!q`ibMxYhs0H?1fpkl9qSodM%1g+JjwRVifRj2MN2B#&W^g+9zd+o<9S5b z#Rk|TB8b5MGh-*s8ck#RhC4eTrPg4>u%!xpPj!PCvZfk`K;QtzMXBQoXYFa=u*DWs za@SK*k~AVz9Jw+q#w@rl9KawAfy2+beN=)WGu-wVg517$fd`U8Jb?uV2eD50x0~|o z!&oAS|Lb3A8; z>W*y}FXePA$f)U^$ zOA69%m(7;tP}uO0Cze<-VZ}k~E_7H-V0J0#<7?H1Sr-A!?65bmSFM{kEK4-+<>t08 zRaO*57c=RUM68X14soY^zf|4bS(6|||3r>$HL<1~VCw9e$q$>?2DqLjwHHg3jsnG& z)%3SmlQkxO)mu-+sF^@&$Y@cRayXc3-7rbANWo4x9M~in|8~+4$P>)7u8QKVH2qtH zCUd33IZzKUV)1(d%X-@K%MFT)Vm`ycoH5IrO58l1kLk$nfC?I<#QC9w{a!3-z?!#_ zD@^wm5@S`47cb0%naRLyeMOn~LpY3#yM;(%~fG}hl0Z~ifE)xdB%!jR^ADRQm^H0)wx`Fr^Tf;nqVMJXOz?}!6 zx=L}1jiT1U?k|dDRg0tXZo}bXN6j>jELh`lkwwj-&#|Px zdrOWq<^h6~;21JE&v@k$GSzT#QAhL3M^E0`F)fZneF{727Fo*2zhI=PSu9{WQm5lD?%a8aw=5x)ekCb z^JowDL>7tB!UVgBASkffxOgYEkRRtQuTW1jw>IglJ7ZYZ}CZknUMO$8WFG*Rto4JzsLdq*(RHxQa- zBKctIX^g&a-Q14wKVN>_VYR5P7;Bf=QK}4Ly4r&gwWZ{iDty_q^c-E(=cQEusy3NT z76l~Ao$F=#y+O~0c2aM-71R&V36k6v7B4gc`C_atj`j-^onmAhr3vG1Xa>gDv`?qU z*iO_{#eDqA@ypDB zAtz!30Pg6gBVrEur}toJHN_qc?2H~t9ktR& zqS`nZ|3C3l_Uz|jtegCwqmXAo2K^O;ML0E{65n28qv>6G!QN1V&2fHWPj~hMvV5N? z#Ty)(FVO$IR5BKHUk)KbK;97l=Tea}WF=?)f7XB|&2!iPV-471H{bZ>uj2_rt42Lr zJZpar&K%a9CtN5GPg8|nwC&)KPLZ)y+hI);agEiz6X7UyJB5>}lX~?{#{=v)=s+kH z5truUMq=-`XDw|`HeZf3N^h_vl3IJLe^;CG{HZ*r(PAvjT#Hlo6Mt@Oacr{DukCNL zQHrHj!>5vy#J4C~$8e3m03--bHqV`9!$FZf9X~kebL?(Jno7QGvp^K|p78aSUscp{-3^X^E z+PA$_I+6Zvg{?rZ)Np2VAesCM7|kuTD+!%6va(~WD5*`eRjW+qTLUoLdTo!l2D<&! zl5U%|r(HG{5RDd*aGG($)?iQFkHk2Ium$I&T`kPVVJLJIgg2ITk8!$I88*OB5Hm+kc)+8=W|2`v()Mue6Ye{Lec__pu;ri2dyE{a2*joQ z1n!D2hd2&CwN3Dde@;jF2u)_=1`ZVRA_4by&cSRVA{tORs(-%+h?bGV-_$H zlk{o5e8$6(pNwh({zkmj#pa#2leTT-oY2m~qil*u=|>O<#T){l)Zy4M@!~<(LR$lT zF~C=6oUBCdX>}4d5SF|^_ljAS_ZqCaX;8iu7beKiG?gyRwxebyTJCABL@`^%RhCc@ z{N0DO-w(WDPZn|tm-~d4C-e>VGsVia-ajLM&y0(i^%H8`5)sYN2emksB zXN*?=v!eclDh{ZGgXc7^94%6d(cAl>a72x(pifw$d2{gCVOyv-))s*< z@EI(iSURoh8NDt|GQ$>tI%7E!#6~@%NxRV1JUvBng?$I;l0q1_@G~~bo8h2+Q^-!R z@VtK~Wl|3DrenA(EcXh?@Z1UTY^UZyC0t?q!>`#W#o_5jKbB$Txps)Jy-U{7*a%F4 z_%&WR@O3~6mt6?{sl!sSYaQ}*9xXzv3TwK#Z?K(LwV}F9qj|CBHt9a5;37k9{EtBY zg0oOEz1kSykfA1mvSE5kscjnl49jFsrQS*XHhPwn=3X{b77o9%VcVg`d)8sU;VKaN z4=(d+UfhEtXyv9ofJ?12yrooUW{s6j# zfyl#*Ur>_8PL#`n?(hLe|IU%r@PO$j`Yb*O{*I7(MF*#M69@fXVgtxF21^vzHzbx4 z$`u0e|KN-s+PiN>;SNm(MW=4b79|tiId-)&;f}`if?@LsZEnrX5qU$n`rG%!u+W-X zZMDR;`sa9G(elr13{rmj71peuVcGBZA8$8&`@jL{RVn@~EpdV==bgb%S*16YU^IwZ zl%1PiL#+y6ZuMCMsYJ9^w$Iah|)y_A)iM@qmTjASj>23B?!!{bh%$hnEd5Tj^#|{M3(>!y{aV zafq$kHhakR6Uc9n%OVAz(=RJ%4r@@XTm)%A!t8iJ(5bkAP+8mp0tfgr_z0>jI51kiC&W)0R z&5pGO25|vs3Bww{fMedys8q(9?$H>1r8|~!`aF%iC>G2JL-G7`CYI+nLwT~c(ze51 zvNT!6SzaaGbDsLwl{M^zXQvJYBWLMKYF)~URGan^>kWvD%X;;{)6r%Y!J!FIepnXbjB*T91ZVM`nP@@>oxLWLp0XFUBhJ$}b5XS+ufb zjmWJ_qD=Yz&?TYD=lAwc1K%onYW96b!yUWC@TxiD*)e~FpD}6~bqU70l*2EUeF2wg zNjJ?eI|d2YgB!7u;q!dMG}W1+z9%|yIkiQqZ>$RTuq`lOjr;*Uc;dX)L9Fr|UtM3W zT^@kJ%eaHn(Soz~%&PM9a)dTX?C|Hv*1^_MgHZ#2=e#FawGJ+kGB-c~d=oU9-uvh+ zKI47+C<3w~?k$8eB-^2JcCmBtvrUPQ-Vkb>wn8fF1kFFz3G06^9VuC!1YE40;Tute zmZ@vqfkoH<84ZN=A^89yF+f0i1XEE$AmqLULMkkV{~Zeq>B4%TFA;owaCM}aJv*Z@ zL!lpzpMufK1W3X7SN;G^L`JY58oMIbZdeQV2FY7m*Ks^o_TJOCEmGHIIRR5)sH)Pa z=&F3`X0o@vp0FwUdbW6ys$av8HvAA|ZE)QF>fVW(>3&&71DXCs?=s-e|3d))iggFO zz%=iEx*bP&b?0!}I(jn#^I{vYXJp4fQqx!|FA8cLvW^}LDY^|Mu)bw7xH%iA34;_A zLIbAC5%pa7Exnrl(4U^Q*Gyl4^YY80g~=yKEvY(PE1&3~ZE>uZ^K zMXAr13*0X^dewGDvIz|ud;;gAc8m9by9pjE>*fi`R>oRN)Gko`5n zki*#b1*}xVUJ{J9>7ii2XMbWp1VBKtmRwM@`D`8);hMgUy`-G2yL9&bC-={Y@q zL>2WQq@h%mc#ptkcrRh;*s>Ovm}s{d4+U!b`a=Eenqx|LH8P@laJj(y zaRnP8nl!>SV=%(_ZLnhkcGGYbp?xy@m&J7%-HRJn;u=|quO(bMROpd1^jo2uvNjK{ z#|JdrAaEmY;`%kHr(*xp1MG!GSxRFWXT%TM*=Qt}?$u-6UVl!fTlFBfFg^H?z=!b> z_8Lozb`;Lc%rus)9()Y@G~6n18*b;Obm#_7%pAJstN8HA&36jiC3nhY_O6@pajD*W zI0ecY*d{xldu5AqyOj4nf&FT8G`QE0k3KDMzif)~qOz0q;6Z`U;Imv!J45kkcde96 zmVzA;Q9BL@Jb#P}T^!n6ZC2*Y+?Ff*2ElOJc^pq@_`JX|e1UN4_`t2s zqcPak9jgy_G1zE4gydH4;=CC!%)^uTl7=q}JcXxo@_&#GoZUd@Bs-S!q4f8Rz_a)& z2bwHB!H`iOjs;_!I=4?s0WuUeZyIrpY{Q-x_!_>>HtUR-y;gc!&s|i6$*#$fvBMqQ zP^f*ade+8Q!#4@VgL#rbP3v3N>rq3)3&Z@(e#{8DFX}Vn;eZ=2;w25=5%?~?M_4g# z5jiqLrhlxJ!;CPTm@+~SejxBe{D||AY&%#IRxbB{D@x1WKDJzJ!_>$a`H8?!lbNSo zTKMMzzrZi8_Rrqv3kQrnRx|07TYfF@8@$YM6*gnRPL|->x1{ooAHrwp=eM#g`yFBV zuxPSJO$*7^klt@Zltvb-?u|#GSMdi8e-!u={(nrU91k!@5*zW<@ey=0!M_Up4Sy#T z2BWpHMuTgSwB`8l$ZGb_VZlELXi)00Mr=3!f!757E%QcUXGrhb5RAn7Wxw`@z<*@3 zmS5ji-_+XT#&K?Rhy*z(kM%lU!&n`%Jy(>Cp+i<% zR}Kn1iprD~GWIkZfoRMO8`6};f=-mCEPu94SsS=W_j5XwJ9X7HL?w=S+@8>ULUS$w z$KPn!4FH`iu#OgRsFV(x5VOvHYT&bUsnIFiEM@>O8{t@{mTb$^%XOJp_sKtZx@jpb z)2LKX8I=>t$4=5Z3{j}%VaR;d!wes4laUgjNSS(N>NG*8(-~HiRF`=!=pC!+ECA-}Ogc-W)q<+2hEOpMSgTRo z8j5qTv{?`9U2y z#AU&6_VsV+BrGeaA9val>C)YUzTtMA|f^?BQRkQ};xuw~5mbGaVnrvIHbW;z7r7caud6R5QnG-Ii zh(=LXo^1{ja*Whb%XDSp9I<3-9#1<|H{hZ72)ayyPP0ahLC@YN=yG~Lr|4ci(xVVtI0IRl#EG9u$ztT8 zD+OIeS93Aqlq&({nnG2bUmjKOCCjPo88$S_ zbR*rwMzD;RIoEC*OU1}Jz9;A2j4@tm@q>bHkweQIi)f>Lu(8pMDC}~}hXs9vK5EU# zNoul6_=dkz%G!E+XrG{4={CX%j6uCvQCpZs8r{|Z`|;Ui9n^d14ncR)UGg2C-8R}| z=|lh56WWQ%@^L};(0?baW2Q`5E}6u*o)~AJ6m%c$=XM}wTKSl8;dow8O_c7Z1>G;- zkQO8lE;V{E`!VRCIkS)+pw9|=hz<~BVLfH^$y9<22s)@h4EgI(*#aJrcZcL~rtXdi zdQ9!`%U_=p_!Aw_=n2BgoUbo;hjs6;V#ao^;~p!fhh?C}jek6FE5N;7)0S4>#&-G3 z!3}atuq&*`;t|&Px?CS8tQocWUCUEIXmHE3W>RbNptmK8TunA|V|nkOpp6N%3|YSO z$oWY}SsT~>Ix}F%Pg7WVP4V6iBho5MyL!jL^GTZ?3CiF0qBGVVWZN}OZac$SeK4{9 zz9hk`-+DES#eaFErsi^zt+PHX8S+Z8V$9!6NIbNM|Lv7WM^=KX98W5dFCVRM062np z$Ww$GfQ8pu4T;K2Z25S534%M8(XN~}f!7n9 zmcq)7TO|i2*$?pL%LFO984B2CjJ2@azSY#3q3l@;1IZN360t1^3rBTEO&(mj4C~mS zeE6xs2COnsxqSfhNgK3ika{K;?KC?eH6 z{&|AOkbm(*trIuU7B=E8^0n43ln;@C&*y^)k8+Y41 z!drBi3ay@c*t1bTNlW6$272y9t&|Pfx^XsWSy0R=9YMxfA-%UFpf66pYG1sZuxdin zGb^)TjXz|6uKMH{&6c~j%2m*EG|ZDS$jGpBw||)Nh~L;4ltpRrD8>I&DKXD%O-3}f z*^vDQCy(jtO*0maMfASSMy%TmM5pCDrnw!{9Wyk?OyQX2n9T(%t*T&nm)T=@8FF{`}tQOxDVlJ)`2KZHe(V(G&SmPgO<-;<#6-_?xj3?{jX z*{jCMsKH{a#Y&t*&+^j+78&{~JqP*UX-XVYcTyk+J^+ zK3gh|ud~V@!Q2GOk74=Dm4|UUUk|A5lA(OHWxXP7p`2wCCi*&k!$!51Q90ypDPi@) zDQLG^-2@jJ6oxt0t`zI}N|RjT<#=l2YGCoCfbQ}H)|SjmVEs^T8Z*@3PT_LXH>I`J z-M9EodIb;LHrC7Km;QWR!otp~NPl2s0*z;z>*XLOXvmqHZvet=PybI=p{=9Z?Nb2G#qDE?x9cE3~jI( z%CD$Q;G&@-wr5btP#1lNzN?<+)A#86jA5ZXwXqzK_A+cfKf8+*{;K;8W#At^^*a zN-K92rtsgLtl|qfignCG2Y;6FwE~?u2VMND8*v124KBhB=)p(Pi#uQ{)vUEdO{wOt zlxps>RU_|SpdYXbUAR^LuLB%Zag@^!=|>#3(lkC49LG|)GSz>O;u0BmG_{G zNREN$j^P{C&Z=VPF@Jn3fp0q>O!>))%GOtKhQE?M<|D5kK8RnupA_VSEzfQfBh|>Ey^f z{y2f3t$fN-OC__KpTMuu$$P3Y;!T`{Z)V42iinpaHKYv}C^3$dCvhq`T_X^)x zys<@=Fhf_-)izt_*hcX%Tdxe@_X+&v!68=vScWk&t!~jI`NqRWw%c~h(vf7GVVnLK z{=tj?8Y<--EajaXt#@a*Z)u8RX-djcOUk4~%b5ISbgsn&&0@CQ{J@m7s-0GfdR?Wc z>BUZ&7~f0~=YN-V15{A$Dt0}wDnD--F6xSCO`|x}c)CAVH$Ku-I8#n4szM zBH=3X5;Wue8tp&+LREr__F)PmtWpS1NXt^L$luFse3H}qeJscQTx=ge9UjDXzJDhk zLI4L4#(*)nTeQMBZvm?p&FmRmIv7TH`>mmZ0_f;C#3$iMW1y z>b4}UrhjTSI*?#yj$k?-!+ad&D)>2WmLA7)Ji(s%c}^K$KpVb@3-BbT^rtyxe+4l- zqtw>SLTthb^hf#=D{wJt=+E>Q71Z&R+Ttm-#ZzjF+iH_{e^m)QAEr{ED;>T&S%Ivs zy-BZP!#SIvBRZ-pA&of3mYVRih|1pm1qN2#a1{1Ejvsy4*Ue%QAk9+sHzV%t-?O6eypzSRN0%1ZbbGy57R zv)6g;Kb&gbWIG?vNN_7`VGB9QNO36D_kUU|ukp%D>Nzt!N280i>9y=8uiL!V*lseT zyy6%RRvyFgD*l(CJ!xmjw-a@``JG|WWJH&+>m~bWt6a<)gh+1x+nUlhHBOSM~ot_#DlyVaEfn5B!Qy6Y>Ae%s}k(+a|kQV48 z4zdxfwCz(+&Rti6J~XuJ%7iaS!Qbj|WHZZg%Pb$ug(Pj3I~{rUpgG7%Qd&*W?YR)y zPLkQEGSYjpb7nit-DBhIK8MprvVS~11}5mpWSSJ{oTOu5W5Lyi0LAmhr6!A5cvq>@*?(kf6jJ^yifpgnJZKGV|9S^D9YcGmzs z($bzkK#7)iZGaB9v=MZ1!E=tz9W6pIN5 z_fd`_#~l7wY{7AN>v5dO-R`T7636^?#dN-62h?L-9Va;!I8H(Fe*sWS0|W{H00;;G znus`4DiBMDh%^8Iy^og>^8_=O&j<++1~*?)F<&y5(YOa1f7r7U*)bp{ge@8Z5(I+_ zPLd%EBr|bl0^(X*+q$-HRk7N|TB%AE5hsDTfD78Hb*;74wsy1D?yc4h^>@yFZzhvj zk_r0#e13nB_vXEO&pr3tv)^+cKmGLJBLFbpQS1Z((g0u&P~ zJuL{hk>8CoVLVJg$alB3Eot*NZ(9@acX$FJuP2C*TesD{-Cfa1C^3Eu4VY*zWjfPJ zppFv?e=Vv{F<~l9qm}J&2RyzI!n|~7WwAvqfwoy=p%aRr*nkofN}&v4qR$hmX!rU& z%>no3kOioM;Cn+mE0*aCLQbnEWZkdZ+2KJbDJrfTup2iG(r7cF!hqvUmOAsoOt-3{%Cg#No&5@cSS~C#G1X%!xsO6_8WtoJ|T3~2KOJSJ- zf6Gl+0V`?I)9*@BL(8{{?gCyGxj96z)V%|qR&OvA*l7WX^dh-A388vW>Yxt*TOVXW z9jr2--h|b#24QM4UTX7f_q4^%f&{&TI3sRtn*9M!#abI%-L7^iQK=Tzn$Q62h(~DF zHUZ|O5RurS^s-dcYDi1nAveKV4;u_Pf6atO*oZJ`;NcM*G-fSnrh=jvo?P9u)zchW zSX|3%c!mjQ!day0sHLq1Yqq!p+BDP{a5kyQUX8WV9o#}%%L$v<5;c?ANN*M$Q4LrT zrxBgtfz1ZAny>}Dq#k>*tZ8!xi7ST^UQ0dAZKT#(mU}&IE%a<`QSs?9^9gu1f49+u zD)eh2(>7=`pxp!?_z^~?Xh#zy$fR?UQ6}+`0p}nr8uV;yd)eLq!hFfG(MtnZ;`g_C z+&%+35oRUh{HUl{K-119q^YUBGucO*jwEN5~=~tarC{vH}qpz66&bOy|K{fBpW}HV>$a}a!~H(;S@z-44m(;#`Y zhr2C8_H4KUt~B5(6Rw7Dkab8#2Cakx9(Oy!m_BP@J>@CVWnn(~f!CODe=S@`g6;0; zXd}Zjp-=nGojwh)V!3^@FdJ@wZy9i-2{*yb5ot(-P0~PK7Q%v}exaqt%?Y=_w+*<} zgxladG>wFI_H6wDVzi8q-{Oz)$z~gEsgeJl3Ezj?$>iH?fpA<=e|?h5?j5Ax`tpYp zz7Ah8U^l{vpMyh|`8wMTf4D1!*^OfkZXDr?d_cIy2|r>Se*F2=Lai_4Y4rpQxGz1m zV7Kp83->Y-`^cd560Vl0+3Ieq4zzZ*lPk6C+-6S)Gk_EJ!-EEdP3VGdguHlCTSm^- z+{5}fLi*uCR`&-@=wa82{l*41&mMN=@+rD`*n~&mQR32e_cl+Bf8W>a4sGy;w$L4+ z?h6Iuw6!hyN!_jc;V>(U$Kp(1q|5qjcmj49@FYTM8Z?U#D+`{2rw#at2|tBrBJ;O+ z%HQm6Bg?MVu3k;HKEE#h72)(8{LFyoO?Uxb>_fYV=a@}m;osp2)VPD5-l@jINHW!Y zknl1qg;x;ji;_B0e<>!jFsbHP;*HTAufi`3c+G@g!mnt(`a~{TBHKGU*_~OI#D)$4 zJBgS&;SKn;0l#4@^IMvK8kRxhaePr+OqG^Vv4XGPArvOEeKR@u!5F506aHYrA35mG z?I5DF7e_vHz}@U|Qt0)T34eySE!MaF!*$6QBB>vq;N++9e>$}<`3^JnUpdA~vs3YU zcJlsi!arE#*zv9oTAbsAf5E>E_>T$yg?Gu%@&{TYzOxk;(rlYaxsu}%aX}I0CY8UK zuGZKs)VQM8b2Z?9v`jIrG|u*YB$$ouTEp+7{hjb0taZYN@R0!@GqI175g4rdj8L40 z)7?wyPnpu6e<7TaE~N+W&=;@ILPTMpG*O|0{MsZgeQm_2r>J8C!sG#?4pOpcV5Wgt zCOUc1i_>OB1k(v?ckR}WXvV^B`DP6I?`qqj0ILyG|OhRIxm|bQL8q8F z6{lIAxhBPFIu@(tcSR-^_j%XIPfJZK>+^J@6DQ&f6D#mI>*-3juca+%Y9{CjY^NyM z;!}-7e}jHPB%Fn_4V+`*T%4DXnn~3S!O0E)XWgnvfs_{2*7hfK&D^j;Hz!&Vn-wPE z3oByu2lX;JaXwZu?=2)ZlAzZzYmLPdNG;%EgwaJww2>vB*;s|u1}-tN1}QQgk-V8D z0x?=+ax#c<`pt>UafN{^O{~R}5Q_RqOT;B&e;qnafv~!0z(M*;k|fK{V4aDpus&u& zmWY@4n{J6WPNV~1SvH=6YYl8LaUGsYx_7YI8p2_VJ4kL^$W2RY_o5gwt1|~o}Es?W`zFq?*IU3l4s&s25vI(Y;^aV1&V=0!&2Mp;+0e) zf6IOz%m%htcBZqpBMDM-f*>U(4xQKp(utd)(m?V(XAXKCa(&Qi;#Sj2@LHMRC6x_zm%>S(yZvji}A~BKE5)L*A%P3S&F{O z=JIQn)=b^YGLG4J87wpK@_rRcyk=YJr`G`<>Pi!@!mBOMq`}Ig1eyJcPfrhDf4k4Z zEc_;3W8k$WUWeDGo5;5JA!K#!TC#G3)2H|?6K}+u$i3F7P0ahNCfFu{$BFmg9s_@D;=Onu z!h`|18DU7!B4@R)#X{(`&u1D^(7d*IBcpb|iTm&YD~S?I^9||H{1}W1Yx@UH3}Y9O zeRIIy9;5G*==2mDSK0_<;sLUq=Q^j7ep%YI!%auqc5)fBUo`R&2sU zCLYGeNGgM@U{6XGne=Cx`os7M!gyyU9-+&5WHj+<6Mup~MNn;icS{mZPqvpwk@$!B ztclOD59RO%tGxjh;OE(!eSts~Xj3Q)*d8Cgg@0kU;2q}pG&cFQ*7%!=f5(4Ve|Fk>%o~#P)7ZC9 zn%TvF;lB<159bm7OC~S1-AGn9P@Ob=H3%3B^FiTzJox_*c6~vsH83GkCcXRSR~tIX z1N2hxZr}%`nNt)9Y5B9^*SXKz$to_g!0+Qn%!(f)tm-d?nMjv@(J5Y^ji2DB27YD= z5Qs46SZY(q$KWKCf46yy7HX^h!#%i6Yf3kJvp>-84uy!c6(P^Lw(H=dd6Jv@iIApH z!eM!Aj6iM3({7EwslV~P)U1Nre!kHS9z7G`>=gWF`7gYTJeE#QAKPF~s1;6AWDC>M z?tRm+2q*N-g$yo>yU;0yh+ISDnPRBOCvDivF{?YQJyUJ!e}#WD=W%LdIXtET>l88E z#9C3HDKhx1>)`Sz*d9w zy3W~JQAfW>fBrJX6ofOXL&O~VqTCQOOi>}oy_hh-tjLG*1Ok4#p4op6f4a|x)L@^GeHOFCY(va3#avFZ z*7T=U&*}7ZdbGy(@V=?e5?9a^wh^c3;A@S(x015@!D$SBz9|+&61e*5MyIG03k|Wz z6eozqgD!N6%`w`APF8`>nl|@NPry!Jt&8X2l)2!<;IL2A7^o!VRifGuOH5J2JluO@bQoaIr#dzx3+UJE?VE6QE^lX05{0JH-@hMFWZPW^bD|ZpSAr zz3A=KUtLIsIMo#E#RdZBZf0G9aMu^)oO}W7f1aEiysO{Y?$K2qqR|u^#p$FZ$T%${ zk>`l7-%cD&Ws5USahBL*3CZbskzXs7Uq z&4y?-#TMZu%aII{my}?D^lX9&s%^1i4$Jw7eU4C(CRTPXxz-7FVw)-2M7s@bC)xfc zf8ysBhBh}b#!e?96n@cRh;vL4U==ns8A9Z4Y_Tv|uZwhMlT6X@r*YqqDLT0$KWO9! zJ4|tIh1md0RWZ967hPWu9oHEXV z(@s?uqWF>tW6;1YYuPe8g$Ce=Uev zfE;&FKU(FroQD)HWZ#uvII!QFYD8Ma?}_gl;&xN~K-`gHgP_mmpq)?53;C_z>jEB+ zl?PgxT;8>!;Z`d=K2N|+NKA?J>5F*uX_mNC>^8)YOmUaEyYJ?9?`USt%GGO6ZP>J? zuDX65`Rhp`vBd5%#gE0k2-4j`e|VQ!$>g;3z``t?CH9))em+Z4&`f%`GvJAt3A?=1 z;sI0a=N9>Pi(aJ`bmDRm=4hZxp9M(Rflm{UK4gmQ?~_M*KpZqgk0~A!57UaoL)Uw& z8c~fjZyhCtlazA213F7QYKlXASY((zwU(H7!GPVtKP1uN0l5JvV5E~8o@OZ-Z_ZiqKb zyiWYOFR%9QFBc^3%HTWre@}@2?8p|sNAgYofG{Tc=D&^8pTt{+__HbA7Js2IBpCxO zqdyH~l&vC=V6^UQ0rUPKY_heRrA^RZX==Tzp~FC3rz+k7$&B(3Q~Xo>3jtS9IM9ze zwO!yfojRca!x z*Fqku%9Pb|iJhYD42Jyey)~lK5&-qBBDLC+--U9iDVNFR2*SOZ;3Kiq*6VMlb>+W_ z&aiEZ`6bQk6uH)r4W?Wtxxj6(&J6j6daQMR4R47q;BQX4@fn@Y!c@rg8-(}HNbgKL zZa zXEeKK1UvlXXta$RyxbtCrWl? zEy?j18IVCkhD_PX`fT~2^jAX!ueBb1iOKzDe|m$b26@tk+(FX#Il(S>bI`Oisvx(6 zG~{`a9UVQLbz1@+cgs=_1zLu@AmZ9aUio)exLlO>R}9EWiXYx7F9hj?B6*1^zbwCE zXS8&;NHNU3H1?|!x4FrW`>On!A-`_Q%Xk$=#ej%A`fhlG>)mbM7V_)l6%+-_D^u+9 ze{Xb(S@LRAeuIOlTwP?(vg8QUf#e5F7=5Ggm0#m{!L7Rev$d!J{9>i{~Jwt6W2lH2K&xGOYV}l81mbu zyj9+2rw~)2u$)-ZKDaJxSFJ&q-Y>?{f94B$knfuEd-D6_C)BJwwf^Ky%j>FFL_R8! z3V_xQ&C!#ZB&Qw9~{ZLX-+7yE>-Q{E}>GV!pyo2!U67O&5ed*qJ|c`uvk`-ntoZd*{qlU#jAf0)tU zvGMzSk%PhVe!0((514Yle30DyLG{)yy00a&C6;icDM17x+{LMX*=5RZd4QZCrkY(> zLIIAB6;`HGT#{DB7PiSAQ$8dgw$6I!lhW}OwmQWRo8+VHB_5*SFX24e`b_y4`B}9s zhJ1qbNt{P-Sp!aT%E#ewHoPjIe=_CM@+ZXM9i5@xGr0q7R3&$de@tO;PLObD^Mw39>tybf)W_m`!(pd*N4~_fCZ>0#I^n(EkUvi-@3T@) zWEz?Cv*jzW!wJvH*G&0Keb^}EKeeO7Itb~MugfvL^7B4aIvM*%2lXOzF*n%9fe%>oB-3Ve=(_^5G&G211m{{ zQ;%`Obs%kB8YlpjeU^gX=11D$l=5$s~JG#MefM85W*-IH%^ZkjmY0k(+nTU!j7!KgD` zF~}omBqclNf9L}rwgWq3u>G@qI-b_i&)9yCL8&EuH?lP#Loq{HxdT17*wYR=o=V7j zC)WZ|=Xi(DU6BBZ1!#vw-TXv|I!*kAjI*euL|d4w=#c~R1^U2_KWC71N5X2;TOQA@ zX~;T5J*e7vURk%jIOF;)lqYMJ8cWg*&AnMFggM8Of7@dJsJT}T6Fhi6NZk*Hi~8d& zCOMZN*xQKd?-$Zad3!5#O46@#x3plb%?y2uaA zxv7O|cNmv8-zZ(@kABRQkCc(8I-F^g_^DG>RD603UuueL zt<9v>e{ml>ld@AyzK^)%V(-*UY0f} z4aN>vhwVs4^5O#zLC7{mfzXeLWn~pwH>d+g0~D|DffxE=#r3S`<3FI9w3-Fs8-p); zOxhu)W-D#-Cwe0r^;fa_6tN-9PgC8;Rlm{XW)6C8q#-=9)n^k2oGh6VJ`i9B7Pfcm zf3$vCmO867;NRi1N*JTZGdJ3z(`PvmYh>0)oIkF#~DlVxP#lT``*|52br z0+V5ZXNgZ^aQ-}~JD|VKJ2?$RI4##c>{>WSzBoFwzSRW$f4zwQ z7ujaJj)0#-Oh0)i41l+D;$i8m7p?Bd7}R14A{JT)nXB8{Vow;g#_uWqpKY%UZ*#Xd zwYaCmDEcY$xL6`yZ>MuYpPX8UR{jWPQ=j6C5%w^BJnt!U5zZ<4LOjp(596)p{78E+ zveBHd^P7ID1#iuG`s|n|pXD4Ke>A0wVEQv*JrcKiJEzPh;Yw@6yWc(0glIl?7fPot zRLI>LWM^kUHceuTnDKIK)cM@=lvydy?x)%_N#mQ}iJk|4oea0fjXhIW8^jb}@z!l1 zwkWJ`N@PUxwsBC8)5io;Kuu%wnS6jn&29F7%yLUA8HwAyO7h>kqzqTnf8I9=x+Q*ph*s0xVeN?qb2HShoa%M;hM|6Ks^6&J_WAHM`9}V2 zo{Bn6R_fv298u5N?Xka}jU}|*$r`}=)Mao=o!`H$vm+(bBzBDXjKv^dT0{2Vw>a@_ zjk~R_fx?}IdEh6i{N4%Kf9emWdP%*Ur(UK9Z}Edao9cP>LY{ho9=zjJe^q}o)Za~b zME!%@sRWSL6s<33M-AJ}|M#@Ao5$|NB`HgZpLeGE7da5x#o~CS(C6{A1Pk51LMy4y z-xdg38ytn0Cx1c|_%D;>T~j@yp3PJLET z{!@PTnW>&oPv$w0kFpIyHRs{)a}9?g5Dtf_9#x0(9LC^4tk2Dom*+X0`rF^>*xuX> zhsopRn5suTl|8ttkq_sS0l@#PThNrYH$-S~P0Tb#YoQ{!> zQHH~1Iz~IjkYO1pe~7k9i}+0+pN`Zpe~>H+UT<8cg+y^`KQWInGHZPlf9uj*cF~3< z`9i1RC`|G7|9*ma!!fD9FGcq0L{_1<+f;<9Y00wTnBJ#!C0SGcgothoGa(xUp!^dU zFq*$E0s#5^|4GraKhwRCZ`1D~kjwY{e`o3I2pFlaqhXA`e~yO<(e{6VS%Ccil+pb= z^rRCc{RXHg=>jb20i&@CvK|Cy2|fsU{AYNHd=N(Q-B@~ENUtY72$S#E!2U{qCqX9t zy%7{#LugzJ!{It`!Syug4KNvQf*EkL{2R3}tzqQfxs@9{5Cpz%Yyw8tM2Az4Nn%`aX5KJ!UAw9*XZi zmPZL|`OzGrYi2pjKasxcjrmdSDZ+ue&b4DGV*E${txP=cxiU?sm*B zD^Rm4e;qCdhGA=EhAZPRbld_NyWwxHj6={-X}FBT5ZDD{dmz;4GQzN(|D4+m7j(gw zD>HY)UEJWSyI^(?d_AVY0k|CDAbbsJi#aB?8HGT%O>F?c;2ltQ|{}{%?y)Yf_BVpYO3*dgJB9!Xj0ay$B;dFQq ze_H6>Rzf*I*j+)CxDI;Y7MjIv@Ce*Nbl3xj;YoN5o`c8XWq1N!gCp=dJOzJ(r{Nv= z348+2paY)8Y-Qb^IC}z^mZT zcmupmGx{Um41dB~>GvJK0t|9vNI(S!Qkx|c}`Tkyhs zY=bfO+biIxGF1*Z2ur1gs9cs>?6Jg+)}mJBu~Jk+>DNk9uGXme(NXiGqvl6Pf91E< zSi_=Y@iPsL6sx6f)3hLhVuWFK$9HUst_h3N)#%%5L) z6own{#Al?d`?mZ5948s@=4wNp&#GdD8euD{Q;33+uDPtJ{VP2!h>>MZ{2PNdAe}YjYo8!@dDVPbfFbk^C3Cl@V*P;ncm(;p=!nF;|U( zOYjLTnd9MnJVJL4m;#+@ikeDGFbi7MG&P;1r3xBVkt!zMUk(kbe?*l+7V-TGz1mJK z1ycY&BPSr6c^bzWI1v#IoMqsd2F^FI%7CTR3RM|iWZTS{w#_uUu``V4$E`h%jj@_W ztkqQIWP0CLGpv;tA5eSpg0QOe5MIo7DU6p?X1X%Ncxh!8ukBb`TwYqI%W(j|j-)GV z$vD#5QIjhwO=5+4e>pC*AZJ$P5Z92WVB|jhMi_6fOer_a-3=pML&oOiUNnyW6)5@> z#+xg1U75VF-*Gt(yQ-g0oy%E~hJ7hVkwVF0$*F z^6xP2tsLqadIU}=api?^{~>Iu%y;Dv!|t)&crc8QZr~q}e~*j#@nnID{2J=Y593p8 z^m1LA_u%1v$pKUHG%c${`JmuEyfU4FwsyAm5c0#-G!p zT>bDhGF=e{y@Fgkz(UBtiNv6jU^q^Ji8vLG!)Y)dr^6yFf*LG^l~@L6;0)*>#km9L zz*S^OcHum@e*+i5UAPeT;UefF!}1s|CWCV#yn$8l4pzgzu?F77W$-aBhfi=N3amvZ zo`fdW;RLM55)!luJOvkHgXV#sl8f0$3b}$5N-inx3eE2MWFSve#}T`;I8{a19jl0C zXKD_r(o8P&-4mMK`R<5jckc12nnmo+J!X^NoI&%ge^hhSTw?beoUP^&<7d*`%hY_e zfY^N^PS7IYBs%1&;|Z5+qDGdgR11kq7ok#%2yzb8z$fYiwU|_32fSe8d=>0hC#ovQ z)1tB*%+KHq$YoJc)mlz2qkk7`S-FSB1wJ%X4L#Zga1@5o&rwwdwjG5b^!H=fATo#K zJBN`le=SwZY+-6(=bEk~d+?3Mk{osa)y^Omq>S{IF8pvW?v46D(}=8qe`k{>Y@(Q;8FJAB1=tFsaSM^#3uPF9 z1sKvevdx}In4PTZXwk~RRI5}yO`5<*@vfBbs@1ggv_xu+I)!GH3#Do;`Rx)4NH+@A zK)%6HTdi0y)*J`r5>cj>Ym^YWWq4*2@3KLv(@P&66E*sf6!tNe6~GsUMV>r z>}9`SUs4_x!{SG~hB)O~;*{%RM$3zif5t+2h7NVJ)tTxnf=Rxj+7ua06xgFJ2CWpp zNkJ32RAR)ec#_>1Lo#O+zEi_u`i~MW+wGXfYJ=WwArcYQPCbFvVbm@PrYYVUJ7-Tx zWBp^+6VrN9{`j7fuo%4|`m0jy6BCAs$<+A+8%DR7s>3?Ey3_$tngNmond z*_FUtSRlrc16d4iO^5mVx`61&S2uX{)l%k_L_5G+Xq~=I@^vf8{C7yczDx4;Jt)B+ zKm~d83&?${#5$L8Z>BZTa9ho zwvC;n$&Ss&W@Ed_j%_x!ZM}W2bFTOLKIfgq5}^>lFKb-gF2 zPx}G47e5SOe_$9jHT@L$sM!pfqrzX38+34#HeEMmX`u1 z%r_GKu#h0AI+g}hRT=)wzgI*!viEuV zO^`8T#@o6@e?GGK73PQ#GMU1P84F8BeOfqd5?j!Jt*UiYg;-z^uoj`QJllb!{U?in zx9tPhSpz(fQTnm9+|D@TNw=)+1G6%P%pcYy%u{|0u-}z&Z&b>1W}( z*h@vsKW%kdQSV&CeLbql~6pCP1gT*d}0x zYK?&UohakR6Q#o=dJ|K(o0!z8{7XpMGQ1MyP_@#WzxXbFB^Xq3!8WbZ_EeDKQl`D@ z!X$lg_=2q5t|UqNbk2X2fQR@dUnNNjB7-Zx4d1iw<)9)E?>R=To-W#of_DJ*`Ic16 zN(l?O?k!7u{5nau3ox9g8xol2U?qbM1|9CwMTX`Zk!9I_X9Sk8xDS})O^-;$K9j?o zquRo@23$xyQXCgCA^@hSarAJQNkXZzwjD%VfeKNvL0h|$ei1qYP!k7}g}f=Ny1OWQ zgMfNIwo>%=oPx80?;QHi8IiN`?hj{{U;VOuJGSdPh$y223wN)X`aA9F=63Ww<^#0G z&ZgQe%h;BA1Jvn4CC(sRLhxooq12>?;o^@WXx!@V0>=HlKrbo(g@c{f-)B?=jOSlB z&Hr6V!A{KjvEoEyptE9^?8>F6>O5zlv&B)f~c%UmIo(^52U8UF7%+j-rfOM~zE*fma9z`ceR8h(tL3(THQOjZxz zLnZ6S^S6&%-H+Q6y3@d?>`By+0b;JRk29f5W|p9NpBr9bpj?4^7C^Kg3zwusI6oa_ zP7FOc!h~BmSty1&R7V;1z95U-64nlDsF&iO-9Md0FR}^BUz&ryF1K~61MtrsFE2GU ziQ-d?^z!bK?&{D_r{wXl5y@`3+dgCWwsw*K92Cx>^PZaMrPMo-MD9(knc)WTn^YIu zUZ2pdYW~2VMc~BZ>#gsy4&i))LHLOzds*#X57cc#J1MB;I2Dc+wsv@{sJvyt6-3tx zrfE-Ww3$5AV=lzg zqzXl7AvW~0YSEvV@b_2^!>MyVTJ}M@SGC7l@FM|1Wv9mqHUhoDwp|@0FBiJ(u@tRb zHrA~psURc-PEjVt^Hl9hpZ>IOvS8Ly-Gdo(uQd8HCMFuBF=Y=A$ykQVlpwzgY6XZe zEu=y*C^?){lWB(70$sW8$B9+t>;eyK9$XmF1qSkOXJrlp_9NN8+UaHK_^Q+;j+MGA zs{6oC@?qFTGpKKxK3WPJFe}AjdwaE6?Mom5{XZnyv$6rQIa83LcENhvv3M4GZt2eK z>|4xiTKIp^EB>Xj?Q?;t8>?_=vrs={GfBSh zx@}tCC73;g7{(-I)(bJ2-o3%&Ql%!u(v5C^I>{4}6a){YvONiZYZw5>#ureS1l+_cmITlH$VGI`Ec}wRI z?3Hl9O)h&~_`w%5d9dVjBQ%eAQR<0WQ7d2m&Aud)RnFlw_554=K2L|2elwTHi0k78 zY+?1?OgoV`$$O_)jROyX#7OoFFX%6@64y~)Pp0!popfUGTdHRS)NlS#p{6aJLoDq9L5-aSb$?!V-9oLx&SgQ4m=`V~COs z-F@l$lp5*@;PD3II$ z)jE#zG$j|*>^RB0^x~IYvkV+hRF|rB2`O`c0E#T(exa{x5j~L|>o9LE`dgoz64{dR z>kO`AkL+v%IVsF{Z#TvqsDCp8brT+fEU>(Io6K>@7^iVcE0d;Q*18~46yLUe* zjyewEIp#eDO&!IP)Pis=rmy~CLC>}imtV;y6Gd-Qpl;NoTRyH#qLXsV5! zcftR*)_N;OI;MyE@&)yugR{h!FJDX@?U~Z3`5O2%SZz%OQy5JE zD#&ifC5n0n<`Tc4H5jM=`{;r$G{d}#VOiSbIUE7$bRrywI-kGAi<{!3^ku;N$2D7^ zGkx(4=z$!AlXrTA`5+B&0Y`ub{?@RtTqB?~YkHKG+^wbxIA7Tglhy)YrU_sC+rV;Z zp`6!`t6>;8v6Mivh8>)%oku@+BR$)FE}?RUZ!)t`p<-Lbzl3lIUKIS94&4kB28bERqi5z=Ie2(SxQp< z*!Z5J_aQrir4NVQ9ul+Siv0$sL|pR;+~$Nevvirm)}5~kn1uy+mUi433Dz6iFb?(` znkv%h+Ia?A>J5`C+%?)XV4ZLQrkE&P#ht_$1U(Df4e$0(+>XRQ*a$TPhuClm;O!Zw+R7?kg6Q13Vy7a;Hl0%7uaXl*hAG&If=}=1 zkY!9&yg@hu@svjC%|wqW^6Cdyu?1q#SXUT-vd<>3TvaSTx*xdSV} zp0*yBe#l|Ls#VzJXv!gb7g)Tbiji?slxjY3NQ+Y5z0kMW)0Xs^znOet$`dZCNiW4P zog;YXBY_hGz9ViH{#@<7vjlmtHNILr{5ky#SF1<)OS;N*$oI~g+h{8f^omR{dsQ1G z$-!@IqybiijkWSx{8RUr?~Y>8Iq1x245jI8CUv6uOlmCF`Zcw}8VIJZVmm5c5P_t5 zfh?}W;X5jBaQ>>#p1Q7;H|N{$9sR@UMA3vwV;u2gR5=Yc)l2q)O`8Eev!)udme_Kx9vDvt zgbS;b8PJ*q!&Yov6u#h>|Gp;97EW4!5WajV#82Bcf?!Mgehr8FA0`?1H5@r`!U7Fs zvcwoaPbh0@do~jtF9ACTBG;dd4=u9JOm&lfWM_zy{yCo5JY6K8S@PO_`P%(%rJykP zYB5;PpUTdgm8HdYd+~nJwW+`1%m3ITn+q`=`FTaXqr^iS8}(7dNcTZ#LRzmI-PW5g zCPbr0pm$zRdz#6O<@@b*of-_}Cvs>$*jD;BTkz4p42lVRD?Ew6K#i27YHzfXldahP z;T$5G!o>}x@F!^S!<%}@j|d4`EiV!LfENzd(L<0m)_z2#7#cj11VpjuN51Y`$z7vH zg;4;%lKr%WU1~(BCXrmQ>SJ!_2Ol`8ARpJ4y!a;7rIX5D3t@|QEByhwYl)G>Nb!cD zI4Y&M0o62$mGlS}k?91o{h>MSsixC$=HyxQr?|Dp^CvSk8|yZIQfQ5u;@ukagcW-i z@#q~k(bE<1?rgHL(}v!CiAzf$q{_vcp@W{^-6!BDVZlo1doM^#=B7tly-5|O8mPyp z^y(A|#EBvB^S)vG^TQ5UjOv@R7ydgDZ>K?5{-^+-l&x~)v9AdkEDxD6BVvrHR7$ca z-*(A-8VClxh0ooyO0sPQ5p!K>kr^>K?A2cA2yrJL>%^6f?P6sv@o_Q4c zF@%uqbxBZ;)_X!+W#j#`U z8%fb9(#~8D+Y6Au7Z#yoG|ai2=`*&o3mm5}a1(O&s_e>B+k2dpewBesCQj{icb$Ij zY%t1y&_t2}sw8k8T(`;?%m5kR@^ohMkzNNP)_afYI<#a=-Z&N%B+HXgrwE2tmPiaP zQY9FxaOFT&_0BR|=L07T8V}vg^5Pw-t7#Z=7KNmMYIq7@&Ocx_ugQyNpfv$Hb8m@3 zgiNk?XO5`+q+MNPPmKJ6DS^gU*1}h9Tu=B`Oh@u46K*?Gr*_5itj*|_t5(@Ry3Sk( z6Hgn(G?8Y2ZRmvpte9h1K|883vmpOMllHLXI>&#GaSGS1abT0Rj9L(vA&L6dcv{PO>FEeE8aRbKav{gh_+a>>J%0(TaG@8j8gSc2 zu?RTrk`rxT6G2>>>uD_W6yc|!P>56Xe&3uLTw4DfDENKr=TkfnX>bN_V#}T^b}+lk{F{QtEiuj&_YdKcfe;!Qar3||3@3a`;CS^}jMcSX_S{$q6-*Ww^B|7} zhx_;F=uVcR+`BZyVFK$zu=V+hubWOY!MRznCXuqZ03KBV-wh#PHT%?8HSncG7724c z_&Y)dW~CxKrJA}DQiMzoZrV!6eVtg`u-edis9i=VLH%%$Np_0ck-d!P9Sy1&Vdm~PH3BbrzVrN^D&V&yFgGrURyIPZ1&;FEhy}3ofU6L4FR?M=$z3^)CFY0=?7rlEWKp&;B(ZM9&a$nYJZv{DQN+T)}bg8zr){ z77#Ea9oXKFIpOE~BS}hjguPZ&4IG(l1#h#e#rk!4?2%qv18J$*Dr3B6joY(HRxR0p zmlB5d-_+eRTjmoG{Yv%iH>9HKO}n~!>pQi zjPV>nqn69opU%@#*BBgd$|mPMTfz_h#sgX4|5Z^puxgM`>B1MIPkf$P(uQg-9>{3w zTnu69qv_D=d{$ttOro4+HzE1meX3y569SoLcOs-Sd_g><H6Dvr{n zg94m5Reu*vGPa-_;+OOFc+ujSSn4%bWGyg)D7{g+J{7l<#TH;FR0`9$VA9N*6kdYm z@WY&}kV6tbcK`?3@W|ru%Zm9S7jWx~w5A~3@VN(*qeo!Tw*siW>ITkXM!4Y*_C4i9 zW9_OI;fs}ZKkQe9zBxabSCu0^zE8V$lnNygU<>93icOuvym6Xw&7_#jT4l?b`Yh0} z&($nqK@zVOM|mj8JXW8MP{elvPuI$qK%{W26l#y*A?txxX!d>{;>P_UOB{t;2&I$+ zNlxfV?V3gx5(Q&}AvsVt;;GkG?oZlvCl7AxM92smu>KjH2jQAk!rF~iU{A5As;GoK z)JoS{{#cw~=V&7h?HLXW2^bR;rvy+nmlkW4Hsa#TKxU2RBdTt;d*-(X&8M;{&;9SsYm$_w6c zk>=ptylqgFg@RrAIMfbWKWmlOknKov&4fw<;97t$Wib$ z`TlgaH37?~)INb^3Rfy3kEx@?{vJ`YJCbF(O2cXTDq2rY)tz8_6_1KhFL)#^HsUrE zKsByxEPeUXn>BB$wPyo5rV%P3DMX)W=>27#Z8pNOBGxyb+1t7;o9#afF-L?=Md&*&r&J_gTB-C? zCzcmkR%vRw?TedP8S75+7p`S|%E+~Rm~F4Q-x_{)3wxN>aVm$i7Fv<8vF7*}1OAP8 zk*cQCAb@5*CSUGXKyiLaw+#IdVr&Y&2GE!T{8H{Mj|NwaER*i~BZ}{ri4i6E#F{*O z4aJ#@i;vXUuyQ*f!W*Tk*-2|~ql_f{9!pWh%lq060b9gKNMW9;H0eDC-kIInYLF6C zQow;XI^yUVY)r$W)+mRkp>laoFej6^&agb1Zwvo(vTp1AVPmgXI5X}G3$90~1yIMf z{d{9$iK^6od9Y3&jMB*Vn?8h|B6dgfBCC?SWy^vI+JM|JP0;sR<7U zt$PI;*U65_AaD%Umj4lEo(L!HH$Z;9L8xOOs4lvI!sm64&v$XbVGB9*8C2UwLvbaT znT{z0=pL3R*nL-95H5nPha(3+0H^N=CiV6J9Eg;WM6tX03R~o&V_6K$YI`1V#^@&( z7u~M7pC%JB5A@@|?mmm3H#^z!MtRSnm1{UD4QJ z1;`XygmlCQoE;>PSzn2^fL@Wt>cS7MdS=VMX`P2&pnUg>=tHmA{5tp$9)mmq4&qeT zPO-=P&ZmiMMA3=)BIiz-Vl9$-mrey++TdaWN7mmRYy!tWIgE$KpWwlRNY#MqR z8!r%R3JBnniK00gHeZz`l&5TmX4Q)jQZ?&ck2y9u)V9hK^(SIh;J$)YdH}g&%EPc~ z8n)s}J>(hsMtt`4Rr%YOnLVxtXO`-#{J#BGr!KN)S&oWD`x#z7RnRG7+Ab39&7qhe zh@t`Q%9hu%q%@RvuCw2WA>}vrm!|uA2bhzSy-%*P(&U^0FEl{umxs(Z>Y^O2+mchG ziOd%0QONxuaR{O^AiNr<8iOuDnL`=EJnRXiYyh;QLpQ7k)BVBn|k1oWc3 z5g6GnN9dI4=n2LAjMqb-qQm^C*n@++smD~nL4_WugzOiExRI&$ZKL2iGkoUg5es1; zD9j08DKR>Y<`_U!k|K4Cepim&e??71cBuwGX{ba>*e0_hA~ng>CrD%`PWI(X=_jU{o1TQF-Sb8RR&dEy z#78z8J1Gb#2sj9K2^#B|ocJfM>4$C!m|O@<%n6v-Vr2fq>)|K>6GXA;D5JV zHRo#CX^xZv0fy?qR9)GuW+#)|4#VPys6sDzT=-LlY9puU5wbN zQtPAgtR(({{zHSmqL-@mw~x&gURT;&BgPXD=ZdGBg#2gP6?XRu&J)~!KYlrbVc7rv z>k*Np<(WaS|Npt_fBo0bHS|?=#c=~Tp)o|PXd>~~R4SE&ATz(%|M`v$4*MltH>Z`= zi=?N(x&3E^=Q>Nk&+8m@7LSmSiNLb+wq9`fdR-r_iY*`BfZLSy@GR-<^}6%&M@K`? z$HT7T7a6TfNk{=2@`fXEVsHg!qj#fV&lQXy04P#J@~wJ@@A~5hasdgi4Ct@DC;tXk zSYlW`h5v_GPYY_~+Q95X($0fi04QJayj{6l2*Oqqtu9L#|DDFH9#<&Ucr`7uMcqf|@v;N!bYaDp%kNt41gGKmc!D)w0M4jQFjlQ4Y0(6z)GY=p5y`Nr1 z7)Y&?s0o=%pR#;ywy`se{MH_Qr^FfB@>5{D#Y>cW#w~6*um(+Uu2vWJb}>6#4VRA= zg+enigFHDpmMqv$oO9%#7i2b&+^;}P__04|tIkE>0=gSBEdLJMP#{r3?svz&o7=A4 z>tZpJX#RPn?)u9D!cPsU{nfz*$eFUXe&FP#0Rg(pLfwR#rpUm|914e2&U4PfbhN*u zGMBf&g&Thw$m$cjw`osd+t6@XDy{PfZ}W>D6U)rOY8h#DMoZ!O+!rLGICw75f$4zQ`_6}I z>6z;U9SM93k>%%g8sU`1cdKc@G-}fliWC7pZ1AD!!bz0-#-jEHzTnc^6cyXPspOsC z2(ys|879d1L`2(KJtM~o{n-aCELP9eJxU^HGjOimyzySsf9M|{(@`MNhRe#VE+h<~ z?GIjw4=Z5-2$=4Yr`>^~=Ou;3x`s9Ubvh!c3Zx`x)NqLmTqH|sWm13!)bQY#p3vTJ zZZPt5_Q}Y|$FkJ<&;n4n5D4MZY-TJ{yGQ+9mwzZJOI=3v9IKhq@TKGK4q4lR^d5YR zx=EslI%O_@+(2ax*oO4f=L~PV|H(7pQ#_({_$61^Dz6wP>=`aUo7dr$WXIuy*loQrYkiY{nY>hCbNaViiP0a1uFIBVI0Ww{M7pHBK^{BS&&jEWtv#6 zc)1I8JH#&_}_ByKED_M>kRqjOEk=X7q4vp(<-h~`yWz=>Q}^K zEU_z0tR^d(EhQFV)mo9ti(n9Kgj3DPm~gcO98(IloaZ#qfDPJ_iwdacH}W8vH{Q z=m5p!&q7(Z99*Y%*LH+%gf658mUi1_Y1I&AQ|?9fH9QjXnSOWhb_j)d;NV8l+?jrl zP{aQz#g@#wqsIwX$*T9@SDH@;Y8K+za83EPVVOR)A_M9?ragu>;oH-JWhl5ZllAeV z(^zIsHEH8-_$2}hmX0oRMl8%excPi#DFTMw$4!c?q`>1jqvjI4GPP_$CbqX6~R zQxvr1OrninHc#;*z-zqsQXF11Ie5VrNQ+Qg>QwK1jI<$l^vkx#7;My7KB;X3w zixByGO@>cpHnXq%E<@8Sh!`f;Z)3kG|!ba`vxefYGH> z?A3WQ)#NUuk&@$U*0i`B?g4<(89@M6pVDmR8LOJ0LZA>cYBB$fsy1Sz2KOasjyN`= zi_7G>c{gDhtvV5!?Fb*83NVV#f;0!(<#m|0NpM4H7)QUr; zMfop8eWCt4MA`p0L{&}|FqP3h6m;9|wTr$Fi|-ZCH5x1nv0JfA{FJpiqp(JJ+mdu` z_obI@@x3~0_J`W+rMha#z%>P+Zi(U@Vv&My1KL)ye>r+wun7YF1z~Do_`;wQVO1z4 z;w`k|E9_f*vSW$Y;8b98_Cx#)M{4Q%(t#xFhr)_t0J_i}<7e76OKr1wj(9c}#H3zl z^QBl`#hmU2xK$0WJ@<=ofdC97^VjvHKLKNKeHsM}A|2J%4Lvh|mu(igGBOv*Kpd4U zSEi3;eTwv>j@hBLAIi2nwaLW?)xU5hb7X3GeLEDgjjvDI?}aGk^@gy1lB67l@dJ&d zzD80&ap4}sdFQS{pssnSbNXD< z)3fd|_08)H^%eWDO|AbyeSaEYUi%?xqw5oF+<6vOuzK1YvbtLp$ev_=d}EYLXSnOS zuSIs?5H{!%>x#UhTD^SDk>1Te>JH4#HQ~$uHav{FhK)`vJtdT2{3`f6a+u~Fjffg!%(w_D=S(@sQg^S1 zppPIxK3omTnX!%}E@JG40P8G}wQ zEaJd&_>ixW)-$k(|27pp6YUySkQ*BE#S?m9-}8vC2!v2_c8RyH9dEoWTjJP@0?zswLBab=<9R+8Z~C`6O!~UZv8++XO)^L= z-ugfUYu=`I+teokV;SYIIXqRl4KWET2@MT%WDSc)EH?UD_0gEiZ62I2fZ+BxdT#~jM^U6?nl96HMA=h~r5aF+UJi=4~ zSf6-{qh^g(JcR>+{PB!k1lgO!$aQA@88l!u9!rM&mYEIHhMY-FkpGb>_Xda_icWk` z&MLkIKp-SIV+ZQ`mH$TI=rp%)utHCL?6q&lw0}zob-D9hRAn5APA%BjD1t)`<@ic~ zE?LEElpu=R@0a{GGi)Na$(8_sb+4I37||`e3U)t`@9! zAOnpAVLw4vAeQKn55uYAGa86Md+)GBqdA|hNjcf{P=hEG5PEANj;J79>c*PIas~0j zY*`xngprv*CuLmaNPF~Jz}3$g6C1cT1iOCXsz#MnCt{KG*O7zn9QLybx@8^S0k@(A z+=*Fs#@j-Em}D(-T|hS|Y7ZEA!Zb_5<+!3L(*zuys@xvli8r?GP*@rae;>qiRTE#* z38RyV+_oz@p536HM3AjqSIc2H=r1N`{5HIe2(GKb8=W>CU^iw`mN2yG{R$+sglAiu z?OHp@6=lQf$#S#>-jK6lFGh-N%zLoh9r#Ceu<7x>`2TbL>*06_?g263!Q>l8cI1N%fLRqcI#A1QjRpzsuiS=Y+7#fxS^B>Bet})!A+ZRiLfSy zS3Lu|Rliyubf1sbxfhaEF~@Ha#$UnZ57T(~&*$m-1|-`0FoMacxSnn|k4=m3H0Kt+tp3S%&dACcI#oQM$FU8RQCAWd)7if8?HZSs41R3D7vk~j|Xmce|qt-`(7XJLP z|0}(V(v*u3XG3v#^Bssb59_0YNEzV`N@zZfJ=53qmVO&{a+9Tz+kpW0#%No^$Ngdv zVsxJs%gjpvftyuy$9inGkvo;)#dzcy@0K`B<&Nr$=FjbDpBXPo!`ahi> zijb6|2QRnZH-PR+EG?EtD?fWKXz5Ih9I-+Gu#C0Qdj zN#)26wENA53qSX7UWr?V1p~_8$Vau`x<6fa_zF3%lM@^yKk<3izoAj zm02%{hLXRcWD>f2TV`GeyWBUWP9)j`i=G=F4e8islXtUaaUDn;Oyzu8lUwKtp z<8rUqP~K4gJ8?Vi#Dh7ZzkE?fOuDZ4KLV?a1y2GjZe9&e(a=c@Tvsv7gS94!gigbO zD8F<|lpE&ETyNz=>YKXqd=j3rrhE7~G(`kV8sf`Tf)rPX-3MR%&Hm+^d)PfOY52+i z3H1k>R@y53IQ-j@NO1lo%jQ7|c%8wu?MW+{T-0RL!=7gXP1cgL-5F(_Hxbsyi1~MN zXFDEnp7QeD=)$X8^{csV#z3t`VNja4DB@=0De}0D2@DEtf# zU`qH!1?dVt9N%PO!=(-2U(mB`EGh>itjWe~l{>^eA@p3XY*DuO*-mnqG7^(44odCw zQGPn@Jerp4*-vS0==In2Z=u|+w}AsxS{m*o=M@Ha#6V5w@*+!Hvi!(wWvPIAAQOP` zdBANHxUc&Y@8;E|bpZBzLIK04Y&)w^sgsniy3G@B$}BE+PBTUD=)&yGUZVjwG+xUy zQ^md?b_tzmm3;m2^DkPO_EZvtcwQZ~*#aG0IkZNS7Pvi@31GKH0*(=FZ|*o>2(cFL zPJ*quO1$&qMfE*7Hw3oiGBFfW{^9o=i-iX%nuWnILA%!O_ z8y{n0N%2moi+D!?OA4DZo>2!D0CY`cz~x+r;rrmR=2A^HE<2ZSJxID=>Y`5|QLyM& zMtjS2`sfF2Dxp830N(@FA+Wd{*CoLx>yC(|+S$!k{LY3#Zstu%6&Xluj2~V^8J&n# z3wk9d9=}7of(V5ppD@bWhzt}pYftV9>h;%4pZF0J;7?p*2?v|IE5C(T{TY#9Is8U& zOe=9x0b`GZP=(JjLJR6Y3>-nTNQiR^yJC^X$7qdIWDe8HExB1136LV{CtJ$hdi%3= z=|u(d|NFiVqa=dI11U9oaRDJ!0aH)}`IWRVSDcfcUSbzRLnr?!OQWZM=?NuM*@|Oy z34mVg3Y?-}R6?g-%l5bp$Wy4XlmjvwpS>h*JL^2rL zq%Je!dWc*7Mb`uR3V{ije3~1Q5DSqjA|K#+(f+MW{X|W=gqO_3No9lqHCz9q2D5|3 zIw{~I78JysgvHb3$}_hOkPL7oGJXrm4Cn&J#5)Sw_9I3vZBGi!ymX8k^hzSqycP=8 zVq_NP7oVG_3F=7b8K{Pus%DyIuABZIt*+iuPkI79d7aSH)bwKWa5N`pK8?H9 zyaljC6$Blh#KNgf(C@Zsp>_fJUYwc3AZKJKTG;)tQ^4xANLuTV?RD{|PPXNqR6bxS zX+2`7fog3RerdxkvX4TH!&-RwiOWfKAU@hHD*v-pe#RptRltqUJJIFyoIPJ~UpFh= z*4`=(Q4uIUQ+Uxc7VpJo9Bak`)?YR5)$R~eZBaOp%}fb)((vjnN+DWjba74L1Nqf3 z!FExBd~=XjFM*N5`kRMu@2Q*wqLC7D+gnHhXYix$}i!@iwb27Btc1GGfUfB8jmLFzHe$rkS*_6P}Qle8=^tlghI8Vc2EgfzFTl2YcikG{xm*QFC z8gLy}AX=Qh+zYFhm5A*xH@^q6?HD7H{G%%+Qh-yqyGCsHJNA+y-U6gqt2%2aU(uR} zcS8nmLFyv`cvY%vL@CFy%Kvw$on0pPyL)P;#m1EwZKOlRs7(3=bgnpaRG2b<(V!)R zEO!H?r@ruK`0xx=$wxbIwel(B;qez@0dPl`R{+0q*1W1D`V_i6raIy7H4z6p5t4k4 z5DjtXj?zlQsyoq7;q(bPfA5O;$jmF^sOIQ-g-dFBS)47I$Bp{-NS#$`R>yGy3+#n@ zRs852_ScGBvWO389#W+2oiw1I_~KMU^x}9!onM^PlUMq=%GUYL_fi^Mw?*fM3WSpi z0C^hhJZw18YbBM4)J;b?)=d+6!@t18<*A?%>W`8#(#qnhr6#BmiK@Ct(a`EG?JtNX|$SY8~=6?sKYR`V>!Zt7AR;GF+2*ga4EE zM?g#^t;5o7P6UX8MoRZ!{cxzzU9S0%OTxr4$yROs`;Tje1Ln&qJU^Y9eMpi~k~nq0 zi&yyRcZQ!#-+9t{)7XKbj#q>xpOF78GfDRBqBsBPkin-V2>-wD#Q*&9N*KGkDY-bh zIf`1jJJ|kDLtDO*JmVhB zKo=3k+U6#;G6QNcn~z*YK3)iNG#A+(if^b4>J)1gMI3r-BP`X*n6GIa>h?K_3v&+! z6KPhGRg&#iB9T=V`p^AUy{%Sb?ND}(1jN{YZ(cxnMEfUbKQKixGYZ28W`emzSJ6kQ zwPTs}yQ@8R@teyK*O;5SosDQPQ%%dILD^;(RynAXGQi^lOG1t*1};~CmnL~0xB9jU z#?ZE_PZqj{v8VvE{I>TE+Fwk@0fehK4$qlKGnFyk=a9+HnC<)WQIe*=(I{>bGuT6T zZfs_!PRjuOJv2s5lhp$onLT~$S)Te7+{tY}ECFXdAtupB${Lr#0{T(C)n?`t3c|R2 z?c~uDZQ$e$mZ&9b%37GUA!u8(o>AO8Lt$sLGJ+&4#k+SpmF!8cxRLTNMYly#DI1l% z1C#60>}C+#Z@yo~9S?Ve3i16Pf^Rce>wWfjnQe*D2?bR5*1~e{lDl)~S2}scjZ; z+)ZlAzs_LzRzwIa^yk=+Dw(^XKf>u0va@Am&aN4)S$(#a&X&FBKL*@AF;a$4G6x!n zSbr$}K?M%1D02Ko1v-NvJg0{MGd_S#TKvIqLCp~3I&Vz25uRPQsXie(0+PP5o*(EvjGZ?Uaf_am+m(%Clf6;d-iO)04&Qw& zEP!u^nU`PmdR1^CpgBU^IzxCt3c61cGmYYl``(+wyw%JYNd-ne!0wK|KZpRr1S^*W zCfCBV%v52Y-Y5wj_iN^pnLLu;yYOy0?Uw7|rrM;ZeAPorhkVj~=0`q6JBy=F=CxpL|~I>~I_GMAI)k$N5eITFgd~zVdQwhsfqG&X*mk zJhB)z%P!bv&?kIq3}$dCaWos1m7TvPV0!N37&jSVy?N_q)>B4nW}#Vo-v4<1pu0_a zzr~r1cA47Tm9)`rMNC9&6+2eA(A;Y^>$fJX*#?8?#Q@REvh@tsYx87vrR%OI3RGyitkbIZpBpzbgN75oV^RVrm!6bATGwK&Wvd5vSuYa6JUH4hnOY8 zjl7p=C|z8vciI}ed(o<}PipPL+Nk?*}2FYGAnS~SHEYo%Y z$kq);u03Ze>JFDk0$yMKl88Lmc=+UwTP{7UiyMzMOji%JMX1$>P#+ZcdueY!_ukp{ z-8d%M!EzJgKf5?gSCUKyn#U6AR{J+QQczOFH`CjN{8K2Ozfhw1M4u$vfbuXhf3CbYl-~^8`Z^M5!@<=G5A6$7=n(g+}kHT*NHB zq^EB`4zeya%>J&k$A$zVkAcK3Wf71iZy8<+dqJDX?uVWzwz`8lhyUkxzt{osz|lBl z1=vE+Ab&l83?NeW6PL*+wS+6U{8e&T(FMI^sPIweo!AJzUnMcq!oe zv#z-Op+(u_@Gq8eHKq!jXl7M*Q*x9{$UJrng2g-mlEzzI^G&PIHp_ zf7APabI)`e^(a0KMj7!WD>lCKJe$WF$)J6f7M>(&rJhHA`L` z)fmi1mF?o$H+cNWZhsQsc-v&Z+Sp@Wl5K5iGv3dZ{r30x?()cR=6G#^q#p;fEtM}j zXsxBgar3pa#c{KHVe|8SWb+GP3l)}K%uP(5{rljwOAN5aX>2=G{Pd=eBLM-|MAV}f zWs*e+q6<2|_M8$|tW01j2CWi_!h(m{j$pvVgh=Y6-&Tb+oM+hg!p8somKRUDJ zAHmDgprN`WCkb1dulB3V*x39PJritS!?&K3afjp8EW_gQ=ZNUWW+h$i9&9kU9N z6;@Q##DtWG^=38je}umDiS!X2{_(452;1(gux%!5>Iol6+y^vS%I=G&DoPbiR|YKGzm-x(|5iL3H<#6nIS50j>)|OfN-aIVQtP1bn{Sh2N{0mK_YH zgMc~|%t1rve_D##@glzo9{PB1BbV%1-0w!mHc%3d^fHt>SwyrpV6Ema#(d<9v36zC zQ4H|gl7^BXE{mf_TP~DmGdrP79eZu`!%woY+cU}~h2h;{UnKYM6M%&gv+ovDG;n@Y#3`SXt-_ z8%M2^__a)y(_HnIGTjv)U_Do;Blx~g?fBk0ZK(SYp zP=9w;siW?kZ^HtM5$=_DW0hRIXtzsBuG1Uar>4yms{{ypvHA7pCoDGp6LI=SyA@Qp z!rsJlwoaWE!sX(A5+`LtDa%0uR^S|}SuWo^bJ#~YN<_8OB|G>T4wYh7c`rw%UiChX zwt~i``4CQ11iOT3IPXj%O?hO%VN}I}pGSdw9W#1W$|&hvZn$Ec!Y1S*7Oqkp_d`2Q zPxU8seRO5Y<49LpXh$uZ-<=@WQ8$T?yR`db`Mi@?0Ld7iHx*$#6~>z42Qaoh4057G z$L{cIs3VyGHH$dx1UP8M$ToWa2BTY223~viEhbSL)x!vInz@G}6f8=tKoJ^tJk;m9 zvc8Q>BGqzf2yg`mvizG(mK&Oqe}n8Pl3%_n)Yo!O3r*3`6{GWtwLD6$9`)34za(CG z{mMv7*D5uIBl(nVy`;~G4Xng`b=z9eReetlWvdjw-jq*Ozv$v@bM;8(n`|TuS3}y! zU%F$&K4TQ8ZMziEX6SU>;qxklaa5WlU{YfJ=H7aV)b1Vo_g3FdBP=v_lQpYsB!c&c ziPc(WN_7M)8!vn|EU1Q5hRNFJ0<%j9rUwMLKPHrRtMucmY;Lmo0YpkVf`xxM_-D+m zcEdHiN!5N4=)NBM!xJv1#n>*9LH~Sc$@z*ic6zz4t;5T&`W@xyGGdMU8i&Fyp<5E2 ztR7vNA<{yANO|H<(rQ4~f*k#f9~|k4CEB;8FX9Yy_$Aa~W$=J!G9ma=dsaK*rguWZ zQz;a&-I*$TU4Gg%Eg*k$(vvET>Nh;ND!leZk#wS7^eXK0Rblc*(W5yOIz9cOh&yU_ zCAA#t7>yi}pfc)JgtW>t(|R8OdhB$x#CcH1QTFKzUGxPX_xoX_%?RMUOa+~ZO+!vF zkkp{io8}@#>2DM{CNs1hk}}EYQg^h0X(qfVwx479Cre zmeb@&R7Wy)@Sm*}>ol69RT`i0`E~93Dphr=JAW6*a*>6=F>eZAqQ*hu|KsW$yEBct zY#mi>+qP}TW+Iy`z=cVGkk`!sJ zZ#CbEvf?vanmZ^{gC&-w{=>Z;%pIG~m>uDuGrAOeBz3b7^2dWD^n)pGy3*JLh$J~? zborhlGi3GNh@vyl0U_Nyr-lFZwSjAex^{Oy0(JezS!kSkf4ngU7feF@$xSD0s|Rm`q3XbLi0L!$R9eU-9*Lct1RSxy zr^(wf0u6;lW3u zGxVhXhIz!hwmtY$bekwy#PLAiFVL_80<)mn2d*#t-T4eNK7X&_ha;xUpTj^937N~F9HpcR zLs$LP>@cHkbb7kh*;RJEAA$x-*j`3J^yl@Np|2b{*T`t zDe+_|GJkNbn`DBey6wX(8kgeNAa_vYV-ynWD>u$_M_%_`dOEd$>?Z^mE$ad2)LLX>ttjj{LIZ~I zSiDEUF>c#OTiiTmGMjYgiT!}k(`|(q5Qor6Cn3P9+Xf$r)Ykc8Z53Imqv2ZT3QbHK zXwy9ruE7ts2I6i$-BmQkH?z(J_doVFe(_>^hGXMv1fyh=8Vkb{Y2`9x6;!r*cWL=g z0h#2{SJJ7-r)s;OM~nx)01cUgRgSqHdvN1b=<}#5{v^hqn|^B)5lKJ|oPzpV6@qBZ z`4<0uqkn|lKA}O;%#SUsoJdX3C12Mx$k9ik0m3xLqtLRpx6g)P3khY`;@HJ=7gTYS zN!28cgvsJb{+@lH0TdMA66;&nqLLzv3*m5PSY7Ex*pkxo>_b{VjmAxlgjSu!MtydV zu8fiXHaoZfHH{yn;B~b|XRG1m2R&GFLx!d{=T;<%t2od)GY6Un({5D5v zK|o~u|BEsx_8*zR_xR_(=RN-3MrunB*E~bmE>=l`J&vW-ug z1>Zc4l`;S8eYz5O%5j|$*ywq>d0v4c1PLr^VRHov2QG@R5yLg2(DDbBHqIWK#tYQg z*%JS)Vmt5NYm8&a=2{rhKXE|J9$xr z>-Yd7nE&B)U==V{-(e>HdHz)L`X?mFso&u+v^SwnOb8q{AK4hTNa-J9*jB-1fTC<8 zI(J6JakBb?Je(dYyBxwI)hUBp?Xri z#XBxxF(@Q_s0efzXPQOH3_+gd@(=L5Dd=`!{CPV(HoRClfcUTd_3KuzH>`aahp33A ztQ&T?66(_`n%hdmrHh;RAKK>vo88a?=E1;3W>}Jl_JH6G!xJ_b@tbFxJ{(5d+gF7j zh4f~SZOE$r$CwM2?ixjTlV<#5*h&zlD7QskjQT33WZF42_*AhWCIq8+N?;|>QWnd= z*wz$N}KIDkE zn%RhAF##aE9lIz=+}@8>2vdN;gxeR{(X}4PQJ+Q_s`jg#a2dBAaT)D{kZ=~5RnK=p z!GIYtiRyVb38pejh35ooN8Ij!lpLq@ORwNjNo*#Ts=IgOpE2&lvA2wkD1!odRL5?3 z9IqFAId~x@6DZrhvB2EvBRgp<;}zumGY2B^=)J0|lSpn&EM`^!LF92mWy200b=A}B zB&#Pk{=I`8Og6c;psU0EG)y*dpiC?oGyqz>gk(9gYQ7!QCz#TDMBPI5&d)I+lnQw> zzMkKp+yA5n_0qPf+5p7 z6Wrwf+zWcr-vNz=_7KNmPVbIZcyoRjldoVA^HWxNFa>7}`6E*)x-1sj(F5X%uV>w_?!=MU z4q~hWiCkms6tU$T{zwI>2gg@qtguhU@+5oJUj1Qg)FJ=sTxMUC)vIb z*tP2bv6q)VKW9Ro)+LZ#bUT&jO}AM!LpR~kNInT!^W!biv;ppKBKqUixq%Gm^l|77o>fD?v|{l*{F zEk(p0E6(Mw@TIDHg5a$aV>f-GRL;M+o6ulfL5^%R{O){FK@Y zynQ^Tdbk0a%(5Mz9$aIAf5HOE967C?SePrI0QeZ!uu=p-sx8f&l-4XvlV;7+sFhuJ zf=}aqylcg1=dNHf=lbqj^|*n44V5vO@hDJv)W*Npb&L}v1}>M+^Qg=CSx}BR*PPvA zvC~pWBA^ZDJyWy(E+eo0~5SIY*=Ib%Pyr+jqpL0>LIlS9f}#(vzIe zSqp%I-X+s!4;kS%wmIcRVq^H=xfycDj7KSzkIUJ4mxPbQO=7Kb(_$xFb zP9m0fzjWx46wNJzk&|narTnNSmyj{YRFrNgkt#;Z_iJu& z%LDvD&f$tdXBicLnN6lbTh?g~CqkeG@qNs({ecRhen`L9jmCJ(G+3D{c1{#hPJ-=` z9^cOmQE(u5G6)?h!{akRXgmHCX#hlg;=&Aaq517o4Wws)Ft;dyoP}B zNQp=)w`c+GPp|ClUOHp9YYt>d%fr7n6&|wyR}?e2i&C{OuAZb+5f8#t5eKKI?+I!= zD!v&kevRc4oOX%A=vMB4Ng->g6T)hDLgnm^mp@72f@v= z-hH~J4;4|knxZ_F{>j(9jOTEe;=Ou_6v4|-BA)7usvo~I=IX}3(ht1T%Eh8A2)htr z+j)5h*bT#Zi30#7s1i7>sH0)hO93|HJa6r2=mP1xH~M|+Q&*NgFy=72(v&2~ z;<)zs_%1V0G<)_g&7ChbxRPTt=JKdBevb-V0It-l2ysS0w>qZMjq%9(kirhS;1zv} znOJoj{(8{*1F05@vd3pt8MKyo6chUEj*aqzHb0fLu`1+Nl-pbu;|9$2z5Dw;wi}f4 z2jd}-(hYYUcfsxC;P_#B>sZV0U-N?^u0^kO8Y&on)R&`Y-(vw8>M0XQ%T(kJ)6yg~2*Ej2O0F#1T_LHAe;`_emOz|_vesT>` zzi*$xNSZ+0{6_mNZ+LE%f*J0+)92xCwudG#`*w5!ORisk#MgZNu4pskm>(PbvDN5;k71epzHY!g1xZV9>+_4b|ov?xJoa=@7L~!gE7?Wm{jH$SF0}NNly_7X%P| zjZt?;TpxLwpw7Dxiy^EBIcB{sz551_C%e>+0mbghs)7O5d0Np1V@Me&(^B#l8E4%Pw|B6DQZ5Ut9Bn;3H?EUi&F7Z*9qeGW zfc0_QF~lmKmtW`{3X^s@)s*aAf>=q|H0+^i|Mbc`A7?Tfok=&4JDs!!DtZR2R7yA4 zfYq1z0BqWv@ZT8WErQMbvzz;-Kf~SK2SxFH2@X$y@t09ZmVIVS*49iaQ+MVZTu*919w*5_ode+_B$YIaoSPu(ZD%}|r@e3p0;6;?N4LE~SPD*`oWlHXpL z1&2ufqLZo7tkMk6vzvq8pi@0Jq9c|=ZG-aaK8v~$R9#c454iZdLh&n|Y}s`q88s_W zf=c^4SQ|YQMFuj|6G93qti!*OWYeUmB?0B=5tVHdo7jWsmru40KJGXqus>MXMV$)s zEcn@9ypp%mDxW6T!8Q?%Hf#ksd$orXcdNjAQg%Bify}Q^1}gykFnI>4TDJxV=Jx+UR_|^Oglx#!J?yj zuIdGH+4(-a%h*V!z6YcS(5gTSSJGaYL6KLA)mjdFC8lYWJh!IbY>{$p6x|syaYNt5 zAn^|zeJ6JvoU@I5p;%)mI>LEOf|#B#<#)A0{vaF1?@Z7iDaze9xe?RX8|;2XS-XK} z=NwFmwVoUX;a0{H;v6>bRvlz+Q!YPIm+KWQRgGXvQdj&eNOW%naaYqOWWEoaq6yTW8Ecj1E$(tGcJ5i?%_X?P$i{jt^)Y zK>+87_Uy7zfo>!=1-Hbc01gC?7+;*#AUc8AER>)hSBx%myYUs{pW5(}BsX+d5*@O}qbskyhZHl8@PY(g{Rx~# zp1I?#9U^E1R`-EJU;P@6>3nV?G1$i@?`sMXrk&)k38S|IJ&fvWr2WAA^{Tz7zj$5j zHG&Mpsv|vQ@5r{5SL`P`;j}=7At#dq4cJ2dG9uI}qr+xKdr}nZ9#_nC!0`IJzY}V` zVhSmDQloR^P!7$`0j2YfZuEdM*67!58u=CZH9rgk%*snM`Yi|E*F))Z>!9$&*O)x4 zzQt+W{bd>XH2mh*3>YtQr+1`hYu`BL34M$Re~wHSe$_r@vi+6nj{9Gz!=({R$ag(I z0LH#sr29nLlM;oQ?uDN=WBW*Y_;d7(l7;SDyW0)I4eNC8tJlBa8Q5$SDtz}S=nyoO zAI2^~H(r3SY(kEE60Y~h&7+dNTkGwUty|`n+<2%pAd`Y%(-2Yg`pZK*(e)URLGOy- zr=xbt%H+@{w$#|pR=_pU>$hp0f;KuH5Kf;Eb~q`1XZ*)|Dopdw;IeUEZJDZyL%F;i zuWCBDPgIpE_JT+_zi4N5Pi2E!q)SfMRWSg#hl-X$5{^$BQGvEkpkyNFc8X-^c4+qP z37JKjKnc&Fmumc%eIKfj{v1@g-{HdE(c=<(yTr;Tkvm?Z z;^Jy$FAs8_K-I4N0~4M@O@-3uC65ah1o=y*oPz^JBri}=VnAI%fx+e_&NUsHRJa!C zRAE2BW;GzibYvz5NK~zfje@P>1^5zWo;{n3_GALq3;`!HZs^f#UuC)xNWM{gLt$@M zpla?-Ts#u&ZdxiEZ*bQ#ny)JiXF2GGD{x(fY$)U!mr_z}iZ>de1mlQgdkJHt@P;)m zlVn3diH8EQvF>I>(Vi1H`=YFQE|@^LV`o|l=M1V+=n9@W`po@tm@6f+OvkdEIM3rj zG?zRQxqd4lubROGGG!N+^=)s zzHT4cQ@o{?QC({^SbMoVJ74Fbo%k6gkSHE|myYh`f_J#FS}v$thk!cjE^UQx{$6U* zoX$0D=?phq#+g4nVy`~wf()6tbQ-t)t#IZ|w)4U*SDyEV(6CC&foBq>4vWiLeD;dp z7?w|0DB8KXew1PR{;i+;9drZ)P^a_3d-FQ}IgGo?8FuX#hAIT#_W0d2ZWwn3?*7}g zJRSDd*0viVpsj`EYjW6;e)4RNcB2hHZjCMK$xk>@=s{SY(cXI93Q|*B$_usM}y7eNN8!IM|>8b>hWN$$PC(UeHMFVdyIVwQ(#1UeKb5Gb;S62PM{K6N@ZN<-r00p;USPb&+?(m zeV4loU9MPK{!lAl4GQ&eV`xNuzUPTq{-NPppzxCNQTVN+%QO{~vmo*mVr6p-Hq&yK zJ}%FEy}9jFLktTc=RX4mx*d-t%Q$^E-A3_6o6$W24N~BwqnzfLu_@W>Z&D|teF(B1 zW4M#3(_+%xVCg7ix!kPFbG94DdG|z{5;c*!$&`^?-})jCJO%yjL=P6muyWNwl2JhZWA(JLK~ueO97c^a zTFu{C9de%u`Z2<;6Q>&$Kozg*FPp{Pa&T)KMGzwKt5Z&}P*vY`3yskwtSsY^ss-PJ zKXefcOFsH;d%kW@@dXds6u|wqWNvNrhurxVfDi+68r5LV0|O}QZ_7?kj|Fh;#SL!v zU*C;dL0eWti5j?7(-B)Erc%ZKKwVjmtas7u-C9Mf&aSIAy0aaV@Xu68ya#seCoF2# z2GX6^Na1=9nU7Flnf@l2q+5f()0w$jR@FH2-sNCyNDHPcHig?iN0T2~|5}B3+!u`* zYKiFqU3p0kqV5(BHSb`F7hWu zRJFbAbpI@3XSSLfru9>9=?&&mDsry%Q^=jnY7UQQbm3cq+!TVa6fy@-&UjDSyQWq= zf6bLmXD@ZzpcD_u<(7S$v|a%--^|k7ggxEbM_>h~1T9hN zy_k@qCGjcOtH)*Xq&{_ZYZ2f~`-!GJFPxiJECBcgd_IkU#{zx>+AH1f8?)anE0>Fb zTxGiH((oz^&%&7$R5D|sTL{Gu^TKBty&jY$k#0`JL-vS0$wm9x!G}}aTe3G83VHk; zEAhk>AF*t~tDKp@((9SU_`EWwnYrwWWCp0O*@+L$GfxRZHv z5xDt~-1XkvxbWm=+R1$**2;$B>MLlsRmRBhox)h3N3j0e$#BZE;UP?+WTEk=pJc4o za>qEi1qsig+?0}6)HuyzLM_7C2!X2dN^0prw=nS}QJq!mV2IL=oqfG3ULnF#i*Y!o z9-at5Y%Eg8cVVJ(3BMHcN=rIn10DEe2JBw394!@_+ry-wtDLfFx1DpgeZt(VP6y%{0LgRx5F2r<2=;7J@>02A1{;l zsg<~MrbieMhvXkmF#OGQca*-kfF8iSl<;eO=FS*>iF=}AYBqkk#T5QMPjcH#8Hn}H z5THY3SRRWw*|PJ|PrkO1Yxbj(Etvq9@4V`xAno{AnAvVQF7`Ai(fG`m{P+)2)YaN# zBv1Ie@Yd8(mv^|IV(%{|xVwf}E%oskOx#=j-XWNm-QtLt7vmO^owR?4e>}^NSOe-e z6C5i!>0W9_6PW&J-nJ~A`3CL?0)XS`K#Ex^*1tMppJ7*xEf0R4NhCW7Uy~~Uk@EL6 z+Yk?ovlF%O3(iB^TX%?}3B^fO-7}ty83pde`MN(7t+h*J^3qt@o}abUq5wT_r?WbE z;(Hl>o#@%NWJz)Cynrt1IkY}GRAZ%R1t)RqLDC|Wx(l~UcHGr%=sXnIPN3joixU=M z=~ITgz1-{1*RdAjx=n{BfmfNEtJ3<~TtJS3Vd_3@6>BUQs6S{v2;|)(GcCd~(^b_u z%W`TTTuH6%Mb%8NpwWsh)H&!;hZ^QBf3PNmX~*POD2U;UuhH4M!`59}{?Z4DpOQY& z(AbhYPp#6w5M-5=Am+$gQy}OIQCB|UbQvF$+1wY1znmF(F-Z6Ae*(aYM%ZNwr*I%A@civ8Pd9~Ky{)ve0& z?A0TZ+e{0toa|#i3Y4)GMEJk3IX^C5FsZ9B7_}@XXi7sU?b}C<4B#s>JdJf2j#^_S zK3)({=n3O&We19+QP6{lGep`|n0k$q$v7xCjFC_dt#DUvJzW?NGZL*ZZ6~D&lsxV%eZ_MUsO|?Fl3Y`cet$iLMht!n8=D?VTl+fOssgSXH9%inS2H z?7r?T?n-2``$F;K0eBl=wF#B|D9WbVNhIA_RWpwmN}*=64Yi}Iqk4?3HbM_E zoiHfkY3Kk|RxciW*sAd01-jb}#Nc?p3TkiQHpj!ODa~hM9SHsv-w(Vv_kDaIdwb3F z{}}8ISO{<2Ttnnq>CCCk@z2-_m-KcR7rek5_@t<{)2yW%D@g@#2TCa^a+?Xoi*iFB zJnkn__+CrfGsQDS?LuhQDriGcNjY`mZhYG4brALpn38hGLfZ8QalmtUs|| zLp4-xQd^I`09zP~5U~D@lyvPzYdxg$85`5pM%VSGRc^Ft>E@kG@P*=eh&n{kF$G`U z@`JTBE~iSVYp@8bg680XuePH>rZ?}GdPSbAeRD%r(ZTVfcOSvMAkhL^yZr};n1gpY zmvzMV=3(r;1UYC6Ca|tXh~~Qf*vQ#MGnI&p&^@2v0&$hQ? z?T7dfclAMPFTlN+gz+RF_dhkdXRb_y*c3q5V)|$r2;V)J1VsyH2FFRi6uHv^fNy}&U0RqMe56xUCzuagg(``MfX`Qfkze@TyZL$3_}+S9zo zT15bs0HbcXvap%Znl;OhcJ_Ea+Znq-4UIW@mB##DFMslIL*{Z5x;Egy?Th+!qQC9I zb}pNME}N1#6zEv)gLET9?W=P`1EZyO6}YiCoS+4R#cs^;p;MnLRq~_x}#+^2e7?c zy2HJM7Zx;(LP8Bc#SP?=eN)Pl`HtL+vqoc*4oM(Q4Z1Vad1U(_1Wv4F!R=9|DZ_IDZU5_reMW-$d3%Dz3vEMaOF$#>yF}gV8ntu#9C2>TQ1~LsIS`Gw2^@+#b2|hoOWcF z2HfynTI_fZW-!H2Ry`*~@y8m0KMLFaZ*iy}V2-gJ~|JNQ6D!V!eP6ETa5|}5+%Y*{^XA8Ps^O+2?jq5FaX7bZ>PQNdQU?XKEV1IKV z%$gRGX+sb*JB9~Yje&`UQ)@S#Crzy7AK!9v^ig&n)0MM*LD8FhZnGlppP`Xb1&VwD z#VO)6GVfHLW7Q6vAtJxdEb`UamKBKBX#bTw;r`-k*qc%!Q-HS}o3WMA_g|wFx3pq# zrsuG;%GxA@|6`MQ7j%|8KZBBp0Yu@+6}gB6Pqh6ReN=Wq;RYGWiIEEtZ9zjoDNIUYqPV{sGFeiuyJUZ7Az=1kB>jc;Orj z{&GgeD_OXt;p|X{r;CeZNp?_0Pb7k>MEJgCPzc>utH<_rsmmAksq)Hg0;mVi3$THZ zXxHJ~${F~k^Ja36G(3O6w8={pHXVhp3t;gIs)^w&Y1 z;S*^swh5ZhFi!#qxafyWfC7lgP)8&ZBg~&G(ddFhhatCZi;rp8;d4&r(kC_+5cj2=hx8`fCY?}v?dgkl5TmAe6>I**22VpbULfD_aP~d1!)Xh(AX{}EI#5M z+BK^nOx?cfHKZW;*U-tXsbAjY3!GW$uL0v{Qov|>ij&?15CwEH;pcCuacKPD#9J1W z{3y{PYz%DLh7O6r3N1kZuOzYt;|oM8`Ccm7ui_+0$OF|E^wh?D(7c4cbj4)$&^^!> zC>J%iEG7F}99kZ$q%Fo@C4w%$m$XbC!iS6k{Q!cF*&3KyVNrfT%*qcHW-BL_9v?y~ z{+JVRB5#lez#mk2+TL9NPWDHsMUt~Z!ddX}z=53*1eGYjdX$6s;|Rbze48hK*G-|2S9=B?3_v{8}% zo@LfK7`A>1=!HvOSv8X`R}KJ34B6`Vk#xyRV&kd%__7&9Ded zmC`#2X&wVQllU-^r>J2%=YHTb9NlpGzhl}u<47~nk*65NHg1_T)@Xum`65-NWwl@l?T3D^4V_#x{a9>H zRSXr($I9V3=PRAvy{jy=a#C0upX+{W`?QNm?$I#spD6bP}3IH0@4(&!QkS1KlH+ zs5Wxz#m`d$mB+$PQFZnl77e$ozlZ(=6@sZ?YxWy;C$0Lp!-wz>ei@=yI9=j<>3(L9 z?qTXCEb_giU9!xhS1*mM@C=W&9r1An%h^?&Z7?=f3&(Z~RZu+<(hD&$ZjpSMzIpo} z0fB2XWV8Q9t&o)Lk7tGB2YyF6qqZ!`+gn4mAZC??GjM}@3p>+rJND1PioX=Sr%=UR>oS^fcaJCxB1 z8piK2&%#aNaG@MPE~Jqo{1Wx9HdQ}!2s~*3DN1l6%U)u9TS-evtBk~C%+2qX<(ch- zZzWrgLgC3N&BS+CLLQY^mP3rzyi7hQq5G3s<3iC0KMo;(o^Qe4fGk~xHK@ zNnf)JevwUbqkTG?4crlnedD{sNE~9zBk9c{-Al`sQ3yd|tWB60#ifac{b?vCPx823 zdXw$O;SGE8)1QvQrZ?D`QJCFwY>Xnc{HV%%gaSFR-{&+QmK+zb^X|gqgdM^o;3en? z@~cdA6xEk@eCr3i*vRKbfs|ns(m?QC)Q)IgC#$S9bI*EWgCa2pmpF%@#TGlf;@Z>4 z;yI3pEOtZ&svp0n@9Lq5b9EfBs-(7cb*d~ui$_P>taCh9;|2;IqAHOZM)o1{RvEDp z&D3__s9NWVI*Qd9o5Gd0Y8_H-4;)q~dIe~19#b^DZ8mGD8tnb|?M?SDkAcG*rQ*MX zhBp>W+J3BHO)z^OJw+q^hNSM(bD&At_&c|c+FNqk+oO~x^0ZXq*9V6$=ft9sz6qx) z_b&`5ti4ewZyec4ovyag@9QP(*7r==oK8dMCobHP_Ssyi#^&%(*inp$e^pdZE&KkM ze<57Sc9Du{wj*G@K(O|bw7#v)xTzXk`x>ITdz9+dod7V7W!>O}qHP^WQ);VU{cpT#ei9<#w-S9Mozjbcl@VV8 zXlWNC(>k#tKQ4;9F4^EzWua+UB`G$u~+Af%yu30akeS9(Qm-L8R64$9~q*F(2 zq-+k{Q`bty*V%#gy!jfnSC-aebB5N-U=C4I|H?(5?o*5Ij}s1};SLDa#RQ?A`HCYepmyk}dX{yN}1Xz4-bT^?wu zap*|h08OTm!e0ySXjUfNeN&#$0ev_*`-q*de`RkfmmvU|<}Li`AD8DY`@vsc>5`+$ zMPA~zrEkQitqIG8_9Z79%>?T>yRX3iE#&4_g(G#;r27sZU4TwEwD`f8e!TFEHro$t>WljAlh_Q)mm zW5Kv6dALP0vs+E1`hfl(#>?h>bTfz8pU?vyA7D}eO`@Ka6x}+#xT%#?>|Xn2x_1k^ z#|MnzU8-)v52+Q4cM)z@Er|-(LV6q@wDAj09|O9v@20l;69A{r;#L$D`C7jb@Wg1h;;#eVNF0-UiYtM#)~%CxtX|#+V2Fu zqiDJ9WpZWA3H%*VZFz=LNi0>&cxL#2(xElOrux6?aI;^~XF_F@3#lQU=)(`V2WBN- zddRwiOGTm}Y_hc1LlttD62HSu)Ucy(v|4m{iOl1P$n;8HrL=^JK#98pwU#gs5- z3TW?OUIi-w+H2{^eAX&hC(WNyl(XJ9WMLUR)h^104VSG6Ow+tdfVd~|SliEWpFmwD zH8J>08$MB=9be((0`Ho~G5F6kmNi@hcq}}Mnf_&YmW7#F4N*)r2o(b3cx9FY(I!%s+`Y!mE&$+`WFD&jNIxG zja*0Gy_oAa0zM}nrN%X`4JC&>JHWegl-U8RZKMLQr-rtKHc63&7 zlysrLm(X3y8SO|oL6J~D0rS)h*)Gy6R-phgn)HfkybHH5KFqwvr4|P4+qMjb-bh>5 zW?&IGR?*1c=nRtdx~4z>Y&cpoYr`W^Zr|3|$!z7_S;VHAvMrgc@}^Q4@mN!RR7y%k zilmjCQ9jP>D2Fs2S^V(~m+YF_fh7*L@r%iIE z#b}dpTC9CI|Cc=1VgcVMJC>YcJXI26AJ(XiaR|!=$_NW*#KCj?)-Pj@KNnE?V#U(g z`OcS7ikUu4V{Xg78z-!oLb$kwgaIs6fOF>!!(=%!d8@S=c5BnHN6c<~eBVD3t5w>^ zv8qD;Fu4#+VdTs}p;Di>#?7r&O}#c;Hf%u$X|-5tmI5c>7%(dTAzugikC{J+ID)oZ zvW!9vZp^4zSY#n4{rm9{nSRzR?gOAzes1SGaFEV}ZL= z2DZJI9<)7c&E{>aB?v^e5)Vd}_VxH z$ee)Hbvq*#Du>P&38%?qz`BHuJb~8HnM)DVr72d{y8n@cH#dgmWrC~Eua!W7*I1F= zYTc|~M&O3HiZ?Hb7v}_Jw`j#CVv*%b@tl>qxCWo|@7)<{?PF5_w%0oqOATj80PFFlQ*4d6 zNIiZZP;?o2Q2ghwOI&aM0)`ufFwwS~*AJzFi&DdQA+C2qZB~l4 z6~+N=PBy0tPq!q7BJ z3lH{(SeQtiY?KacNYG%y8Xmy)FXd~vB-gncoIhHkYa|BBhCEOk;Z&m=l2cq18>eoL zWkMayiEgWOu5Bc+54?bqO~TBfrni;aFE$@sI!xM3<1Pu;XnOZD@Nt(yry^BkW=fIR#0HD|}eqOn} z#L3n9gR~T9eA)Wmn{QJ3Af8dxJY4bgzS$|g2*mMmkrTM&(|D@=nkXr>d`fkXk8l08 zgdT3Id<=pbVx4n>y*07)p;q>5ZpOG?gV)5~0OJOlMqW3`1z)Pd5?e+yj87!gE@|>i zA~%3!d|5gsWBXogKFRR(90fuaH z9p^j(4}8m$r=uIZ2voFg-s+|l8E`i16~1=*?2y4jl?{4r>`=#qJaD8bZ9oo z4G@v$1i5hZ6%qRndw-$~emS(7e)xEJ$!v2~-eY1Fzm)FlAN?>Dr;{kZ=t*KuOwNvM z`vuTHLjwa?O_P#>%`bc6#~A1f_cqOdpHeDT zn4CV5^H^*D4wqbpA8>NR4{GbcCK?1<5-m_&O!KSJ3-t^#Z*uWPCTdrU0}SiTFL)6e zhZX~6w(LxActJ_n^d6=BITxsXpDUZ~>_@bXFVllssxFqyp%Ud$-zw zId7LY&q8RZcx|;doI}1Ya=$Cj%48M(4*r;t%dNB}YqKn=^ZLDj8xTzy*#oDiQ`rKRWUqS3TCgE>2 z+05@ygg>loltQL;i^$f@J;e7z=7iTT1!6tM4a{CQoDp@vaWAB9(;I7 z=Ja%XMEJgl8KTW9cll9<6P<*+3*Q_2cA+bj7*}ZtIj*}Arj_+=7^yONQ!KDJ6rq*_ zDw!n|AdK&{r}{d&2!&U;?O`i@=^;}b*f^SB%?y|b;OTsnE5tw?lQd^J?5&u-eo@wu z3J&`u9*kboHOA5j#Cin_gISc5y%{aumErg5rY#$~&IRz?`gqo{b!@Jgu z(EQZwHIzAuR+Ob3qLLiWID)G~(B1qAvbl{|o)IUBc}wy}PX?#GKWgI%6D+I~5w$NB zY*xB5T~%6AoFtF<&*Oy$%7ABc?>h$!caOL`$<&lXblSqOSe4+O6|%uD$Qh?*8SZDZ z6O;~nd!r)|?0iqF09Mqu4}R)!H72K@8fZ!sHA9;cpqk3Af33RIcC!le(QxfTd(~DvC7o zQCAXoP*zPqv2L%!cecsHBdG-i_BV^{V*dGMn{0xltRMJ}8$6e=Qhg@_pW!ztZ;@6F zRa1=!NoKt2mM-DQ&6RBEcFXjfj{7BhlKyO7iUxIatc+|DK4;^F(z)B@!h7&5j~0NE zV61^hRJl+q9e<~G z)q&4{k_~Myy@&)*D}K%lFCrCRmQO$3ByP$>@ev?OWgSbec*lZ2NX`+X(^qIodAaxO z_k1Q})d}#=DSM(SoAa=%Wk25FRSGgTyGW>o?E#@-2L_F7Nlt9y?ece>&fY;3|G573 zXXFa_1;N(^x+a~ORduKO?nWpyRon#*%2sS+>8@wu44_g%oGJgKmE| zm?XxYUUY*e9xXafvI3jFRC!Ml;)N111JHqe!Zo1&J~JW9IT)Kf!K7?+!8285Y~L;m z{%bV(4&!{p*00}+u)&c&@*=kmTJuO|oXd=VLMO&I$&V51CxA{7ftEuEExg`-H6Zlu zWFAZ%brnGCz6A31@WTXb5NMsJ zd`uIu3SKgqXX@ApYKA3tpiHRWqwaSOVDal3)~^$Y1Zm9ZqT$NDtm@4a-tY>nR_mra zvYbw;!dx1*uM%6@H@Jq8=r;1g)sP=z{n;;{$zZR9(Bd*+@V9KCy}6OXU8*v-w&U+} z`Qs+1XJv;Aynb*SOMRyZ{aQLt5YQ~OrGHRe;37AXy+jwHPFHOVtJ}pC;C3Ro4c5Fg z)QziXyEWNvn=IsvdQD_rE3HdwV%Fmq!Zm{<0!)Z1ri*}5L<(f2gGoFaJf=|H(Gp%W zI#mZxtNvLf^bvCEIN~%yhqDr^Ah1Uv*z)670d>Ux;4W)cpJT+C@J&>N@*^{#i5|iN#p- zhXPV#!z844rxrS-b21(6b-0Nq-8kaX2IBV}C68DXI{~VB}vs*ui$ZF2! zby07%CAkwwCDIsP>U{{ZN)4**m)@2drBEngPmi0CO_=O)WG2_{>i`ixgO$%b_c-^# zM%Q*SWB+4sWaC~2fw$}K*q2XJE65um;)p@!Nsp@dZCf;|yKe*G!6Rm}AUH<`ftrDe z@X3Kn{V>5-aV`PL)Cjn#^GUZ=^u z+A5MNHF{R#C6DK)(vSg8!Ikkd?k-W{_1HD}FZ*nqn)V}Ejq&9K63tT62;~)=oV4X3*9pS@a8)gb5{Rp_HHXKoT?T@PN3PRnU zVh-`rzMz-8e%Kew$Kc(L%07p*_7?1^t^*s#2aP@11}WoBYvUkhbz9Z51`nfMO}_5T z?v6`Y^A4%u{g+{^J$QbF>1yJyL@z+GXO@g^7#O1GEAP>;$%HsoOo^gN;nEmsGG0Hv z-C&PZgZ9$W6b$s%=Yqevg}bo=@(O$@!C&R30)kI2!0z#X9(HtPM-Xk~)4SNTiUfNx3Ryu?m=9-8{zT7MRRbarU{xq+Hf;)p*+>z!!J)iFSzzfKy;n6ounS zc)CvLbhM1<-Gve3ks&2nfDs!NSBouAGO*T2=2l6jC<6x+E)QB=pM=B-G(ka1?9M~sw&j@8X1ubR z#+}=Fyta*!hYphRjqfAWV2mNkiQg$WYkG_sr|f4L$A`5Td6omZF)TPe9!Hj?3;Bba zO+F|@Jm_$aK^BK-WBcTZisMNY@cKCw#Dx$P(1Aazh56!?&p8lvvQWGZ&5UH)MOQ55 z2S0c$5p=b?@8EAe^FG?m5qMkE<=3VfJdP#xYO~c{M`4~LKy8*MEbp5B{6364n}d0- z2zoeySYNNnj>NOku8qItX){f&G^LtPzRbmn^O5z{Y;BqAgtUgpkSD(82J26lv9~ji z-v%NT6Z}erVda5?Qr*)E))|y7&Z8R@ZM(%swS3zGnzT-DSF^aO- zB|>&>LM?CQSi_k#4L2zd)=SBK$*w_2Xd{-Q%+Vxaa(KF_7O^0XF2r0U1zC z?&96b4Tp$`E;m%L2AJ7ySW4#K1vC&8U>1H$(o!^4ZeTko$fUCjbQ5O33)@?Uk6xt8 zixfwpP*al}Y<3nE8B&BSP3%8+fLgNC6(GO*iy{1^c|p+@L^%X2zNFLFSYolc-z9&q zwLvVj>xIX&Kv^f1rOH(+wN90(;h&&d9m03dJg<crtS9Ea$>xF-byNXJuK|Qp5#$G-M>as@g!rdXN%4>%1~^ z@8%zW$UnJ`2$c6hSEre%mkw%3o!;=v#K>Wgd4G~DqKb1LiCiP4lF+H4*+^wxP?DsF z*2pOX^5?=n6i=Er$2Yq9eQN19Ctk4%XiOp1GmtTgKKl%#zTZQ5a{SD?N#2_(@{F7y|%EWd|W%$2%At}LHb z%-hLd$wn+_gQjhcP`l4bu8Ec#f!YzKAregKSLPx1i~&E(XoQR}}VXn4oqVblf7EFSV*j-@;4B)gw&$r)HHgc80%%#WQ z)yM$k6IRWkRlWTmi;joe2npu*NU!qSq$3v}3qt*0p#t$=p^^-=1jPqd&{R-M^t<-_ zqOw4qL9_L_2_m3MG)PpexDCW+6UJm(32j4}r@vjH5*_==-~Dy-n)7HgZQxcqdu!O} z;UVWTb#j@?o~_>j$q;k~uOzWnqhlq6(YJ`!Im$cWpXOp|FZvvmd*%mQ(9$0>d2r-0 z9f^k%6+U5yW%Jy+Bl-Zm3H`Y#j9XS#BDF``cM)&?9y3al|l)%Qw@M6m-c1Q6Nc2dCy)McaCa( z&jkB5TlzjR9pr3GEgWX~6U*u!OgH4MEgN3`W(IBa7O} z01ag{M{V4eA<#%tv7;Sa&#?xj&Fb8lF@zVE+uPrG85x@SI0FMM_N4)(m$) zrb*Gx9+nukBU1-RimMQOu3DwFyNKM^xPC5ndgI*t9PkT)oDw_Mh@-XE=mJ3XX*Dvn z#xcM!ztUWqP-4GOHQFHFX^veAhGT}=u0N6PY9fM&)Ne^;ljOL3&1LMw_0+VNkX=kC zb3(gu|HH#P3DCG99T;%g}~Ni)ilL;Dq|-;_JN?MaTe(mY&Kt(?uN z3wRlm-0Je*6`szR=23-xIy2qW;ZIkdshq}OX8IiYOn4Ugos{b#Cvq4R6%h#t3I!ha zY;VGXB9SdgD9E}4NrDK|&QVKf?8(Ag zVb*5yvOEUTM{8IvD`L7ub;zSsGrnGmVZ?a)4q|yxA)Jm{sw}U-zO;KV1i*5 zJ{v=S!x?WNKG6Gql&)2(#BIsOtUSCY16Cw9YDZ|Je^-;RklC}wDmRPG5(eAMnr(YR z{O?(vh4Azz$9J`%f=JeP{N|her`r5?2qoEmN6?xL3L|1BV75S3*ml~EGFd5X-me?H zlu8u|N>oThiKp3b0|d*z*YK9ZpVy_xytohS51sNy1#U-=7y_iZkoRwe#!w z74C=gwhc%TL42?kQW0TrjcfnUi~$}_gNMmjC|aS*(r>a^P|V7@#epGYE?yZP^9Fev zK5$vQa8E*HwoY#KB5oV$N;^08{K0tfd<@KV&EKJ|eatO`}tHo(vfN81Dm>Oq(>m_Ok8csIdbLA@`9c-foP}{*m$2J$r2j z?zqaNb013gAE+7X6u^{Bk;X$ctYK#dGjLAr9U@sW8+Y^Xz?J$`RXIy-m1T{)nr=C; z^o(mghZl_9>z!6=ZDh?Z*u+=vw!l_khx~tkW-aE5uan=oTJIZo`#-rF^1s`fWCnLA zG+?31H=J7)bpxYHCQgo^Uy2_Q8>~Q*Kn5(yr&L8s8br7Z++R(%!q5SyD0RF)75bk1 z8O-x$oe8T(n#x(Gv#73r*;^wR&TMzw%zHz_ zjh8qcti_YlM^~wv&%8lKC!=EFNCAjyh>G@bRKn+loz<2}HcJg=ZS;XV@##N{tY5=n zJ5FR&1^N4G_7+*`85UUTQG~QTs-`Uo`!R)zE?!!#L!fqDwR@bGxN>dXV``k#R6%u%DkB+OKCH!<|uG5m_V7ht}^3h%Y|( zC@USGf;FA|6=ulO9Sno6%F{1SUdUBV@fbLeV?}SBv@|L&Z)v9(YH=MR1FSeMBDH|t zk{m8U`I)H}vCWu8p`mz#S`N!jpD(iVZ=Zy=nEv5!EyfhV{OzQ#+&4-kzIHO_Lm6g( zT7MbuK-*HY^{dxDUCSi|WA4k?m07jysZKec4U;0Zk)kQRt#@PAA2K@mw{TNgtL5kw z{P4k~fks#BTvuf-k??+fE|3QTEojEZZ&a?(`W&LquB@Ly=`|Tb2oeiPHtDn6Ly}SJ z#wouxw~1>;luB+b5sZdEh)y9pi45Q{)_bRp8-1v#`_Fm@vQ0uk>vd`<2t3jm3fA(t zP*hCYPt9FNjten-#7l0A_g<%Oh(y})HHn0rGc%J1eC!jJ6fY9tH_*_|I1GPB%=r|N zIG|lzf)j(FML41Z{@iMsTk^Ae2P`))SeNFOcbkGz7`5huKre z0g)J_4KD&IIduD~INcjxH7dv8HL~(h9V6l}e~b%0++t$ruX$qrKF65^*bLvPAHV$i-&x?G zg!m#$<;M@h!*6xYj~_pbZLR75T5@pO)4SNl=YahF|Ia%A4LF?W`01%K@c6^?&*3I_ z`h+YLv~{T>Q9uAUbO6u5@}F*2oHMs z?2fnd4r(ILqw$Wml`Oc|ug>T#`tyyIRA#e3a+#ceFO}Px_!(VXU@*Zufr?mCYY)Ty1-7)V&{FHIctDdTa1X^OqbAzFI zb20q+%7mnII&*3DKh=Dv;F`fv7i|5dGZ+W;^24$*KT2t!RVm9WRHJ-VEtA& zv!r>bPPOZvDLnI>fOE=feHAzfOJcRW?)%0q!nmeMpW&oqOO>aes=t7aEk$YZS-jsu z_g*UM&T-_Pr6X&S(N}z~=$n|U|giPGDEI`#KPIQVv z{)6F$*cy?b^|8n?mR-glB)kPuo8QA8l7ZrFvAF$I0l|<{Ra0T=2>{E95ImH6!k&7E ze1eo`M!t)={~?hwoU^>tP9-AKFmag(^2RA5Qi!IaaRJaf1334Ra7gr;pcHkhKyDDH z$*JO_FK8!X-S;t7ZJ@bxiS%*E@SO&L@mLH>`Dksd>(WO@RO=BL7LZV;DDN zsA3*RtE9d^YE@rYQfpwTQkdH-sVLLqC@eGL0#hgP3vc&+l8P4fI7n51xFZ>7-$B&q z&T)xzhpn^kpIZlcG75;?&lN#2YC#NMU<9?Aot@vegm*nn=&q%fx3zg>NA4J<2TvK+|kNg?TnrPg-r9;biiJwgbi!7T%0)tarAjeZhXVoN>0 zK>x~ymJJYf>B=i5J^cwG02;b>oLWnj$mY7}9)qXW$S9+F3@(c+ioAu*#`qZy^-A4>jf>ggS;>$;xSSiCF;gxr8W@uEW&dCh7tGdpLG z5gvB1QrhMEK=ce)+@-^$4v>OsUmF|W(jmFO1u7go5&`kq^Yexw9jKerb3JXtFk@0V-wLSwj zYHZs9T0Nx(y2_+RK3J^s!Oc=TEuB}dz*zq-ZKc!w%2S6`#VqN$q4QiFv-Cc`o{~J` zOD=5>AGQdc%my)yLR+2XH7akVs*h7cvkPyiy2u!kH;H>28}dWJGxEBekaaOiV;6qD zs_L$?4k`n9e9DDV6%1sGq4t_cdibYLo<}IM&7L>8e#S{#BPI0Au>-j*W9h`~FBu55 zC?#3%CE$nDO{it|q}^;iIkV)IEf^zRA2cu!ucXpo-v8 zZF#IfaBVLrl1j&32PudcoSkNfDQnDYj?Sa9JRbI^K2taC?%MH&Mo76Puxg@83ZhC9 zQxmDxv*eEmn;%d!l>V2Vl;dd1i_OG(IT#c+Y$UQvi24Y#gRFuBQlR)1!tgmkk)bHW z&Q=PHL*f9T_VkWMAw4H1fdmy zQw8=Gol>t+H!1fPoYB9&Jx%*!Ud2`c`yz$oB@~#{*4gB6bbNp`Zc`S5@0Xh!N}7b6e)YsEpw?X%Ql?I^=_XGw5L>`F++L=|UJ6BIXg_W$9L zok=oJC2?xnrq%ONtj0~L^Nw!LO1($_k*@26g(Nwp(xLT#FasmOF|QM($rc2@Y?iMp z9W>LZbAwkyD-zF6d$Rw z#dB|6KQdTiOMSBTDdI1B&$0*(ut?hibYUFE zN?$#6)b>k7hz`2lu=$;Ng=U0GmMtB3+`vR#P{UYBUooP9kxUg0!;KDD9%qGDx%o~K zMpcEz^fk@0!@UBkM!O;u|4nAXx{pY1Js!-X5#|n6sVZlPH5jCVnO2Hls4*R;<*QG9 zMOP!M_Q2%UsUwzY%)kwG=cdiIi1)`z6O|rLTE_7PjF_kpcrC-kFVt~ab_stl<*zK6 zgJ`ELmrB4=Z=jJV=>ULrsv?Vq`?T)-5ew<(6pe$M82KQ(vrf@z#t8~ABE$`4aLrI?g^^2 zaBy9` zs$wCRNlGUxWZdLs^+8fo%quU7g(ZXX#u#bmM9#IUTw@oNmJ;S>;?}vPo5JvvZ2`Je zE2d;^8M;X1opC@`<89ardRf68WgQL8)G!&M#u2-uZ&r!^Ld@aYyM=Ft70Mg;>&Q92 zxdhR@l%w-9>$%oQ74yo&c{b%lR>&nya5pkuxG^V1=UNA$GkII=2Fc2w_>J+;z)@wymPUM zOYC?Xj@MEDLdCzd=B)%>M9x=_<&L-wvpRw`KRH<#oB=MQ6eCS4Ml~nR?TDG+vY1!y&LJRE zLGYc-nJ0EaWfS&uW^*qQT}@Lyf?vHTWyh%2mFj?BOeslKzj94FSXYvK!tAC9ds6YVhZN%iN z?uo=>%LKm$VP+f14N(M znokVaSXn=Y)Qxd2J<`dvvUHwaQkgVrr`)o7QGHv1P@+6!I={;u?=|IZkmIIm?}#Y8 zeDwt#nNZS`W6+&zQis1nPxhAh1G7W{{?RJNik#|Lgn8Y{R(zRif{hJn(AEmWy6u2t z#imi*L4a})>($M}7|5g%GnggCq#zPU`M6bTD*?43bn!-6RU=$Gh z%igBWvP}<9@l?Y_m-uBx%&$0yUlb^q-9#N=kX`Ex?WQr!+n zRy$o1z#Xyk2O0|B%X4!npxBKcviz%6(J@Aw zNSi!3^HMDS=kOEji-Dex7HPYST+;S43){ajlpyYh9bp;11dO~8;tkyVCbL2?c3fUs zH>4=09m)iVa27>#y`Vef5phJ;BqmH?mQFC~>UMUVLWU!Vm>V4wUl6g>LyWz?6^oI~gvWqRtPxgN@>fVk>Cg{Zd>Bmr()$1wFHL4OFgj((gN_xol z+exF~zBqZ`$3aH#X_%#Wl4YBg9+#nkoRPGIs}V;UG>#w~{#CeCKG)6wWE8!m?;p#r zOk=f&O~re5jv(y(!i1GPSyOv%r?Be)F;P-qXg@%^J6xj4)@!J7Fr07*Xz~<0xaXWJ zLt26IQ{`4k^(4gB@wjLCRY@Q){%aeyNK$we9B za7*%`oQo|m&&?W+W_WS%CP_1DmXS^jWQz<_PibdUk-=eJH@;2G^)pqeS>1nlr?B+0 zTt8Ep!@3vt|E601WYRjEy9q+s+~V{u}>Sib#k>M?_Et#-H!N*aEF5xw34)h8;eu34=WK;Sd) z*G5W&hjj<(liy+DZS&&yy|`_Aecb*E?y0A~U%lfWPb^RgBdZt4CrF#GCGVqhy|BJ( zjZ`zgJiKSaLXttLx?GZVp%r%2tn4X%TG$&J<-~feB-#_Y+~LTo_1cCFi8%>W ziIa7uid=OGMJbO1ui~~w4R*6@>qxsYd*=Cf)|O*=;w84udqGkCx`ILd7W~_$qE<_p< za4y%@CV&ra`)<98G7|1LkOVh?jL34ABYOa^$M>?D%`GnQo$xof+CwuA)_r&$TzNw4 z5JG)|(`N|uz`G)(KaBFvB$L~r7nC@DK=0Ihse+Fkj|>;SGMvqpEcJGkQdSm!A4jbpTcjy~rYdnDOB0&N{9u?9me2 zQW)ODT+sp8vmMRxLOuUJ!9PUp%=2&Gw)p$`0k7v49^;B2sxK130`H#KW+-)6mL!H+ zzEdJap7IVB50r?Do4hI?5x-T~SEZBqj2+=W%qBo%XoijOOQT5V$yw+57UA4kN=fA+CqVqcD4Z* zAcvrQ51Yv$59n8g&WR*(pX+{Ah>(E^<^aMLw=2O#PURGR$joFD8N)d`9is;7Q$VH} z#SDuR?mlX(d!3%h!TA%T2uIhha5a6zr^Xz})mCHDi271Kq6o)5H!6}RKdu1fy3M@E zrN`>Jl&v9@D^u~R8+P;&El?s|n^}ZW!wi*BQ}Z-nqoah6k0&ZpSZ#aqP7m&mWPqhw z!#QR`62pl`0hCe$5(%lt<&2nBQ8Oq+*h9+)Sm(%$tEk2r^5;2=8zS&p{Sq-j~ZTOfL=^0`k%_dX~HkDW0S>!>GY^+=U$Dt$}d_O5I{NP_;T?#Iif+)WB&SnflJT zn=Xr1tRgm!_Z{;#XpuBsj=k&yWf#R@+P`63jOs8tJHDm70z^ZqMH;lHiuBDNg!Uz% zWhfI8ub_zv$AZ0ezWqk|c@2(hapoeW$IPPf9ILBzN^Q3g_UK}HuAb*}wqZWzwfm*3 z62Ipj=FAc%&m?Xtnrv5{^MHR#9vO=`)P9>mfEYHZ-S2l(&^oG^*jjWU&+z&d9g9FF zp2#aw#UxVk2xM4=DfgF#$PUq!gjGcY02fY!ngOZ1j`l&QW@#1LamTgK=}k%*f#^c5 zKYud!s(TP4%nWdvV;3~zh&4$I4>|{N$Y;je>)01HI&Q0FJq-E_>n{{+uYD}6$2P6wUCAzr>$m|PI)l+w`>I2iZ zG{F+8LG&X%a2wiIQI`~mv92}KOO1%1I0qhfvz69;(3_f($cbm5($s9PQMwuD;?b|yW z<@zv>X_)~&%fuKXJ0qAo#*<^N9tK9}>8X85_BOPVZ{wr1Fz`pijTaHcz_&kvtv+$i zMzXg0#6@@~eTi#Drgj4L3;ju1BTP5A=Pq*#?e)tWq>V`}WA-Oy-X)IKJ#LY!1dr8# zir#*$%OR{Mdh$DY}F^w%^7(Qoqd#a;4nuORL;YyD3Oien*?LRw8v= zcr-Sxmp*l-p4=%xMc$Dk4U^4>tX>6EE}u(rOy7gsF}+YK7#Nzj+z}#U%`Ss z*I{{2ebExL_5!RgXb7TeUl7sg88EgEOqK-@X2=VnuES~Q? zI8aMvE5aZHO$%E?_Q{PGgOiB_tZON=7SEmH02fRbPj2S%8wy6Qr08n0kIC|d=cXg! z=3MW2Z8N|p72qOA<9qp$f7MWiP8$G0O2(*GqIU8gk9mTC>un{Xuh1}3u zwR24YE8kuz3r?-4gr%N%ftp#@fRT>XPNJ<9T;d1*NdQ1cT7**mjIbfAXi@qs(`1es zsJE}7)wsG>`tK60UmrVtFMq;4gBe5cvYZI-4|l`o>W@-57Q)oh-}(nlSDh>p521IoZz=t9upFiqcBxH%^<=(J1XIHd z%hsg(7mdqb+)_a*JWGo=%e{)fnScjs^~I&U#2Vj8t2k}6Z^OqvwByVJjyn}|gM)P* z?N03-1g%s7ylg0-IS-U=WFCH&j2OQv4=zgOBE-pDnU8A%yeB1WRkqw6~(5TPS}w8O#mvduV!C!Gk&+2EBnNxP9Dg3P0U(_YbZd5?W)8H zLKP!TlbjWhtnddE9Wrp_D}!;$#CQx*o)Czn|4KGjEn<^TpCZ5jnA?=3$Ar+xIRC~_ z{>LM%ouexK{Ud^Q7JcZzR$@H_2;ze8Po_$)=?OJbSfrLu`_D>mK3VM$t&nvOz+1aW zL=l|2y0eO8?jRzRdiL0=npZaO&_$=x#nMPBRY;6AzV?RmqT1;uYcT=9iGW`Oux740 zAj(y$mB*(InUJoICM1K$#S7($MvSH)t|pVZ)o7I`uPT}o11xvZ8SROIkUvSLY7wp3 z`tdO>BPH3SjE;CrC^Ypmwfql>nTseDvvMS$keb@KV6}+?zm-Svj%;Tib3D{qYNhZQ z?|hX5CakNgQGdG}iDajj7Z=l4AullBjGQ{EXHcZpOh5r!$c-Dxz7^WlxE@s4~~N~ZHt|=oO4Tc z3v^}3kDQ!PujLa&lkBs5NiJJa)Iyn=R@znzQB@)v`wtW;7lm^9PCNC*bD|q1+mtNS zQ&+bdfFvzr)gcV~PHJuH?p9r#7=;X2Nx!`2c;N7yqQfTXQCdsGnd>dA=~zM3qH<#n}-92S!O%HW(jZdi{{O0ZWZi z);LbLR~-73O6b}@%%^Ph;uArMOGlP#uO&&ui;d-t5hnH5}@x zr>je7r{w$K6$BCOc-mbe^>||c4UVUQZJnqd%tB9{@uqJ%We~h2^CNp{?b=t&bI-%*C>{NGC zz_?Nc4hkr|4R)oj%3ZiX^1jnjW1rE2g;%Tx+VB^)a}n{bG067WYE*#GH1O}nv6kzD zQOVi>4p6@!ZE1Im4n_dZ<{k>f>d#u(r|rUEFXjaM9#%~xK+;djKJKm_n0L(BU#VQo zPF24wnJvS%IxPiUVs=3vP!#KRyZl!gbX#qJr$Ka-6oAY&J_Gsr=>S-yHP2)ui+NAtJ@1vWuUDMPP%sKWk-n3{oV&gR8grDo}^Fh1YgM_y+Io4g~X#s$hs zfWjY2==s7^#tc?LTneKI&$q%zt}iy0M8QYw;<%i(#`jr8o6=XZl1Y>p@tKZ=>N|Bn z5lUjFhu<5fr(O@<=uvlG-eW=1QTakuy+@W+<@b<>ZD;9Y%nu;v#-#`{akva=$c0(i zOfN3KU5oi7wlF%3z3lwJt3oF+DfuC^_TAyzl7iUh=YKsIE_Pa)$SsosC3o|l->FmG z89n4q`(}d9EY5LRKje$_%AylqDU_Rm4>RV`E~oR195l61Aqem5<>msk^7xjgZ*xVG zE?*YAmIt6zga^e~c($h~_h1!RYK4iLTvM+iY*sv#<*y4#OKO+%V-KN9{L}N~CqD~& zC}JZoMEq5Rc-4z0Gtmgm77}$5{=K5cLpa@&$w|Gz7W~3>6g@_PkYRo}d4=T$X3b&G z=XDt+8n{hzqXXyc&yT{DrQ|gC$WGV;jSiPe z+eq;X`N7b3VU$!5d(jqyTX6|*AimA1`0<$ix$gqr73!HOHS=7TMWPh%G@SJXTxqp{ zJ?58%kJN8}H2IF{4H>~7I~-t{en@?%iT5RyN|G&9rVK>>4@>H4uT)Z)+$=-~)Le%U zNiAB4O^FBdXDbG!_yak>GTds|NM?*Xr%*C}1vGKAX|leW3UBn8B7QaAtfySjtN`PNo?R^;vc9>RkouTMwIxVxCG-*N zp?p0l@(%JQLoQpB7d_Pgx>yu>CwVu{PcdpndfSxY5+<7cNDuTV6qLW9p;tEn6S;eD z))}2Kr=r}RpPHj*c?iD`x87+L3_P(6?1L9|q@STrd^HWYJdnIR5`Cy55Zi7C?&O{j z;BLS&ZlPj$t@^&O=>NLdJyAbaPi+nJbZY)kGc59M@Q$3XCiScaO2^@muGcP*fUYP8 z&T;jy7C{rePI0(j3OGX6(K@U@yhavzqZHD`*mO~i&D$=1kv?ccO=!oHY(YJhYKa(d z*4c@XYlt-okl30{(__UYlAzkmU9mj09k<3^J{n6FZI*2jKX^d*yv30~BDe%U5KrU8 z(p(w!zJ{zPp|;!uYszHv1v@t8n@-oxb3~mG;w0*sl)LnS0;dSyuZVk06vGe0_iDLU zcus&LN&sJ~M+-I8Fuy|>*gucIfd5Y;+`Hx`E3=e8m!I;X80xB++qjaU-+VD`1A6yn zA_4loqLjawKRQc;f;`7*%|-6^9ludOsg=26Zbm-nGw&0KU)p7IN`Lkh**g2%8w7{a zO=pr+8C^wHZ34DSfU;AmMHq+%VH513VtMg)f+2zqRybc8aeUfOv{ZH9F5f!=LzS zCL)~e5{3_C3z;Q5QIIF`(=S4FS}6CLpb+nz?qYkey|+y)Q8tyjP>DLNucj2>{$ zCR#c}t_ojM(7}|l9H=7?jdX0;`}yi&Zh-wEYy1PNVh|hQV4xsRFaX$jT3Bw0Pgu9& zY^)DN$zp>nw_?ouMhvxz(1bcxmb10+0)yfi zv$W(4YUo#ZC?{yFmMsztg*AK*BN$_`@g#u0^Y%F4ii;3zZCe~6VzqGAu*NWvMrGKd zq>;PO#@HW^m{bnNkRNQyA8hH9{>iah?`&@x*gwgkAtUvrtqMDQmDWmxYP9rGPX;}E z?-A3kS4J3B3G*mKA}xfDv{CB^i7c8@ii)`0xJfZnv1RgSu8~=yx1y@#x7Vg@ z8>2HRYeQ#Du*GpENIp7H;Z)nwk|=;q%v=(qjMybk--@9IEq0cYMLFI%4O?s+bKpdY zxXZwqJ@ydxkmR2097hkw4Eo~Cj)Z;nsIoSUwa-3p` zJ(W3*NaP-PDaOKjZp+ka=F{{KkYIaAFguGYEF49bTlwFED{19$qd8hj_j)CF7S@+YmAx1RU*UG-N~tg z2P{fwxRv0T!QG;C`xdtOvQ|2#*g*Er9)hM7|9?l~`OUro&1m!;&3DSX;Nrl{?DicJ zJ!9-8?4=pD&Q>x*2&XHIx(T2Xn+s+FxMgf|glb)w{pfAqj)e1ZlyWAPb^ntx+1a#cf^{^p7S0&YK-jVpSpCaxs--`xrjo0^^6T;QvWIk z_A*)GJvk}WMC)~9?58;1WUOo2yfYoNl9LQ8ePZ+8{K2G@efCW1-6&vsZcB_=efqnx zv855giRr%YQzS6{xr4ta{a{xpU`{GD)sV;BDtQoo&UM(8V}#Z*ob2q3XHLCJg))t) ztVn$_gVkx%&w{H_Hb3RbpV^;$!J|7ATFgMQ9-x>PgFQbYwcy413tRRU*!RWiKjNWsn4EJyN0 zY+VS+k&C<_-DMxs<{zN0Ehw7$HgHA}x$4DdGLY7lp=;<~6+cOzd!;~*2Dc73sr{3J z)I1gY)NzUL)xpjd@95(~laqMgG3#RK3~LoNk6dzxuoDT-LV8grj;7w*gow3)Uz{&a z?3-3V1#!4D(g$`V{osotNAy)HThktSB?jxZ!F$8&6%m4kOS21PMSb8W=}3XQU&E*; z2oamwb$=K=r_`&a|2AmrJ}9#^bk5__(G)(Rm3H73jK6Z_t#DvPzFKCYMd9EOW6|ft zj;K4fSB7s|7?_Dk@2plhp@p=BOVP{Ak5w_uboHy*gh7uyQ@FmmHb%;h}$GeKW=bV2$A*cMkM zU4H9LadGeWUaz@Dx{HqS>*#1hs)=W_lcIl0>QOM(nk#{7N9k-HGOi)q2vrtB{ z0auX(X<tS(QJOf{@rBrP9Lzd5CvJ{E*6@sFx@U>&?h{wjXNo=HNi2a{YRB z|0q_NJ#EeL4JSYZN9~HolhnYMbV2Lp;g1Z*BLd9 zGL?tuczXM8ME5gp0i6{7EG+p~XfBxm;Z3?RcMoB_62zRhZ>t8n1WD(4z$B2YhS3yV zj0HRWe0?euJbQzewEOWTz81)XXMXJ=42_+cD>#`xWgrm7h|HwbFoK?|E72NJJ$>>2$odMXD7Wuzni-_KyStFy3ehVHIG zLP`l~kdp3_79^!51PST*wVt)#=h=Ioz0W?wLU_-_3TnUi2WD(56Rj-1 zVGi<{Evl)oiowstkoPuO@eb|Ilr-3z-_R)k$lA)WF1I_c_@zJZxc%<2PkRu`kPM1Rl-Bae*_OJbKK$PFGV z!mwjF=3a~JAnDzku9WZPL+8E82RZ}s?3=4>MJ%AJ9J=scwd5Y2o|gtH103;I7vW)} zb~;x3-)O&UI7_R_W2M0E(56ufPJ3DLy^=ZI>&U=OK1i9ctBN%7E{^8WCQ!S7NNRP| zo70F@Ue>N;$F2mapCg#tyNM)>_C-@ zbNwx;r3V$ylm%pW!6(BMPn$uqt98CCEKmbNmt>-0{`#DTZen zK-;tWq&$gi9yUS*#2QzfL+S;T9!yozc1q@--rZX7#5Jqc(Qd`LR&hL!q!iR;fqVXW z@4L_SMFY|2=Z#MLq|cD(C!Y;}nn?TlT3Gy9!#YMQ)miEXC+oX06%Whs#>eXvMEcg|!C#Nl`hvDD8SW`(QMKM;;%<&zp zo>^Eg%R3e3Jy~l6jILnC*sHTT4SbCPx;ws$8}CE{d2d7uc}o^}5Fi%HmNmM26Ypf5 zG||=AUCd#1hfD{?vs#-IrIl8gd|^cLJ@#cur>hX>{(e`;#$u@J6Na0yy~6H`=O}Q8BS)jsJrUNah|DtZiUkPna`(I(HoAvGP$N) zm;23NkgG?Usc!2QQ4X`1{KnlR`t!64diorV;SsXk-ZAbSOLHvG7wVjRLf1o9s}}VG zCI|5ptYtMOA77ce7av=^Y&RawOIQYH{alyJ3-WKIUJlu&RRfg5dusXEAba+8EVskx z&EISrflPxmN<*elj^h$S&rYiC;4RLFB&E5CbuG3VlX1mm@Irr&FyI&D}C;#IA>hVD0fP=);m?* z6BKOod~W0OMFGm`c1zopHns4l1~Zb(E7Y%!+)Dkp8u#?x-vyX86E|{aKmw)Hz&#ME z#yvG?G^xXkKTVT!G1Yj)*^Sg)Q@=;tS#Yb`xw^_1;Z-}6cuMX@BjRbrLMp{jg8SwG z`hXcyNYNEz;Rtb+^UHg?TMYv5S)Y@N#B~4fF07Ul;_~l5G|ybI%K70)QYhqM@N5yB zjF4Vg@-OQ~gvqJrFQ$qQ<+_vB?X_;rQb6!#1P@B3S^Di7Xbh1$f+Q#~QL(n>Fo>{F?k z#pBHR&n1ml9tpzql4{AEEUg}-IABZ`Pgz}DRjl!;bK3VCp%aS!LO8%FS8O5k(z1d@ z3$1Xq31k7z4Nn&d<6)T4ac4Lf{%^sCkl&m+79`l~EELPyABX&!z?pB*-RmbGOUL2! zSeNb{GXPCrOjdsh>#8DRcfgLYffnG(OBey%fH#g4cVas)zaYT}a|!^Y8@f3qbm=4s z-6>+n*_=b*LsVo{vh(WVrX!g$U z7Pc8-woNB}5ll!7b#bKNjysyr*wMPR6y2(vLhQi~AtZLyM@7w9`JfrTrZ%LSZ|GZ+ zP3})Vn&q=j&n*?KU39%}KG5vIYuNgU>3HOVNxp)rKeL?IHGPECiuO<|?i}X5N^(gq z4zku`MIH$|W*^%FasnPeN}!qXum>sfRydhCGt>!(4tTsH_RnD$rcAdO@u3N(X#Q{k zm#BPQU>7ofv_`5gerv)GcQ~y8aq|)J>Mf4B6aHIEg7YSvfY6m=@bVnb-Nsx;zgvdM z@*Ahth_>-=Pk;DOefRhZDHZW`p$=#Nz@|3%*K^M|q;C;*7B6#;u|N%(fGR=?l3Yre z+gRTB`h>Y=R|uTvl*5K*BoZ&!6Z@_G@{nylz;wW*xcndwr7L+x>=Z~Gg=6L)RFTxM zs=cA;LTF5~Lb*ki(}l{qaklR~NF0y8Wv>rNa?|33-W|WZ42gt!8R#{c2$&F!qln8rnugWuAMfXgq!=v#`V(wE zzkDZk;;^Cfl;`thT;36i*yzuuDRLQ=%|?#s#`OhB%yw(ZOHW}?tFMmFi#c3jaF9cRr!;acn-eFFN(Kx9M(i8_W^B+}VK7?Aw|3TJ?VGtZsz89l_- z!56`(jy6TWJ35@yBo_A+Ls0qoXuli6Y+;=uh`pb#~oVdk%RKRLs*7qiELW zUL2RS?>$V$(FqxOXw{zKH(Agt5a!FKAl_ok$<@9oms;1v)Gvygl>J9PzLeLl?#BXR|CCajs z6ldHFVV6x1GioFDqozM$y<+lBlKF*WR6t_EF;7wq5_!T7e*NwVn2~@z{dH9Pgqe`e zc@G1c-~sA;ZPYFCvmALgs;3)8GY7jn9Vs*N#)(y@zb@mG>M)%2 zPD2@_H!i5SLwBmC8FqRxwt8)mX{?zRW}?%_rKyQimYAqV>d+--C38|!j+ zN1PA#y^$o(k<~N4jR~f^w!QciOJ?X4OwQh%a{!7#_-?a7Wa-8e`;OwXl4_**k;84* z+?diC>82oi&(_?qyBg^DEy}P9Ym(&8g)ytKvV@nOCegU$BZp9cYg0cPG%<^W=?o>grDDhHFn#;J7v*EX7*8H^#%5F4GtnQK zA6K786?^AM*n5@k!h+yPU?G3r=aj)PpOCyl-7Myh!CqceV%10oR8=+@;YZ)KXw5bm z>EKUOLsvBgh8(ntD@cf8Zjr4-q#tuJq8x)tjn=0YF9Q}YtBJw0{V_3y*V^&JC2sz_4j~fEW@oUUMwu?!~bN z@Q}7xV~!3@4o%pC$Ik+W0(aVRR9sIY$#$P#Z!wO6+vWZ536QVWF1PzTgl-MOK=#2) zKc);{u{fijoGBWMljb)?*lt??bQ;Eg3DUB!CLXT>DRH*T3cm zx53u$D+xrmA;#@H0;(@oMb2RjyTqO`U8rRQ+9w5KbTw4wfo>%c-KJ7;1^Dg|o~G4{bD3?$Mf`4m}VVAs_3tbVI(9cf3v2RosXH5Tz0I3u?Tvw#glQ z6=uNmoJObf$RwDg}|v7@~KMb-onatu1#Y^P4iFyxooKyDmR=kPv9v?M>3*{bWUf`j>G7D_LFK3WZvJj`*Mn3y3f;N4&pgv3T(+hE$S$sry0k zAihC{t-+73ec3*0p-uS!JfkVoO0=CUHJkesFeb5Rdfw6h$Do~%IYRbAsn(huuUfHF#6e#OK^3@fY zbbx0Sj<=j+oBlS$y6cP)K)x+_ByxzJdm?ffa$%;|%0II9`iuV4{A&G3b)TkpN>T1T zDhZ*vVkqKwK>=xJyy!+Upjk$>2BX1>_Y5%r9^LTlh}s;Q+05(&?Hq|;mW?6^o`FT1 zqH2*J=m=I*&KB<*2w7m8kxv{Al%?k@J~uB%hj?`fq#kVyY##=bC*Pg3Y%Z9!5l7kQ z8(QG}n18NsL6aaGb+45Oa_4vmAbEHnzR!7wTGe&Xgv!^|czLguXWYilB5zq`b?54q_ zzvJVCX#=kEEpRH4n`JCYP^ zj@V5|vY?~X^~m8YgO2^a;ZNHCbn##c8_5jG+YxS6LXG4~@ByN()40GXXl-BN&$(=y zZ>~kRAg?8pQh`%g80d#+9Nlk}Nx7NclJz;7QLKO0<~pSxWW~w^L?<(Yt>!IzCl_9u zrCx(r_Yf=IDaFdg6IjB1U9to)7d^ePkOY+9jT zk~5U7uFd_#WJYPfe~k9!532DUC+XS!A$9G{b=UYMZIV5mDMXYJUCzqka7VURNK7E<>_?-=I|9n*b_oh#krPj?S-_@{hH$9VifSsBsSc`c{ZGFU$@EmFczK*XgbB#v-N zR4N|tB8w~{1{V;^T`DXhdCp9uO}V!d36 z6Ymk9+P~#t`H8z1-_`w1v(l2jxu>}kR%78I!%MwOHpZXKBVLw`(SFXM zK$~_NwuY3KRm@0zvw1!yI2P;Umh)Np{P|7G_kxTda$xkpK|g=c7rqdBr=F{FiI~?l z0*mzXX{+6WBBb)?fWh^Vd}ML#=wo+OZ<^9qd6`w=eg1UGd(5;muSz!L^7-`Ot=G5b zm!5oep-6VXSsF_SzwzY}8M!+W!sxtdMf5tUkNtI4P9UyaapY*zq*XX3pwCxGXcXN& z&jJ4o^tqg}oC$>$9R1cJhK}=i*QHsz*&NZ11u5zcnx+*v{->U^r5BM+?d#%qv7wen zsmlXTT|T+7^Q_VfNLHr&NYZwS@N|P`kvi}aFHasAG@rci#ZMpHpcR@p5+W^cV_cY! zrAj{v+Q8Vc)^qsvZg3$)bC2?jW|(e;sZVI=98`ZG7(nC*%xQZvz`*x2Xmo;2PxLk? zThq18Li&A^e6+#)CJaiB6eC?Wl|+UGk#h`El=)mb#-q1H#b;j@dY?R7Y3Q-TdgDX% za32GzqD(^UD{Bn9dU4Qrl;Ucs(v>EPH#1{9k&uhtb6JXY!$xNszycf`+#nZBuis^g9Oo;pP>a1% zZBc?9htX)Y$H{#!KOTk}0Gn|ulKN(wCYm5@a-@HYj7dc+U_6ks06z7Z`Q9$C?vbNtwQlBC&tNKh!-3z5fPJhpDGh(RZ+}QA>0DOYxTXq_=TB z;P`dcWfFpP)|}pV2_1IEbIL=Z6W*ZD#k-cue%H3*+eV|3eteRn&%$Sd^($F;O3r|G zcm397Rg!EVoK4(jZa>SG+TexN(Y%)0VYdcbX?wt1-zq-V60}BfxrJQw{qf`TZP@c| zP>{h#Cd=hUTfL{1KP$8O92f8%z4U(adC8-l3F+KZ2nO;bi^9QhCl`Nt4ajhSJanYL6e=UcK)E1T__ zW(Qb<&|D`kR$<8&RLJc}G_%54%bk4o;F`G0k6aGo;t^ADm+x04Do59JBtY#zBv4_C z+UKg^vM~%Wq zjV)JCqD_rtH_W+j>BnVlGk`p6e!lVPQMDB)?TOpNzY^UsA{t}swyp-XNlaQ=VMg3m z#du-azeKBXi=)8$Vl@oPgycyW#j2jTc*Sh4Y|`ymrSKo|=>Jr17z)^v5y1teFIG?$ z^)h5J9S>P4_^ZE&`r#eZs%aFCn$uGW29K~L4&Bs^Y|?*RqD8eL7%badQF9`ws}b_FD{nsRO7M=W^M%Djeq?53jyzk>+IsYhWPbShRZD06-AAehL(o$C59E3~ z9RlQf5YpP1U;U`$!8zZ-TfT~l7tS+3$1h`X@(V1n@gURhhnO~{XoJum@6=18+PxB*bSs+CxgsZb?kV$S7HLJ&bRLvQ z?OZe*ncB=ub22+2Ldxtc74d0MxbMH&DJ1bY#?z+ToS}~trz-!fg@H^{EEXjM(TqxVQIjLwkYP>w0=D+US!A6 zq9a~dgG2g3={MMHqbW<3yQuT#3CLU267=iGgBNsqLKYge=$h#FE0Zbxmx}HtxN6e1 z?r=Od#5dmE!--xwV>QV#j|(3FNhuGLy~~t(9OmtA%?>k#U=HrzVvB(!g#_e<=QFvF z*_3t3Pv(wg`sgGNzH;Zp-yZMcV$UyBa)*3r)7&#!JV<``a}sr?af%n&(=qF3$$WvZ zTE$+?O-PDVe8yXOdTwQjSufFu)IRfu(ahrQhF#GKa<7ITuL)Np+Ne$>TwA`0qs;lO zjrQkQ5?N48dTltLuCQN&ROZ)8yc%{xCeFVV98Omf8@J&Y;a0*-Y~f%%G()x7PNiy$r<|9=H7(Q*Fd|9bdWc)rQcf{2IY5dO?$- z4oKg^y?Evt=^W3p%|&=H93{zK0#5VfV0QJy*h?94d)b^26r8#Us?;f2JMS^E&_kvC9{Ht9`uo&cZWd$6>~63D#DeT1RZ;M6);J*C&TW5>Fd(41G^n%$rEa5e11)*dNAX!GK8{D`LVm7zmXbxqPxFqzPF7kIuBVKP(5?i-a>qEWYp7%GdcjPuauTNCawygQ#p^D}UxDPM@of-#&0D6k?4f-fmjfx0IRd{` ze-xWY$N155zDxlR!p$1Ex@CM7XX3KEJ!p?kbLs}ymLs`|_uSMfkm6JO) z`l5Rk*WG)>()J+{Wg*wtxB!DQX?cIr*-DDi7hQ>wllCAstUBLuOZEeH!ydKOi26ai zJq>R;a=R$CyqL*Ew*GELE^_)9_*F(Vq`1p{f)taA48V`RAeT=90KVv1wpitg-(s^&5+=U#E+*jdc!Jc-YNQe02MV?t;eu;jucgw^#NwF%?U3nTy} z`PNti+j9|oQe>`}*OOexFzbnz0sxZ!Uw{q7tAYy9#m~;`cYOVe0oq#f!ON+6-=wTz zg-`3V^0E{cE2|v8ex26N-kFQlxUbTU?RI6_r?E51&_y!u&9Ckd5b6F%NI|}FS^Xev zH+~wVyX5Dk8`J1NsV}0qco9-w~&gGZHSBN(z`nU@+2?|oqaNzma zvLo?s4(0WdorUf;RTC#gUhe~3g?>y+RVY^^6)z~14XB<+$ztI472ASh z&j%OL2tqOb&OTZW8NHLige6Hz$jeP5D9w6om#Hl(b4WvonJ&;oFy*@Uax1x93=! z`%X;e`Z*QF+JMZCEb+Q0yIzI46)lk<9z1DuHsG(q@XvbBW)kJ%f=Gi~xo!4CS}%uP z3fAiQB+=E+^bTZUlWVwAhuP;5L>qRuXH2f>4rH-H*up>f)oWesc@?Ja#TV3ORn%rJ z#?@%@#SiOO)XsWsz%a8E*WTcLrtE6%Sk8FH$)ItC^T8iEO?=WxQ}xP&8AJlP+}g3a z+$x6a336#ioHP$}Vdz+3;slq~VwoaMRtSxsj_FNQSRcbdzhSo-dD?5|wAU+6ta-Iz znhlH0i_1=|`L)Dm7Gy-tGXkucN#Mm;Qfh*jphl|ZTN0v6Sc_HUq@czggc|q{z0xj` zK7=3oXsTa`pp^BRui_>2M6Ck_K++;nu{XHfKc}npMBu$6txdCh(8;mKdlc6ZeWY6> zQEwqaLT9aD}?m?PICzVB^-!Feq|~&}udA z`QYKaejTnGn&pV24ylkoLv`ynUtixgKNbU%H(yq?#Zm9kK!YRh)GMIQ_9y`}hQ8FW z@>eV7Yi+j^FZ5B)rvZprmMpr8#_smkSJHq!QswG3L7-8KD{V+dliD$!MPwBXj1XI( zpvGmFECtE%pemug8rPI^ET}z#467ne)&Bh5ZSL}`*kvL3X2l0I8AWCoB0d;~Wu_X~ z*g9$H(Sp1+wd3Yj%H@-4_^>^xxq~WrvE!TDe5j3R=FinyTyaAx`iTR*dIpljJzNb+ zCRD;;(aCr!y%0k~Mb@5WKBaj@RPaI^aCI(P)55U{_VP|UZ@rL6{R+w#W3}Un_Z1m( z6QT}0Pc+V7!8ICN8e)x@eo9WVZ{;U#F7zt1Uw*Ov;v{xDr2ma?zn3Y}Izq6&?96lZWZfTBC6bnW2MQ1F!5ZT@(IFW#K}2*E z+wvv+>dfUtX~GIOcOy`_vZ>q>H2$dzHblSvCZ@sma0a*KBU?Wx?rJeMOO1FX7~YZJ zX$#XY`5ymt-8m$t1CMJ@a!n0Goy6cpT9{V`QvSZLeB8A+Kn56x#&hnqG3K@5gj7Q_ z9&cgCE9Au#e@TOy|2aGnCmfSke=+vPZ0%XHbv#=^GJIGcHpi*ata!F!^}P!yq=D4QTQ#p&duMI%Sy$qZ zLHE;!T*LVLJ=;9KB#VKYtvt z5=7TlaVOKdv`!;i*DYCKQ-9!4d;c`vGQQ`am}bA&aKLQ<<)#!px8$5zPIMiFYte-^ zG2n_j_T%8vxfuzVxTG=YTJ7Z(S7=%rb2s?Slb4ui>F`J>lfH#iPWU!HeTe8)8>4r5 z&i>RGeuwZZY>(jXhAxKk0MdccK+P)mt37Aocph%j0bym2Y}CpJ000Cso4Sn;fKG2Zl>zfCVtW79kY#ds=KjHu%zK#WH?y5jP z;i4O-7gm2bsYN1EA{GsGoI?t|Z8GGi5e~c>m6qHa9W%*GaZ+Txk{xGTmX*904}My; zT)bYnm|s?!4mcdO!IT1YeOf%Z0g76EyLkAyF?w|7ZR7{v$^J@oA%>1oZB<g5EHr~~$rU!m-mB{|09uNP@uA>4>Ycw4GK)^33 z0Ai4rz13;?H*)kJLz}Df>0RkBLqyp^76H+1;E7o+Rx=xFA#DCSlZ8+RMJ-J;qI^wk zoLNsLmeh#|_+k&oayg1woKIM@i>)QyD2OatnuX!jMV&_4pl8f0w-!p2x;#UF zpe>`u%Atv7PlPKeUQ4_XFI!yjtIn*h@zw-YDOpI^kNQ*$5o~^2aM!Qp!&k{PD(@3Q z{Gnx-px^B_#BM2T&euX~YAe+tjHk4->{=*{7lY+>W38CxNoPS{(Er?kd@^j(Nwo$m z3xGFd^X!n6{QD28{4((>o_DnQ`#BDB7<-xyLAk4@JUO+o9Z8N)6Vx(1@Q5{e3!+v* zmQ(SQsa&&TpGoezOsGz(=Li!Qi8f^sa2>QJ;n0i8u6pB%Nrl4kpP=@wuhn$I9t&I8 z6_Lm4V#$|S+|Pdn2HnzEZw?~bm0_SrHk8c;ICMdl)QQeB7B^m2tFptgo^65ypuf_O8Gwjj%>YM z`cMWLy;LBUMjKV(d*}h8xr;ImSziUpCrwgmZ%GVG87eU3(^*)s%kbt_@I}utDAZ~9 z74_TL%*m$720-($3*4dlL?Lyk(O4DrRLYNwAspmIKViK%v#B?phdoS)#agH4fvP%& zK+04Gxs6iYcdyN;`G(ZAG!JZWhFowP%bMW5YwwbYZba2W@9>@4gMwuGx}BEZOZ8v| zUH)>bw2?8v%@Zgmc_qcO0W1S-ft=d-TnhusWDfiXjY7FtzXxKW6IZr(U5lyZ6f%zm z7W(NG3FSWT9j|@TOzFUqVW{ndU_DVSIAF>cS(eec97;kUJC>91ekVFUHZ!LmkTS|3 z{lZ~umSC{Jlqi`K@116LUvn_`Ce1eIgut8VAs5>YdAy6&<)xQ0o{{~h%AlUu%xvo7 zO8K}6I(-x3jQSRg3l^V*XyhYGoi8#Nn)@ef&-|A%7to)fv303?<3D#Wk2DBh`dSwE zIr-(%f!KtfgyJ4);$aS6;`wX(EpmP=6(X$sp&Lz+U^(Cn@MX-A=+B~K2bQ;E_3I89 zS1f%FG$>!m`Zk!~cBU9EJ`)G6z3O9M#(omRO) zuhfwB@>4o*m>fa4hIrm;T^-5*Y5|cY3+=haDs*`4K}HxT!4|4btmld0t0b8Ws7`?Q4DH z(rfue$JhC3`N{X7ySkiLuwfH}I zk_Upx76H`U0>w>33(_AH@II!7Br8EyQMkrMJ}X!kvqfw7E+W13WA=xpc>U|=+6)Kx z7gO~(*AHOVMF1(ts5n_AmhUP4H2ckt2jkns9kUVQwAm>k`?q5*G!8W%Y_ZT5q`#JF zDv|)cas~T;qHp9(ux%Q+KSVyYci+Q5aniKKal}v1uA^YXHoBDzpP}}gFG$Yas|ayu zV83|T(`uA$5y$*gjmO)7$0pAZ+J)m8~#4%*v=KW|7if(yKKnRwmG?^9xYTOXQb$tYxIW5wK+xzBO>$?~w3 z=Cqx!+wqD_2eibJ7wqh}cjJS;A1S9hHq4%0ApBJ99mA{od_v>Wy=eF8!)a`_3IyivreH0@Tp@K}5tsYhAAIBN3;pf2fXA<#xkyXv6N!;+Z#+j*h)&-=lEWk$P96VYsl1=lZ3f8}T zPu|s>?CVXz5aYIR>F^ZQ4}ayQRK*u@#pxAK4qE(~ zx$5+9lwy3w`gZLHT@LWD%b89GNHWe;0NYH@1H}fg)O1<1JxlEM2W>UmJ<*D><P*-6-#m%p5LT=11n_8dilv|+_>GkN0n*pdXRACHB ze&^JlPAcyR@qf^2?f=1iJkEa7f<1D1ujc!p-9I0x)cOr3yqLY+EB1jH=RvJ!B@GQM zi56D{lCZUTg@^~fR0n!05)wQ(t**o|O5>y#yovvnrXLflJkdfKz)WY5%+lopJUd!w zrbeii0dkG79o5-L1!jc>-?kO3jg&k$ztSrIaPqQ?_&z}1i9o7pE4?DQ2C=!@>ctmD z2FH@usP)hG@n1PKZL0ch&5$wnDMzWAAGkg91}ToXxa0h)iSrDd!9jUNGBwv~sHTLL)ubJysSK`u04}QZT7Gh$8qR6xF{grrtGD8RjWAK?aA;GbDiAJ2xbubdc;#uM zlui>W(adJZQuCWRZ(Fp`u$a{)D=Dy1H)Ps5Oj!FSM5ZgjrLK*6^Mlad({WU@;GQ41 zRj@;DSRuD-4~}@rA%NA8i3b-hP~TG`5vt)yC>Fbl6ss_{$o3-Q7kyTp?k?|Vk&$Xp zxU5a8)@e_mQInas|AeiW*z-cq)Z?en*|LtUu}6#T^z{`Fv;vJr(r4i_n-PVQVqi#za?&z0|fZui!# zy74ZF@G_lnn%(<_J0{*Mg|alqinp@43~^r#M*P{0^@zpU+7Vuw%M_BxcJT62-EYwjh6+Ma`V{Dc*qQnFIHZDAB5oih$g#oNKmwk-prO z7aQIJx%WHV_vKnx!VlbW9cN5c>$%|0;&;cn1goFEWW(M&Tc5?*veeg(2VvB*QmhwU znO6QBcj0)2f7TROaS1^FF4t`6Px|^2;b$)cSENea+gwTParv^dYV-(KvNxbqvl~Y# zlAQPM+{H=*c17k-r)x584V22q+=JY>;;0UEgmT(K9K??SbU2#({9vsUi#S`e+y`f; z$!if1$TWU01FLWe@)tkEMjhX$>zx{|@9~@P2JWN2@_QBh{aP>A;6~u0!)`i1uaQjC zE;BpdFNQd8$civDW$PL>w~=J zW!_7{kXh!rWTr|{;-NoBT(%BbvH&-81CxtRmZ-sKs^4K)i*FnQ?;0%aNSQ)~3TmpA z)53a7T_-;5it;UZSq~=n%TGs3o%oO;P>3d~P&EvGkfMIU@-dzKBP^(4{{EQPrBG#h zZ%H;+>Ckn@emCFWsoAF4@-MC<#}Ic!z~o=NCK% zJseSo5QiM{(&;a%kqZZ^&hIbgzpym`P)PWd`0=TA_PN*c<+7zFnBw4gIov^E`qzp@ zJ4LD;_R(Hp*NVj@PrpbUzAb3W53r0r54#rC_$YqL&2F6BXPP;L#Qt%%c|+~hivV$D zl|eh1OF~ik!7*NMB1AnXy?>;OU^ z!Ql=d=n=T@Ldkc#fXGKnKbYR-2mbq zEf}{@&ndUi=p=VgdC?sp;gKV74^6)R9x8wF0Em8kH2?5VdDF~*k6lXJ0sd2NS6JYy zN5)TB;6E9=!U3Hho4o-Cba({n@Ic*1Pyr8gdj!-7Pyj{%`aD{&5rJNhAO;cW@d)6M zfWD6)014>-2=4v`{>Z?HM+*`F68-na3IRYJUIKuwj}`|Y(C`tA0HGmdQGiB|mSPlW z2=XUTkoGS?MTJ^IP=UdZWB?iv^a!H=1*qu2=ts*NbYR>gV8Q@qJc3>fXc~f;K)Xjv z5hl?45l~_Q10F#>7Suw54YYZ*gkuBUAHgj)@bx3`#{otFA?wme+bKGy0h??C@W52K zPY*zXE`B7k)4H;^WT%0~#Ha&;nL z2;g_LgY!uZFxLS9AN-35X#L23Nep$pMhtarPXg8dB7s_7lLEsYvqhK|P1OJ!A3?IE zBm+7=I!GjgIydBBo&h@_L!5JwLsybb4$Xju0vP+quB8CRJOT+ysPkb;XgGB$C|LLx zm{CJH`_#~8xzYfG9#`@`_nQ+~`2rFQlNM#UaD?8(fM*R{9(GNI>?(ZvZC=Dfn*?DGAwXzX7HcWP|<&U@1tae*+R} zDEYlKq&~kboX?=DhG&pA{kDih-b?xj24tXeWmzcai!5~QdU8;Q%W_bM*3Y4;!{<M(|`RNMp#I!vHB zE0{v&;x}i|6si(6g9fcKgL-B(hkDNa7vNh!Eph(>1WTyJ&+?xZq+3D9BKTi`U=10I z-=Q+Bp~axHfflvi1{$2%7FvU1Tj&NSvxAJqZ&kD%bTtU}kg@n}3A2Y<9_*oGVdnt( z<|(Hc5QP%*)tyG!NToy$i5@nK-z1r=`nrC{9#uskYaShbxg5MW^V0WdK7eFg+?p8Zla$AerSrR0e-3%76V)z!`Ic zUGabnkCN(|FZNv!dLt?fjP75OaJO?TA%fBKZVEm!`-yxELIc0$VgvcJhgMSCf3IwUZ z-z8qa`9}j;`5G4)7;_(*lqW7?oG2uUJtWHCCBDS}R{{3h2QWV7#FI1NCIRv74gB9F zRFeG3sfq}?gctiLqxg^;VtF7%()()(R#boJTyj9VNAEL~a1oJ^?9Ct%{_fo`+W&Gv z`w94m-LPH++d~Wk6C(1zJv(OjkDc@r+ON$Q-Zf{CR2CtZ)&5->PU%0X#Bm{!f>)nI zt7$a=zpe|Z4F+Vje-B2k+#ii9DUkUw{pYZphgpy^7GwQy2=^ENCB$|FV0vT=*sTXW zha`joN$Bql6sh&cxsd|!pZ%83PVA}&p+AMt{~kFdz5kp`90M30orCzK-9h($x3C;gw5;D-JQ&_NB| zR=9a)O!km`&>)h(hln`(zW^#F#9$*;XnS6T@_!QWq#E=Xz|jr(fdHWk z{-4veIsFek$PbPjA9siDLUuYTq@MMJ|2s{E zS%2uJ8qjHqs3o$lgk<#l)cborSd0JAkLaNb;D}j&M+KpWL)L5ZSBcci{?K2(L81ij zT|*aO7M1aK0#Z^*$R7VY#Bt>x4b~fYT(I2@0Q+$jc-&cbj`+7JjE8^Zp3Z;yZak9~LxJ))SbvATcH zcPWJZ_Yw8E?N2(CI?$r#W#s+xfn+ZWDXP_9*;jY|p-Zv=iNKweNN9h~*?R_z-A#z_ z03tN|OIY6fmyrFjN?Z(y!X-L3S4AQ{a;8u9mMZ@~B;`Zqn`KWBOAj4(_Y#CtBJP=ELO)$~7fSmU&I z;6G<`b-TtW3S?tsLRR~?@0{5`zK_|UzSsMXit8YB705nP`+t31dq`7J7(bhvdUuMoN@p)-^HmlZE<dGPaLZrmxN0@%cB?sdqUV2`)q zO(3$Fli%>TlN<`RH7Ul|^oB#OkwD9S5#hFQbCqa=82ndut2 zqAgU7P&Eik65Lj?1T&puD^1k5g-}KvNWFjvo;FX0yR>deVzUO%%Av-wo0IK4HZTdq z>x8?bky!4T!YzkhO3a~r*wv+%Q|GBcgHRhn^NH05Gq}}SXM1{GzB0Z66pRW%$V5O@ z2RNv!Sz|U>i*VO~yMm1tY`b?T5^^g97C=cw(FK>Wxo$XEqa79i0y1? z=9cq*2=g-Tu8BeDdV^54p8GO<+?~=33^a6#{xB8t@Py5i?tFr1#y}BR?j`<@fr?ia zEx8FdxRZWD6XZ!~d|V-(?c6|36>0NJA@ECu&=CzDG`=__(c|U$6lCmF`q&-P7)HjR ze>_#-Y!$%5ij^UfS9n;NAH>qjm3RafT03HlMJ=~4h&2gMLK(L zuj)|_GS|Nja0z+Q@C_7TzUZi6w)EDwd+x--bV`R7&mu~HO)bErE8P{iLT(>A(mB-+ z;{Z4lbRmIxp$wZWH*2UM)~{;}kN)T&HskX}L~OS@9wudB2dv_Wd=0Kz(gzp7$Pd=v(6?t3si5%tqP9xE7p#0$LysJEBKUES`6fZ_Lg(~MFW RQeb0|MP#}H=IRGeqrZ9WU#|cF diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 5e60f4e44..f09b67e29 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -53,11 +53,25 @@ val localImplementation = configurations.create("localImplementation") { isCanBeResolved = false } +val adapters = configurations.create("adapters") { + description = "Adapters to include in the JAR" + isCanBeConsumed = false + isCanBeResolved = true + shouldResolveConsistentlyWith(configurations["runtimeClasspath"]) + attributes { + attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.REOBFUSCATED)) + } +} + dependencies { // Modules api(projects.worldeditCore) api(projects.worldeditLibs.bukkit) + project.project(":worldedit-bukkit:adapters").subprojects.forEach { + "adapters"(project(it.path)) + } + // Minecraft expectations implementation(libs.fastutil) @@ -124,8 +138,6 @@ tasks.named("processResources") { filesMatching("plugin.yml") { expand("internalVersion" to internalVersion) } - // exclude adapters entirely from this JAR, they should only be in the shadow JAR - exclude("**/worldedit-adapters.jar") } tasks.named("jar") { @@ -138,8 +150,14 @@ tasks.named("jar") { addJarManifest(WorldEditKind.Plugin, includeClasspath = true) tasks.named("shadowJar") { - from(zipTree("src/main/resources/worldedit-adapters.jar").matching { - exclude("META-INF/") + dependsOn(project.project(":worldedit-bukkit:adapters").subprojects.map { it.tasks.named("assemble") }) + from(Callable { + adapters.resolve() + .map { f -> + zipTree(f).matching { + exclude("META-INF/") + } + } }) archiveFileName.set("${rootProject.name}-Bukkit-${project.version}.${archiveExtension.getOrElse("jar")}") dependencies { diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java index 1f58f13b3..7c8d2d656 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java @@ -500,10 +500,10 @@ public abstract class Regenerator + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter; + +/** + * Reflection helper to deal with obfuscation. + */ +public class Refraction { + private static final String MOJANG_MAPPED_CLASS_NAME = "net.minecraft.nbt.ListTag"; + private static final boolean IS_MOJANG_MAPPED; + + static { + boolean isMojangMapped; + try { + Class.forName(MOJANG_MAPPED_CLASS_NAME, false, Refraction.class.getClassLoader()); + isMojangMapped = true; + } catch (ClassNotFoundException e) { + isMojangMapped = false; + } + IS_MOJANG_MAPPED = isMojangMapped; + } + + public static String pickName(String mojangName, String spigotName) { + return IS_MOJANG_MAPPED ? mojangName : spigotName; + } + + private Refraction() { + } +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UnsafeUtility.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UnsafeUtility.java index ac6c0c275..34750c533 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UnsafeUtility.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/UnsafeUtility.java @@ -5,7 +5,8 @@ import sun.misc.Unsafe; import java.lang.reflect.Field; /** - * This is an internal class not meant to be used outside of the FAWE internals. + * This is an internal class not meant to be used outside the FAWE internals. + * @hidden */ public class UnsafeUtility {