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 330f9fb6e..46b5e204e 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 @@ -7,7 +7,8 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import dev.dejvokep.boostedyaml.YamlDocument; -import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditHook; +import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditCommandHelper; +import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditBlockRegister; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; @@ -86,11 +87,13 @@ public class BukkitBlockManager extends AbstractBlockManager { // Cached command suggestions private final List cachedSuggestions = new ArrayList<>(); // Cached Namespace - private final Set cachedNamespaces = new HashSet<>(); + private final Set namespacesInUse = new HashSet<>(); // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; + private WorldEditCommandHelper weCommandHelper; + public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); this.plugin = plugin; @@ -128,6 +131,19 @@ 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(); + } } @Override @@ -148,6 +164,7 @@ 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 @@ -175,7 +192,7 @@ public class BukkitBlockManager extends AbstractBlockManager { public void initWorldEditHook() { try { for (Key newBlockId : this.blockRegisterOrder) { - WorldEditHook.register(newBlockId); + WorldEditBlockRegister.register(newBlockId); } } catch (Exception e) { this.plugin.logger().warn("Failed to initialize world edit hook", e); @@ -228,9 +245,11 @@ public class BukkitBlockManager extends AbstractBlockManager { @Override public void initSuggestions() { this.cachedSuggestions.clear(); + this.namespacesInUse.clear(); Set states = new HashSet<>(); for (CustomBlock block : this.id2CraftEngineBlocks.values()) { states.add(block.id().toString()); + this.namespacesInUse.add(block.id().namespace()); for (ImmutableBlockState state : block.variantProvider().states()) { states.add(state.toString()); } @@ -238,22 +257,10 @@ public class BukkitBlockManager extends AbstractBlockManager { for (String state : states) { this.cachedSuggestions.add(Suggestion.suggestion(state)); } - this.cachedNamespaces.clear(); - initCachedNamespaces(); } - private void initCachedNamespaces() { - for (Suggestion suggestion : this.cachedSuggestions) { - String id = suggestion.suggestion(); - int index = id.indexOf(':'); - if (index != -1) { - cachedNamespaces.add(id.substring(0, index)); - } - } - } - - public Collection cachedNamespaces() { - return Collections.unmodifiableSet(cachedNamespaces); + public Set namespacesInUse() { + return Collections.unmodifiableSet(namespacesInUse); } public ImmutableMap> blockAppearanceArranger() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/SuggestionHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/SuggestionHandler.java deleted file mode 100644 index da6cb7da2..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/SuggestionHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.worldedit; - -import java.util.Set; -import java.util.function.Predicate; - -public class SuggestionHandler { - private final Predicate matcher; - - private SuggestionHandler(Predicate matcher) { - this.matcher = matcher; - } - - public boolean matches(String input) { - return matcher.test(input); - } - - public static SuggestionHandler of(Integer... pos) { - Set valid = Set.of(pos); - return new SuggestionHandler(input -> { - if (input.contains(" ")) return false; - - String[] args = input.split(" "); - int index = input.endsWith(" ") ? args.length : args.length - 1; - return valid.contains(index); - }); - } - - public static SuggestionHandler custom(Predicate matcher) { - return new SuggestionHandler(matcher); - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditBlockRegister.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditBlockRegister.java new file mode 100644 index 000000000..279ada1c2 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditBlockRegister.java @@ -0,0 +1,24 @@ +package net.momirealms.craftengine.bukkit.block.worldedit; + +import com.sk89q.worldedit.bukkit.BukkitBlockRegistry; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.block.BlockType; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ReflectionUtils; +import org.bukkit.Material; + +import java.lang.reflect.Field; + +public class WorldEditBlockRegister { + private static final Field field$BlockType$blockMaterial; + + static { + field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); + } + + public static 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); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditCommandHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditCommandHelper.java new file mode 100644 index 000000000..31d3f331d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditCommandHelper.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.bukkit.block.worldedit; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +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.*; + +// 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; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditHook.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditHook.java deleted file mode 100644 index ae2f3198f..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/worldedit/WorldEditHook.java +++ /dev/null @@ -1,116 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.worldedit; - -import com.google.common.collect.ImmutableMap; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.bukkit.BukkitBlockRegistry; -import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; -import com.sk89q.worldedit.internal.util.Substring; -import com.sk89q.worldedit.util.concurrency.LazyReference; -import com.sk89q.worldedit.util.eventbus.Subscribe; -import com.sk89q.worldedit.world.block.BlockType; -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 net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.ReflectionUtils; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.incendo.cloud.suggestion.Suggestion; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class WorldEditHook implements Listener { - private static final Field field$BlockType$blockMaterial; - private final Map handlers = ImmutableMap.of( - "//set", SuggestionHandler.of(1), - "//replace", SuggestionHandler.of(1, 2) - ); - - static { - WorldEdit.getInstance().getEventBus().register(new WorldEditHook()); - field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); - } - - public static 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); - } - - public WorldEditHook() { - Bukkit.getPluginManager().registerEvents(this, BukkitCraftEngine.instance().bootstrap()); - } - - @Subscribe - public void onSuggestion(CommandSuggestionEvent event) { - String input = event.getArguments(); - String command = input.substring(0, input.indexOf(" ")); - - SuggestionHandler handler = handlers.get(command); - if (handler == null || !handler.matches(input)) return; - int start = input.lastIndexOf(" ") + 1; - int end = input.length(); - - if (start == end) { - List suggestions = BukkitCraftEngine.instance().blockManager().cachedNamespaces() - .stream() - .map(ns -> Substring.wrap(ns + ":", start, end)) - .collect(Collectors.toList()); - suggestions.addAll(event.getSuggestions()); - event.setSuggestions(suggestions); - return; - } - - String last = input.substring(start, end); - List suggestions = new ArrayList<>(); - for (Suggestion s : BukkitCraftEngine.instance().blockManager().cachedSuggestions()) { - String id = s.suggestion(); - if (id.startsWith(last)) - suggestions.add(Substring.wrap(id, start, end)); - } - suggestions.addAll(event.getSuggestions()); - event.setSuggestions(suggestions); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { - String message = event.getMessage(); - if (!message.startsWith("//")) return; - - Collection cachedNamespaces = BukkitCraftEngine.instance().blockManager().cachedNamespaces(); - - String[] args = message.split(" "); - boolean modified = false; - - for (int i = 1; i < args.length; i++) { - String token = args[i]; - - int colon = token.indexOf(':'); - if (colon == -1) continue; - - String namespace = token.substring(0, colon); - if (!cachedNamespaces.contains(namespace)) continue; - - ImmutableBlockState state = BlockStateParser.deserialize(token); - if (state == null) continue; - - String internalId = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState().handle()).toString(); - args[i] = internalId; - modified = true; - } - - if (modified) { - event.setMessage(String.join(" ", args)); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index 423bdd269..fc5b6b2a5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -4,6 +4,7 @@ import net.momirealms.antigrieflib.AntiGriefLib; import net.momirealms.craftengine.bukkit.api.event.CraftEngineReloadEvent; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors; +import net.momirealms.craftengine.bukkit.block.worldedit.WorldEditCommandHelper; import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; import net.momirealms.craftengine.bukkit.font.BukkitImageManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; @@ -178,12 +179,6 @@ public class BukkitCraftEngine extends CraftEngine { new ImageExpansion(this).register(); this.hasPlaceholderAPI = true; } - // WorldEdit - if (this.isPluginEnabled("FastAsyncWorldEdit")) { - this.blockManager().initFastAsyncWorldEditHook(); - } else if (this.isPluginEnabled("WorldEdit")) { - this.blockManager().initWorldEditHook(); - } } @Override