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
This commit is contained in:
Ossi Erkkilä 2024-06-01 08:30:00 +03:00 committed by GitHub
parent 05db94cfac
commit ad0362367a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 4235 additions and 2 deletions

View File

@ -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") {

View File

@ -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"

View File

@ -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")
}

View File

@ -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")

View File

@ -0,0 +1,3 @@
plugins {
id("buildlogic.libs")
}

View File

@ -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<BasePluginExtension> {
archivesName.set("${project.name}-api$spongeApiVersion")
}
tasks.named<ShadowJar>("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")
}

View File

@ -0,0 +1,67 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<ServerConnectionState.Game> {
public static final ResourceKey CUI_PLUGIN_CHANNEL = ResourceKey.of("worldedit", "cui");
private static final SimpleLifecycled<RawDataChannel> 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
);
}
}

View File

@ -0,0 +1,81 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<Component> shortDescription(CommandCause cause) {
return Optional.of(command.getDescription())
.map(desc -> SpongeTextAdapter.convert(desc, LocaleResolver.resolveLocale(cause.audience())));
}
@Override
public Optional<Component> extendedDescription(CommandCause cause) {
return command.getFooter()
.map(footer -> SpongeTextAdapter.convert(footer, LocaleResolver.resolveLocale(cause.audience())));
}
@Override
public Optional<Component> 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()));
}
}

View File

@ -0,0 +1,39 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.sponge;
import org.spongepowered.api.data.persistence.DataQuery;
/**
* Kinda mirrors Sponge Common's Constants class.
*
* <p>Internal. Do not use.</p>
*/
public class Constants {
public static class Sponge {
public static final DataQuery UNSAFE_NBT = DataQuery.of("UnsafeData");
private Sponge() {
}
}
private Constants() {
}
}

View File

@ -0,0 +1,249 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<Biome> 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() {
}
}

View File

@ -0,0 +1,71 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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> biome;
/**
* Create a new instance.
*
* @param biome the base biome
*/
private SpongeBiomeData(RegistryReference<Biome> biome) {
this.biome = biome;
}
@SuppressWarnings("deprecation")
@Override
public String getName() {
return biome.location().asString();
}
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<BlockType> getCategorisedByName(String category) {
Registry<org.spongepowered.api.block.BlockType> 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());
}
}

View File

@ -0,0 +1,183 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
};
}
}

View File

@ -0,0 +1,96 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -0,0 +1,91 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<org.spongepowered.api.block.BlockState, SpongeBlockMaterial> 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<String, ? extends Property<?>> getProperties(BlockType blockType) {
org.spongepowered.api.block.BlockType spongeBlockType =
Sponge.game().registry(RegistryTypes.BLOCK_TYPE)
.value(ResourceKey.resolve(blockType.id()));
Map<String, Property<?>> map = new TreeMap<>();
Collection<StateProperty<?>> 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
));
}
}

View File

@ -0,0 +1,172 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
};
}
}

View File

@ -0,0 +1,125 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<org.spongepowered.api.entity.Entity> 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> T getFacet(Class<? extends T> 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;
}
}
}

View File

@ -0,0 +1,155 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<ItemType> getCategorisedByName(String category) {
Registry<org.spongepowered.api.item.ItemType> 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());
}
}

View File

@ -0,0 +1,47 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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()
);
}
}

View File

@ -0,0 +1,47 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,200 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<? extends com.sk89q.worldedit.world.World> getWorlds() {
Collection<ServerWorld> worlds = Sponge.server().worldManager().worlds();
List<com.sk89q.worldedit.world.World> 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<ServerPlayer> 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<Capability, Preference> getCapabilities() {
Map<Capability, Preference> 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<SideEffect> 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<Actor> getConnectedUsers() {
return Sponge.server().onlinePlayers().stream().map(SpongePlayer::new).collect(toList());
}
}

View File

@ -0,0 +1,313 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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> T getFacet(Class<? extends T> 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 <B extends BlockStateHolder<B>> 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;
}
}

View File

@ -0,0 +1,70 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,45 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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() {
}
}

View File

@ -0,0 +1,507 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<ServerWorld> 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 <B extends BlockStateHolder<B>> 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)
.<BlockEntityType>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<SideEffect> 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<ConfiguredFeature<?, ?>> 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<? extends Entity> 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<? extends Entity> getEntities() {
return getWorld().entities().stream()
.map(SpongeEntity::new)
.collect(Collectors.toList());
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
Optional<EntityType<?>> 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());
}
}

View File

@ -0,0 +1,487 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<Server> event) {
final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME);
if (Files.exists(delChunks)) {
ChunkDeleter.runFromFile(delChunks, true);
}
}
@Listener
public void serverStarted(StartedEngineEvent<Server> 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<Server> event) {
WorldEdit worldEdit = WorldEdit.getInstance();
worldEdit.getSessionManager().unload();
WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform));
}
@Listener
public void registerCommand(RegisterCommandEvent<Command.Raw> 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<org.enginehub.piston.Command> commands = manager.getPlatformCommandManager().getCommandManager()
.getAllCommands().toList();
for (org.enginehub.piston.Command command : commands) {
registerAdaptedCommand(event, command);
Set<String> 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<Command.Raw> 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<CommandCompletion> 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<Boolean> 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<Boolean> 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<ServerLocation> 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<ServerLocation> 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<? extends BlockEntity> 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;
}
}

View File

@ -0,0 +1,63 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<UUID> onlineIds = new CopyOnWriteArraySet<>();
/**
* Get an concurrent-safe set of UUIDs of online players.
*
* @return a set of UUIDs
*/
public Set<UUID> getOnlineIds() {
return onlineIds;
}
@Override
public void run() {
List<UUID> onlineIds = new ArrayList<>();
for (ServerPlayer player : Sponge.server().onlinePlayers()) {
onlineIds.add(player.uniqueId());
}
this.onlineIds = new CopyOnWriteArraySet<>(onlineIds);
}
public static ThreadSafeCache getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,149 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<CommentedConfigurationNode> config;
@Unreported
protected final Logger logger;
@Unreported
protected CommentedConfigurationNode node;
public ConfigurateConfiguration(ConfigurationLoader<CommentedConfigurationNode> 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);
}
}

View File

@ -0,0 +1,62 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<CommentedConfigurationNode> 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();
}
}

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -0,0 +1,35 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,272 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<DataQuery, Object> 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<LinTag<?>> builder = LinListTag.builder((LinTagType<LinTag<?>>) 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());
}
}

View File

@ -0,0 +1,210 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<StateProperty<?>, 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<Property<?>, Object> transmogToWorldEditProperties(
BlockType block,
net.minecraft.world.level.block.state.BlockState blockState
) {
Map<Property<?>, 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<Property<?>, Object> states
) {
net.minecraft.world.level.block.state.BlockState nativeBlockState =
(net.minecraft.world.level.block.state.BlockState) blockState;
for (Map.Entry<Property<?>, 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<? extends Comparable<?>> 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<Property<?>, 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() {
}
}

View File

@ -0,0 +1,158 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <https://www.gnu.org/licenses/>.
*/
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<LevelChunk, BlockState, BlockPos> {
private static final int UPDATE = 1;
private static final int NOTIFY = 2;
private final WeakReference<ServerLevel> world;
private SideEffectSet sideEffectSet;
public SpongeWorldNativeAccess(WeakReference<ServerLevel> 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);
}
}