From 6a1c5249632974e8143a597b8deac5f8c114680f Mon Sep 17 00:00:00 2001 From: iqtester Date: Fri, 18 Apr 2025 23:42:39 +0800 Subject: [PATCH] feat: Add Block Properties Suggestion to WE & DebugSetBlockCommand --- .../worldedit/WorldEditBlockRegister.java | 32 +-- .../bukkit/block/BukkitBlockManager.java | 16 +- .../command/feature/DebugSetBlockCommand.java | 3 +- .../core/block/BlockStateParser.java | 187 +++++++++++++++++- 4 files changed, 198 insertions(+), 40 deletions(-) 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 3f3766162..371262dec 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 @@ -16,46 +16,29 @@ 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 final Field field$BlockType$blockMaterial; private final AbstractBlockManager manager; - private final Set cachedSuggestions = new HashSet<>(); public WorldEditBlockRegister(AbstractBlockManager manager) { field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); this.manager = manager; - } - - 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 { + private final class CEBlockParser extends InputParser { - protected CEBlockParser(WorldEdit worldEdit) { + private CEBlockParser(WorldEdit worldEdit) { super(worldEdit); } @@ -68,22 +51,25 @@ public class WorldEditBlockRegister { } if (input.startsWith(":")) { - String term = input.substring(1).toLowerCase(); - return cachedSuggestions.stream().filter(s -> s.toLowerCase().contains(term)); + String term = input.substring(1); + return BlockStateParser.fillSuggestions(term).stream(); } 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)) + BlockStateParser.fillSuggestions(input).stream() ); } - return cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(input.toLowerCase())); + return BlockStateParser.fillSuggestions(input).stream(); } @Override public BaseBlock parseFromInput(String input, ParserContext context) { + int index = input.indexOf("["); + if (input.charAt(index+1) == ']') return null; + int colonIndex = input.indexOf(':'); if (colonIndex == -1) 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 259629544..51dd78971 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,8 +87,6 @@ public class BukkitBlockManager extends AbstractBlockManager { // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - // WE support - private WorldEditBlockRegister weBlockRegister; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); @@ -146,8 +144,6 @@ 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 @@ -167,7 +163,6 @@ public class BukkitBlockManager extends AbstractBlockManager { initSuggestions(); resetPacketConsumers(); clearCache(); - loadWorldEditRegister(); } private void clearCache() { @@ -177,13 +172,11 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - this.weBlockRegister = new WorldEditBlockRegister(this); - this.weBlockRegister.enable(); + new WorldEditBlockRegister(this); } public void initWorldEditHook() { - this.weBlockRegister = new WorldEditBlockRegister(this); - this.weBlockRegister.enable(); + WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(this); try { for (Key newBlockId : this.blockRegisterOrder) { weBlockRegister.register(newBlockId); @@ -193,11 +186,6 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - 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/plugin/command/feature/DebugSetBlockCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java index 80cf55d91..58ca767dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java @@ -19,6 +19,7 @@ import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class DebugSetBlockCommand extends BukkitCommandFeature { @@ -33,7 +34,7 @@ public class DebugSetBlockCommand extends BukkitCommandFeature { .required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() { @Override public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { - return CompletableFuture.completedFuture(plugin().blockManager().cachedSuggestions()); + return CompletableFuture.completedFuture(BlockStateParser.fillSuggestions(input.input(), input.cursor()).stream().map(Suggestion::suggestion).collect(Collectors.toList())); } })) .handler(context -> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java index 8f1837886..f8cc31c6e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java @@ -8,9 +8,183 @@ import net.momirealms.craftengine.core.util.StringReader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; public class BlockStateParser { + private static final char START = '['; + private static final char EQUAL = '='; + private static final char SEPARATOR = ','; + private static final char END = ']'; + + private final StringReader reader; + private final int cursor; + private final Set suggestions = new HashSet<>(); + private final Set used = new HashSet<>(); + + private String input; + private int replaceCursor; + private Holder block; + private Collection> properties; + private Property property; + + public BlockStateParser(String data, int cursor) { + this.reader = new StringReader(data.toLowerCase()); + this.reader.setCursor(cursor); + this.cursor = cursor; + this.replaceCursor = cursor; + } + + public static Set fillSuggestions(@NotNull String data) { + return fillSuggestions(data, 0); + } + + public static Set fillSuggestions(@NotNull String data, int cursor) { + BlockStateParser parser = new BlockStateParser(data, cursor); + parser.parse(); + return parser.suggestions; + } + + private void parse() { + readBlock(); + if (block == null) { + suggestBlock(); + return; + } + + readProperties(); + if (properties.isEmpty()) return; + + if (!reader.canRead()) + suggestStart(); + else if (reader.peek() == START) { + reader.skip(); + suggestProperties(); + } + } + + private void readBlock() { + this.replaceCursor = reader.getCursor(); + this.input = reader.readUnquotedString(); + if (reader.canRead() && reader.peek() == ':') { + reader.skip(); + input = input + ":" + reader.readUnquotedString(); + } + BuiltInRegistries.BLOCK.get(Key.from(input)).ifPresent(block -> this.block = block); + } + + private void suggestBlock() { + String front = readPrefix(); + for (Key key : BuiltInRegistries.BLOCK.keySet()) { + String id = key.toString(); + if (id.contains(input)) { + this.suggestions.add(front + id); + } + } + this.suggestions.remove(front + "craftengine:empty"); + } + + private void readProperties() { + this.properties = this.block.value().properties(); + } + + private void suggestStart() { + this.replaceCursor = reader.getCursor(); + this.suggestions.add(readPrefix() + START); + } + + private void suggestProperties() { + this.reader.skipWhitespace(); + this.replaceCursor = reader.getCursor(); + suggestPropertyNameAndEnd(); + + while (reader.canRead()) { + if (used.isEmpty() && reader.peek() == SEPARATOR) return; + if (reader.peek() == SEPARATOR) reader.skip(); + reader.skipWhitespace(); + if (reader.canRead() && reader.peek() == END) return; + + replaceCursor = reader.getCursor(); + input = reader.readString(); + + property = block.value().getProperty(input); + if (property == null) { + suggestPropertyName(); + return; + } + if (used.contains(property.name().toLowerCase())) return; + used.add(input); + + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + suggestEqual(); + + if (!reader.canRead() || reader.peek() != EQUAL) return; + + reader.skip(); + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + input = reader.readString(); + if (property.possibleValues().stream().noneMatch + (value -> value.toString().equalsIgnoreCase(input)) + ){ + suggestValue(); + return; + } + + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + if (reader.canRead()) { + if (used.size() == properties.size()) return; + if (reader.peek() != SEPARATOR) return; + } else if (used.size() < properties.size()) { + suggestSeparator(); + } + } + suggestEnd(); + } + + private void suggestPropertyNameAndEnd() { + if (!reader.getRemaining().isEmpty()) return; + this.input = ""; + suggestEnd(); + suggestPropertyName(); + + } + private void suggestPropertyName() { + if (!reader.getRemaining().isEmpty()) return; + String front = readPrefix(); + for (Property p : properties) { + if (!used.contains(p.name().toLowerCase()) && p.name().toLowerCase().startsWith(input)) { + this.suggestions.add(front + p.name() + EQUAL); + } + } + } + + private void suggestEqual() { + if (!reader.getRemaining().isEmpty()) return; + this.suggestions.add(readPrefix() + EQUAL); + } + + private void suggestValue() { + for (Object val : property.possibleValues()) { + this.suggestions.add(readPrefix() + val.toString().toLowerCase()); + } + } + + private void suggestSeparator() { + this.suggestions.add(readPrefix() + SEPARATOR); + } + + private void suggestEnd() { + this.suggestions.add(readPrefix() + END); + } + + private String readPrefix() { + return reader.getString().substring(cursor, replaceCursor); + } @Nullable public static ImmutableBlockState deserialize(@NotNull String data) { @@ -28,26 +202,35 @@ public class BlockStateParser { ImmutableBlockState defaultState = holder.value().defaultState(); if (reader.canRead() && reader.peek() == '[') { reader.skip(); - while (reader.canRead() && reader.peek() != ']') { + while (reader.canRead()) { + reader.skipWhitespace(); + if (reader.peek() == ']') break; String propertyName = reader.readUnquotedString(); + reader.skipWhitespace(); if (!reader.canRead() || reader.peek() != '=') { return null; } reader.skip(); + reader.skipWhitespace(); String propertyValue = reader.readUnquotedString(); Property property = holder.value().getProperty(propertyName); if (property != null) { Optional optionalValue = property.optional(propertyValue); if (optionalValue.isEmpty()) { - defaultState = ImmutableBlockState.with(defaultState, property, property.defaultValue()); + //defaultState = ImmutableBlockState.with(defaultState, property, property.defaultValue()); + return null; } else { defaultState = ImmutableBlockState.with(defaultState, property, optionalValue.get()); } + } else { + return null; } + reader.skipWhitespace(); if (reader.canRead() && reader.peek() == ',') { reader.skip(); } } + reader.skipWhitespace(); if (reader.canRead() && reader.peek() == ']') { reader.skip(); } else {