diff --git a/bukkit-loader/src/main/resources/config.yml b/bukkit-loader/src/main/resources/config.yml index e3e398072..0546ca672 100644 --- a/bukkit-loader/src/main/resources/config.yml +++ b/bukkit-loader/src/main/resources/config.yml @@ -210,6 +210,10 @@ performance: # Disabling this option prevents the plugin from converting custom blocks to vanilla states when chunks are unloaded. # While this can improve performance, custom blocks will turn into air if the plugin is uninstalled. restore-vanilla-blocks-on-chunk-unload: true + # When you edit a map locally using CraftEngine fabric mod, the custom block data is not immediately synchronized with the + # server's CraftEngine internal data. Enabling this option will synchronize the data when the chunk is loaded. + # (This option only slightly impacts performance, which has been fully optimized, so you don't need to worry too much.) + sync-custom-blocks-on-chunk-load: false # If you disable this, it's a must to disable the above option. restore-custom-blocks-on-chunk-load: true diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index d9bd89399..2998fa762 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -121,7 +121,9 @@ public class SaplingBlockBehavior extends BushBlockBehavior { throw new IllegalArgumentException("stage property not set for sapling"); } double boneMealSuccessChance = MiscUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45)); - if (arguments.containsKey("tags")) { + if (arguments.containsKey("bottom-block-tags")) { + return new SaplingBlockBehavior(Key.of(feature), stageProperty, MiscUtils.getAsStringList(arguments.get("bottom-block-tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList(), boneMealSuccessChance); + } else if (arguments.containsKey("tags")) { return new SaplingBlockBehavior(Key.of(feature), stageProperty, MiscUtils.getAsStringList(arguments.get("tags")).stream().map(it -> BlockTags.getOrCreate(Key.of(it))).toList(), boneMealSuccessChance); } else { return new SaplingBlockBehavior(Key.of(feature), stageProperty, List.of(DIRT_TAG, FARMLAND), boneMealSuccessChance); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index d0819e6b6..95eb2c69c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -1823,6 +1823,12 @@ public class Reflections { ) ); + public static final Method method$LevelChunkSection$getBlockState = requireNonNull( + ReflectionUtils.getMethod( + clazz$LevelChunkSection, clazz$BlockState, int.class, int.class, int.class + ) + ); + public static final Class clazz$StatePredicate = requireNonNull( ReflectionUtils.getClazz( BukkitReflectionUtils.assembleMCClass("world.level.block.state.BlockBehaviour$StatePredicate"), @@ -5371,4 +5377,55 @@ public class Reflections { throw new RuntimeException(e); } } + + public static final Class clazz$SingleValuePalette = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.chunk.SingleValuePalette") + ) + ); + + public static final Field field$SingleValuePalette$value = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$SingleValuePalette, Object.class, 0 + ) + ); + + public static final Class clazz$HashMapPalette = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.chunk.HashMapPalette"), + BukkitReflectionUtils.assembleMCClass("world.level.chunk.DataPaletteHash") + ) + ); + + public static final Class clazz$CrudeIncrementalIntIdentityHashBiMap = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("util.CrudeIncrementalIntIdentityHashBiMap"), + BukkitReflectionUtils.assembleMCClass("util.RegistryID") + ) + ); + + public static final Field field$CrudeIncrementalIntIdentityHashBiMap$keys = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$CrudeIncrementalIntIdentityHashBiMap, Object.class.arrayType(), 0 + ) + ); + + public static final Field field$HashMapPalette$values = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$HashMapPalette, clazz$CrudeIncrementalIntIdentityHashBiMap, 0 + ) + ); + + public static final Class clazz$LinearPalette = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.chunk.LinearPalette"), + BukkitReflectionUtils.assembleMCClass("world.level.chunk.DataPaletteLinear") + ) + ); + + public static final Field field$LinearPalette$values = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$LinearPalette, Object.class.arrayType(), 0 + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index d5bc16d95..8b6a9a69c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.world; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.config.ConfigManager; @@ -253,6 +254,55 @@ public class BukkitWorldManager implements WorldManager, Listener { for (int i = 0; i < ceSections.length; i++) { CESection ceSection = ceSections[i]; Object section = sections[i]; + if (ConfigManager.syncCustomBlocks()) { + Object statesContainer = Reflections.field$LevelChunkSection$states.get(section); + Object data = Reflections.field$PalettedContainer$data.get(statesContainer); + Object palette = Reflections.field$PalettedContainer$Data$palette.get(data); + boolean requiresSync = false; + if (Reflections.clazz$SingleValuePalette.isInstance(palette)) { + Object onlyBlockState = Reflections.field$SingleValuePalette$value.get(palette); + if (!BlockStateUtils.isVanillaBlock(BlockStateUtils.blockStateToId(onlyBlockState))) { + requiresSync = true; + } + } else if (Reflections.clazz$LinearPalette.isInstance(palette)) { + Object[] blockStates = (Object[]) Reflections.field$LinearPalette$values.get(palette); + for (Object blockState : blockStates) { + if (blockState != null) { + if (!BlockStateUtils.isVanillaBlock(BlockStateUtils.blockStateToId(blockState))) { + requiresSync = true; + break; + } + } + } + } else if (Reflections.clazz$HashMapPalette.isInstance(palette)) { + Object biMap = Reflections.field$HashMapPalette$values.get(palette); + Object[] blockStates = (Object[]) Reflections.field$CrudeIncrementalIntIdentityHashBiMap$keys.get(biMap); + for (Object blockState : blockStates) { + if (blockState != null) { + if (!BlockStateUtils.isVanillaBlock(BlockStateUtils.blockStateToId(blockState))) { + requiresSync = true; + break; + } + } + } + } else { + requiresSync = true; + } + if (requiresSync) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 16; y++) { + Object mcState = Reflections.method$LevelChunkSection$getBlockState.invoke(section, x, y, z); + int stateId = BlockStateUtils.blockStateToId(mcState); + ImmutableBlockState customState = this.plugin.blockManager().getImmutableBlockState(stateId); + if (customState != null) { + ceSection.setBlockState(x, y, z, customState); + } + } + } + } + } + } if (ConfigManager.restoreCustomBlocks()) { if (!ceSection.statesContainer().isEmpty()) { for (int x = 0; x < 16; x++) { @@ -286,4 +336,6 @@ public class BukkitWorldManager implements WorldManager, Listener { } ceChunk.load(); } + + } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java index 9e7e4f18e..64d50a78a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigManager.java @@ -95,6 +95,7 @@ public class ConfigManager implements Reloadable { protected int performance$max_block_chain_update_limit; protected boolean performance$chunk_system$restore_vanilla_blocks_on_chunk_unload; protected boolean performance$chunk_system$restore_custom_blocks_on_chunk_load; + protected boolean performance$chunk_system$sync_custom_blocks_on_chunk_load; protected boolean furniture$remove_invalid_furniture_on_chunk_load$enable; protected Set furniture$remove_invalid_furniture_on_chunk_load$list; @@ -236,6 +237,7 @@ public class ConfigManager implements Reloadable { performance$light_system$enable = config.getBoolean("performance.light-system.enable", true); performance$chunk_system$restore_vanilla_blocks_on_chunk_unload = config.getBoolean("performance.chunk-system.restore-vanilla-blocks-on-chunk-unload", true); performance$chunk_system$restore_custom_blocks_on_chunk_load = config.getBoolean("performance.chunk-system.restore-custom-blocks-on-chunk-load", true); + performance$chunk_system$sync_custom_blocks_on_chunk_load = config.getBoolean("performance.chunk-system.sync-custom-blocks-on-chunk-load", false); // furniture furniture$remove_invalid_furniture_on_chunk_load$enable = config.getBoolean("furniture.remove-invalid-furniture-on-chunk-load.enable", false); @@ -342,6 +344,10 @@ public class ConfigManager implements Reloadable { return instance.performance$chunk_system$restore_custom_blocks_on_chunk_load; } + public static boolean syncCustomBlocks() { + return instance.performance$chunk_system$sync_custom_blocks_on_chunk_load; + } + public static List foldersToMerge() { return instance.resource_pack$merge_external_folders; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java index f90eaaad4..d64ff68c0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java @@ -24,6 +24,15 @@ public class ChunkPos { this.longKey = asLong(this.x, this.z); } + @Override + public String toString() { + return "ChunkPos{" + + "x=" + x + + ", z=" + z + + ", longKey=" + longKey + + '}'; + } + public int x() { return x; }