From 71fbf7f7b97c6fd895bd6199ecb89df6e6427313 Mon Sep 17 00:00:00 2001 From: iqtester Date: Mon, 14 Apr 2025 18:08:14 +0800 Subject: [PATCH] feat: Re-implement WorldEdit command and suggestion support in a proper way --- .../worldedit/WorldEditBlockRegister.java | 98 ++++++++++++++++++- .../bukkit/block/BukkitBlockManager.java | 26 ++--- .../bukkit/block/WorldEditCommandHelper.java | 85 ---------------- 3 files changed, 110 insertions(+), 99 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index 6905c77c7..3f3766162 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -1,24 +1,116 @@ package net.momirealms.craftengine.bukkit.compatibility.worldedit; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitBlockRegistry; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.momirealms.craftengine.core.block.AbstractBlockManager; +import net.momirealms.craftengine.core.block.BlockStateParser; +import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Material; import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; public class WorldEditBlockRegister { - private static final Field field$BlockType$blockMaterial; + private final Field field$BlockType$blockMaterial; + private final AbstractBlockManager manager; + private final Set cachedSuggestions = new HashSet<>(); - static { + public WorldEditBlockRegister(AbstractBlockManager manager) { field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); + this.manager = manager; } - public static void register(Key id) throws ReflectiveOperationException { + public void enable() { + CEBlockParser blockParser = new CEBlockParser(WorldEdit.getInstance()); + WorldEdit.getInstance().getBlockFactory().register(blockParser); + } + + public void load() { + Collection cachedSuggestions = manager.cachedSuggestions(); + for (Object o : cachedSuggestions) { + this.cachedSuggestions.add(o.toString()); + } + } + + public void unload() { + cachedSuggestions.clear(); + } + + public void register(Key id) throws ReflectiveOperationException { BlockType blockType = new BlockType(id.toString(), blockState -> blockState); field$BlockType$blockMaterial.set(blockType, LazyReference.from(() -> new BukkitBlockRegistry.BukkitBlockMaterial(null, Material.STONE))); BlockType.REGISTRY.register(id.toString(), blockType); } + + private class CEBlockParser extends InputParser { + + protected CEBlockParser(WorldEdit worldEdit) { + super(worldEdit); + } + + @Override + public Stream getSuggestions(String input) { + Set namespacesInUse = manager.namespacesInUse(); + + if (input.isEmpty() || input.equals(":")) { + return namespacesInUse.stream().map(namespace -> namespace + ":"); + } + + if (input.startsWith(":")) { + String term = input.substring(1).toLowerCase(); + return cachedSuggestions.stream().filter(s -> s.toLowerCase().contains(term)); + } + + if (!input.contains(":")) { + String lowerSearch = input.toLowerCase(); + return Stream.concat( + namespacesInUse.stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":"), + cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(lowerSearch)) + ); + } + return cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(input.toLowerCase())); + } + + @Override + public BaseBlock parseFromInput(String input, ParserContext context) { + int colonIndex = input.indexOf(':'); + if (colonIndex == -1) return null; + + Set namespacesInUse = manager.namespacesInUse(); + String namespace = input.substring(0, colonIndex); + if (!namespacesInUse.contains(namespace)) return null; + + ImmutableBlockState state = BlockStateParser.deserialize(input); + if (state == null) return null; + + try { + String id = state.customBlockState().handle().toString(); + int first = id.indexOf('{'); + int last = id.indexOf('}'); + if (first != -1 && last != -1 && last > first) { + String blockId = id.substring(first + 1, last); + BlockType blockType = BlockTypes.get(blockId); + if (blockType == null) { + return null; + } + return blockType.getDefaultState().toBaseBlock(); + } else { + throw new IllegalArgumentException("Invalid block ID format: " + id); + } + } catch (NullPointerException e) { + return null; + } + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 01b2c7495..894478dae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -87,7 +87,8 @@ public class BukkitBlockManager extends AbstractBlockManager { // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - private WorldEditCommandHelper weCommandHelper; + // WE support + private WorldEditBlockRegister weBlockRegister; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); @@ -128,18 +129,11 @@ public class BukkitBlockManager extends AbstractBlockManager { if (this.fallingBlockRemoveListener != null) { Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, plugin.bootstrap()); } - boolean hasWE = false; // WorldEdit if (this.plugin.isPluginEnabled("FastAsyncWorldEdit")) { this.initFastAsyncWorldEditHook(); - hasWE = true; } else if (this.plugin.isPluginEnabled("WorldEdit")) { this.initWorldEditHook(); - hasWE = true; - } - if (hasWE) { - this.weCommandHelper = new WorldEditCommandHelper(this.plugin, this); - this.weCommandHelper.enable(); } } @@ -152,6 +146,8 @@ public class BukkitBlockManager extends AbstractBlockManager { this.modBlockStates.clear(); if (EmptyBlock.INSTANCE != null) Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); + if (weBlockRegister != null) + weBlockRegister.unload(); } @Override @@ -159,7 +155,6 @@ public class BukkitBlockManager extends AbstractBlockManager { this.unload(); HandlerList.unregisterAll(this.blockEventListener); if (this.fallingBlockRemoveListener != null) HandlerList.unregisterAll(this.fallingBlockRemoveListener); - if (this.weCommandHelper != null) this.weCommandHelper.disable(); } @Override @@ -172,6 +167,7 @@ public class BukkitBlockManager extends AbstractBlockManager { initSuggestions(); resetPacketConsumers(); clearCache(); + loadWorldEditRegister(); } private void clearCache() { @@ -181,19 +177,27 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - // do nothing + this.weBlockRegister = new WorldEditBlockRegister(this); + this.weBlockRegister.enable(); } public void initWorldEditHook() { + this.weBlockRegister = new WorldEditBlockRegister(this); + this.weBlockRegister.enable(); try { for (Key newBlockId : this.blockRegisterOrder) { - WorldEditBlockRegister.register(newBlockId); + weBlockRegister.register(newBlockId); } } catch (Exception e) { this.plugin.logger().warn("Failed to initialize world edit hook", e); } } + public void loadWorldEditRegister() { + if (this.weBlockRegister != null) + this.weBlockRegister.load(); + } + @Nullable public Object getMinecraftBlockHolder(int stateId) { return stateId2BlockHolder.get(stateId); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java deleted file mode 100644 index 88839c776..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.momirealms.craftengine.bukkit.block; - -import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.core.block.BlockStateParser; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -// TODO A better command suggestion system -public class WorldEditCommandHelper implements Listener { - private final BukkitBlockManager manager; - private final BukkitCraftEngine plugin; - - public WorldEditCommandHelper(BukkitCraftEngine plugin, BukkitBlockManager manager) { - this.plugin = plugin; - this.manager = manager; - } - - public void enable() { - Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); - } - - public void disable() { - HandlerList.unregisterAll(this); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { - String message = event.getMessage(); - if (!message.startsWith("//")) return; - - Set cachedNamespaces = manager.namespacesInUse(); - String[] args = message.split(" "); - boolean modified = false; - - for (int i = 1; i < args.length; i++) { - String[] parts = args[i].split(","); - List processedParts = new ArrayList<>(parts.length); - boolean partModified = false; - - for (String part : parts) { - String processed = processIdentifier(part, cachedNamespaces); - partModified |= !part.equals(processed); - processedParts.add(processed); - } - - if (partModified) { - args[i] = String.join(",", processedParts); - modified = true; - } - } - - if (modified) { - event.setMessage(String.join(" ", args)); - } - } - - private String processIdentifier(String identifier, Set cachedNamespaces) { - int colonIndex = identifier.indexOf(':'); - if (colonIndex == -1) return identifier; - - String namespace = identifier.substring(0, colonIndex); - if (!cachedNamespaces.contains(namespace)) return identifier; - - ImmutableBlockState state = BlockStateParser.deserialize(identifier); - if (state == null) return identifier; - - try { - return BlockStateUtils.getBlockOwnerIdFromState( - state.customBlockState().handle() - ).toString(); - } catch (NullPointerException e) { - return identifier; - } - } -}