From ad0362367ac5c7124bf1e61394e7c619637c7646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossi=20Erkkil=C3=A4?= <52257907+avaruus1@users.noreply.github.com> Date: Sat, 1 Jun 2024 08:30:00 +0300 Subject: [PATCH] Bring back the Sponge module, updated for 1.20.6 (#2538) * Bring back the Sponge module * Fixes * Specify the SNAPSHOT build of SpongeAPI to use --- build-logic/build.gradle.kts | 8 + gradle/libs.versions.toml | 5 + settings.gradle.kts | 6 +- verification/build.gradle.kts | 2 +- worldedit-libs/sponge/build.gradle.kts | 3 + worldedit-sponge/build.gradle.kts | 88 +++ .../worldedit/sponge/CUIChannelHandler.java | 67 +++ .../worldedit/sponge/CommandAdapter.java | 81 +++ .../com/sk89q/worldedit/sponge/Constants.java | 39 ++ .../sk89q/worldedit/sponge/SpongeAdapter.java | 249 +++++++++ .../worldedit/sponge/SpongeBiomeRegistry.java | 71 +++ .../sponge/SpongeBlockCategoryRegistry.java | 44 ++ .../sponge/SpongeBlockCommandSender.java | 183 +++++++ .../worldedit/sponge/SpongeBlockMaterial.java | 96 ++++ .../worldedit/sponge/SpongeBlockRegistry.java | 91 ++++ .../worldedit/sponge/SpongeCommandSender.java | 172 ++++++ .../sk89q/worldedit/sponge/SpongeEntity.java | 125 +++++ .../sponge/SpongeEntityProperties.java | 155 ++++++ .../sponge/SpongeItemCategoryRegistry.java | 44 ++ .../worldedit/sponge/SpongeItemRegistry.java | 47 ++ .../sponge/SpongePermissionsProvider.java | 47 ++ .../worldedit/sponge/SpongePlatform.java | 200 +++++++ .../sk89q/worldedit/sponge/SpongePlayer.java | 313 +++++++++++ .../worldedit/sponge/SpongeRegistries.java | 70 +++ .../worldedit/sponge/SpongeTextAdapter.java | 45 ++ .../sk89q/worldedit/sponge/SpongeWorld.java | 507 ++++++++++++++++++ .../worldedit/sponge/SpongeWorldEdit.java | 487 +++++++++++++++++ .../worldedit/sponge/ThreadSafeCache.java | 63 +++ .../config/ConfigurateConfiguration.java | 149 +++++ .../sponge/config/SpongeConfiguration.java | 62 +++ .../sponge/internal/ExtendedChunk.java | 43 ++ .../sponge/internal/LocaleResolver.java | 35 ++ .../worldedit/sponge/internal/NbtAdapter.java | 272 ++++++++++ .../sponge/internal/SpongeTransmogrifier.java | 210 ++++++++ .../internal/SpongeWorldNativeAccess.java | 158 ++++++ 35 files changed, 4235 insertions(+), 2 deletions(-) create mode 100644 worldedit-libs/sponge/build.gradle.kts create mode 100644 worldedit-sponge/build.gradle.kts create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CommandAdapter.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/Constants.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeAdapter.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCategoryRegistry.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCommandSender.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockMaterial.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockRegistry.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntity.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemCategoryRegistry.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemRegistry.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePermissionsProvider.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeRegistries.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeTextAdapter.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/ThreadSafeCache.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/SpongeConfiguration.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/ExtendedChunk.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/LocaleResolver.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/NbtAdapter.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeTransmogrifier.java create mode 100644 worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 7ded3026e..6f3df3d80 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -8,6 +8,10 @@ name = "EngineHub Repository" url = uri("https://maven.enginehub.org/repo/") } + maven { + name = "SpongePowered" + url = uri("https://repo.spongepowered.org/repository/maven-public/") + } } dependencies { @@ -19,6 +23,10 @@ implementation(libs.jfrog.buildinfo) implementation(libs.paperweight) implementation(libs.gson) + + implementation(libs.sponge.vanillagradle) + implementation(libs.neogradle.userdev) + constraints { val asmVersion = "[${libs.versions.minimumAsm.get()},)" implementation("org.ow2.asm:asm:$asmVersion") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc35744cf..df54bea33 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,8 @@ codecov = "org.enginehub.codecov:0.2.0" neogradle-userdev = "net.neoforged.gradle.userdev:7.0.107" fabric-loom = "fabric-loom:1.6.9" +sponge-spongegradle = "org.spongepowered.gradle.plugin:2.2.0" +sponge-vanillagradle = "org.spongepowered.gradle.vanilla:0.2.1-20240507.024226-82" [versions] kyoriText = "3.0.4" @@ -26,6 +28,9 @@ lang-worldeditBase = "7.3.1" lang-version = "1309" [libraries] +neogradle-userdev = "net.neoforged.gradle:neoform:7.0.107" +sponge-vanillagradle = "org.spongepowered:vanillagradle:0.2.1-20240507.024226-82" + licenser = "gradle.plugin.org.cadixdev.gradle:licenser:0.6.1" grgit = "org.ajoberstar.grgit:grgit-gradle:5.2.2" japicmp = "me.champeau.gradle:japicmp-gradle-plugin:0.4.2" diff --git a/settings.gradle.kts b/settings.gradle.kts index 72b6ef718..4583aac19 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,10 @@ name = "EngineHub" url = uri("https://maven.enginehub.org/repo/") } + maven { + name = "SpongePowered" + url = uri("https://repo.spongepowered.org/repository/maven-public/") + } } } plugins { @@ -35,7 +39,7 @@ include("worldedit-bukkit:adapters:adapter-$it") } -listOf("bukkit", "core", "fabric", "neoforge", "cli").forEach { +listOf("bukkit", "core", "fabric", "neoforge", "sponge", "cli").forEach { include("worldedit-libs:$it") include("worldedit-$it") } diff --git a/verification/build.gradle.kts b/verification/build.gradle.kts index 404af4d40..18efa04e9 100644 --- a/verification/build.gradle.kts +++ b/verification/build.gradle.kts @@ -43,7 +43,7 @@ // Generic setup for all tasks // Pull the version before our current version. val baseVersion = "(,${rootProject.version.toString().substringBefore("-SNAPSHOT")}[" -for (projectFragment in listOf("bukkit", "cli", "core", "fabric", "neoforge")) { +for (projectFragment in listOf("bukkit", "cli", "core", "fabric", "neoforge", "sponge")) { val capitalizedFragment = projectFragment.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } val proj = project(":worldedit-$projectFragment") diff --git a/worldedit-libs/sponge/build.gradle.kts b/worldedit-libs/sponge/build.gradle.kts new file mode 100644 index 000000000..3f6c7e06c --- /dev/null +++ b/worldedit-libs/sponge/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("buildlogic.libs") +} diff --git a/worldedit-sponge/build.gradle.kts b/worldedit-sponge/build.gradle.kts new file mode 100644 index 000000000..828644671 --- /dev/null +++ b/worldedit-sponge/build.gradle.kts @@ -0,0 +1,88 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.spongepowered.gradle.plugin.config.PluginLoaders +import org.spongepowered.plugin.metadata.model.PluginDependency + +plugins { + alias(libs.plugins.sponge.spongegradle) + id("org.spongepowered.gradle.vanilla") + `java-library` + id("buildlogic.platform") +} + +platform { + kind = buildlogic.WorldEditKind.Mod + includeClasspath = true +} + +commonJava { + banSlf4j = false +} + +repositories { + mavenCentral() +} + +minecraft { + version("1.20.6") +} + +val spongeApiVersion = "11.0.0-20240520.134918-37"; + +sponge { + apiVersion(spongeApiVersion) + license("GPL-3.0-or-later") + plugin("worldedit") { + loader { + name(PluginLoaders.JAVA_PLAIN) + version("1.0") + } + displayName("WorldEdit") + version(project.ext["internalVersion"].toString()) + entrypoint("com.sk89q.worldedit.sponge.SpongeWorldEdit") + description("WorldEdit is an easy-to-use in-game world editor for Minecraft, supporting both single- and multi-player.") + links { + homepage("https://enginehub.org/worldedit/") + source("https://github.com/EngineHub/WorldEdit") + issues("https://github.com/EngineHub/WorldEdit/issues") + } + contributor("EngineHub") { + description("Various members of the EngineHub team") + } + dependency("spongeapi") { + loadOrder(PluginDependency.LoadOrder.AFTER) + optional(false) + } + } +} + +dependencies { + "api"(project(":worldedit-core")) + "api"(project(":worldedit-libs:sponge")) + + "api"("org.apache.logging.log4j:log4j-api") + "implementation"("org.bstats:bstats-sponge:3.0.0") + "implementation"("it.unimi.dsi:fastutil") + "testImplementation"(libs.mockito.core) + + // Silence some warnings, since apparently this isn't on the compile classpath like it should be. + "compileOnly"("com.google.errorprone:error_prone_annotations:2.11.0") +} + +configure { + archivesName.set("${project.name}-api$spongeApiVersion") +} + +tasks.named("shadowJar") { + dependencies { + include(dependency("org.bstats:")) + include(dependency("org.antlr:antlr4-runtime")) + include(dependency("com.sk89q.lib:jlibnoise")) + + relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4") + relocate("org.bstats", "com.sk89q.worldedit.sponge.bstats") + relocate("net.royawesome.jlibnoise", "com.sk89q.worldedit.jlibnoise") + } +} +tasks.named("assemble").configure { + dependsOn("shadowJar") +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java new file mode 100644 index 000000000..a6359ddba --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java @@ -0,0 +1,67 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.lifecycle.RegisterChannelEvent; +import org.spongepowered.api.network.ServerConnectionState; +import org.spongepowered.api.network.channel.ChannelBuf; +import org.spongepowered.api.network.channel.raw.RawDataChannel; +import org.spongepowered.api.network.channel.raw.play.RawPlayDataHandler; + +import java.nio.charset.StandardCharsets; + +public class CUIChannelHandler implements RawPlayDataHandler { + public static final ResourceKey CUI_PLUGIN_CHANNEL = ResourceKey.of("worldedit", "cui"); + private static final SimpleLifecycled CHANNEL = SimpleLifecycled.invalid(); + + public static final class RegistrationHandler { + @Listener + public void onChannelRegistration(RegisterChannelEvent event) { + RawDataChannel channel = event.register(CUI_PLUGIN_CHANNEL, RawDataChannel.class); + channel.play().addHandler(ServerConnectionState.Game.class, new CUIChannelHandler()); + CHANNEL.newValue(channel); + } + } + + public static RawDataChannel channel() { + return CHANNEL.valueOrThrow(); + } + + @Override + public void handlePayload(ChannelBuf data, ServerConnectionState.Game connection) { + ServerPlayer player = connection.player(); + + SpongePlayer spongePlayer = SpongeAdapter.adapt(player); + LocalSession session = WorldEdit.getInstance().getSessionManager().get( + spongePlayer + ); + + session.handleCUIInitializationMessage( + new String(data.readBytes(data.available()), StandardCharsets.UTF_8), + spongePlayer + ); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CommandAdapter.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CommandAdapter.java new file mode 100644 index 000000000..be08709a1 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CommandAdapter.java @@ -0,0 +1,81 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.command.util.PermissionCondition; +import com.sk89q.worldedit.sponge.internal.LocaleResolver; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.enginehub.piston.Command; +import org.spongepowered.api.command.CommandCause; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +public abstract class CommandAdapter implements org.spongepowered.api.command.Command.Raw { + private final Command command; + + protected CommandAdapter(Command command) { + this.command = command; + } + + @Override + public boolean canExecute(CommandCause cause) { + Set permissions = command.getCondition().as(PermissionCondition.class) + .map(PermissionCondition::getPermissions) + .orElseGet(Collections::emptySet); + + // Allow commands without permission nodes to always execute. + if (permissions.isEmpty()) { + return true; + } + + for (String perm : permissions) { + if (cause.hasPermission(perm)) { + return true; + } + } + return false; + } + + @Override + public Optional shortDescription(CommandCause cause) { + return Optional.of(command.getDescription()) + .map(desc -> SpongeTextAdapter.convert(desc, LocaleResolver.resolveLocale(cause.audience()))); + } + + @Override + public Optional extendedDescription(CommandCause cause) { + return command.getFooter() + .map(footer -> SpongeTextAdapter.convert(footer, LocaleResolver.resolveLocale(cause.audience()))); + } + + @Override + public Optional help(@NonNull CommandCause cause) { + return Optional.of(command.getFullHelp()) + .map(help -> SpongeTextAdapter.convert(help, LocaleResolver.resolveLocale(cause.audience()))); + } + + @Override + public Component usage(CommandCause cause) { + return SpongeTextAdapter.convert(command.getUsage(), LocaleResolver.resolveLocale(cause.audience())); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/Constants.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/Constants.java new file mode 100644 index 000000000..7edfd16c6 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/Constants.java @@ -0,0 +1,39 @@ +/* + * 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.sponge; + +import org.spongepowered.api.data.persistence.DataQuery; + +/** + * Kinda mirrors Sponge Common's Constants class. + * + *

Internal. Do not use.

+ */ +public class Constants { + public static class Sponge { + public static final DataQuery UNSAFE_NBT = DataQuery.of("UnsafeData"); + + private Sponge() { + } + } + + private Constants() { + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeAdapter.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeAdapter.java new file mode 100644 index 000000000..401456b4a --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeAdapter.java @@ -0,0 +1,249 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.sponge.internal.NbtAdapter; +import com.sk89q.worldedit.sponge.internal.SpongeTransmogrifier; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.item.ItemTypes; +import net.minecraft.world.level.block.Block; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.persistence.DataContainer; +import org.spongepowered.api.data.persistence.DataView; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.registry.RegistryKey; +import org.spongepowered.api.registry.RegistryReference; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.world.biome.Biome; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.math.vector.Vector3i; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Adapts between Sponge and WorldEdit equivalent objects. + */ +public class SpongeAdapter { + + public static org.spongepowered.api.block.BlockState adapt(BlockState blockState) { + int blockStateId = BlockStateIdAccess.getBlockStateId(blockState); + if (!BlockStateIdAccess.isValidInternalId(blockStateId)) { + return SpongeTransmogrifier.transmogToMinecraft(blockState); + } + return (org.spongepowered.api.block.BlockState) Block.stateById(blockStateId); + } + + public static BlockState adapt(org.spongepowered.api.block.BlockState blockState) { + int blockStateId = Block.getId((net.minecraft.world.level.block.state.BlockState) blockState); + BlockState worldEdit = BlockStateIdAccess.getBlockStateById(blockStateId); + if (worldEdit == null) { + return SpongeTransmogrifier.transmogToWorldEdit(blockState); + } + return worldEdit; + } + + /** + * Create a WorldEdit world from a Sponge world. + * + * @param world the Sponge world + * @return a WorldEdit world + */ + public static World adapt(ServerWorld world) { + checkNotNull(world); + return new SpongeWorld(world); + } + + /** + * Create a WorldEdit Player from a Sponge Player. + * + * @param player The Sponge player + * @return The WorldEdit player + */ + public static SpongePlayer adapt(ServerPlayer player) { + Objects.requireNonNull(player); + return new SpongePlayer(player); + } + + /** + * Create a Sponge Player from a WorldEdit Player. + * + * @param player The WorldEdit player + * @return The Bukkit player + */ + public static Player adapt(com.sk89q.worldedit.entity.Player player) { + return ((SpongePlayer) player).getPlayer(); + } + + /** + * Create a Sponge world from a WorldEdit world. + * + * @param world the WorldEdit world + * @return a Sponge world + */ + public static ServerWorld adapt(World world) { + checkNotNull(world); + if (world instanceof SpongeWorld) { + return ((SpongeWorld) world).getWorld(); + } else { + // Currently this is 99% certain to fail, we don't have consistent world name/id mapping + ServerWorld match = Sponge.server().worldManager().world( + ResourceKey.resolve(world.getName()) + ).orElse(null); + if (match != null) { + return match; + } else { + throw new IllegalArgumentException("Can't find a Sponge world for " + world); + } + } + } + + public static RegistryReference adapt(BiomeType biomeType) { + return RegistryKey.of(RegistryTypes.BIOME, ResourceKey.resolve(biomeType.id())) + .asReference(); + } + + public static BiomeType adapt(Biome biomeType) { + return BiomeType.REGISTRY.get(biomeType.toString()); + } + + /** + * Create a WorldEdit location from a Sponge location. + * + * @param location the Sponge location + * @return a WorldEdit location + */ + public static Location adapt(ServerLocation location, Vector3d rotation) { + checkNotNull(location); + Vector3 position = asVector(location); + return new Location( + adapt(location.world()), + position, + (float) rotation.y(), + (float) rotation.x() + ); + } + + /** + * Create a Sponge location from a WorldEdit location. + * + * @param location the WorldEdit location + * @return a Sponge location + */ + public static ServerLocation adapt(Location location) { + checkNotNull(location); + Vector3 position = location.toVector(); + return ServerLocation.of( + adapt((World) location.getExtent()), + position.x(), position.y(), position.z() + ); + } + + /** + * Create a Sponge rotation from a WorldEdit location. + * + * @param location the WorldEdit location + * @return a Sponge rotation + */ + public static Vector3d adaptRotation(Location location) { + checkNotNull(location); + return new Vector3d(location.getPitch(), location.getYaw(), 0); + } + + /** + * Create a WorldEdit Vector from a Sponge location. + * + * @param location The Sponge location + * @return a WorldEdit vector + */ + public static Vector3 asVector(ServerLocation location) { + checkNotNull(location); + return Vector3.at(location.x(), location.y(), location.z()); + } + + /** + * Create a WorldEdit BlockVector from a Sponge location. + * + * @param location The Sponge location + * @return a WorldEdit vector + */ + public static BlockVector3 asBlockVector(ServerLocation location) { + checkNotNull(location); + return BlockVector3.at(location.x(), location.y(), location.z()); + } + + public static BaseItemStack adapt(ItemStack itemStack) { + DataView tag = itemStack.toContainer().getView(Constants.Sponge.UNSAFE_NBT) + .orElse(null); + return new BaseItemStack( + ItemTypes.get(itemStack.type().key(RegistryTypes.ITEM_TYPE).asString()), + tag == null ? null : LazyReference.from(() -> NbtAdapter.adaptToWorldEdit(tag)), + itemStack.quantity() + ); + } + + public static ItemStack adapt(BaseItemStack itemStack) { + ItemStack stack = ItemStack.builder() + .itemType(() -> Sponge.game().registry(RegistryTypes.ITEM_TYPE) + .value(ResourceKey.resolve(itemStack.getType().id()))) + .quantity(itemStack.getAmount()) + .build(); + LinCompoundTag nbt = itemStack.getNbt(); + if (nbt != null) { + stack.setRawData( + DataContainer.createNew(DataView.SafetyMode.NO_DATA_CLONED) + .set(Constants.Sponge.UNSAFE_NBT, NbtAdapter.adaptFromWorldEdit(nbt)) + ); + } + return stack; + } + + public static Direction adapt(org.spongepowered.api.util.Direction direction) { + return Direction.valueOf(direction.name()); + } + + public static Vector3i adaptVector3i(BlockVector3 bv3) { + return new Vector3i(bv3.x(), bv3.y(), bv3.z()); + } + + public static BlockVector3 adaptVector3i(Vector3i vec3i) { + return BlockVector3.at(vec3i.x(), vec3i.y(), vec3i.z()); + } + + private SpongeAdapter() { + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java new file mode 100644 index 000000000..66e1b9c42 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBiomeRegistry.java @@ -0,0 +1,71 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.translation.TranslationManager; +import com.sk89q.worldedit.world.biome.BiomeData; +import com.sk89q.worldedit.world.registry.BiomeRegistry; +import org.spongepowered.api.registry.RegistryReference; +import org.spongepowered.api.world.biome.Biome; + +import javax.annotation.Nullable; + +/** + * Provides access to biome data in Sponge. + */ +class SpongeBiomeRegistry implements BiomeRegistry { + + @Override + public Component getRichName(com.sk89q.worldedit.world.biome.BiomeType biomeType) { + return TranslatableComponent.of( + TranslationManager.makeTranslationKey("biome", biomeType.id()) + ); + } + + @Deprecated + @Nullable + @Override + public BiomeData getData(com.sk89q.worldedit.world.biome.BiomeType biome) { + return new SpongeBiomeData(SpongeAdapter.adapt(biome)); + } + + @Deprecated + private static class SpongeBiomeData implements BiomeData { + private final RegistryReference biome; + + /** + * Create a new instance. + * + * @param biome the base biome + */ + private SpongeBiomeData(RegistryReference biome) { + this.biome = biome; + } + + @SuppressWarnings("deprecation") + @Override + public String getName() { + return biome.location().asString(); + } + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCategoryRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCategoryRegistry.java new file mode 100644 index 000000000..17b5236d8 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCategoryRegistry.java @@ -0,0 +1,44 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.registry.BlockCategoryRegistry; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.registry.Registry; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.tag.Tag; + +import java.util.Set; +import java.util.stream.Collectors; + +public class SpongeBlockCategoryRegistry implements BlockCategoryRegistry { + @Override + public Set getCategorisedByName(String category) { + Registry blockTypeRegistry = + Sponge.game().registry(RegistryTypes.BLOCK_TYPE); + + return blockTypeRegistry.taggedValues(Tag.of(RegistryTypes.BLOCK_TYPE, ResourceKey.resolve(category))) + .stream() + .map(blockType -> BlockType.REGISTRY.get(blockTypeRegistry.valueKey(blockType).formatted())) + .collect(Collectors.toSet()); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCommandSender.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCommandSender.java new file mode 100644 index 000000000..45d00da69 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockCommandSender.java @@ -0,0 +1,183 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.AbstractCommandBlockActor; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.block.entity.CommandBlock; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.util.Ticks; +import org.spongepowered.math.vector.Vector3d; + +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SpongeBlockCommandSender extends AbstractCommandBlockActor { + private final SpongeWorldEdit worldEdit; + private final CommandBlock sender; + private final UUID uuid; + + public SpongeBlockCommandSender(SpongeWorldEdit worldEdit, CommandBlock sender) { + super(SpongeAdapter.adapt(checkNotNull(sender).serverLocation(), Vector3d.ZERO)); + checkNotNull(worldEdit); + + this.worldEdit = worldEdit; + this.sender = sender; + this.uuid = UUID.nameUUIDFromBytes((UUID_PREFIX + sender.name()).getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String getName() { + return sender.name(); + } + + @Override + @Deprecated + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + sendMessage(net.kyori.adventure.text.Component.text(part)); + } + } + + @Override + @Deprecated + public void print(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.LIGHT_PURPLE)); + } + } + + @Override + @Deprecated + public void printDebug(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.GRAY)); + } + } + + @Override + @Deprecated + public void printError(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.RED)); + } + } + + @Override + public void print(Component component) { + sendMessage(SpongeTextAdapter.convert(component, getLocale())); + } + + private void sendMessage(net.kyori.adventure.text.Component textComponent) { + this.sender.offer(Keys.LAST_COMMAND_OUTPUT, textComponent); + } + + @Override + public Locale getLocale() { + return WorldEdit.getInstance().getConfiguration().defaultLocale; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Override + public String[] getGroups() { + return new String[0]; + } + + @Override + public void checkPermission(String permission) throws AuthorizationException { + if (!hasPermission(permission)) { + throw new AuthorizationException(); + } + } + + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } + + public CommandBlock getSender() { + return this.sender; + } + + @Override + public SessionKey getSessionKey() { + return new SessionKey() { + + private volatile boolean active = true; + + private void updateActive() { + BlockState block = sender.block(); + if (!sender.serverLocation().world().isChunkLoadedAtBlock(sender.blockPosition(), false)) { + active = false; + return; + } + BlockType type = block.type(); + active = type == BlockTypes.COMMAND_BLOCK.get() + || type == BlockTypes.CHAIN_COMMAND_BLOCK.get() + || type == BlockTypes.REPEATING_COMMAND_BLOCK.get(); + } + + @Override + public String getName() { + return sender.name(); + } + + @Override + public boolean isActive() { + if (Sponge.server().onMainThread()) { + // we can update eagerly + updateActive(); + } else { + // we should update it eventually + Task task = Task.builder().delay(Ticks.zero()).plugin(worldEdit.getPluginContainer()).execute(this::updateActive).build(); + Sponge.server().scheduler().submit(task); + } + return active; + } + + @Override + public boolean isPersistent() { + return true; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + }; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockMaterial.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockMaterial.java new file mode 100644 index 000000000..861b9d633 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockMaterial.java @@ -0,0 +1,96 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.world.registry.BlockMaterial; +import com.sk89q.worldedit.world.registry.PassthroughBlockMaterial; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; + +import javax.annotation.Nullable; + +/** + * Sponge block material that pulls as much info as possible from the Minecraft + * Material, and passes the rest to another implementation, typically the + * bundled block info. + */ +public class SpongeBlockMaterial extends PassthroughBlockMaterial { + + private final BlockState block; + + public SpongeBlockMaterial(BlockState block, @Nullable BlockMaterial secondary) { + super(secondary); + this.block = block; + } + + @Override + public boolean isAir() { + return block.isAir() || super.isAir(); + } + + @Override + public boolean isOpaque() { + return block.canOcclude(); + } + + @Override + @SuppressWarnings("deprecation") + public boolean isLiquid() { + return block.liquid(); + } + + @Override + @SuppressWarnings("deprecation") + public boolean isSolid() { + return block.isSolid(); + } + + @Override + public boolean isFragileWhenPushed() { + return block.getPistonPushReaction() == PushReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return block.getPistonPushReaction() == PushReaction.BLOCK; + } + + @Override + @SuppressWarnings("deprecation") + public boolean isMovementBlocker() { + return block.blocksMotion(); + } + + @Override + public boolean isBurnable() { + return block.ignitedByLava(); + } + + @Override + public boolean isToolRequired() { + return block.requiresCorrectToolForDrops(); + } + + @Override + public boolean isReplacedDuringPlacement() { + return block.canBeReplaced(); + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockRegistry.java new file mode 100644 index 000000000..f692e9c27 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeBlockRegistry.java @@ -0,0 +1,91 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.sponge.internal.SpongeTransmogrifier; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import com.sk89q.worldedit.world.registry.BundledBlockRegistry; +import net.minecraft.world.level.block.Block; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.state.StateProperty; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.OptionalInt; +import java.util.TreeMap; + +public class SpongeBlockRegistry extends BundledBlockRegistry { + + private final Map materialMap = + new HashMap<>(); + + @Override + public Component getRichName(BlockType blockType) { + return SpongeTextAdapter.convert(Sponge.game().registry(RegistryTypes.BLOCK_TYPE) + .value(ResourceKey.resolve(blockType.id())).asComponent()); + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + org.spongepowered.api.block.BlockType spongeBlockType = + Sponge.game().registry(RegistryTypes.BLOCK_TYPE) + .value(ResourceKey.resolve(blockType.id())); + return materialMap.computeIfAbsent( + spongeBlockType.defaultState(), + m -> { + net.minecraft.world.level.block.state.BlockState blockState = + (net.minecraft.world.level.block.state.BlockState) m; + return new SpongeBlockMaterial( + blockState, + super.getMaterial(blockType) + ); + } + ); + } + + @Override + public Map> getProperties(BlockType blockType) { + org.spongepowered.api.block.BlockType spongeBlockType = + Sponge.game().registry(RegistryTypes.BLOCK_TYPE) + .value(ResourceKey.resolve(blockType.id())); + Map> map = new TreeMap<>(); + Collection> propertyKeys = spongeBlockType + .defaultState().stateProperties(); + for (StateProperty key : propertyKeys) { + map.put(key.name(), SpongeTransmogrifier.transmogToWorldEditProperty(key)); + } + return map; + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + org.spongepowered.api.block.BlockState equivalent = SpongeAdapter.adapt(state); + return OptionalInt.of(Block.getId( + (net.minecraft.world.level.block.state.BlockState) equivalent + )); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java new file mode 100644 index 000000000..a75ee8fc6 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeCommandSender.java @@ -0,0 +1,172 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import net.kyori.adventure.audience.Audience; +import org.spongepowered.api.entity.living.player.Player; + +import java.io.File; +import java.util.Locale; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class SpongeCommandSender implements Actor { + + /** + * One time generated ID. + */ + private static final UUID DEFAULT_ID = UUID.fromString("a233eb4b-4cab-42cd-9fd9-7e7b9a3f74be"); + + private final Audience sender; + + public SpongeCommandSender(Audience sender) { + checkNotNull(sender); + checkArgument( + !(sender instanceof Player), + "Players should be wrapped using the specialized class" + ); + + this.sender = sender; + } + + @Override + public UUID getUniqueId() { + return DEFAULT_ID; + } + + @Override + public String getName() { + return "Console"; + } + + @Override + @Deprecated + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + sender.sendMessage(net.kyori.adventure.text.Component.text(part)); + } + } + + @Override + @Deprecated + public void print(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.LIGHT_PURPLE)); + } + } + + @Override + @Deprecated + public void printDebug(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.GRAY)); + } + } + + @Override + @Deprecated + public void printError(String msg) { + for (String part : msg.split("\n")) { + print(TextComponent.of(part, TextColor.RED)); + } + } + + @Override + public void print(Component component) { + sender.sendMessage(SpongeTextAdapter.convert(component, getLocale())); + } + + @Override + public boolean canDestroyBedrock() { + return true; + } + + @Override + public String[] getGroups() { + return new String[0]; + } + + @Override + public boolean hasPermission(String perm) { + return true; + } + + @Override + public void checkPermission(String permission) { + } + + @Override + public boolean isPlayer() { + return false; + } + + @Override + public File openFileOpenDialog(String[] extensions) { + return null; + } + + @Override + public File openFileSaveDialog(String[] extensions) { + return null; + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + } + + @Override + public Locale getLocale() { + return WorldEdit.getInstance().getConfiguration().defaultLocale; + } + + @Override + public SessionKey getSessionKey() { + return new SessionKey() { + @Override + public String getName() { + return "Console"; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public boolean isPersistent() { + return true; + } + + @Override + public UUID getUniqueId() { + return DEFAULT_ID; + } + }; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntity.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntity.java new file mode 100644 index 000000000..e8ad6b39d --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntity.java @@ -0,0 +1,125 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.entity.metadata.EntityProperties; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.sponge.internal.NbtAdapter; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.NullWorld; +import com.sk89q.worldedit.world.entity.EntityType; +import org.spongepowered.api.data.persistence.DataView; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.math.vector.Vector3d; + +import java.lang.ref.WeakReference; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +class SpongeEntity implements Entity { + + private final WeakReference entityRef; + + SpongeEntity(org.spongepowered.api.entity.Entity entity) { + checkNotNull(entity); + this.entityRef = new WeakReference<>(entity); + } + + @Override + public BaseEntity getState() { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity == null || entity.vehicle().isPresent()) { + return null; + } + EntityType entityType = EntityType.REGISTRY.get(entity.type().key(RegistryTypes.ENTITY_TYPE).asString()); + if (entityType == null) { + return null; + } + DataView dataView = entity.toContainer().getView(Constants.Sponge.UNSAFE_NBT) + .orElse(null); + return new BaseEntity( + entityType, + dataView == null ? null : LazyReference.from(() -> NbtAdapter.adaptToWorldEdit(dataView)) + ); + } + + @Override + public Location getLocation() { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity != null) { + ServerLocation entityLoc = entity.serverLocation(); + Vector3d entityRot = entity.rotation(); + + return SpongeAdapter.adapt(entityLoc, entityRot); + } else { + return new Location(NullWorld.getInstance()); + } + } + + @Override + public boolean setLocation(Location location) { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity != null) { + return entity.setLocation(SpongeAdapter.adapt(location)); + } else { + return false; + } + } + + @Override + public Extent getExtent() { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity != null) { + return SpongeAdapter.adapt(entity.serverLocation().world()); + } else { + return NullWorld.getInstance(); + } + } + + @Override + public boolean remove() { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity != null) { + entity.remove(); + } + return true; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getFacet(Class cls) { + org.spongepowered.api.entity.Entity entity = entityRef.get(); + if (entity != null) { + if (EntityProperties.class.isAssignableFrom(cls)) { + return (T) new SpongeEntityProperties(entity); + } else { + return null; + } + } else { + return null; + } + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java new file mode 100644 index 000000000..f7b0269a7 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeEntityProperties.java @@ -0,0 +1,155 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.entity.metadata.EntityProperties; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.ExperienceOrb; +import org.spongepowered.api.entity.FallingBlock; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.entity.explosive.fused.PrimedTNT; +import org.spongepowered.api.entity.hanging.ItemFrame; +import org.spongepowered.api.entity.hanging.Painting; +import org.spongepowered.api.entity.living.Ambient; +import org.spongepowered.api.entity.living.ArmorStand; +import org.spongepowered.api.entity.living.ComplexLivingPart; +import org.spongepowered.api.entity.living.Humanoid; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.api.entity.living.animal.Animal; +import org.spongepowered.api.entity.living.aquatic.Aquatic; +import org.spongepowered.api.entity.living.golem.Golem; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.trader.Trader; +import org.spongepowered.api.entity.projectile.Projectile; +import org.spongepowered.api.entity.vehicle.Boat; +import org.spongepowered.api.entity.vehicle.minecart.Minecart; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SpongeEntityProperties implements EntityProperties { + + private final Entity entity; + + public SpongeEntityProperties(Entity entity) { + checkNotNull(entity); + this.entity = entity; + } + + @Override + public boolean isPlayerDerived() { + return entity instanceof Humanoid; + } + + @Override + public boolean isProjectile() { + return entity instanceof Projectile; + } + + @Override + public boolean isItem() { + return entity instanceof Item; + } + + @Override + public boolean isFallingBlock() { + return entity instanceof FallingBlock; + } + + @Override + public boolean isPainting() { + return entity instanceof Painting; + } + + @Override + public boolean isItemFrame() { + return entity instanceof ItemFrame; + } + + @Override + public boolean isBoat() { + return entity instanceof Boat; + } + + @Override + public boolean isMinecart() { + return entity instanceof Minecart; + } + + @Override + public boolean isTNT() { + return entity instanceof PrimedTNT; + } + + @Override + public boolean isExperienceOrb() { + return entity instanceof ExperienceOrb; + } + + @Override + public boolean isLiving() { + return entity instanceof Living; + } + + @Override + public boolean isAnimal() { + return entity instanceof Animal; + } + + @Override + public boolean isAmbient() { + return entity instanceof Ambient; + } + + @Override + public boolean isNPC() { + return entity instanceof Trader; + } + + @Override + public boolean isGolem() { + return entity instanceof Golem; + } + + @Override + public boolean isTamed() { + return entity.get(Keys.IS_TAMED).orElse(false); + } + + @Override + public boolean isTagged() { + return entity.get(Keys.CUSTOM_NAME).isPresent(); + } + + @Override + public boolean isArmorStand() { + return entity instanceof ArmorStand; + } + + @Override + public boolean isPasteable() { + return !(entity instanceof Player || entity instanceof ComplexLivingPart); + } + + @Override + public boolean isWaterCreature() { + return entity instanceof Aquatic; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemCategoryRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemCategoryRegistry.java new file mode 100644 index 000000000..e34acb08a --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemCategoryRegistry.java @@ -0,0 +1,44 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.ItemCategoryRegistry; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.registry.Registry; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.tag.Tag; + +import java.util.Set; +import java.util.stream.Collectors; + +public class SpongeItemCategoryRegistry implements ItemCategoryRegistry { + @Override + public Set getCategorisedByName(String category) { + Registry itemTypeRegistry = + Sponge.game().registry(RegistryTypes.ITEM_TYPE); + + return itemTypeRegistry.taggedValues(Tag.of(RegistryTypes.ITEM_TYPE, ResourceKey.resolve(category))) + .stream() + .map(itemType -> ItemType.REGISTRY.get(itemTypeRegistry.valueKey(itemType).formatted())) + .collect(Collectors.toSet()); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemRegistry.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemRegistry.java new file mode 100644 index 000000000..23936cbb2 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeItemRegistry.java @@ -0,0 +1,47 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.BundledItemRegistry; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.registry.RegistryTypes; + +public class SpongeItemRegistry extends BundledItemRegistry { + + @Override + public Component getRichName(ItemType itemType) { + return SpongeTextAdapter.convert(Sponge.game().registry(RegistryTypes.ITEM_TYPE) + .value(ResourceKey.resolve(itemType.id())).asComponent()); + } + + @Override + public Component getRichName(BaseItemStack itemStack) { + return TranslatableComponent.of( + ((ItemStack) (Object) SpongeAdapter.adapt(itemStack)).getDescriptionId() + ); + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePermissionsProvider.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePermissionsProvider.java new file mode 100644 index 000000000..da99c0eca --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePermissionsProvider.java @@ -0,0 +1,47 @@ +/* + * 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.sponge; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.service.permission.PermissionDescription; +import org.spongepowered.api.service.permission.PermissionService; +import org.spongepowered.api.service.permission.SubjectReference; + +public class SpongePermissionsProvider { + + public boolean hasPermission(ServerPlayer player, String permission) { + return player.hasPermission(permission); + } + + public void registerPermission(String permission) { + Sponge.game().serviceProvider().registration(PermissionService.class).ifPresent((permissionService -> { + PermissionDescription.Builder permissionBuilder = permissionService.service() + .newDescriptionBuilder(SpongeWorldEdit.inst().getPluginContainer()); + permissionBuilder.id(permission).register(); + })); + } + + public String[] getGroups(ServerPlayer player) { + return player.parents().stream() + .map(SubjectReference::subjectIdentifier) + .toArray(String[]::new); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java new file mode 100644 index 000000000..011f932b1 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java @@ -0,0 +1,200 @@ +/* + * 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.sponge; + +import com.google.common.collect.ImmutableSet; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.AbstractPlatform; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.MultiUserPlatform; +import com.sk89q.worldedit.extension.platform.Preference; +import com.sk89q.worldedit.sponge.config.SpongeConfiguration; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.registry.Registries; +import org.enginehub.piston.CommandManager; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.util.Ticks; +import org.spongepowered.api.world.server.ServerWorld; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Nullable; + +import static java.util.stream.Collectors.toList; + +class SpongePlatform extends AbstractPlatform implements MultiUserPlatform { + + private final SpongeWorldEdit mod; + private boolean hookingEvents = false; + private int nextTaskId = 0; + + SpongePlatform(SpongeWorldEdit mod) { + this.mod = mod; + } + + boolean isHookingEvents() { + return hookingEvents; + } + + @Override + public Registries getRegistries() { + return SpongeRegistries.getInstance(); + } + + @Override + public int getDataVersion() { + return Sponge.platform().minecraftVersion().dataVersion().orElse(-1); + } + + @Override + public boolean isValidMobType(String type) { + return Sponge.game().registry(RegistryTypes.ENTITY_TYPE) + .findValue(ResourceKey.resolve(type)).isPresent(); + } + + @Override + public void reload() { + getConfiguration().load(); + super.reload(); + } + + @Override + public int schedule(long delay, long period, Runnable task) { + Sponge.server().scheduler().submit(Task.builder() + .delay(Ticks.of(delay)) + .interval(Ticks.of(period)) + .execute(task) + .plugin(SpongeWorldEdit.inst().getPluginContainer()) + .build()); + return nextTaskId++; + } + + @Override + public List getWorlds() { + Collection worlds = Sponge.server().worldManager().worlds(); + List ret = new ArrayList<>(worlds.size()); + for (ServerWorld world : worlds) { + ret.add(SpongeAdapter.adapt(world)); + } + return ret; + } + + @Nullable + @Override + public Player matchPlayer(Player player) { + if (player instanceof SpongePlayer) { + return player; + } else { + Optional optPlayer = Sponge.server().player(player.getUniqueId()); + return optPlayer.map(SpongePlayer::new).orElse(null); + } + } + + @Nullable + @Override + public World matchWorld(World world) { + if (world instanceof SpongeWorld) { + return world; + } else { + // TODO this needs fixing for world name shenanigans + for (ServerWorld spongeWorld : Sponge.server().worldManager().worlds()) { + if (spongeWorld.key().toString().equals(world.getName())) { + return SpongeAdapter.adapt(spongeWorld); + } + } + + return null; + } + } + + @Override + public void registerCommands(CommandManager manager) { + } + + @Override + public void setGameHooksEnabled(boolean enabled) { + this.hookingEvents = enabled; + } + + @Override + public SpongeConfiguration getConfiguration() { + return mod.getConfig(); + } + + @Override + public String getVersion() { + return mod.getInternalVersion(); + } + + @Override + public String getPlatformName() { + return "Sponge-Official"; + } + + @Override + public String getPlatformVersion() { + return mod.getInternalVersion(); + } + + @Override + public String id() { + return "enginehub:sponge"; + } + + @Override + public Map getCapabilities() { + Map capabilities = new EnumMap<>(Capability.class); + capabilities.put(Capability.CONFIGURATION, Preference.NORMAL); + capabilities.put(Capability.WORLDEDIT_CUI, Preference.NORMAL); + capabilities.put(Capability.GAME_HOOKS, Preference.NORMAL); + capabilities.put(Capability.PERMISSIONS, Preference.NORMAL); + capabilities.put(Capability.USER_COMMANDS, Preference.NORMAL); + capabilities.put(Capability.WORLD_EDITING, Preference.PREFERRED); + return capabilities; + } + + @Override + public Set getSupportedSideEffects() { + return ImmutableSet.of( + SideEffect.UPDATE, SideEffect.ENTITY_AI, SideEffect.LIGHTING, SideEffect.NEIGHBORS + ); + } + + @Override + public long getTickCount() { + return Sponge.server().runningTimeTicks().ticks(); + } + + @Override + public Collection getConnectedUsers() { + return Sponge.server().onlinePlayers().stream().map(SpongePlayer::new).collect(toList()); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java new file mode 100644 index 000000000..1b78106df --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java @@ -0,0 +1,313 @@ +/* + * 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.sponge; + +import com.sk89q.util.StringUtil; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.sponge.internal.NbtAdapter; +import com.sk89q.worldedit.util.HandSide; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.gamemode.GameModes; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.StructureBlockEntity; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.math.vector.Vector3d; + +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.UUID; +import javax.annotation.Nullable; + +public class SpongePlayer extends AbstractPlayerActor { + private static final int STRUCTURE_BLOCK_PACKET_ID = 7; + + private final ServerPlayer player; + + protected SpongePlayer(ServerPlayer player) { + this.player = player; + ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId()); + } + + @Override + public UUID getUniqueId() { + return player.uniqueId(); + } + + @Override + public BaseItemStack getItemInHand(HandSide handSide) { + ItemStack is = this.player.itemInHand( + handSide == HandSide.MAIN_HAND ? HandTypes.MAIN_HAND : HandTypes.OFF_HAND + ); + return SpongeAdapter.adapt(is); + } + + @Override + public String getName() { + return this.player.name(); + } + + @Override + public String getDisplayName() { + return LegacyComponentSerializer.legacySection().serialize(player.displayName().get()); + } + + @Override + public BaseEntity getState() { + throw new UnsupportedOperationException("Cannot create a state from this object"); + } + + @Override + public Location getLocation() { + ServerLocation entityLoc = this.player.serverLocation(); + Vector3d entityRot = this.player.rotation(); + + return SpongeAdapter.adapt(entityLoc, entityRot); + } + + @Override + public boolean setLocation(Location location) { + return player.setLocation(SpongeAdapter.adapt(location)); + } + + @Override + public com.sk89q.worldedit.world.World getWorld() { + return SpongeAdapter.adapt(player.serverLocation().world()); + } + + @Override + public void giveItem(BaseItemStack itemStack) { + this.player.inventory().offer(SpongeAdapter.adapt(itemStack)); + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + String[] params = event.getParameters(); + String send = event.getTypeId(); + if (params.length > 0) { + send = send + "|" + StringUtil.joinString(params, "|"); + } + + String finalData = send; + CUIChannelHandler.channel().play().sendTo( + player, + buffer -> buffer.writeBytes(finalData.getBytes(StandardCharsets.UTF_8)) + ); + } + + @Override + @Deprecated + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + this.player.sendMessage(LegacyComponentSerializer.legacySection().deserialize(part)); + } + } + + @Override + @Deprecated + public void printDebug(String msg) { + sendColorized(msg, NamedTextColor.GRAY); + } + + @Override + @Deprecated + public void print(String msg) { + sendColorized(msg, NamedTextColor.LIGHT_PURPLE); + } + + @Override + @Deprecated + public void printError(String msg) { + sendColorized(msg, NamedTextColor.RED); + } + + @Override + public void print(Component component) { + player.sendMessage(SpongeTextAdapter.convert(component, getLocale())); + } + + private void sendColorized(String msg, TextColor formatting) { + for (String part : msg.split("\n")) { + this.player.sendMessage( + LegacyComponentSerializer.legacySection().deserialize(part).color(formatting) + ); + } + } + + @Override + public boolean trySetPosition(Vector3 pos, float pitch, float yaw) { + ServerLocation loc = ServerLocation.of( + this.player.world(), pos.x(), pos.y(), pos.z() + ); + + return this.player.setLocationAndRotation(loc, new Vector3d(pitch, yaw, 0)); + } + + @Override + public String[] getGroups() { + return SpongeWorldEdit.inst().getPermissionsProvider().getGroups(this.player); + } + + @Override + public BlockBag getInventoryBlockBag() { + return null; + } + + @Override + public boolean hasPermission(String perm) { + return SpongeWorldEdit.inst().getPermissionsProvider().hasPermission(player, perm); + } + + @Nullable + @Override + public T getFacet(Class cls) { + return null; + } + + @Override + public GameMode getGameMode() { + return GameModes.get(player.gameMode().get().key(RegistryTypes.GAME_MODE).asString()); + } + + @Override + public void setGameMode(GameMode gameMode) { + player.gameMode().set( + Sponge.game().registry(RegistryTypes.GAME_MODE).value( + ResourceKey.resolve(gameMode.id()) + ) + ); + } + + @Override + public boolean isAllowedToFly() { + return player.get(Keys.CAN_FLY).orElse(super.isAllowedToFly()); + } + + @Override + public void setFlying(boolean flying) { + player.offer(Keys.IS_FLYING, flying); + } + + @Override + public > void sendFakeBlock(BlockVector3 pos, B block) { + if (block == null) { + player.resetBlockChange(pos.x(), pos.y(), pos.z()); + } else { + BlockState spongeBlock = SpongeAdapter.adapt(block.toImmutableState()); + player.sendBlockChange(pos.x(), pos.y(), pos.z(), spongeBlock); + if (block instanceof final BaseBlock baseBlock + && block.getBlockType().equals(com.sk89q.worldedit.world.block.BlockTypes.STRUCTURE_BLOCK)) { + final LinCompoundTag nbtData = baseBlock.getNbt(); + if (nbtData != null) { + net.minecraft.world.level.block.state.BlockState nativeBlock = + (net.minecraft.world.level.block.state.BlockState) spongeBlock; + net.minecraft.nbt.CompoundTag nativeNbtData = NbtAdapter.adaptNMSToWorldEdit(nbtData); + net.minecraft.server.level.ServerPlayer nativePlayer = + ((net.minecraft.server.level.ServerPlayer) player); + + StructureBlockEntity structureBlockEntity = + new StructureBlockEntity(new BlockPos(pos.x(), pos.y(), pos.z()), nativeBlock); + structureBlockEntity.loadWithComponents(nativeNbtData, nativePlayer.level().registryAccess()); + nativePlayer.connection.send( + ClientboundBlockEntityDataPacket.create(structureBlockEntity, (be, ra) -> nativeNbtData)); + } + } + } + } + + @Override + public Locale getLocale() { + return player.locale(); + } + + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(player); + } + + static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + SessionKeyImpl(Player player) { + this.uuid = player.uniqueId(); + this.name = player.name(); + } + + SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Nullable + @Override + public String getName() { + return name; + } + + @Override + public boolean isActive() { + // We can't directly check if the player is online because + // the list of players is not thread safe + return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid); + } + + @Override + public boolean isPersistent() { + return true; + } + + } + + public Player getPlayer() { + return player; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeRegistries.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeRegistries.java new file mode 100644 index 000000000..04baa3936 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeRegistries.java @@ -0,0 +1,70 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.world.registry.BiomeRegistry; +import com.sk89q.worldedit.world.registry.BlockCategoryRegistry; +import com.sk89q.worldedit.world.registry.BlockRegistry; +import com.sk89q.worldedit.world.registry.BundledRegistries; +import com.sk89q.worldedit.world.registry.ItemCategoryRegistry; +import com.sk89q.worldedit.world.registry.ItemRegistry; + +/** + * World data for the Sponge platform. + */ +class SpongeRegistries extends BundledRegistries { + + private static final SpongeRegistries INSTANCE = new SpongeRegistries(); + + public static SpongeRegistries getInstance() { + return INSTANCE; + } + + private final BiomeRegistry biomeRegistry = new SpongeBiomeRegistry(); + private final BlockRegistry blockRegistry = new SpongeBlockRegistry(); + private final BlockCategoryRegistry blockCategoryRegistry = new SpongeBlockCategoryRegistry(); + private final ItemRegistry itemRegistry = new SpongeItemRegistry(); + private final ItemCategoryRegistry itemCategoryRegistry = new SpongeItemCategoryRegistry(); + + @Override + public BiomeRegistry getBiomeRegistry() { + return biomeRegistry; + } + + @Override + public BlockRegistry getBlockRegistry() { + return blockRegistry; + } + + @Override + public BlockCategoryRegistry getBlockCategoryRegistry() { + return blockCategoryRegistry; + } + + @Override + public ItemRegistry getItemRegistry() { + return itemRegistry; + } + + @Override + public ItemCategoryRegistry getItemCategoryRegistry() { + return itemCategoryRegistry; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeTextAdapter.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeTextAdapter.java new file mode 100644 index 000000000..7709737e4 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeTextAdapter.java @@ -0,0 +1,45 @@ +/* + * 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.sponge; + +import com.sk89q.worldedit.util.formatting.WorldEditText; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer; + +import java.util.Locale; + +public class SpongeTextAdapter { + + public static net.kyori.adventure.text.Component convert(Component component, Locale locale) { + component = WorldEditText.format(component, locale); + return net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson() + .deserialize(GsonComponentSerializer.INSTANCE.serialize(component)); + } + + public static Component convert(net.kyori.adventure.text.Component component) { + return GsonComponentSerializer.INSTANCE.deserialize( + net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson() + .serialize(component) + ); + } + + private SpongeTextAdapter() { + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java new file mode 100644 index 000000000..f305306db --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -0,0 +1,507 @@ +/* + * 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.sponge; + +import com.google.common.collect.Sets; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.sponge.internal.NbtAdapter; +import com.sk89q.worldedit.sponge.internal.SpongeWorldNativeAccess; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.World; +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.item.ItemTypes; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldedit.world.weather.WeatherTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.features.EndFeatures; +import net.minecraft.data.worldgen.features.TreeFeatures; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import org.apache.logging.log4j.Logger; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Server; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.entity.BlockEntity; +import org.spongepowered.api.block.entity.BlockEntityArchetype; +import org.spongepowered.api.block.entity.BlockEntityType; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.entity.EntityArchetype; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.EntityTypes; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.util.Ticks; +import org.spongepowered.api.world.BlockChangeFlags; +import org.spongepowered.api.world.LightTypes; +import org.spongepowered.api.world.SerializationBehavior; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.api.world.server.WorldTemplate; +import org.spongepowered.api.world.volume.stream.StreamOptions; +import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.math.vector.Vector3i; + +import java.lang.ref.WeakReference; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An adapter to Minecraft worlds for WorldEdit. + */ +public final class SpongeWorld extends AbstractWorld { + + private static final RandomSource random = RandomSource.create(); + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final WeakReference worldRef; + private final SpongeWorldNativeAccess worldNativeAccess; + + /** + * Construct a new world. + * + * @param world the world + */ + SpongeWorld(ServerWorld world) { + checkNotNull(world); + this.worldRef = new WeakReference<>(world); + this.worldNativeAccess = new SpongeWorldNativeAccess(new WeakReference<>((ServerLevel) world)); + } + + /** + * Get the underlying handle to the world. + * + * @return the world + * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was + * unloaded) + */ + ServerWorld getWorld() { + ServerWorld world = worldRef.get(); + if (world != null) { + return world; + } else { + throw new RuntimeException("The reference to the world was lost (i.e. the world may have been unloaded)"); + } + } + + // This is sus but leaving it for later world name/id reworks + @Override + public String getName() { + return getWorld().key().asString(); + } + + @Override + public String id() { + return getWorld().key().asString(); + } + + @Override + public Path getStoragePath() { + return getWorld().directory(); + } + + @Override + public BlockState getBlock(BlockVector3 position) { + return SpongeAdapter.adapt(getWorld().block( + position.x(), position.y(), position.z() + )); + } + + @Override + public BaseBlock getFullBlock(BlockVector3 position) { + BlockEntity blockEntity = getWorld().blockEntity( + position.x(), position.y(), position.z() + ).orElse(null); + LinCompoundTag blockEntityData = null; + if (blockEntity != null) { + BlockEntityArchetype blockEntityArchetype = blockEntity.createArchetype(); + BlockEntityType blockEntityType = blockEntityArchetype.blockEntityType(); + ResourceKey blockEntityId = blockEntityType.key(RegistryTypes.BLOCK_ENTITY_TYPE); + blockEntityData = NbtAdapter.adaptToWorldEdit(blockEntityArchetype.blockEntityData()); + + // Add ID and position since Sponge's #blockEntityData does not save metadata + LinCompoundTag.Builder fullBlockEntityDataBuilder = blockEntityData.toBuilder(); + fullBlockEntityDataBuilder.put("id", LinStringTag.of(blockEntityId.formatted())); + fullBlockEntityDataBuilder.put("x", LinIntTag.of(position.x())); + fullBlockEntityDataBuilder.put("y", LinIntTag.of(position.y())); + fullBlockEntityDataBuilder.put("z", LinIntTag.of(position.z())); + blockEntityData = fullBlockEntityDataBuilder.build(); + } + return getBlock(position).toBaseBlock(blockEntityData); + } + + @Override + public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + checkNotNull(position); + checkNotNull(block); + + ServerWorld world = getWorld(); + + org.spongepowered.api.block.BlockState newState = SpongeAdapter.adapt(block.toImmutableState()); + + boolean didSet = world.setBlock( + position.x(), position.y(), position.z(), + newState, + BlockChangeFlags.NONE + .withUpdateNeighbors(sideEffects.shouldApply(SideEffect.NEIGHBORS)) + .withNotifyClients(true) + .withPhysics(sideEffects.shouldApply(SideEffect.UPDATE)) + .withNotifyObservers(sideEffects.shouldApply(SideEffect.UPDATE)) + .withLightingUpdates(sideEffects.shouldApply(SideEffect.LIGHTING)) + .withPathfindingUpdates(sideEffects.shouldApply(SideEffect.ENTITY_AI)) + .withNeighborDropsAllowed(false) + .withBlocksMoving(false) + .withForcedReRender(false) + .withIgnoreRender(false) + ); + if (!didSet) { + // still update NBT if the block is the same + if (world.block(position.x(), position.y(), position.z()) == newState) { + didSet = block.toBaseBlock().getNbt() != null; + } + } + + // Create the TileEntity + if (didSet && block instanceof BaseBlock baseBlock) { + LinCompoundTag nbt = baseBlock.getNbt(); + if (nbt != null) { + BlockEntityArchetype.builder() + .state(newState) + .blockEntity( + Sponge.game().registry(RegistryTypes.BLOCK_ENTITY_TYPE) + .value(ResourceKey.resolve(baseBlock.getNbtId())) + ) + .blockEntityData(NbtAdapter.adaptFromWorldEdit(nbt)) + .build() + .apply(ServerLocation.of(world, position.x(), position.y(), position.z())); + } + } + + return true; + } + + @Override + public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { + checkNotNull(position); + + worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + + return Sets.intersection( + SpongeWorldEdit.inst().getInternalPlatform().getSupportedSideEffects(), + sideEffectSet.getSideEffectsToApply() + ); + } + + @Override + public boolean clearContainerBlockContents(BlockVector3 position) { + getWorld().removeBlockEntity(position.x(), position.y(), position.z()); + return true; + } + + @Override + public boolean regenerate(Region region, Extent extent, RegenOptions options) { + Server server = Sponge.server(); + + final String id = "worldedittemp_" + getWorld().key().value(); + + WorldTemplate tempWorldProperties = WorldTemplate.builder().from(getWorld()) + .key(ResourceKey.of("worldedit", id)) + .add(Keys.IS_LOAD_ON_STARTUP, false) + .add(Keys.SERIALIZATION_BEHAVIOR, SerializationBehavior.NONE) + .add(Keys.SEED, options.getSeed().orElse(getWorld().properties().worldGenerationConfig().seed())) + .build(); + + ServerWorld tempWorld; + try { + tempWorld = server.worldManager().loadWorld(tempWorldProperties).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Failed to load temp world", e); + return false; + } + + try { + // Pre-gen all the chunks + // We need to also pull one more chunk in every direction + CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 16, 16), region.getMaximumPoint().add(16, 16, 16)); + for (BlockVector3 chunk : expandedPreGen.getChunkCubes()) { + tempWorld.loadChunk(chunk.x(), chunk.y(), chunk.z(), true); + } + + World from = SpongeAdapter.adapt(tempWorld); + for (BlockVector3 vec : region) { + extent.setBlock(vec, from.getFullBlock(vec)); + if (options.shouldRegenBiomes()) { + extent.setBiome(vec, from.getBiome(vec)); + } + } + } catch (WorldEditException e) { + throw new RuntimeException(e); + } finally { + // Remove temp world + server.worldManager().unloadWorld(tempWorldProperties.key()).thenRun(() -> server.worldManager().deleteWorld(tempWorldProperties.key())); + } + + return true; + } + + + @Nullable + private static net.minecraft.resources.ResourceKey> createTreeFeatureGenerator(TreeGenerator.TreeType type) { + return switch (type) { + // Based off of the SaplingGenerator class, as well as uses of DefaultBiomeFeatures fields + case TREE -> TreeFeatures.OAK; + case BIG_TREE -> TreeFeatures.FANCY_OAK; + case REDWOOD -> TreeFeatures.SPRUCE; + case TALL_REDWOOD -> TreeFeatures.MEGA_SPRUCE; + case MEGA_REDWOOD -> TreeFeatures.MEGA_PINE; + case BIRCH -> TreeFeatures.BIRCH; + case JUNGLE -> TreeFeatures.MEGA_JUNGLE_TREE; + case SMALL_JUNGLE -> TreeFeatures.JUNGLE_TREE; + case SHORT_JUNGLE -> TreeFeatures.JUNGLE_TREE_NO_VINE; + case JUNGLE_BUSH -> TreeFeatures.JUNGLE_BUSH; + case SWAMP -> TreeFeatures.SWAMP_OAK; + case ACACIA -> TreeFeatures.ACACIA; + case DARK_OAK -> TreeFeatures.DARK_OAK; + case TALL_BIRCH -> TreeFeatures.SUPER_BIRCH_BEES_0002; + case RED_MUSHROOM -> TreeFeatures.HUGE_RED_MUSHROOM; + case BROWN_MUSHROOM -> TreeFeatures.HUGE_BROWN_MUSHROOM; + case WARPED_FUNGUS -> TreeFeatures.WARPED_FUNGUS; + case CRIMSON_FUNGUS -> TreeFeatures.CRIMSON_FUNGUS; + case CHORUS_PLANT -> EndFeatures.CHORUS_PLANT; + case MANGROVE -> TreeFeatures.MANGROVE; + case TALL_MANGROVE -> TreeFeatures.TALL_MANGROVE; + case CHERRY -> TreeFeatures.CHERRY; + case RANDOM -> + createTreeFeatureGenerator(TreeGenerator.TreeType.values()[ThreadLocalRandom.current().nextInt(TreeGenerator.TreeType.values().length)]); + default -> null; + }; + } + + @Override + public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) { + ServerLevel world = (ServerLevel) getWorld(); + ConfiguredFeature generator = Optional.ofNullable(createTreeFeatureGenerator(type)) + .map(k -> world.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(k)) + .orElse(null); + return generator != null && generator.place( + world, world.getChunkSource().getGenerator(), random, + new BlockPos(position.x(), position.y(), position.z()) + ); + } + + @Override + public int getBlockLightLevel(BlockVector3 position) { + checkNotNull(position); + + int skyLight = getWorld().light(LightTypes.SKY, position.x(), position.y(), position.z()); + int groundLight = getWorld().light(LightTypes.BLOCK, position.x(), position.y(), position.z()); + + return Math.max(skyLight, groundLight); + + } + + @Override + public BiomeType getBiome(BlockVector3 position) { + checkNotNull(position); + return BiomeType.REGISTRY.get( + getWorld().registry(RegistryTypes.BIOME) + .valueKey(getWorld().biome(position.x(), position.y(), position.z())) + .asString() + ); + } + + @Override + public boolean setBiome(BlockVector3 position, BiomeType biome) { + checkNotNull(position); + checkNotNull(biome); + + getWorld().setBiome( + position.x(), position.y(), position.z(), + getWorld().registry(RegistryTypes.BIOME).value( + ResourceKey.resolve(biome.id()) + ) + ); + return true; + } + + @Override + public void dropItem(Vector3 position, BaseItemStack item) { + checkNotNull(position); + checkNotNull(item); + + if (item.getType() == ItemTypes.AIR) { + return; + } + + Item itemEntity = getWorld().createEntity( + EntityTypes.ITEM, + new Vector3d(position.x(), position.y(), position.z()) + ); + + itemEntity.item().set( + SpongeAdapter.adapt(item).createSnapshot() + ); + getWorld().spawnEntity(itemEntity); + } + + @Override + public void simulateBlockMine(BlockVector3 position) { + getWorld().destroyBlock( + new Vector3i(position.x(), position.y(), position.z()), + true + ); + } + + @Override + public boolean canPlaceAt(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState blockState) { + return ((net.minecraft.world.level.block.state.BlockState) SpongeAdapter.adapt(blockState)) + .canSurvive( + ((LevelReader) getWorld()), + new BlockPos(position.x(), position.y(), position.z()) + ); + } + + @Override + public int hashCode() { + return getWorld().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } else if ((o instanceof SpongeWorld other)) { + ServerWorld otherWorld = other.worldRef.get(); + ServerWorld thisWorld = worldRef.get(); + return otherWorld != null && otherWorld.equals(thisWorld); + } else { + return o instanceof com.sk89q.worldedit.world.World + && ((com.sk89q.worldedit.world.World) o).getName().equals(getName()); + } + } + + @Override + public List getEntities(Region region) { + return getWorld() + .entityStream( + SpongeAdapter.adaptVector3i(region.getMinimumPoint()), + SpongeAdapter.adaptVector3i(region.getMaximumPoint()), + // We don't need to force load or clone to copy entities + StreamOptions.builder() + .setCarbonCopy(false) + .setLoadingStyle(StreamOptions.LoadingStyle.NONE) + .build() + ) + .toStream() + .map(ve -> new SpongeEntity(ve.type())) + .collect(Collectors.toList()); + } + + @Override + public List getEntities() { + return getWorld().entities().stream() + .map(SpongeEntity::new) + .collect(Collectors.toList()); + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity entity) { + Optional> entityType = Sponge.game().registry(RegistryTypes.ENTITY_TYPE) + .findValue(ResourceKey.resolve(entity.getType().id())); + if (entityType.isEmpty()) { + return null; + } + EntityArchetype.Builder builder = EntityArchetype.builder().type(entityType.get()); + var nativeTag = entity.getNbt(); + if (nativeTag != null) { + builder.entityData(NbtAdapter.adaptFromWorldEdit(nativeTag)); + } + return builder.build().apply(SpongeAdapter.adapt(location)).map(SpongeEntity::new).orElse(null); + } + + @Override + public WeatherType getWeather() { + return WeatherTypes.get( + getWorld().weather().type().key(RegistryTypes.WEATHER_TYPE).asString() + ); + } + + @Override + public long getRemainingWeatherDuration() { + return getWorld().weather().remainingDuration().ticks(); + } + + @Override + public void setWeather(WeatherType weatherType) { + getWorld().setWeather( + Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( + ResourceKey.resolve(weatherType.id()) + ) + ); + } + + @Override + public void setWeather(WeatherType weatherType, long duration) { + getWorld().setWeather( + Sponge.game().registry(RegistryTypes.WEATHER_TYPE).value( + ResourceKey.resolve(weatherType.id()) + ), + Ticks.of(duration) + ); + } + + @Override + public BlockVector3 getSpawnPosition() { + return SpongeAdapter.adaptVector3i(getWorld().properties().spawnPosition()); + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java new file mode 100644 index 000000000..8da64b828 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java @@ -0,0 +1,487 @@ +/* + * 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.sponge; + +import com.google.common.base.Joiner; +import com.google.inject.Inject; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.PermissionCondition; +import com.sk89q.worldedit.event.platform.CommandEvent; +import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; +import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; +import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.internal.anvil.ChunkDeleter; +import com.sk89q.worldedit.internal.command.CommandUtil; +import com.sk89q.worldedit.internal.event.InteractionDebouncer; +import com.sk89q.worldedit.sponge.config.SpongeConfiguration; +import com.sk89q.worldedit.world.biome.BiomeCategory; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.item.ItemCategory; +import net.kyori.adventure.audience.Audience; +import org.apache.logging.log4j.Logger; +import org.bstats.sponge.Metrics; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Server; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.entity.BlockEntity; +import org.spongepowered.api.block.entity.CommandBlock; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.parameter.ArgumentReader; +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import org.spongepowered.api.event.EventContextKeys; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.action.InteractEvent; +import org.spongepowered.api.event.block.InteractBlockEvent; +import org.spongepowered.api.event.filter.cause.Root; +import org.spongepowered.api.event.item.inventory.InteractItemEvent; +import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; +import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; +import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.StartingEngineEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.world.LocatableBlock; +import org.spongepowered.api.world.server.ServerLocation; +import org.spongepowered.api.world.server.ServerWorld; +import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; +import static java.util.stream.Collectors.toList; + +/** + * The Sponge implementation of WorldEdit. + */ +@Plugin(SpongeWorldEdit.MOD_ID) +public class SpongeWorldEdit { + + public static final String MOD_ID = "worldedit"; + private static final int BSTATS_PLUGIN_ID = 3329; + + private static SpongeWorldEdit inst; + + public static SpongeWorldEdit inst() { + return inst; + } + + private final Logger logger; + private final PluginContainer container; + private final SpongeConfiguration config; + private final Path workingDir; + + private InteractionDebouncer debouncer; + private SpongePermissionsProvider provider; + private SpongePlatform platform; + + @Inject + public SpongeWorldEdit(Logger logger, + PluginContainer container, + SpongeConfiguration config, + Metrics.Factory metricsFactory, + @ConfigDir(sharedRoot = false) + Path workingDir) { + this.logger = logger; + this.container = container; + this.config = config; + this.workingDir = workingDir; + metricsFactory.make(BSTATS_PLUGIN_ID); + inst = this; + } + + @Listener + public void onPluginConstruction(ConstructPluginEvent event) { + this.platform = new SpongePlatform(this); + debouncer = new InteractionDebouncer(platform); + + WorldEdit.getInstance().getPlatformManager().register(platform); + + this.provider = new SpongePermissionsProvider(); + + event.game().eventManager().registerListeners( + container, + new CUIChannelHandler.RegistrationHandler() + ); + logger.info("WorldEdit for Sponge (version " + getInternalVersion() + ") is loaded"); + } + + @Listener + public void serverStarting(StartingEngineEvent event) { + final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); + if (Files.exists(delChunks)) { + ChunkDeleter.runFromFile(delChunks, true); + } + } + + @Listener + public void serverStarted(StartedEngineEvent event) { + event.engine().scheduler().submit(Task.builder() + .plugin(container) + .interval(30, TimeUnit.SECONDS) + .execute(ThreadSafeCache.getInstance()) + .build()); + + event.game().registry(RegistryTypes.BLOCK_TYPE).streamEntries().forEach(blockType -> { + String id = blockType.key().asString(); + if (!com.sk89q.worldedit.world.block.BlockType.REGISTRY.keySet().contains(id)) { + com.sk89q.worldedit.world.block.BlockType.REGISTRY.register(id, new com.sk89q.worldedit.world.block.BlockType( + id, + input -> { + BlockType spongeBlockType = Sponge.game().registry(RegistryTypes.BLOCK_TYPE).value( + ResourceKey.resolve(input.getBlockType().id()) + ); + return SpongeAdapter.adapt(spongeBlockType.defaultState()); + } + )); + } + }); + + event.game().registry(RegistryTypes.ITEM_TYPE).streamEntries().forEach(itemType -> { + String id = itemType.key().asString(); + if (!com.sk89q.worldedit.world.item.ItemType.REGISTRY.keySet().contains(id)) { + com.sk89q.worldedit.world.item.ItemType.REGISTRY.register(id, new com.sk89q.worldedit.world.item.ItemType(id)); + } + }); + + event.game().registry(RegistryTypes.ENTITY_TYPE).streamEntries().forEach(entityType -> { + String id = entityType.key().asString(); + if (!com.sk89q.worldedit.world.entity.EntityType.REGISTRY.keySet().contains(id)) { + com.sk89q.worldedit.world.entity.EntityType.REGISTRY.register(id, new com.sk89q.worldedit.world.entity.EntityType(id)); + } + }); + + for (ServerWorld world : event.engine().worldManager().worlds()) { + world.registry(RegistryTypes.BIOME).streamEntries().forEach(biomeType -> { + String id = biomeType.key().asString(); + if (!BiomeType.REGISTRY.keySet().contains(id)) { + BiomeType.REGISTRY.register(id, new BiomeType(id)); + } + }); + } + + event.game().registry(RegistryTypes.BLOCK_TYPE).tags().forEach(blockTypeTag -> { + String id = blockTypeTag.key().asString(); + if (!BlockCategory.REGISTRY.keySet().contains(id)) { + BlockCategory.REGISTRY.register(id, new BlockCategory(id)); + } + }); + event.game().registry(RegistryTypes.ITEM_TYPE).tags().forEach(itemTypeTag -> { + String id = itemTypeTag.key().asString(); + if (!ItemCategory.REGISTRY.keySet().contains(id)) { + ItemCategory.REGISTRY.register(id, new ItemCategory(id)); + } + }); + Sponge.server().registry(RegistryTypes.BIOME).tags().forEach(biomeTag -> { + String id = biomeTag.key().asString(); + if (!BiomeCategory.REGISTRY.keySet().contains(id)) { + BiomeCategory.REGISTRY.register(id, new BiomeCategory(id, () -> event.game().registry(RegistryTypes.BIOME).taggedValues(biomeTag).stream().map(SpongeAdapter::adapt).collect(Collectors.toSet()))); + } + }); + + config.load(); + WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); + } + + @Listener + public void serverStopping(StoppingEngineEvent event) { + WorldEdit worldEdit = WorldEdit.getInstance(); + worldEdit.getSessionManager().unload(); + WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); + } + + @Listener + public void registerCommand(RegisterCommandEvent event) { + WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); + Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); + if (commandsPlatform != platform || !platform.isHookingEvents()) { + // We're not in control of commands/events -- do not register. + return; + } + + List commands = manager.getPlatformCommandManager().getCommandManager() + .getAllCommands().toList(); + for (org.enginehub.piston.Command command : commands) { + registerAdaptedCommand(event, command); + + Set perms = command.getCondition().as(PermissionCondition.class) + .map(PermissionCondition::getPermissions) + .orElseGet(Collections::emptySet); + if (!perms.isEmpty()) { + perms.forEach(getPermissionsProvider()::registerPermission); + } + } + } + + private String rebuildArguments(String commandLabel, String args) { + int plSep = commandLabel.indexOf(':'); + if (plSep >= 0 && plSep < commandLabel.length() + 1) { + commandLabel = commandLabel.substring(plSep + 1); + } + + StringBuilder sb = new StringBuilder("/").append(commandLabel); + + String[] split = args.split(" ", -1); + if (split.length > 0) { + sb.append(" "); + } + return Joiner.on(" ").appendTo(sb, split).toString(); + } + + private void registerAdaptedCommand(RegisterCommandEvent event, org.enginehub.piston.Command command) { + CommandAdapter adapter = new CommandAdapter(command) { + @Override + public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { + CommandEvent weEvent = new CommandEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), rebuildArguments(command.getName(), arguments.remaining()).trim()); + WorldEdit.getInstance().getEventBus().post(weEvent); + return weEvent.isCancelled() ? CommandResult.success() : CommandResult.builder().build(); + } + + @Override + public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { + String args = rebuildArguments(command.getName(), arguments.remaining()); + CommandSuggestionEvent weEvent = new CommandSuggestionEvent(SpongeWorldEdit.inst().wrapCommandCause(cause), args); + WorldEdit.getInstance().getEventBus().post(weEvent); + return CommandUtil.fixSuggestions(args, weEvent.getSuggestions()) + .stream().map(CommandCompletion::of).collect(toList()); + } + }; + event.register( + container, adapter, command.getName(), command.getAliases().toArray(new String[0]) + ); + } + + private boolean skipEvents() { + return platform == null || !platform.isHookingEvents(); + } + + private boolean skipInteractionEvent(InteractEvent event) { + return skipEvents() || event.context().get(EventContextKeys.USED_HAND).orElse(null) != HandTypes.MAIN_HAND.get(); + } + + @Listener + public void onPlayerInteractItemPrimary(InteractItemEvent.Primary event, @Root ServerPlayer spongePlayer) { + if (skipInteractionEvent(event)) { + return; + } + + WorldEdit we = WorldEdit.getInstance(); + SpongePlayer player = SpongeAdapter.adapt(spongePlayer); + + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + return; + } + + boolean result = we.handleArmSwing(player); + debouncer.setLastInteraction(player, result); + } + + @Listener + public void onPlayerInteractItemSecondary(InteractItemEvent.Secondary event, @Root ServerPlayer spongePlayer) { + if (skipInteractionEvent(event)) { + return; + } + + WorldEdit we = WorldEdit.getInstance(); + SpongePlayer player = SpongeAdapter.adapt(spongePlayer); + + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + if (previousResult.get()) { + event.setCancelled(true); + } + return; + } + + boolean result = we.handleRightClick(player); + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCancelled(true); + } + } + + @Listener + public void onPlayerInteractBlockPrimary(InteractBlockEvent.Primary.Start event, @Root ServerPlayer spongePlayer) { + if (skipInteractionEvent(event)) { + return; + } + + WorldEdit we = WorldEdit.getInstance(); + SpongePlayer player = SpongeAdapter.adapt(spongePlayer); + + BlockSnapshot targetBlock = event.block(); + Optional optLoc = targetBlock.location(); + + boolean result = false; + if (optLoc.isPresent()) { + ServerLocation loc = optLoc.get(); + com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(loc, Vector3d.ZERO); + + result = we.handleBlockLeftClick(player, pos, SpongeAdapter.adapt(event.targetSide())); + } + + result = we.handleArmSwing(player) || result; + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCancelled(true); + } + } + + @Listener + public void onPlayerInteractBlockSecondary(InteractBlockEvent.Secondary event, @Root ServerPlayer spongePlayer) { + if (skipInteractionEvent(event)) { + return; + } + + WorldEdit we = WorldEdit.getInstance(); + SpongePlayer player = SpongeAdapter.adapt(spongePlayer); + + BlockSnapshot targetBlock = event.block(); + Optional optLoc = targetBlock.location(); + + boolean result = false; + if (optLoc.isPresent()) { + ServerLocation loc = optLoc.get(); + com.sk89q.worldedit.util.Location pos = SpongeAdapter.adapt(loc, Vector3d.ZERO); + + result = we.handleBlockRightClick(player, pos, SpongeAdapter.adapt(event.targetSide())); + } + + result = we.handleRightClick(player) || result; + debouncer.setLastInteraction(player, result); + + if (result) { + event.setCancelled(true); + } + } + + @Listener + public void onPlayerQuit(ServerSideConnectionEvent.Disconnect event) { + event.profile().ifPresent(profile -> { + debouncer.clearInteraction(profile::uniqueId); + + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new SpongePlayer.SessionKeyImpl(profile.uniqueId(), profile.name().orElseThrow()))); + }); + } + + public PluginContainer getPluginContainer() { + return container; + } + + /** + * Get the configuration. + * + * @return the Sponge configuration + */ + SpongeConfiguration getConfig() { + return this.config; + } + + public Actor wrapCommandCause(CommandCause cause) { + Object rootCause = cause.root(); + if (rootCause instanceof ServerPlayer) { + return SpongeAdapter.adapt((ServerPlayer) rootCause); + } + if (rootCause instanceof LocatableBlock locatableBlock) { + Optional optionalBlockEntity = locatableBlock.world().blockEntity(locatableBlock.blockPosition()); + if (optionalBlockEntity.isPresent()) { + BlockEntity blockEntity = optionalBlockEntity.get(); + if (blockEntity instanceof CommandBlock commandBlock) { + return new SpongeBlockCommandSender(this, commandBlock); + } + } + } + if (rootCause instanceof Audience) { + return new SpongeCommandSender((Audience) rootCause); + } + + throw new UnsupportedOperationException("Cannot wrap " + rootCause.getClass()); + } + + + /** + * Get the WorldEdit proxy for the platform. + * + * @return the WorldEdit platform + */ + public Platform getPlatform() { + return this.platform; + } + + SpongePlatform getInternalPlatform() { + return this.platform; + } + + /** + * Get the working directory where WorldEdit's files are stored. + * + * @return the working directory + */ + public Path getWorkingDir() { + return this.workingDir; + } + + /** + * Get the version of the WorldEdit Sponge implementation. + * + * @return a version string + */ + String getInternalVersion() { + return container.metadata().version().toString(); + } + + public void setPermissionsProvider(SpongePermissionsProvider provider) { + this.provider = provider; + } + + public SpongePermissionsProvider getPermissionsProvider() { + return provider; + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/ThreadSafeCache.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/ThreadSafeCache.java new file mode 100644 index 000000000..b6f131444 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/ThreadSafeCache.java @@ -0,0 +1,63 @@ +/* + * 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.sponge; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Caches data that cannot be accessed from another thread safely. + */ +public class ThreadSafeCache implements Runnable { + + private static final ThreadSafeCache INSTANCE = new ThreadSafeCache(); + private Set onlineIds = new CopyOnWriteArraySet<>(); + + /** + * Get an concurrent-safe set of UUIDs of online players. + * + * @return a set of UUIDs + */ + public Set getOnlineIds() { + return onlineIds; + } + + @Override + public void run() { + List onlineIds = new ArrayList<>(); + + for (ServerPlayer player : Sponge.server().onlinePlayers()) { + onlineIds.add(player.uniqueId()); + } + + this.onlineIds = new CopyOnWriteArraySet<>(onlineIds); + } + + public static ThreadSafeCache getInstance() { + return INSTANCE; + } + +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java new file mode 100644 index 000000000..b9f043752 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java @@ -0,0 +1,149 @@ +/* + * 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.sponge.config; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.session.SessionManager; +import com.sk89q.worldedit.util.report.Unreported; +import com.sk89q.worldedit.world.registry.LegacyMapper; +import org.apache.logging.log4j.Logger; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.loader.ConfigurationLoader; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Locale; + +public class ConfigurateConfiguration extends LocalConfiguration { + + @Unreported + protected final ConfigurationLoader config; + @Unreported + protected final Logger logger; + + @Unreported + protected CommentedConfigurationNode node; + + public ConfigurateConfiguration(ConfigurationLoader config, Logger logger) { + this.config = config; + this.logger = logger; + } + + @Override + public void load() { + try { + ConfigurationOptions options = ConfigurationOptions.defaults(); + options = options.shouldCopyDefaults(true); + + node = config.load(options); + } catch (IOException e) { + logger.warn("Error loading WorldEdit configuration", e); + } + + profile = node.node("debug").getBoolean(profile); + traceUnflushedSessions = node.node("debugging", "trace-unflushed-sessions").getBoolean(traceUnflushedSessions); + wandItem = node.node("wand-item").getString(wandItem).toLowerCase(Locale.ROOT); + try { + wandItem = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(wandItem)).id(); + } catch (Throwable ignored) { + } + + defaultChangeLimit = Math.max(-1, node.node("limits", "max-blocks-changed", "default").getInt(defaultChangeLimit)); + maxChangeLimit = Math.max(-1, node.node("limits", "max-blocks-changed", "maximum").getInt(maxChangeLimit)); + + defaultVerticalHeight = Math.max(1, node.node("limits", "vertical-height", "default").getInt(defaultVerticalHeight)); + + defaultMaxPolygonalPoints = Math.max(-1, node.node("limits", "max-polygonal-points", "default").getInt(defaultMaxPolygonalPoints)); + maxPolygonalPoints = Math.max(-1, node.node("limits", "max-polygonal-points", "maximum").getInt(maxPolygonalPoints)); + + maxRadius = Math.max(-1, node.node("limits", "max-radius").getInt(maxRadius)); + maxBrushRadius = node.node("limits", "max-brush-radius").getInt(maxBrushRadius); + maxSuperPickaxeSize = Math.max(1, node.node("limits", "max-super-pickaxe-size").getInt(maxSuperPickaxeSize)); + + butcherDefaultRadius = Math.max(-1, node.node("limits", "butcher-radius", "default").getInt(butcherDefaultRadius)); + butcherMaxRadius = Math.max(-1, node.node("limits", "butcher-radius", "maximum").getInt(butcherMaxRadius)); + + try { + disallowedBlocks = new HashSet<>( + node.node("limits", "disallowed-blocks").getList( + String.class, + ImmutableList.copyOf(getDefaultDisallowedBlocks()) + ) + ); + } catch (SerializationException e) { + logger.warn("Error loading WorldEdit configuration", e); + } + try { + allowedDataCycleBlocks = new HashSet<>( + node.node("limits", "allowed-data-cycle-blocks").getList(String.class, ImmutableList.of()) + ); + } catch (SerializationException e) { + logger.warn("Error loading WorldEdit configuration", e); + } + + registerHelp = node.node("register-help").getBoolean(true); + logCommands = node.node("logging", "log-commands").getBoolean(logCommands); + logFile = node.node("logging", "file").getString(logFile); + logFormat = node.node("logging", "format").getString(logFormat); + + superPickaxeDrop = node.node("super-pickaxe", "drop-items").getBoolean(superPickaxeDrop); + superPickaxeManyDrop = node.node("super-pickaxe", "many-drop-items").getBoolean(superPickaxeManyDrop); + + useInventory = node.node("use-inventory", "enable").getBoolean(useInventory); + useInventoryOverride = node.node("use-inventory", "allow-override").getBoolean(useInventoryOverride); + useInventoryCreativeOverride = node.node("use-inventory", "creative-mode-overrides").getBoolean(useInventoryCreativeOverride); + + navigationWand = node.node("navigation-wand", "item").getString(navigationWand).toLowerCase(Locale.ROOT); + try { + navigationWand = LegacyMapper.getInstance().getItemFromLegacy(Integer.parseInt(navigationWand)).id(); + } catch (Throwable ignored) { + } + navigationWandMaxDistance = node.node("navigation-wand", "max-distance").getInt(navigationWandMaxDistance); + navigationUseGlass = node.node("navigation", "use-glass").getBoolean(navigationUseGlass); + + scriptTimeout = node.node("scripting", "timeout").getInt(scriptTimeout); + scriptsDir = node.node("scripting", "dir").getString(scriptsDir); + + saveDir = node.node("saving", "dir").getString(saveDir); + + allowSymlinks = node.node("files", "allow-symbolic-links").getBoolean(false); + LocalSession.MAX_HISTORY_SIZE = Math.max(0, node.node("history", "size").getInt(15)); + SessionManager.EXPIRATION_GRACE = node.node("history", "expiration").getInt(10) * 60 * 1000; + + showHelpInfo = node.node("show-help-on-first-use").getBoolean(true); + serverSideCUI = node.node("server-side-cui").getBoolean(true); + + String snapshotsDir = node.node("snapshots", "directory").getString(""); + boolean experimentalSnapshots = node.node("snapshots", "experimental").getBoolean(false); + initializeSnapshotConfiguration(snapshotsDir, experimentalSnapshots); + + String type = node.node("shell-save-type").getString("").trim(); + shellSaveType = type.isEmpty() ? null : type; + + extendedYLimit = node.node("compat", "extended-y-limit").getBoolean(false); + setDefaultLocaleName(node.node("default-locale").getString(defaultLocaleName)); + + commandBlockSupport = node.node("command-block-support").getBoolean(false); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/SpongeConfiguration.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/SpongeConfiguration.java new file mode 100644 index 000000000..bb58a5d8c --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/SpongeConfiguration.java @@ -0,0 +1,62 @@ +/* + * 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.sponge.config; + +import com.google.inject.Inject; +import com.sk89q.worldedit.sponge.SpongeWorldEdit; +import org.apache.logging.log4j.Logger; +import org.spongepowered.api.config.DefaultConfig; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.loader.ConfigurationLoader; + +import java.io.IOException; +import java.nio.file.Path; + +public class SpongeConfiguration extends ConfigurateConfiguration { + + public boolean creativeEnable = false; + public boolean cheatMode = false; + + @Inject + public SpongeConfiguration(@DefaultConfig(sharedRoot = false) + ConfigurationLoader config, + Logger logger) { + super(config, logger); + } + + @Override + public void load() { + super.load(); + + creativeEnable = node.node("use-in-creative").getBoolean(false); + cheatMode = node.node("cheat-mode").getBoolean(false); + + try { + config.save(node); + } catch (IOException e) { + logger.warn("Error loading WorldEdit configuration", e); + } + } + + @Override + public Path getWorkingDirectoryPath() { + return SpongeWorldEdit.inst().getWorkingDir(); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/ExtendedChunk.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/ExtendedChunk.java new file mode 100644 index 000000000..47e0451e2 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/ExtendedChunk.java @@ -0,0 +1,43 @@ +/* + * 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.sponge.internal; + +import com.sk89q.worldedit.util.SideEffect; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; + +import javax.annotation.Nullable; + +public interface ExtendedChunk { + /** + * {@link LevelChunk#setBlockState(BlockPos, BlockState, boolean)} with the extra + * {@link SideEffect#UPDATE} flag. + * + * @param pos the position to set + * @param state the state to set + * @param moved I honestly have no idea and can't be bothered to investigate, we pass {@code + * false} + * @param update the update flag, see side-effect for details + * @return the old block state, or {@code null} if unchanged + */ + @Nullable + BlockState setBlockState(BlockPos pos, BlockState state, boolean moved, boolean update); +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/LocaleResolver.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/LocaleResolver.java new file mode 100644 index 000000000..4f7f4c8a5 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/LocaleResolver.java @@ -0,0 +1,35 @@ +/* + * 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.sponge.internal; + +import com.sk89q.worldedit.WorldEdit; +import net.kyori.adventure.audience.Audience; +import org.spongepowered.api.util.locale.LocaleSource; + +import java.util.Locale; + +public class LocaleResolver { + public static Locale resolveLocale(Audience audience) { + if (audience instanceof LocaleSource) { + return ((LocaleSource) audience).locale(); + } + return WorldEdit.getInstance().getConfiguration().defaultLocale; + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/NbtAdapter.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/NbtAdapter.java new file mode 100644 index 000000000..2b7fe5f77 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/NbtAdapter.java @@ -0,0 +1,272 @@ +/* + * 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.sponge.internal; + +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import org.enginehub.linbus.tree.LinByteArrayTag; +import org.enginehub.linbus.tree.LinByteTag; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinDoubleTag; +import org.enginehub.linbus.tree.LinFloatTag; +import org.enginehub.linbus.tree.LinIntArrayTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinListTag; +import org.enginehub.linbus.tree.LinLongArrayTag; +import org.enginehub.linbus.tree.LinLongTag; +import org.enginehub.linbus.tree.LinShortTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTag; +import org.enginehub.linbus.tree.LinTagType; +import org.spongepowered.api.data.persistence.DataContainer; +import org.spongepowered.api.data.persistence.DataQuery; +import org.spongepowered.api.data.persistence.DataSerializable; +import org.spongepowered.api.data.persistence.DataView; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class NbtAdapter { + /** + * A separator to introduce errors if there is something to be separated. We should only see + * single-part keys. + */ + private static final String BREAKING_SEPARATOR = "if you see this, something is wrong"; + + public static LinCompoundTag adaptToWorldEdit(DataView view) { + LinCompoundTag.Builder builder = LinCompoundTag.builder(); + for (Map.Entry entry : view.values(false).entrySet()) { + builder.put( + entry.getKey().asString(BREAKING_SEPARATOR), + adaptUnknownToWorldEdit(entry.getValue()) + ); + } + return builder.build(); + } + + private static LinTag adaptUnknownToWorldEdit(Object object) { + if (object instanceof DataView) { + return adaptToWorldEdit((DataView) object); + } + if (object instanceof Boolean) { + return LinByteTag.of((byte) ((Boolean) object ? 1 : 0)); + } + if (object instanceof Byte) { + return LinByteTag.of((Byte) object); + } + if (object instanceof Short) { + return LinShortTag.of(((Short) object)); + } + if (object instanceof Integer) { + return LinIntTag.of(((Integer) object)); + } + if (object instanceof Long) { + return LinLongTag.of(((Long) object)); + } + if (object instanceof Float) { + return LinFloatTag.of(((Float) object)); + } + if (object instanceof Double) { + return LinDoubleTag.of(((Double) object)); + } + if (object instanceof String) { + return LinStringTag.of((String) object); + } + if (object instanceof byte[]) { + return LinByteArrayTag.of(((byte[]) object)); + } + if (object instanceof Byte[] array) { + byte[] copy = new byte[array.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = array[i]; + } + return LinByteArrayTag.of(copy); + } + if (object instanceof int[]) { + return LinIntArrayTag.of(((int[]) object)); + } + if (object instanceof Integer[] array) { + int[] copy = new int[array.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = array[i]; + } + return LinIntArrayTag.of(copy); + } + if (object instanceof long[]) { + return LinLongArrayTag.of(((long[]) object)); + } + if (object instanceof Long[] array) { + long[] copy = new long[array.length]; + for (int i = 0; i < copy.length; i++) { + copy[i] = array[i]; + } + return LinLongArrayTag.of(copy); + } + if (object instanceof List objects) { + if (objects.isEmpty()) { + return LinListTag.empty(LinTagType.endTag()); + } + LinTag first = adaptUnknownToWorldEdit(objects.get(0)); + @SuppressWarnings("unchecked") + LinListTag.Builder> builder = LinListTag.builder((LinTagType>) first.type()); + builder.add(first); + for (int i = 1; i < objects.size(); i++) { + Object value = objects.get(i); + builder.add(adaptUnknownToWorldEdit(value)); + } + return builder.build(); + } + if (object instanceof Map) { + LinCompoundTag.Builder builder = LinCompoundTag.builder(); + for (Map.Entry entry : ((Map) object).entrySet()) { + String key = entry.getKey() instanceof DataQuery + ? ((DataQuery) entry.getKey()).asString(BREAKING_SEPARATOR) + : entry.getKey().toString(); + builder.put(key, adaptUnknownToWorldEdit(entry.getValue())); + } + return builder.build(); + } + if (object instanceof DataSerializable) { + return adaptToWorldEdit(((DataSerializable) object).toContainer()); + } + throw new UnsupportedOperationException("Unable to translate into NBT: " + object.getClass()); + } + + public static DataContainer adaptFromWorldEdit(LinCompoundTag tag) { + // copy to container, no cloning used because it's unlikely to leak + // and it's cheaper this way + DataContainer container = DataContainer.createNew(DataView.SafetyMode.NO_DATA_CLONED); + for (var entry : tag.value().entrySet()) { + container.set(DataQuery.of(entry.getKey()), adaptTagFromWorldEdit(entry.getValue())); + } + return container; + } + + private static Object adaptTagFromWorldEdit(LinTag value) { + if (value instanceof LinListTag listTag) { + return listTag.value().stream() + .map(NbtAdapter::adaptTagFromWorldEdit) + .collect(Collectors.toList()); + } + if (value instanceof LinCompoundTag compoundTag) { + return adaptFromWorldEdit(compoundTag); + } + // everything else is raw JDK types, so we can use it directly + return value.value(); + } + + public static Tag adaptNMSToWorldEdit(LinTag tag) { + if (tag instanceof LinIntArrayTag intArrayTag) { + return adaptNMSToWorldEdit(intArrayTag); + } else if (tag instanceof LinListTag listTag) { + return adaptNMSToWorldEdit(listTag); + } else if (tag instanceof LinLongTag longTag) { + return adaptNMSToWorldEdit(longTag); + } else if (tag instanceof LinLongArrayTag longArrayTag) { + return adaptNMSToWorldEdit(longArrayTag); + } else if (tag instanceof LinStringTag stringTag) { + return adaptNMSToWorldEdit(stringTag); + } else if (tag instanceof LinIntTag intTag) { + return adaptNMSToWorldEdit(intTag); + } else if (tag instanceof LinByteTag byteTag) { + return adaptNMSToWorldEdit(byteTag); + } else if (tag instanceof LinByteArrayTag byteArrayTag) { + return adaptNMSToWorldEdit(byteArrayTag); + } else if (tag instanceof LinCompoundTag compoundTag) { + return adaptNMSToWorldEdit(compoundTag); + } else if (tag instanceof LinFloatTag floatTag) { + return adaptNMSToWorldEdit(floatTag); + } else if (tag instanceof LinShortTag shortTag) { + return adaptNMSToWorldEdit(shortTag); + } else if (tag instanceof LinDoubleTag doubleTag) { + return adaptNMSToWorldEdit(doubleTag); + } else { + throw new IllegalArgumentException("Can't convert tag of type " + tag.getClass().getCanonicalName()); + } + } + + public static IntArrayTag adaptNMSToWorldEdit(LinIntArrayTag tag) { + return new IntArrayTag(tag.value()); + } + + public static ListTag adaptNMSToWorldEdit(LinListTag tag) { + ListTag list = new ListTag(); + for (LinTag child : tag.value()) { + list.add(adaptNMSToWorldEdit(child)); + } + return list; + } + + public static LongTag adaptNMSToWorldEdit(LinLongTag tag) { + return LongTag.valueOf(tag.valueAsLong()); + } + + public static LongArrayTag adaptNMSToWorldEdit(LinLongArrayTag tag) { + return new LongArrayTag(tag.value()); + } + + public static StringTag adaptNMSToWorldEdit(LinStringTag tag) { + return StringTag.valueOf(tag.value()); + } + + public static IntTag adaptNMSToWorldEdit(LinIntTag tag) { + return IntTag.valueOf(tag.valueAsInt()); + } + + public static ByteTag adaptNMSToWorldEdit(LinByteTag tag) { + return ByteTag.valueOf(tag.valueAsByte()); + } + + public static ByteArrayTag adaptNMSToWorldEdit(LinByteArrayTag tag) { + return new ByteArrayTag(tag.value()); + } + + public static CompoundTag adaptNMSToWorldEdit(LinCompoundTag tag) { + CompoundTag compound = new CompoundTag(); + for (var child : tag.value().entrySet()) { + compound.put(child.getKey(), adaptNMSToWorldEdit(child.getValue())); + } + return compound; + } + + public static FloatTag adaptNMSToWorldEdit(LinFloatTag tag) { + return FloatTag.valueOf(tag.valueAsFloat()); + } + + public static ShortTag adaptNMSToWorldEdit(LinShortTag tag) { + return ShortTag.valueOf(tag.valueAsShort()); + } + + public static DoubleTag adaptNMSToWorldEdit(LinDoubleTag tag) { + return DoubleTag.valueOf(tag.valueAsDouble()); + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeTransmogrifier.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeTransmogrifier.java new file mode 100644 index 000000000..0bedfe64a --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeTransmogrifier.java @@ -0,0 +1,210 @@ +/* + * 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.sponge.internal; + +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.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.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import net.minecraft.util.StringRepresentable; +import org.spongepowered.api.ResourceKey; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.registry.RegistryTypes; +import org.spongepowered.api.state.StateProperty; + +import java.util.Comparator; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +/** + * Raw, un-cached transformations. + */ +public class SpongeTransmogrifier { + + private static final LoadingCache, Property> PROPERTY_CACHE = CacheBuilder.newBuilder().build(new CacheLoader<>() { + @Override + public Property load(StateProperty property) { + net.minecraft.world.level.block.state.properties.Property nativeProperty = + (net.minecraft.world.level.block.state.properties.Property) property; + String propertyName = nativeProperty.getName(); + if (nativeProperty instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) { + return new BooleanProperty(propertyName, + ImmutableList.copyOf(((net.minecraft.world.level.block.state.properties.BooleanProperty) nativeProperty).getPossibleValues())); + } + if (nativeProperty instanceof net.minecraft.world.level.block.state.properties.IntegerProperty) { + return new IntegerProperty(propertyName, + ImmutableList.copyOf(((net.minecraft.world.level.block.state.properties.IntegerProperty) nativeProperty).getPossibleValues())); + } + if (isDirectionProperty(nativeProperty)) { + return new DirectionalProperty(propertyName, + ((net.minecraft.world.level.block.state.properties.EnumProperty) nativeProperty).getPossibleValues().stream() + .map(x -> adaptDirection((net.minecraft.core.Direction) x)) + .toList() + ); + } + if (nativeProperty instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + return new EnumProperty(propertyName, + ((net.minecraft.world.level.block.state.properties.EnumProperty) nativeProperty).getPossibleValues().stream() + .map(StringRepresentable::getSerializedName) + .toList()); + } + throw new IllegalStateException("Unknown property type"); + } + }); + + public static Property transmogToWorldEditProperty(StateProperty property) { + return PROPERTY_CACHE.getUnchecked(property); + } + + private static Map, Object> transmogToWorldEditProperties( + BlockType block, + net.minecraft.world.level.block.state.BlockState blockState + ) { + Map, Object> properties = new TreeMap<>(Comparator.comparing(Property::getName)); + for (net.minecraft.world.level.block.state.properties.Property nativeProperty: blockState.getProperties()) { + Object value = blockState.getValue(nativeProperty); + if (isDirectionProperty(nativeProperty)) { + net.minecraft.core.Direction nativeDirectionValue = (net.minecraft.core.Direction) value; + value = adaptDirection(nativeDirectionValue); + } else if (nativeProperty instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + value = ((StringRepresentable) value).getSerializedName(); + } + properties.put(block.getProperty(nativeProperty.getName()), value); + } + return properties; + } + + private static boolean isDirectionProperty(net.minecraft.world.level.block.state.properties.Property property) { + return property instanceof net.minecraft.world.level.block.state.properties.EnumProperty + && property.getValueClass().isAssignableFrom(net.minecraft.core.Direction.class); + } + + private static Direction adaptDirection(net.minecraft.core.Direction direction) { + switch (direction) { + case UP: + return Direction.UP; + case DOWN: + return Direction.DOWN; + case EAST: + return Direction.EAST; + case WEST: + return Direction.WEST; + case NORTH: + return Direction.NORTH; + case SOUTH: + return Direction.SOUTH; + default: + throw new AssertionError("New direction added: " + direction); + } + } + + private static net.minecraft.core.Direction adaptDirection(Direction direction) { + switch (direction) { + case UP: + return net.minecraft.core.Direction.UP; + case DOWN: + return net.minecraft.core.Direction.DOWN; + case EAST: + return net.minecraft.core.Direction.EAST; + case WEST: + return net.minecraft.core.Direction.WEST; + case NORTH: + return net.minecraft.core.Direction.NORTH; + case SOUTH: + return net.minecraft.core.Direction.SOUTH; + default: + throw new AssertionError("New direction added: " + direction); + } + } + + private static net.minecraft.world.level.block.state.properties.Property findPropertyByName( + net.minecraft.world.level.block.state.BlockState blockState, + String propertyName + ) { + for (net.minecraft.world.level.block.state.properties.Property property: blockState.getProperties()) { + if (property.getName().equals(propertyName)) { + return property; + } + } + + throw new IllegalStateException("Missing property in " + blockState.getBlock() + ": " + propertyName); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static org.spongepowered.api.block.BlockState transmogToMinecraftProperties( + org.spongepowered.api.block.BlockState blockState, + Map, Object> states + ) { + net.minecraft.world.level.block.state.BlockState nativeBlockState = + (net.minecraft.world.level.block.state.BlockState) blockState; + for (Map.Entry, Object> stateEntry: states.entrySet()) { + Property property = stateEntry.getKey(); + Object value = stateEntry.getValue(); + net.minecraft.world.level.block.state.properties.Property nativeProperty = + findPropertyByName(nativeBlockState, property.getName()); + Comparable nativeValue; + if (property instanceof DirectionalProperty) { + Direction directionValue = (Direction) value; + nativeValue = adaptDirection(directionValue); + } else if (property instanceof EnumProperty) { + String valueName = (String) value; + Optional> nativeValueOpt = nativeProperty.getValue(valueName); + if (nativeValueOpt.isEmpty()) { + throw new IllegalStateException("Failed to parse " + valueName + " into " + property.getName()); + } + nativeValue = nativeValueOpt.get(); + } else { + nativeValue = (Comparable) value; + } + nativeBlockState = nativeBlockState.setValue( + (net.minecraft.world.level.block.state.properties.Property) nativeProperty, (Comparable) nativeValue); + } + + return (org.spongepowered.api.block.BlockState) nativeBlockState; + } + + public static org.spongepowered.api.block.BlockState transmogToMinecraft(BlockState blockState) { + org.spongepowered.api.block.BlockType mcBlock = Sponge.game().registry(RegistryTypes.BLOCK_TYPE) + .value(ResourceKey.resolve(blockState.getBlockType().id())); + org.spongepowered.api.block.BlockState newState = mcBlock.defaultState(); + Map, Object> states = blockState.getStates(); + return transmogToMinecraftProperties(newState, states); + } + + public static BlockState transmogToWorldEdit(org.spongepowered.api.block.BlockState blockState) { + BlockType blockType = BlockType.REGISTRY.get( + blockState.type().key(RegistryTypes.BLOCK_TYPE).asString() + ); + return blockType.getState(transmogToWorldEditProperties(blockType, + (net.minecraft.world.level.block.state.BlockState) blockState)); + } + + private SpongeTransmogrifier() { + } +} diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java new file mode 100644 index 000000000..f83140475 --- /dev/null +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java @@ -0,0 +1,158 @@ +/* + * 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.sponge.internal; + +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.sponge.SpongeAdapter; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import javax.annotation.Nullable; + +public class SpongeWorldNativeAccess implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public SpongeWorldNativeAccess(WeakReference world) { + 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 BlockState toNative(com.sk89q.worldedit.world.block.BlockState state) { + return (BlockState) SpongeAdapter.adapt(state); + } + + @Override + public BlockState getBlockState(LevelChunk chunk, BlockPos position) { + return chunk.getBlockState(position); + } + + @Nullable + @Override + public BlockState setBlockState(LevelChunk chunk, BlockPos position, BlockState state) { + if (chunk instanceof ExtendedChunk) { + return ((ExtendedChunk) chunk).setBlockState( + position, state, false, sideEffectSet.shouldApply(SideEffect.UPDATE) + ); + } + return chunk.setBlockState(position, state, false); + } + + @Override + public BlockState getValidBlockForPosition(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(BlockPos position, LinCompoundTag tag) { + CompoundTag nativeTag = NbtAdapter.adaptNMSToWorldEdit(tag); + BlockEntity tileEntity = getWorld().getChunk(position).getBlockEntity(position); + if (tileEntity == null) { + return false; + } + tileEntity.setLevel(getWorld()); + tileEntity.loadWithComponents(nativeTag, getWorld().registryAccess()); + return true; + } + + @Override + public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, BlockState oldState, 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(FullChunkStatus.BLOCK_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, BlockState oldState, BlockState newState) { + getWorld().updateNeighborsAt(pos, oldState.getBlock()); + if (newState.hasAnalogOutputSignal()) { + getWorld().updateNeighbourForOutputSignal(pos, newState.getBlock()); + } + } + + @Override + public void updateBlock(BlockPos pos, BlockState oldState, BlockState newState) { + ServerLevel world = getWorld(); + newState.onPlace(world, pos, oldState, false); + } + + @Override + public void updateNeighbors(BlockPos pos, BlockState oldState, BlockState newState, int recursionLimit) { + ServerLevel world = getWorld(); + oldState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { + getWorld().onBlockStateChange(pos, oldState, newState); + } +}